@stackbilt/llm-providers 1.1.0 → 1.3.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/README.md +112 -85
- package/dist/errors.d.ts +32 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +29 -2
- package/dist/errors.js.map +1 -1
- package/dist/factory.d.ts +62 -4
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +651 -92
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +45 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -14
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +5 -2
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +157 -43
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/base.d.ts +18 -2
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/base.js +107 -5
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/cerebras.d.ts.map +1 -1
- package/dist/providers/cerebras.js +21 -13
- package/dist/providers/cerebras.js.map +1 -1
- package/dist/providers/cloudflare.d.ts +3 -0
- package/dist/providers/cloudflare.d.ts.map +1 -1
- package/dist/providers/cloudflare.js +86 -9
- package/dist/providers/cloudflare.js.map +1 -1
- package/dist/providers/groq.d.ts +2 -1
- package/dist/providers/groq.d.ts.map +1 -1
- package/dist/providers/groq.js +95 -15
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/openai.d.ts +2 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +56 -24
- package/dist/providers/openai.js.map +1 -1
- package/dist/types.d.ts +114 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/circuit-breaker.d.ts +5 -2
- package/dist/utils/circuit-breaker.d.ts.map +1 -1
- package/dist/utils/circuit-breaker.js +18 -13
- package/dist/utils/circuit-breaker.js.map +1 -1
- package/dist/utils/cost-tracker.d.ts +9 -2
- package/dist/utils/cost-tracker.d.ts.map +1 -1
- package/dist/utils/cost-tracker.js +20 -9
- package/dist/utils/cost-tracker.js.map +1 -1
- package/dist/utils/credit-ledger.d.ts +3 -0
- package/dist/utils/credit-ledger.d.ts.map +1 -1
- package/dist/utils/credit-ledger.js +5 -2
- package/dist/utils/credit-ledger.js.map +1 -1
- package/dist/utils/exhaustion.d.ts +38 -0
- package/dist/utils/exhaustion.d.ts.map +1 -0
- package/dist/utils/exhaustion.js +74 -0
- package/dist/utils/exhaustion.js.map +1 -0
- package/dist/utils/hooks.d.ts +123 -0
- package/dist/utils/hooks.d.ts.map +1 -0
- package/dist/utils/hooks.js +44 -0
- package/dist/utils/hooks.js.map +1 -0
- package/dist/utils/latency-histogram.d.ts +38 -0
- package/dist/utils/latency-histogram.d.ts.map +1 -0
- package/dist/utils/latency-histogram.js +81 -0
- package/dist/utils/latency-histogram.js.map +1 -0
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +4 -2
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +12 -8
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/schema-validator.d.ts +67 -0
- package/dist/utils/schema-validator.d.ts.map +1 -0
- package/dist/utils/schema-validator.js +140 -0
- package/dist/utils/schema-validator.js.map +1 -0
- package/package.json +1 -1
package/dist/utils/retry.d.ts
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* Exponential backoff retry logic for LLM provider requests
|
|
4
4
|
*/
|
|
5
5
|
import type { RetryConfig } from '../types';
|
|
6
|
+
import type { Logger } from './logger';
|
|
6
7
|
export declare class RetryManager {
|
|
7
8
|
private config;
|
|
8
|
-
|
|
9
|
+
private logger;
|
|
10
|
+
constructor(config?: Partial<RetryConfig>, logger?: Logger);
|
|
9
11
|
/**
|
|
10
12
|
* Execute a function with retry logic
|
|
11
13
|
*/
|
|
@@ -38,7 +40,7 @@ export declare const defaultRetryManager: RetryManager;
|
|
|
38
40
|
/**
|
|
39
41
|
* Retry decorator for async functions
|
|
40
42
|
*/
|
|
41
|
-
export declare function withRetry<T extends
|
|
43
|
+
export declare function withRetry<T extends unknown[], R>(retryConfig?: Partial<RetryConfig>): (_target: object, propertyKey: string, descriptor: TypedPropertyDescriptor<(...args: T) => Promise<R>>) => TypedPropertyDescriptor<(...args: T) => Promise<R>>;
|
|
42
44
|
/**
|
|
43
45
|
* Simple retry function for one-off operations
|
|
44
46
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIvC,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAiB9D;;OAEG;IACG,OAAO,CAAC,CAAC,EACb,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,MAAoB,GAC5B,OAAO,CAAC,CAAC,CAAC;IA8Bb;;OAEG;IACH,OAAO,CAAC,WAAW;IAqBnB;;OAEG;IACH,OAAO,CAAC,cAAc;IAgBtB;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI;IAIhD;;OAEG;IACH,SAAS,IAAI,WAAW;CAGzB;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,cAAqB,CAAC;AAEtD;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,EAC9C,WAAW,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,IAGhC,SAAS,MAAM,EACf,aAAa,MAAM,EACnB,YAAY,uBAAuB,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,uCAAjB,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAcjE;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAGZ"}
|
package/dist/utils/retry.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Retry Utility
|
|
3
3
|
* Exponential backoff retry logic for LLM provider requests
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { noopLogger } from './logger';
|
|
6
|
+
import { LLMErrorFactory, LLMProviderError } from '../errors';
|
|
6
7
|
export class RetryManager {
|
|
7
8
|
config;
|
|
8
|
-
|
|
9
|
+
logger;
|
|
10
|
+
constructor(config = {}, logger) {
|
|
9
11
|
this.config = {
|
|
10
12
|
maxRetries: config.maxRetries ?? 3,
|
|
11
13
|
initialDelay: config.initialDelay ?? 1000,
|
|
@@ -19,6 +21,7 @@ export class RetryManager {
|
|
|
19
21
|
'CIRCUIT_BREAKER_OPEN'
|
|
20
22
|
]
|
|
21
23
|
};
|
|
24
|
+
this.logger = logger ?? noopLogger;
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Execute a function with retry logic
|
|
@@ -39,7 +42,7 @@ export class RetryManager {
|
|
|
39
42
|
}
|
|
40
43
|
// Calculate delay for next attempt
|
|
41
44
|
const delay = this.calculateDelay(attempt, error);
|
|
42
|
-
|
|
45
|
+
this.logger.warn(`[RetryManager] ${context} failed (attempt ${attempt}/${this.config.maxRetries + 1}): ${lastError.message}. Retrying in ${delay}ms...`);
|
|
43
46
|
await this.delay(delay);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
@@ -58,9 +61,10 @@ export class RetryManager {
|
|
|
58
61
|
return false;
|
|
59
62
|
}
|
|
60
63
|
// Check if error code is in retryable list
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
if (error instanceof LLMProviderError) {
|
|
65
|
+
if (!this.config.retryableErrors.includes(error.code)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
64
68
|
}
|
|
65
69
|
return true;
|
|
66
70
|
}
|
|
@@ -106,11 +110,11 @@ export const defaultRetryManager = new RetryManager();
|
|
|
106
110
|
* Retry decorator for async functions
|
|
107
111
|
*/
|
|
108
112
|
export function withRetry(retryConfig) {
|
|
109
|
-
return function (
|
|
113
|
+
return function (_target, propertyKey, descriptor) {
|
|
110
114
|
const originalMethod = descriptor.value;
|
|
111
115
|
const retryManager = new RetryManager(retryConfig);
|
|
112
116
|
descriptor.value = async function (...args) {
|
|
113
|
-
return retryManager.execute(() => originalMethod.apply(this, args), `${
|
|
117
|
+
return retryManager.execute(() => originalMethod.apply(this, args), `${this.constructor.name}.${propertyKey}`);
|
|
114
118
|
};
|
|
115
119
|
return descriptor;
|
|
116
120
|
};
|
package/dist/utils/retry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Envelope Schema Validator
|
|
3
|
+
*
|
|
4
|
+
* Zero-dependency runtime validator for provider response shapes. Used at the
|
|
5
|
+
* provider boundary to detect when an upstream API silently changes its
|
|
6
|
+
* response envelope — the classic "field renamed at 2am, parser throws at
|
|
7
|
+
* 3am" failure mode.
|
|
8
|
+
*
|
|
9
|
+
* Philosophy: validate the *minimum* fields each provider's parser actually
|
|
10
|
+
* reads. Don't re-type the full upstream schema — that creates brittle
|
|
11
|
+
* over-specification that churns every time the provider adds an optional
|
|
12
|
+
* field. Only the fields we touch matter.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* validateSchema('anthropic', data, [
|
|
16
|
+
* { path: 'content', type: 'array' },
|
|
17
|
+
* { path: 'usage.input_tokens', type: 'number' },
|
|
18
|
+
* { path: 'usage.output_tokens', type: 'number' },
|
|
19
|
+
* { path: 'model', type: 'string' },
|
|
20
|
+
* ]);
|
|
21
|
+
*
|
|
22
|
+
* On first failure, throws SchemaDriftError. The caller (typically the
|
|
23
|
+
* factory) catches it, fires the onSchemaDrift hook, and falls over to
|
|
24
|
+
* another provider.
|
|
25
|
+
*/
|
|
26
|
+
export type SchemaFieldType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'string-or-null';
|
|
27
|
+
export interface SchemaField {
|
|
28
|
+
/**
|
|
29
|
+
* Dot-separated path into the response object. Array indices not supported
|
|
30
|
+
* in the path itself — use `items` to validate array element shapes.
|
|
31
|
+
*/
|
|
32
|
+
path: string;
|
|
33
|
+
type: SchemaFieldType;
|
|
34
|
+
/**
|
|
35
|
+
* If true, missing paths are allowed and skipped. Useful for fields that
|
|
36
|
+
* are genuinely optional (e.g. stop_sequence on Anthropic).
|
|
37
|
+
*/
|
|
38
|
+
optional?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* For type: 'array' — validate each element against a nested schema.
|
|
41
|
+
*
|
|
42
|
+
* - `shape`: a flat SchemaField[] applied to every element (all elements
|
|
43
|
+
* the same shape).
|
|
44
|
+
* - `variants` + `discriminator`: discriminated union. Each element is
|
|
45
|
+
* routed by the value of `discriminator` (a field name on the element)
|
|
46
|
+
* to the matching variant schema. **Unknown discriminator values are
|
|
47
|
+
* allowed and skipped** — we want forward-compat for additive API
|
|
48
|
+
* changes (e.g. Anthropic adds a new content block type). Only *missing*
|
|
49
|
+
* discriminators or *wrong-typed* known variants trigger drift.
|
|
50
|
+
*/
|
|
51
|
+
items?: {
|
|
52
|
+
shape?: SchemaField[];
|
|
53
|
+
discriminator?: string;
|
|
54
|
+
variants?: Record<string, SchemaField[]>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate a response envelope against a minimal field schema.
|
|
59
|
+
* Throws SchemaDriftError on the first mismatch, with provider + path +
|
|
60
|
+
* expected/actual types surfaced for observability.
|
|
61
|
+
*
|
|
62
|
+
* We fail fast rather than collecting all errors: the first drift is enough
|
|
63
|
+
* to trigger fallback, and walking the whole schema when we're already
|
|
64
|
+
* broken wastes budget.
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateSchema(provider: string, data: unknown, fields: SchemaField[]): void;
|
|
67
|
+
//# sourceMappingURL=schema-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/utils/schema-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,OAAO,GACP,QAAQ,GACR,gBAAgB,CAAC;AAErB,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;KAC1C,CAAC;CACH;AAoID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,WAAW,EAAE,GACpB,IAAI,CAEN"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Envelope Schema Validator
|
|
3
|
+
*
|
|
4
|
+
* Zero-dependency runtime validator for provider response shapes. Used at the
|
|
5
|
+
* provider boundary to detect when an upstream API silently changes its
|
|
6
|
+
* response envelope — the classic "field renamed at 2am, parser throws at
|
|
7
|
+
* 3am" failure mode.
|
|
8
|
+
*
|
|
9
|
+
* Philosophy: validate the *minimum* fields each provider's parser actually
|
|
10
|
+
* reads. Don't re-type the full upstream schema — that creates brittle
|
|
11
|
+
* over-specification that churns every time the provider adds an optional
|
|
12
|
+
* field. Only the fields we touch matter.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* validateSchema('anthropic', data, [
|
|
16
|
+
* { path: 'content', type: 'array' },
|
|
17
|
+
* { path: 'usage.input_tokens', type: 'number' },
|
|
18
|
+
* { path: 'usage.output_tokens', type: 'number' },
|
|
19
|
+
* { path: 'model', type: 'string' },
|
|
20
|
+
* ]);
|
|
21
|
+
*
|
|
22
|
+
* On first failure, throws SchemaDriftError. The caller (typically the
|
|
23
|
+
* factory) catches it, fires the onSchemaDrift hook, and falls over to
|
|
24
|
+
* another provider.
|
|
25
|
+
*/
|
|
26
|
+
import { SchemaDriftError } from '../errors';
|
|
27
|
+
/**
|
|
28
|
+
* Walk a dot-path into an object. Returns undefined if any segment is missing.
|
|
29
|
+
* Does NOT distinguish "path missing" from "path present but undefined" —
|
|
30
|
+
* callers should treat both as missing.
|
|
31
|
+
*/
|
|
32
|
+
function getPath(obj, path) {
|
|
33
|
+
const segments = path.split('.');
|
|
34
|
+
let current = obj;
|
|
35
|
+
for (const segment of segments) {
|
|
36
|
+
if (current == null || typeof current !== 'object') {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
current = current[segment];
|
|
40
|
+
}
|
|
41
|
+
return current;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Describe the actual runtime type of a value for error messages.
|
|
45
|
+
* Separates null / undefined / array from generic object.
|
|
46
|
+
*/
|
|
47
|
+
function describeType(value) {
|
|
48
|
+
if (value === null)
|
|
49
|
+
return 'null';
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
return 'undefined';
|
|
52
|
+
if (Array.isArray(value))
|
|
53
|
+
return 'array';
|
|
54
|
+
return typeof value;
|
|
55
|
+
}
|
|
56
|
+
function matchesType(value, type) {
|
|
57
|
+
switch (type) {
|
|
58
|
+
case 'string':
|
|
59
|
+
return typeof value === 'string';
|
|
60
|
+
case 'number':
|
|
61
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
62
|
+
case 'boolean':
|
|
63
|
+
return typeof value === 'boolean';
|
|
64
|
+
case 'array':
|
|
65
|
+
return Array.isArray(value);
|
|
66
|
+
case 'object':
|
|
67
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
68
|
+
case 'string-or-null':
|
|
69
|
+
return value === null || typeof value === 'string';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate a single object against a flat SchemaField list, prefixing any
|
|
74
|
+
* error path with `pathPrefix` so nested element errors read like
|
|
75
|
+
* `content[2].id` instead of bare `id`.
|
|
76
|
+
*/
|
|
77
|
+
function validateFields(provider, data, fields, pathPrefix) {
|
|
78
|
+
if (data == null || typeof data !== 'object') {
|
|
79
|
+
throw new SchemaDriftError(provider, pathPrefix || '$root', 'object', describeType(data));
|
|
80
|
+
}
|
|
81
|
+
for (const field of fields) {
|
|
82
|
+
const fullPath = pathPrefix ? `${pathPrefix}.${field.path}` : field.path;
|
|
83
|
+
const value = getPath(data, field.path);
|
|
84
|
+
if (value === undefined) {
|
|
85
|
+
if (field.optional)
|
|
86
|
+
continue;
|
|
87
|
+
throw new SchemaDriftError(provider, fullPath, field.type, 'undefined');
|
|
88
|
+
}
|
|
89
|
+
if (!matchesType(value, field.type)) {
|
|
90
|
+
throw new SchemaDriftError(provider, fullPath, field.type, describeType(value));
|
|
91
|
+
}
|
|
92
|
+
if (field.type === 'array' && field.items) {
|
|
93
|
+
validateArrayItems(provider, value, field.items, fullPath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Validate the elements of an array against either a flat `shape` or a
|
|
99
|
+
* discriminated `variants` map. Unknown discriminator values are
|
|
100
|
+
* forward-compatible and skipped.
|
|
101
|
+
*/
|
|
102
|
+
function validateArrayItems(provider, items, spec, arrayPath) {
|
|
103
|
+
for (let i = 0; i < items.length; i++) {
|
|
104
|
+
const element = items[i];
|
|
105
|
+
const elementPath = `${arrayPath}[${i}]`;
|
|
106
|
+
if (element == null || typeof element !== 'object') {
|
|
107
|
+
throw new SchemaDriftError(provider, elementPath, 'object', describeType(element));
|
|
108
|
+
}
|
|
109
|
+
if (spec.discriminator && spec.variants) {
|
|
110
|
+
const disc = element[spec.discriminator];
|
|
111
|
+
if (typeof disc !== 'string') {
|
|
112
|
+
throw new SchemaDriftError(provider, `${elementPath}.${spec.discriminator}`, 'string', describeType(disc));
|
|
113
|
+
}
|
|
114
|
+
const variantFields = spec.variants[disc];
|
|
115
|
+
// Unknown discriminator value — forward-compat. Skip validation
|
|
116
|
+
// rather than reject, so adding a new block type upstream doesn't
|
|
117
|
+
// break every consumer on the next deploy.
|
|
118
|
+
if (!variantFields)
|
|
119
|
+
continue;
|
|
120
|
+
validateFields(provider, element, variantFields, elementPath);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (spec.shape) {
|
|
124
|
+
validateFields(provider, element, spec.shape, elementPath);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Validate a response envelope against a minimal field schema.
|
|
130
|
+
* Throws SchemaDriftError on the first mismatch, with provider + path +
|
|
131
|
+
* expected/actual types surfaced for observability.
|
|
132
|
+
*
|
|
133
|
+
* We fail fast rather than collecting all errors: the first drift is enough
|
|
134
|
+
* to trigger fallback, and walking the whole schema when we're already
|
|
135
|
+
* broken wastes budget.
|
|
136
|
+
*/
|
|
137
|
+
export function validateSchema(provider, data, fields) {
|
|
138
|
+
validateFields(provider, data, fields, '');
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=schema-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-validator.js","sourceRoot":"","sources":["../../src/utils/schema-validator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackbilt/llm-providers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Multi-LLM failover with circuit breakers, cost tracking, and intelligent retry. Cloudflare Workers native.",
|
|
5
5
|
"author": "Stackbilt <admin@stackbilt.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|