@fleetagent/pi-ai 0.1.8 → 0.2.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/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -24
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -1
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.generated.d.ts +225 -0
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +227 -2
- package/dist/image-models.generated.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +37 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +6120 -2593
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +5347 -2110
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +259 -9
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +6 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +4 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +5 -4
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +303 -330
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +5 -4
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +492 -499
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts +4 -2
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +52 -35
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/faux.d.ts +19 -22
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +106 -70
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot-headers.d.ts +7 -4
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +24 -5
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +139 -140
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +3 -7
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +216 -219
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +4 -7
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +214 -218
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts.map +1 -1
- package/dist/providers/images/openrouter.js +37 -27
- package/dist/providers/images/openrouter.js.map +1 -1
- package/dist/providers/mistral.d.ts +7 -6
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +287 -265
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +8 -5
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +493 -391
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +7 -10
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +670 -664
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses-shared.d.ts +5 -4
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +390 -360
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +5 -5
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +134 -109
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/simple-options.d.ts +4 -2
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +112 -137
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/stream.d.ts +0 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +0 -1
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +91 -63
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/error-body.d.ts.map +1 -1
- package/dist/utils/error-body.js +15 -4
- package/dist/utils/error-body.js.map +1 -1
- package/dist/utils/estimate.d.ts +4 -0
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +3 -1
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/headers.d.ts +3 -0
- package/dist/utils/headers.d.ts.map +1 -1
- package/dist/utils/headers.js +21 -0
- package/dist/utils/headers.js.map +1 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +17 -19
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/oauth/anthropic.d.ts +4 -11
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +100 -117
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/device-code.d.ts.map +1 -1
- package/dist/utils/oauth/device-code.js +51 -38
- package/dist/utils/oauth/device-code.js.map +1 -1
- package/dist/utils/oauth/github-copilot.d.ts +6 -9
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +47 -42
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +8 -17
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +27 -23
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -1
- package/dist/utils/oauth/oauth-page.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +7 -12
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +72 -89
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/pkce.d.ts +5 -4
- package/dist/utils/oauth/pkce.d.ts.map +1 -1
- package/dist/utils/oauth/pkce.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +7 -2
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/provider-retry.d.ts +16 -0
- package/dist/utils/provider-retry.d.ts.map +1 -0
- package/dist/utils/provider-retry.js +94 -0
- package/dist/utils/provider-retry.js.map +1 -0
- package/dist/utils/retry.d.ts +14 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +84 -0
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +5 -4
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +102 -99
- package/dist/utils/validation.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
|
|
2
|
+
function isProviderError(error) {
|
|
3
|
+
if (!(error instanceof Error) || !("status" in error) || !("headers" in error))
|
|
4
|
+
return false;
|
|
5
|
+
return ((error.status === undefined || typeof error.status === "number") &&
|
|
6
|
+
(error.headers === undefined || error.headers instanceof Headers));
|
|
7
|
+
}
|
|
8
|
+
/** Mirrors the pinned OpenAI/Anthropic SDK retry policy; review when either SDK is upgraded. */
|
|
9
|
+
function isRetryableProviderError(error) {
|
|
10
|
+
const shouldRetry = error.headers?.get("x-should-retry");
|
|
11
|
+
if (shouldRetry === "true")
|
|
12
|
+
return true;
|
|
13
|
+
if (shouldRetry === "false")
|
|
14
|
+
return false;
|
|
15
|
+
if (error.status === undefined)
|
|
16
|
+
return true;
|
|
17
|
+
return (error.status === 408 ||
|
|
18
|
+
error.status === 409 ||
|
|
19
|
+
error.status === 429 ||
|
|
20
|
+
(typeof error.status === "number" && error.status >= 500));
|
|
21
|
+
}
|
|
22
|
+
function validateServerRetryDelayMs(delayMs, maxRetryDelayMs, providerErrorMessage) {
|
|
23
|
+
const maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
|
|
24
|
+
if (maxDelayMs > 0 && delayMs > maxDelayMs) {
|
|
25
|
+
throw new Error(`Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerErrorMessage}`);
|
|
26
|
+
}
|
|
27
|
+
return delayMs;
|
|
28
|
+
}
|
|
29
|
+
function getRetryDelayMs(error, retryIndex, maxRetryDelayMs) {
|
|
30
|
+
const retryAfterMs = error.headers?.get("retry-after-ms");
|
|
31
|
+
if (retryAfterMs) {
|
|
32
|
+
const value = Number.parseFloat(retryAfterMs);
|
|
33
|
+
if (!Number.isNaN(value))
|
|
34
|
+
return validateServerRetryDelayMs(value, maxRetryDelayMs, error.message);
|
|
35
|
+
}
|
|
36
|
+
const retryAfter = error.headers?.get("retry-after");
|
|
37
|
+
if (retryAfter) {
|
|
38
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
39
|
+
const delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - Date.now() : seconds * 1000;
|
|
40
|
+
return validateServerRetryDelayMs(delayMs, maxRetryDelayMs, error.message);
|
|
41
|
+
}
|
|
42
|
+
const exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;
|
|
43
|
+
return exponentialDelay * (1 - Math.random() * 0.25);
|
|
44
|
+
}
|
|
45
|
+
function createAbortError() {
|
|
46
|
+
const error = new Error("Request aborted");
|
|
47
|
+
error.name = "AbortError";
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
function abortableSleep(ms, signal) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
if (signal?.aborted) {
|
|
53
|
+
reject(createAbortError());
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const onAbort = () => {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
reject(createAbortError());
|
|
59
|
+
};
|
|
60
|
+
const timeout = setTimeout(() => {
|
|
61
|
+
signal?.removeEventListener("abort", onAbort);
|
|
62
|
+
resolve();
|
|
63
|
+
}, Math.max(0, ms));
|
|
64
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Reproduce the retry behavior used by the OpenAI and Anthropic SDKs while making
|
|
69
|
+
* their backoff sleep interruptible. Their built-in retry timers ignore the
|
|
70
|
+
* request AbortSignal, so callers must invoke the SDK with `maxRetries: 0` and
|
|
71
|
+
* wrap the request with this helper. Provider-requested delays above
|
|
72
|
+
* `maxRetryDelayMs` fail immediately (60 seconds by default); set it to zero to
|
|
73
|
+
* disable the limit.
|
|
74
|
+
*/
|
|
75
|
+
export async function retryProviderRequest(request, options = {}) {
|
|
76
|
+
const maxRetries = options.maxRetries ?? 0;
|
|
77
|
+
let retriesRemaining = maxRetries;
|
|
78
|
+
for (;;) {
|
|
79
|
+
try {
|
|
80
|
+
// Each retry is a fresh SDK request, so X-Stainless-Retry-Count remains zero.
|
|
81
|
+
return await request();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (options.signal?.aborted)
|
|
85
|
+
throw createAbortError();
|
|
86
|
+
if (retriesRemaining <= 0 || !isProviderError(error) || !isRetryableProviderError(error))
|
|
87
|
+
throw error;
|
|
88
|
+
const retryIndex = maxRetries - retriesRemaining;
|
|
89
|
+
retriesRemaining--;
|
|
90
|
+
await abortableSleep(getRetryDelayMs(error, retryIndex, options.maxRetryDelayMs), options.signal);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=provider-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-retry.js","sourceRoot":"","sources":["../../src/utils/provider-retry.ts"],"names":[],"mappings":"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","sourcesContent":["const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;\n\ninterface ProviderRetryOptions {\n\tmaxRetries?: number;\n\tmaxRetryDelayMs?: number;\n\tsignal?: AbortSignal;\n}\n\ninterface ProviderError extends Error {\n\tstatus: number | undefined;\n\theaders: Headers | undefined;\n}\n\nfunction isProviderError(error: unknown): error is ProviderError {\n\tif (!(error instanceof Error) || !(\"status\" in error) || !(\"headers\" in error)) return false;\n\treturn (\n\t\t(error.status === undefined || typeof error.status === \"number\") &&\n\t\t(error.headers === undefined || error.headers instanceof Headers)\n\t);\n}\n\n/** Mirrors the pinned OpenAI/Anthropic SDK retry policy; review when either SDK is upgraded. */\nfunction isRetryableProviderError(error: ProviderError): boolean {\n\tconst shouldRetry = error.headers?.get(\"x-should-retry\");\n\tif (shouldRetry === \"true\") return true;\n\tif (shouldRetry === \"false\") return false;\n\n\tif (error.status === undefined) return true;\n\treturn (\n\t\terror.status === 408 ||\n\t\terror.status === 409 ||\n\t\terror.status === 429 ||\n\t\t(typeof error.status === \"number\" && error.status >= 500)\n\t);\n}\n\nfunction validateServerRetryDelayMs(\n\tdelayMs: number,\n\tmaxRetryDelayMs: number | undefined,\n\tproviderErrorMessage: string,\n): number {\n\tconst maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;\n\tif (maxDelayMs > 0 && delayMs > maxDelayMs) {\n\t\tthrow new Error(\n\t\t\t`Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerErrorMessage}`,\n\t\t);\n\t}\n\treturn delayMs;\n}\n\nfunction getRetryDelayMs(error: ProviderError, retryIndex: number, maxRetryDelayMs: number | undefined): number {\n\tconst retryAfterMs = error.headers?.get(\"retry-after-ms\");\n\tif (retryAfterMs) {\n\t\tconst value = Number.parseFloat(retryAfterMs);\n\t\tif (!Number.isNaN(value)) return validateServerRetryDelayMs(value, maxRetryDelayMs, error.message);\n\t}\n\n\tconst retryAfter = error.headers?.get(\"retry-after\");\n\tif (retryAfter) {\n\t\tconst seconds = Number.parseFloat(retryAfter);\n\t\tconst delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - Date.now() : seconds * 1000;\n\t\treturn validateServerRetryDelayMs(delayMs, maxRetryDelayMs, error.message);\n\t}\n\n\tconst exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;\n\treturn exponentialDelay * (1 - Math.random() * 0.25);\n}\n\nfunction createAbortError(): Error {\n\tconst error = new Error(\"Request aborted\");\n\terror.name = \"AbortError\";\n\treturn error;\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(createAbortError());\n\t\t\treturn;\n\t\t}\n\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(createAbortError());\n\t\t};\n\t\tconst timeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve();\n\t\t\t},\n\t\t\tMath.max(0, ms),\n\t\t);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\n/**\n * Reproduce the retry behavior used by the OpenAI and Anthropic SDKs while making\n * their backoff sleep interruptible. Their built-in retry timers ignore the\n * request AbortSignal, so callers must invoke the SDK with `maxRetries: 0` and\n * wrap the request with this helper. Provider-requested delays above\n * `maxRetryDelayMs` fail immediately (60 seconds by default); set it to zero to\n * disable the limit.\n */\nexport async function retryProviderRequest<T>(\n\trequest: () => Promise<T>,\n\toptions: ProviderRetryOptions = {},\n): Promise<T> {\n\tconst maxRetries = options.maxRetries ?? 0;\n\tlet retriesRemaining = maxRetries;\n\n\tfor (;;) {\n\t\ttry {\n\t\t\t// Each retry is a fresh SDK request, so X-Stainless-Retry-Count remains zero.\n\t\t\treturn await request();\n\t\t} catch (error) {\n\t\t\tif (options.signal?.aborted) throw createAbortError();\n\t\t\tif (retriesRemaining <= 0 || !isProviderError(error) || !isRetryableProviderError(error)) throw error;\n\n\t\t\tconst retryIndex = maxRetries - retriesRemaining;\n\t\t\tretriesRemaining--;\n\t\t\tawait abortableSleep(getRetryDelayMs(error, retryIndex, options.maxRetryDelayMs), options.signal);\n\t\t}\n\t}\n}\n"]}
|
package/dist/utils/retry.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import type { AssistantMessage } from "../types.ts";
|
|
2
|
+
export interface RetryPolicy {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
/** Maximum retry attempts; the initial call is not counted. */
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
/** Base delay in milliseconds. Delay is doubled for each retry attempt. */
|
|
7
|
+
baseDelayMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface RetryCallbacks {
|
|
10
|
+
onRetryScheduled?: (attempt: number, maxAttempts: number, delayMs: number, errorMessage: string) => void | Promise<void>;
|
|
11
|
+
onRetryAttemptStart?: () => void | Promise<void>;
|
|
12
|
+
onRetryFinished?: (success: boolean, attempt: number, finalError?: string) => void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** Run an assistant-producing call with bounded retries for transient failures. */
|
|
15
|
+
export declare function retryAssistantCall(produce: () => Promise<AssistantMessage>, policy: RetryPolicy | undefined, signal: AbortSignal | undefined, callbacks?: RetryCallbacks): Promise<AssistantMessage>;
|
|
2
16
|
export declare function isRetryableAssistantError(message: AssistantMessage): boolean;
|
|
3
17
|
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8DpD,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC9B,gBAAgB,CAAC,EAAE,CAClB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnG;AA8DD,mFAAmF;AACnF,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EACxC,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,SAAS,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAgC3B;AACD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAK5E","sourcesContent":["import type { AssistantMessage } from \"../types.ts\";\n\nfunction buildProviderErrorPattern(patterns: readonly string[]): RegExp {\n\treturn new RegExp(patterns.join(\"|\"), \"i\");\n}\n\nconst NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([\n\t\"GoUsageLimitError\",\n\t\"FreeUsageLimitError\",\n\t\"Monthly usage limit reached\",\n\t\"available balance\",\n\t\"insufficient_quota\",\n\t\"out of budget\",\n\t\"quota exceeded\",\n\t\"billing\",\n]);\n\nconst RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\n\t\"overloaded\",\n\t\"rate.?limit\",\n\t\"too many requests\",\n\t\"429\",\n\t\"500\",\n\t\"502\",\n\t\"503\",\n\t\"504\",\n\t\"524\",\n\t\"service.?unavailable\",\n\t\"server.?error\",\n\t\"internal.?error\",\n\t\"provider.?returned.?error\",\n\t\"exceeded request buffer limit while retrying upstream\",\n\t\"network.?error\",\n\t\"connection.?error\",\n\t\"connection.?refused\",\n\t\"connection.?lost\",\n\t\"other side closed\",\n\t\"fetch failed\",\n\t\"getaddrinfo\",\n\t\"ENOTFOUND\",\n\t\"EAI_AGAIN\",\n\t\"upstream.?connect\",\n\t\"reset before headers\",\n\t\"socket hang up\",\n\t\"socket connection was closed\",\n\t\"timed? out\",\n\t\"timeout\",\n\t\"terminated\",\n\t\"websocket.?closed\",\n\t\"websocket.?error\",\n\t\"ended without\",\n\t\"stream ended before message_stop\",\n\t\"http2 request did not get a response\",\n\t\"retry delay\",\n\t\"you can retry your request\",\n\t\"try your request again\",\n\t\"please retry your request\",\n\n\t// gRPC based providers (e.g. NVIDIA NIM)\n\t\"ResourceExhausted\",\n]);\n\nexport interface RetryPolicy {\n\tenabled: boolean;\n\t/** Maximum retry attempts; the initial call is not counted. */\n\tmaxRetries: number;\n\t/** Base delay in milliseconds. Delay is doubled for each retry attempt. */\n\tbaseDelayMs: number;\n}\n\nexport interface RetryCallbacks {\n\tonRetryScheduled?: (\n\t\tattempt: number,\n\t\tmaxAttempts: number,\n\t\tdelayMs: number,\n\t\terrorMessage: string,\n\t) => void | Promise<void>;\n\tonRetryAttemptStart?: () => void | Promise<void>;\n\tonRetryFinished?: (success: boolean, attempt: number, finalError?: string) => void | Promise<void>;\n}\n\nclass RetrySleepAbortError extends Error {\n\tconstructor() {\n\t\tsuper(\"Aborted\");\n\t}\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new RetrySleepAbortError());\n\t\t\treturn;\n\t\t}\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\treject(new RetrySleepAbortError());\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\ninterface ScheduledAssistantRetry {\n\tattempt: number;\n\terrorMessage: string;\n\tdelayMs: number;\n}\n\nasync function finishAssistantRetry(\n\tretry: ScheduledAssistantRetry | undefined,\n\tcallbacks: RetryCallbacks | undefined,\n\tsuccess: boolean,\n\tfinalError?: string,\n): Promise<void> {\n\tif (!retry || !callbacks?.onRetryFinished) return;\n\tif (finalError === undefined) await callbacks.onRetryFinished(success, retry.attempt);\n\telse await callbacks.onRetryFinished(success, retry.attempt, finalError);\n}\n\nasync function waitForScheduledAssistantRetry(\n\tresponse: AssistantMessage,\n\tretry: ScheduledAssistantRetry,\n\tsignal: AbortSignal | undefined,\n\tcallbacks: RetryCallbacks | undefined,\n): Promise<AssistantMessage | undefined> {\n\ttry {\n\t\tawait sleep(retry.delayMs, signal);\n\t\treturn undefined;\n\t} catch (error) {\n\t\tawait finishAssistantRetry(retry, callbacks, false, retry.errorMessage);\n\t\tif (error instanceof RetrySleepAbortError) {\n\t\t\treturn { ...response, stopReason: \"aborted\", errorMessage: undefined };\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/** Run an assistant-producing call with bounded retries for transient failures. */\nexport async function retryAssistantCall(\n\tproduce: () => Promise<AssistantMessage>,\n\tpolicy: RetryPolicy | undefined,\n\tsignal: AbortSignal | undefined,\n\tcallbacks?: RetryCallbacks,\n): Promise<AssistantMessage> {\n\tconst maxAttempts = policy?.enabled ? policy.maxRetries : 0;\n\tconst baseDelayMs = policy?.baseDelayMs ?? 0;\n\tlet attempt = 0;\n\tlet lastRetry: ScheduledAssistantRetry | undefined;\n\n\tfor (;;) {\n\t\tconst response = await produce();\n\t\tif (response.stopReason === \"aborted\") {\n\t\t\tawait finishAssistantRetry(lastRetry, callbacks, false);\n\t\t\treturn response;\n\t\t}\n\t\tif (response.stopReason !== \"error\") {\n\t\t\tawait finishAssistantRetry(lastRetry, callbacks, true);\n\t\t\treturn response;\n\t\t}\n\t\tif (attempt >= maxAttempts || !isRetryableAssistantError(response)) {\n\t\t\tawait finishAssistantRetry(lastRetry, callbacks, false, response.errorMessage);\n\t\t\treturn response;\n\t\t}\n\n\t\tattempt++;\n\t\tlastRetry = {\n\t\t\tattempt,\n\t\t\terrorMessage: response.errorMessage || \"Unknown error\",\n\t\t\tdelayMs: baseDelayMs * 2 ** (attempt - 1),\n\t\t};\n\t\tawait callbacks?.onRetryScheduled?.(attempt, maxAttempts, lastRetry.delayMs, lastRetry.errorMessage);\n\t\tconst abortedResponse = await waitForScheduledAssistantRetry(response, lastRetry, signal, callbacks);\n\t\tif (abortedResponse) return abortedResponse;\n\t\tawait callbacks?.onRetryAttemptStart?.();\n\t}\n}\nexport function isRetryableAssistantError(message: AssistantMessage): boolean {\n\tif (message.stopReason !== \"error\" || !message.errorMessage) return false;\n\tconst errorMessage = message.errorMessage;\n\tif (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage)) return false;\n\treturn RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);\n}\n"]}
|
package/dist/utils/retry.js
CHANGED
|
@@ -25,15 +25,20 @@ const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
|
|
|
25
25
|
"server.?error",
|
|
26
26
|
"internal.?error",
|
|
27
27
|
"provider.?returned.?error",
|
|
28
|
+
"exceeded request buffer limit while retrying upstream",
|
|
28
29
|
"network.?error",
|
|
29
30
|
"connection.?error",
|
|
30
31
|
"connection.?refused",
|
|
31
32
|
"connection.?lost",
|
|
32
33
|
"other side closed",
|
|
33
34
|
"fetch failed",
|
|
35
|
+
"getaddrinfo",
|
|
36
|
+
"ENOTFOUND",
|
|
37
|
+
"EAI_AGAIN",
|
|
34
38
|
"upstream.?connect",
|
|
35
39
|
"reset before headers",
|
|
36
40
|
"socket hang up",
|
|
41
|
+
"socket connection was closed",
|
|
37
42
|
"timed? out",
|
|
38
43
|
"timeout",
|
|
39
44
|
"terminated",
|
|
@@ -46,7 +51,86 @@ const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
|
|
|
46
51
|
"you can retry your request",
|
|
47
52
|
"try your request again",
|
|
48
53
|
"please retry your request",
|
|
54
|
+
// gRPC based providers (e.g. NVIDIA NIM)
|
|
55
|
+
"ResourceExhausted",
|
|
49
56
|
]);
|
|
57
|
+
class RetrySleepAbortError extends Error {
|
|
58
|
+
constructor() {
|
|
59
|
+
super("Aborted");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function sleep(ms, signal) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
if (signal?.aborted) {
|
|
65
|
+
reject(new RetrySleepAbortError());
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const onAbort = () => {
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
signal?.removeEventListener("abort", onAbort);
|
|
71
|
+
reject(new RetrySleepAbortError());
|
|
72
|
+
};
|
|
73
|
+
const timeout = setTimeout(() => {
|
|
74
|
+
signal?.removeEventListener("abort", onAbort);
|
|
75
|
+
resolve();
|
|
76
|
+
}, ms);
|
|
77
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function finishAssistantRetry(retry, callbacks, success, finalError) {
|
|
81
|
+
if (!retry || !callbacks?.onRetryFinished)
|
|
82
|
+
return;
|
|
83
|
+
if (finalError === undefined)
|
|
84
|
+
await callbacks.onRetryFinished(success, retry.attempt);
|
|
85
|
+
else
|
|
86
|
+
await callbacks.onRetryFinished(success, retry.attempt, finalError);
|
|
87
|
+
}
|
|
88
|
+
async function waitForScheduledAssistantRetry(response, retry, signal, callbacks) {
|
|
89
|
+
try {
|
|
90
|
+
await sleep(retry.delayMs, signal);
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
await finishAssistantRetry(retry, callbacks, false, retry.errorMessage);
|
|
95
|
+
if (error instanceof RetrySleepAbortError) {
|
|
96
|
+
return { ...response, stopReason: "aborted", errorMessage: undefined };
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Run an assistant-producing call with bounded retries for transient failures. */
|
|
102
|
+
export async function retryAssistantCall(produce, policy, signal, callbacks) {
|
|
103
|
+
const maxAttempts = policy?.enabled ? policy.maxRetries : 0;
|
|
104
|
+
const baseDelayMs = policy?.baseDelayMs ?? 0;
|
|
105
|
+
let attempt = 0;
|
|
106
|
+
let lastRetry;
|
|
107
|
+
for (;;) {
|
|
108
|
+
const response = await produce();
|
|
109
|
+
if (response.stopReason === "aborted") {
|
|
110
|
+
await finishAssistantRetry(lastRetry, callbacks, false);
|
|
111
|
+
return response;
|
|
112
|
+
}
|
|
113
|
+
if (response.stopReason !== "error") {
|
|
114
|
+
await finishAssistantRetry(lastRetry, callbacks, true);
|
|
115
|
+
return response;
|
|
116
|
+
}
|
|
117
|
+
if (attempt >= maxAttempts || !isRetryableAssistantError(response)) {
|
|
118
|
+
await finishAssistantRetry(lastRetry, callbacks, false, response.errorMessage);
|
|
119
|
+
return response;
|
|
120
|
+
}
|
|
121
|
+
attempt++;
|
|
122
|
+
lastRetry = {
|
|
123
|
+
attempt,
|
|
124
|
+
errorMessage: response.errorMessage || "Unknown error",
|
|
125
|
+
delayMs: baseDelayMs * 2 ** (attempt - 1),
|
|
126
|
+
};
|
|
127
|
+
await callbacks?.onRetryScheduled?.(attempt, maxAttempts, lastRetry.delayMs, lastRetry.errorMessage);
|
|
128
|
+
const abortedResponse = await waitForScheduledAssistantRetry(response, lastRetry, signal, callbacks);
|
|
129
|
+
if (abortedResponse)
|
|
130
|
+
return abortedResponse;
|
|
131
|
+
await callbacks?.onRetryAttemptStart?.();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
50
134
|
export function isRetryableAssistantError(message) {
|
|
51
135
|
if (message.stopReason !== "error" || !message.errorMessage)
|
|
52
136
|
return false;
|
package/dist/utils/retry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAEA,SAAS,yBAAyB,CAAC,QAA2B,EAAU;IACvE,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;AAAA,CAC3C;AAED,MAAM,0CAA0C,GAAG,yBAAyB,CAAC;IAC5E,mBAAmB;IACnB,qBAAqB;IACrB,6BAA6B;IAC7B,mBAAmB;IACnB,oBAAoB;IACpB,eAAe;IACf,gBAAgB;IAChB,SAAS;CACT,CAAC,CAAC;AAEH,MAAM,gCAAgC,GAAG,yBAAyB,CAAC;IAClE,YAAY;IACZ,aAAa;IACb,mBAAmB;IACnB,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,sBAAsB;IACtB,eAAe;IACf,iBAAiB;IACjB,2BAA2B;IAC3B,gBAAgB;IAChB,mBAAmB;IACnB,qBAAqB;IACrB,kBAAkB;IAClB,mBAAmB;IACnB,cAAc;IACd,mBAAmB;IACnB,sBAAsB;IACtB,gBAAgB;IAChB,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,mBAAmB;IACnB,kBAAkB;IAClB,eAAe;IACf,kCAAkC;IAClC,sCAAsC;IACtC,aAAa;IACb,4BAA4B;IAC5B,wBAAwB;IACxB,2BAA2B;
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"../types.ts\";\n\nfunction buildProviderErrorPattern(patterns: readonly string[]): RegExp {\n\treturn new RegExp(patterns.join(\"|\"), \"i\");\n}\n\nconst NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([\n\t\"GoUsageLimitError\",\n\t\"FreeUsageLimitError\",\n\t\"Monthly usage limit reached\",\n\t\"available balance\",\n\t\"insufficient_quota\",\n\t\"out of budget\",\n\t\"quota exceeded\",\n\t\"billing\",\n]);\n\nconst RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([\n\t\"overloaded\",\n\t\"rate.?limit\",\n\t\"too many requests\",\n\t\"429\",\n\t\"500\",\n\t\"502\",\n\t\"503\",\n\t\"504\",\n\t\"524\",\n\t\"service.?unavailable\",\n\t\"server.?error\",\n\t\"internal.?error\",\n\t\"provider.?returned.?error\",\n\t\"exceeded request buffer limit while retrying upstream\",\n\t\"network.?error\",\n\t\"connection.?error\",\n\t\"connection.?refused\",\n\t\"connection.?lost\",\n\t\"other side closed\",\n\t\"fetch failed\",\n\t\"getaddrinfo\",\n\t\"ENOTFOUND\",\n\t\"EAI_AGAIN\",\n\t\"upstream.?connect\",\n\t\"reset before headers\",\n\t\"socket hang up\",\n\t\"socket connection was closed\",\n\t\"timed? out\",\n\t\"timeout\",\n\t\"terminated\",\n\t\"websocket.?closed\",\n\t\"websocket.?error\",\n\t\"ended without\",\n\t\"stream ended before message_stop\",\n\t\"http2 request did not get a response\",\n\t\"retry delay\",\n\t\"you can retry your request\",\n\t\"try your request again\",\n\t\"please retry your request\",\n\n\t// gRPC based providers (e.g. NVIDIA NIM)\n\t\"ResourceExhausted\",\n]);\n\nexport interface RetryPolicy {\n\tenabled: boolean;\n\t/** Maximum retry attempts; the initial call is not counted. */\n\tmaxRetries: number;\n\t/** Base delay in milliseconds. Delay is doubled for each retry attempt. */\n\tbaseDelayMs: number;\n}\n\nexport interface RetryCallbacks {\n\tonRetryScheduled?: (\n\t\tattempt: number,\n\t\tmaxAttempts: number,\n\t\tdelayMs: number,\n\t\terrorMessage: string,\n\t) => void | Promise<void>;\n\tonRetryAttemptStart?: () => void | Promise<void>;\n\tonRetryFinished?: (success: boolean, attempt: number, finalError?: string) => void | Promise<void>;\n}\n\nclass RetrySleepAbortError extends Error {\n\tconstructor() {\n\t\tsuper(\"Aborted\");\n\t}\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new RetrySleepAbortError());\n\t\t\treturn;\n\t\t}\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\treject(new RetrySleepAbortError());\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\ninterface ScheduledAssistantRetry {\n\tattempt: number;\n\terrorMessage: string;\n\tdelayMs: number;\n}\n\nasync function finishAssistantRetry(\n\tretry: ScheduledAssistantRetry | undefined,\n\tcallbacks: RetryCallbacks | undefined,\n\tsuccess: boolean,\n\tfinalError?: string,\n): Promise<void> {\n\tif (!retry || !callbacks?.onRetryFinished) return;\n\tif (finalError === undefined) await callbacks.onRetryFinished(success, retry.attempt);\n\telse await callbacks.onRetryFinished(success, retry.attempt, finalError);\n}\n\nasync function waitForScheduledAssistantRetry(\n\tresponse: AssistantMessage,\n\tretry: ScheduledAssistantRetry,\n\tsignal: AbortSignal | undefined,\n\tcallbacks: RetryCallbacks | undefined,\n): Promise<AssistantMessage | undefined> {\n\ttry {\n\t\tawait sleep(retry.delayMs, signal);\n\t\treturn undefined;\n\t} catch (error) {\n\t\tawait finishAssistantRetry(retry, callbacks, false, retry.errorMessage);\n\t\tif (error instanceof RetrySleepAbortError) {\n\t\t\treturn { ...response, stopReason: \"aborted\", errorMessage: undefined };\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/** Run an assistant-producing call with bounded retries for transient failures. */\nexport async function retryAssistantCall(\n\tproduce: () => Promise<AssistantMessage>,\n\tpolicy: RetryPolicy | undefined,\n\tsignal: AbortSignal | undefined,\n\tcallbacks?: RetryCallbacks,\n): Promise<AssistantMessage> {\n\tconst maxAttempts = policy?.enabled ? policy.maxRetries : 0;\n\tconst baseDelayMs = policy?.baseDelayMs ?? 0;\n\tlet attempt = 0;\n\tlet lastRetry: ScheduledAssistantRetry | undefined;\n\n\tfor (;;) {\n\t\tconst response = await produce();\n\t\tif (response.stopReason === \"aborted\") {\n\t\t\tawait finishAssistantRetry(lastRetry, callbacks, false);\n\t\t\treturn response;\n\t\t}\n\t\tif (response.stopReason !== \"error\") {\n\t\t\tawait finishAssistantRetry(lastRetry, callbacks, true);\n\t\t\treturn response;\n\t\t}\n\t\tif (attempt >= maxAttempts || !isRetryableAssistantError(response)) {\n\t\t\tawait finishAssistantRetry(lastRetry, callbacks, false, response.errorMessage);\n\t\t\treturn response;\n\t\t}\n\n\t\tattempt++;\n\t\tlastRetry = {\n\t\t\tattempt,\n\t\t\terrorMessage: response.errorMessage || \"Unknown error\",\n\t\t\tdelayMs: baseDelayMs * 2 ** (attempt - 1),\n\t\t};\n\t\tawait callbacks?.onRetryScheduled?.(attempt, maxAttempts, lastRetry.delayMs, lastRetry.errorMessage);\n\t\tconst abortedResponse = await waitForScheduledAssistantRetry(response, lastRetry, signal, callbacks);\n\t\tif (abortedResponse) return abortedResponse;\n\t\tawait callbacks?.onRetryAttemptStart?.();\n\t}\n}\nexport function isRetryableAssistantError(message: AssistantMessage): boolean {\n\tif (message.stopReason !== \"error\" || !message.errorMessage) return false;\n\tconst errorMessage = message.errorMessage;\n\tif (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage)) return false;\n\treturn RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);\n}\n"]}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type TUnsafe } from "typebox";
|
|
2
|
+
export interface StringEnumOptions<TValue extends string> {
|
|
3
|
+
description?: string;
|
|
4
|
+
default?: TValue;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* Creates a string enum schema compatible with Google's API and other providers
|
|
4
8
|
* that don't support anyOf/const patterns.
|
|
@@ -10,8 +14,5 @@ import { type TUnsafe } from "typebox";
|
|
|
10
14
|
*
|
|
11
15
|
* type Operation = Static<typeof OperationSchema>; // "add" | "subtract" | "multiply" | "divide"
|
|
12
16
|
*/
|
|
13
|
-
export declare function StringEnum<T extends readonly string[]>(values: T, options?:
|
|
14
|
-
description?: string;
|
|
15
|
-
default?: T[number];
|
|
16
|
-
}): TUnsafe<T[number]>;
|
|
17
|
+
export declare function StringEnum<T extends readonly string[]>(values: T, options?: StringEnumOptions<T[number]>): TUnsafe<T[number]>;
|
|
17
18
|
//# sourceMappingURL=typebox-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typebox-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/typebox-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,SAAS,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EACrD,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE
|
|
1
|
+
{"version":3,"file":"typebox-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/typebox-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,iBAAiB,CAAC,MAAM,SAAS,MAAM;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EACrD,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GACpC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAOpB","sourcesContent":["import { type TUnsafe, Type } from \"typebox\";\n\nexport interface StringEnumOptions<TValue extends string> {\n\tdescription?: string;\n\tdefault?: TValue;\n}\n\n/**\n * Creates a string enum schema compatible with Google's API and other providers\n * that don't support anyOf/const patterns.\n *\n * @example\n * const OperationSchema = StringEnum([\"add\", \"subtract\", \"multiply\", \"divide\"], {\n * description: \"The operation to perform\"\n * });\n *\n * type Operation = Static<typeof OperationSchema>; // \"add\" | \"subtract\" | \"multiply\" | \"divide\"\n */\nexport function StringEnum<T extends readonly string[]>(\n\tvalues: T,\n\toptions?: StringEnumOptions<T[number]>,\n): TUnsafe<T[number]> {\n\treturn Type.Unsafe<T[number]>({\n\t\ttype: \"string\",\n\t\tenum: values as any,\n\t\t...(options?.description && { description: options.description }),\n\t\t...(options?.default && { default: options.default }),\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typebox-helpers.js","sourceRoot":"","sources":["../../src/utils/typebox-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"typebox-helpers.js","sourceRoot":"","sources":["../../src/utils/typebox-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,IAAI,EAAE,MAAM,SAAS,CAAC;AAO7C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CACzB,MAAS,EACT,OAAsC,EACjB;IACrB,OAAO,IAAI,CAAC,MAAM,CAAY;QAC7B,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,MAAa;QACnB,GAAG,CAAC,OAAO,EAAE,WAAW,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;QACjE,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;KACrD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { type TUnsafe, Type } from \"typebox\";\n\nexport interface StringEnumOptions<TValue extends string> {\n\tdescription?: string;\n\tdefault?: TValue;\n}\n\n/**\n * Creates a string enum schema compatible with Google's API and other providers\n * that don't support anyOf/const patterns.\n *\n * @example\n * const OperationSchema = StringEnum([\"add\", \"subtract\", \"multiply\", \"divide\"], {\n * description: \"The operation to perform\"\n * });\n *\n * type Operation = Static<typeof OperationSchema>; // \"add\" | \"subtract\" | \"multiply\" | \"divide\"\n */\nexport function StringEnum<T extends readonly string[]>(\n\tvalues: T,\n\toptions?: StringEnumOptions<T[number]>,\n): TUnsafe<T[number]> {\n\treturn Type.Unsafe<T[number]>({\n\t\ttype: \"string\",\n\t\tenum: values as any,\n\t\t...(options?.description && { description: options.description }),\n\t\t...(options?.default && { default: options.default }),\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA0QlD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAMvE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAgCzE","sourcesContent":["import { Compile } from \"typebox/compile\";\nimport type { TLocalizedValidationError } from \"typebox/error\";\nimport { Value } from \"typebox/value\";\nimport type { Tool, ToolCall } from \"../types.ts\";\n\nconst validatorCache = new WeakMap<object, ReturnType<typeof Compile>>();\nconst TYPEBOX_KIND = Symbol.for(\"TypeBox.Kind\");\n\ninterface JsonSchemaObject {\n\ttype?: string | string[];\n\tproperties?: Record<string, JsonSchemaObject>;\n\titems?: JsonSchemaObject | JsonSchemaObject[];\n\tadditionalProperties?: boolean | JsonSchemaObject;\n\tallOf?: JsonSchemaObject[];\n\tanyOf?: JsonSchemaObject[];\n\toneOf?: JsonSchemaObject[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isJsonSchemaObject(value: unknown): value is JsonSchemaObject {\n\treturn isRecord(value);\n}\n\nfunction hasTypeBoxMetadata(schema: unknown): boolean {\n\treturn isRecord(schema) && Object.getOwnPropertySymbols(schema).includes(TYPEBOX_KIND);\n}\n\nfunction getSchemaTypes(schema: JsonSchemaObject): string[] {\n\tif (typeof schema.type === \"string\") {\n\t\treturn [schema.type];\n\t}\n\tif (Array.isArray(schema.type)) {\n\t\treturn schema.type.filter((type): type is string => typeof type === \"string\");\n\t}\n\treturn [];\n}\n\nfunction matchesJsonType(value: unknown, type: string): boolean {\n\tswitch (type) {\n\t\tcase \"number\":\n\t\t\treturn typeof value === \"number\";\n\t\tcase \"integer\":\n\t\t\treturn typeof value === \"number\" && Number.isInteger(value);\n\t\tcase \"boolean\":\n\t\t\treturn typeof value === \"boolean\";\n\t\tcase \"string\":\n\t\t\treturn typeof value === \"string\";\n\t\tcase \"null\":\n\t\t\treturn value === null;\n\t\tcase \"array\":\n\t\t\treturn Array.isArray(value);\n\t\tcase \"object\":\n\t\t\treturn isRecord(value) && !Array.isArray(value);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction isValidatorSchema(value: unknown): value is Tool[\"parameters\"] {\n\treturn isRecord(value);\n}\n\nfunction getSubSchemaValidator(schema: JsonSchemaObject): ReturnType<typeof Compile> | undefined {\n\tif (!isValidatorSchema(schema)) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn getValidator(schema);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction coercePrimitiveByType(value: unknown, type: string): unknown {\n\tswitch (type) {\n\t\tcase \"number\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\t\t\tconst parsed = Number(value);\n\t\t\t\tif (Number.isFinite(parsed)) {\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? 1 : 0;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"integer\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\t\t\tconst parsed = Number(value);\n\t\t\t\tif (Number.isInteger(parsed)) {\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? 1 : 0;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"boolean\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (typeof value === \"string\") {\n\t\t\t\tif (value === \"true\") {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (value === \"false\") {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof value === \"number\") {\n\t\t\t\tif (value === 1) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (value === 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"string\": {\n\t\t\tif (value === null) {\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\tif (typeof value === \"number\" || typeof value === \"boolean\") {\n\t\t\t\treturn String(value);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tcase \"null\": {\n\t\t\tif (value === \"\" || value === 0 || value === false) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t\tdefault:\n\t\t\treturn value;\n\t}\n}\n\nfunction applySchemaObjectCoercion(value: Record<string, unknown>, schema: JsonSchemaObject): void {\n\tconst properties = schema.properties;\n\tconst definedKeys = new Set<string>(properties ? Object.keys(properties) : []);\n\n\tif (properties) {\n\t\tfor (const [key, propertySchema] of Object.entries(properties)) {\n\t\t\tif (!(key in value)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(value[key], propertySchema);\n\t\t}\n\t}\n\n\tif (schema.additionalProperties && isJsonSchemaObject(schema.additionalProperties)) {\n\t\tfor (const [key, propertyValue] of Object.entries(value)) {\n\t\t\tif (definedKeys.has(key)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(propertyValue, schema.additionalProperties);\n\t\t}\n\t}\n}\n\nfunction applySchemaArrayCoercion(value: unknown[], schema: JsonSchemaObject): void {\n\tif (Array.isArray(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tconst itemSchema = schema.items[index];\n\t\t\tif (!itemSchema) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], itemSchema);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (isJsonSchemaObject(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], schema.items);\n\t\t}\n\t}\n}\n\nfunction coerceWithUnionSchema(value: unknown, schemas: JsonSchemaObject[]): unknown {\n\tfor (const schema of schemas) {\n\t\tconst candidate = structuredClone(value);\n\t\tconst coerced = coerceWithJsonSchema(candidate, schema);\n\t\tconst validator = getSubSchemaValidator(schema);\n\t\tif (validator?.Check(coerced)) {\n\t\t\treturn coerced;\n\t\t}\n\t}\n\treturn value;\n}\n\nfunction coerceWithJsonSchema(value: unknown, schema: JsonSchemaObject): unknown {\n\tlet nextValue = value;\n\n\tif (Array.isArray(schema.allOf)) {\n\t\tfor (const nested of schema.allOf) {\n\t\t\tnextValue = coerceWithJsonSchema(nextValue, nested);\n\t\t}\n\t}\n\n\tif (Array.isArray(schema.anyOf)) {\n\t\tnextValue = coerceWithUnionSchema(nextValue, schema.anyOf);\n\t}\n\n\tif (Array.isArray(schema.oneOf)) {\n\t\tnextValue = coerceWithUnionSchema(nextValue, schema.oneOf);\n\t}\n\n\tconst schemaTypes = getSchemaTypes(schema);\n\tconst matchesUnionMember =\n\t\tschemaTypes.length > 1 && schemaTypes.some((schemaType) => matchesJsonType(nextValue, schemaType));\n\tif (schemaTypes.length > 0 && !matchesUnionMember) {\n\t\tfor (const schemaType of schemaTypes) {\n\t\t\tconst candidate = coercePrimitiveByType(nextValue, schemaType);\n\t\t\tif (candidate !== nextValue) {\n\t\t\t\tnextValue = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (schemaTypes.includes(\"object\") && isRecord(nextValue) && !Array.isArray(nextValue)) {\n\t\tapplySchemaObjectCoercion(nextValue, schema);\n\t}\n\n\tif (schemaTypes.includes(\"array\") && Array.isArray(nextValue)) {\n\t\tapplySchemaArrayCoercion(nextValue, schema);\n\t}\n\n\treturn nextValue;\n}\n\nfunction getValidator(schema: Tool[\"parameters\"]): ReturnType<typeof Compile> {\n\tconst key = schema as object;\n\tconst cached = validatorCache.get(key);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\tconst validator = Compile(schema);\n\tvalidatorCache.set(key, validator);\n\treturn validator;\n}\n\nfunction formatValidationPath(error: TLocalizedValidationError): string {\n\tif (error.keyword === \"required\") {\n\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\tconst requiredProperty = requiredProperties?.[0];\n\t\tif (requiredProperty) {\n\t\t\tconst basePath = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\t\t\treturn basePath ? `${basePath}.${requiredProperty}` : requiredProperty;\n\t\t}\n\t}\n\tconst path = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\treturn path || \"root\";\n}\n\n/**\n * Finds a tool by name and validates the tool call arguments against its TypeBox schema\n * @param tools Array of tool definitions\n * @param toolCall The tool call from the LLM\n * @returns The validated arguments\n * @throws Error if tool is not found or validation fails\n */\nexport function validateToolCall(tools: Tool[], toolCall: ToolCall): any {\n\tconst tool = tools.find((t) => t.name === toolCall.name);\n\tif (!tool) {\n\t\tthrow new Error(`Tool \"${toolCall.name}\" not found`);\n\t}\n\treturn validateToolArguments(tool, toolCall);\n}\n\n/**\n * Validates tool call arguments against the tool's TypeBox schema\n * @param tool The tool definition with TypeBox schema\n * @param toolCall The tool call from the LLM\n * @returns The validated (and potentially coerced) arguments\n * @throws Error with formatted message if validation fails\n */\nexport function validateToolArguments(tool: Tool, toolCall: ToolCall): any {\n\tconst args = structuredClone(toolCall.arguments);\n\tValue.Convert(tool.parameters, args);\n\n\tconst validator = getValidator(tool.parameters);\n\tif (!hasTypeBoxMetadata(tool.parameters) && isJsonSchemaObject(tool.parameters)) {\n\t\tconst coerced = coerceWithJsonSchema(args, tool.parameters);\n\t\tif (coerced !== args) {\n\t\t\tif (isRecord(args) && isRecord(coerced)) {\n\t\t\t\tfor (const key of Object.keys(args)) {\n\t\t\t\t\tdelete args[key];\n\t\t\t\t}\n\t\t\t\tObject.assign(args, coerced);\n\t\t\t} else {\n\t\t\t\treturn validator.Check(coerced) ? coerced : args;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (validator.Check(args)) {\n\t\treturn args;\n\t}\n\n\tconst errors =\n\t\tvalidator\n\t\t\t.Errors(args)\n\t\t\t.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)\n\t\t\t.join(\"\\n\") || \"Unknown validation error\";\n\n\tconst errorMessage = `Validation failed for tool \"${toolCall.name}\":\\n${errors}\\n\\nReceived arguments:\\n${JSON.stringify(toolCall.arguments, null, 2)}`;\n\n\tthrow new Error(errorMessage);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA4PlD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAMvE;AAyBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAuBzE","sourcesContent":["import type { TSchema } from \"typebox\";\nimport { Compile, type Validator } from \"typebox/compile\";\nimport type { TLocalizedValidationError } from \"typebox/error\";\nimport { Value } from \"typebox/value\";\nimport type { Tool, ToolCall } from \"../types.ts\";\n\nconst validatorCache = new WeakMap<object, Validator>();\nconst TYPEBOX_KIND = Symbol.for(\"TypeBox.Kind\");\n\ninterface JsonSchemaObject {\n\ttype?: string | string[];\n\tproperties?: Record<string, JsonSchemaObject>;\n\titems?: JsonSchemaObject | JsonSchemaObject[];\n\tadditionalProperties?: boolean | JsonSchemaObject;\n\tallOf?: JsonSchemaObject[];\n\tanyOf?: JsonSchemaObject[];\n\toneOf?: JsonSchemaObject[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isJsonSchemaObject(value: unknown): value is JsonSchemaObject {\n\treturn isRecord(value);\n}\n\nfunction hasTypeBoxMetadata(schema: unknown): boolean {\n\treturn (\n\t\tisRecord(schema) &&\n\t\t(typeof schema[\"~kind\"] === \"string\" || Object.getOwnPropertySymbols(schema).includes(TYPEBOX_KIND))\n\t);\n}\n\nfunction getSchemaTypes(schema: JsonSchemaObject): string[] {\n\tif (typeof schema.type === \"string\") {\n\t\treturn [schema.type];\n\t}\n\tif (Array.isArray(schema.type)) {\n\t\treturn schema.type.filter((type): type is string => typeof type === \"string\");\n\t}\n\treturn [];\n}\n\nfunction matchesJsonType(value: unknown, type: string): boolean {\n\tswitch (type) {\n\t\tcase \"number\":\n\t\t\treturn typeof value === \"number\";\n\t\tcase \"integer\":\n\t\t\treturn typeof value === \"number\" && Number.isInteger(value);\n\t\tcase \"boolean\":\n\t\t\treturn typeof value === \"boolean\";\n\t\tcase \"string\":\n\t\t\treturn typeof value === \"string\";\n\t\tcase \"null\":\n\t\t\treturn value === null;\n\t\tcase \"array\":\n\t\t\treturn Array.isArray(value);\n\t\tcase \"object\":\n\t\t\treturn isRecord(value) && !Array.isArray(value);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction isValidatorSchema(value: unknown): value is TSchema {\n\treturn isRecord(value);\n}\n\nfunction getSubSchemaValidator(schema: JsonSchemaObject): Validator | undefined {\n\tif (!isValidatorSchema(schema)) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn getValidator(schema);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction coerceJsonNumber(value: unknown): unknown {\n\tif (value === null) return 0;\n\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\tconst parsed = Number(value);\n\t\tif (Number.isFinite(parsed)) return parsed;\n\t}\n\tif (typeof value === \"boolean\") return value ? 1 : 0;\n\treturn value;\n}\n\nfunction coerceJsonInteger(value: unknown): unknown {\n\tif (value === null) return 0;\n\tif (typeof value === \"string\" && value.trim() !== \"\") {\n\t\tconst parsed = Number(value);\n\t\tif (Number.isInteger(parsed)) return parsed;\n\t}\n\tif (typeof value === \"boolean\") return value ? 1 : 0;\n\treturn value;\n}\n\nfunction coerceJsonBoolean(value: unknown): unknown {\n\tif (value === null) return false;\n\tif (value === \"true\" || value === 1) return true;\n\tif (value === \"false\" || value === 0) return false;\n\treturn value;\n}\n\nfunction coerceJsonString(value: unknown): unknown {\n\tif (value === null) return \"\";\n\tif (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n\treturn value;\n}\n\nfunction coerceJsonNull(value: unknown): unknown {\n\treturn value === \"\" || value === 0 || value === false ? null : value;\n}\n\nfunction coercePrimitiveByType(value: unknown, type: string): unknown {\n\tswitch (type) {\n\t\tcase \"number\":\n\t\t\treturn coerceJsonNumber(value);\n\t\tcase \"integer\":\n\t\t\treturn coerceJsonInteger(value);\n\t\tcase \"boolean\":\n\t\t\treturn coerceJsonBoolean(value);\n\t\tcase \"string\":\n\t\t\treturn coerceJsonString(value);\n\t\tcase \"null\":\n\t\t\treturn coerceJsonNull(value);\n\t\tdefault:\n\t\t\treturn value;\n\t}\n}\n\nfunction applySchemaObjectCoercion(value: Record<string, unknown>, schema: JsonSchemaObject): void {\n\tconst properties = schema.properties;\n\tconst definedKeys = new Set<string>(properties ? Object.keys(properties) : []);\n\n\tif (properties) {\n\t\tfor (const [key, propertySchema] of Object.entries(properties)) {\n\t\t\tif (!(key in value)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(value[key], propertySchema);\n\t\t}\n\t}\n\n\tif (schema.additionalProperties && isJsonSchemaObject(schema.additionalProperties)) {\n\t\tfor (const [key, propertyValue] of Object.entries(value)) {\n\t\t\tif (definedKeys.has(key)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[key] = coerceWithJsonSchema(propertyValue, schema.additionalProperties);\n\t\t}\n\t}\n}\n\nfunction applySchemaArrayCoercion(value: unknown[], schema: JsonSchemaObject): void {\n\tif (Array.isArray(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tconst itemSchema = schema.items[index];\n\t\t\tif (!itemSchema) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], itemSchema);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (isJsonSchemaObject(schema.items)) {\n\t\tfor (let index = 0; index < value.length; index++) {\n\t\t\tvalue[index] = coerceWithJsonSchema(value[index], schema.items);\n\t\t}\n\t}\n}\n\nfunction coerceWithUnionSchema(value: unknown, schemas: JsonSchemaObject[]): unknown {\n\tfor (const schema of schemas) {\n\t\tconst validator = getSubSchemaValidator(schema);\n\t\tif (validator?.Check(value)) {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\tfor (const schema of schemas) {\n\t\tconst candidate = structuredClone(value);\n\t\tconst coerced = coerceWithJsonSchema(candidate, schema);\n\t\tconst validator = getSubSchemaValidator(schema);\n\t\tif (validator?.Check(coerced)) {\n\t\t\treturn coerced;\n\t\t}\n\t}\n\treturn value;\n}\n\nfunction applyCombinedSchemaCoercion(value: unknown, schema: JsonSchemaObject): unknown {\n\tlet nextValue = value;\n\tif (Array.isArray(schema.allOf)) {\n\t\tfor (const nested of schema.allOf) nextValue = coerceWithJsonSchema(nextValue, nested);\n\t}\n\tif (Array.isArray(schema.anyOf)) nextValue = coerceWithUnionSchema(nextValue, schema.anyOf);\n\tif (Array.isArray(schema.oneOf)) nextValue = coerceWithUnionSchema(nextValue, schema.oneOf);\n\treturn nextValue;\n}\n\nfunction coerceToDeclaredSchemaType(value: unknown, schemaTypes: string[]): unknown {\n\tconst matchesUnionMember =\n\t\tschemaTypes.length > 1 && schemaTypes.some((schemaType) => matchesJsonType(value, schemaType));\n\tif (schemaTypes.length === 0 || matchesUnionMember) return value;\n\tfor (const schemaType of schemaTypes) {\n\t\tconst candidate = coercePrimitiveByType(value, schemaType);\n\t\tif (candidate !== value) return candidate;\n\t}\n\treturn value;\n}\n\nfunction coerceWithJsonSchema(value: unknown, schema: JsonSchemaObject): unknown {\n\tlet nextValue = applyCombinedSchemaCoercion(value, schema);\n\tconst schemaTypes = getSchemaTypes(schema);\n\tnextValue = coerceToDeclaredSchemaType(nextValue, schemaTypes);\n\n\tif (schemaTypes.includes(\"object\") && isRecord(nextValue) && !Array.isArray(nextValue)) {\n\t\tapplySchemaObjectCoercion(nextValue, schema);\n\t}\n\n\tif (schemaTypes.includes(\"array\") && Array.isArray(nextValue)) {\n\t\tapplySchemaArrayCoercion(nextValue, schema);\n\t}\n\n\treturn nextValue;\n}\n\nfunction getValidator(schema: TSchema): Validator {\n\tconst key = schema as object;\n\tconst cached = validatorCache.get(key);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\tconst validator = Compile(schema);\n\tvalidatorCache.set(key, validator);\n\treturn validator;\n}\n\nfunction formatValidationPath(error: TLocalizedValidationError): string {\n\tif (error.keyword === \"required\") {\n\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\tconst requiredProperty = requiredProperties?.[0];\n\t\tif (requiredProperty) {\n\t\t\tconst basePath = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\t\t\treturn basePath ? `${basePath}.${requiredProperty}` : requiredProperty;\n\t\t}\n\t}\n\tconst path = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\treturn path || \"root\";\n}\n\n/**\n * Finds a tool by name and validates the tool call arguments against its TypeBox schema\n * @param tools Array of tool definitions\n * @param toolCall The tool call from the LLM\n * @returns The validated arguments\n * @throws Error if tool is not found or validation fails\n */\nexport function validateToolCall(tools: Tool[], toolCall: ToolCall): any {\n\tconst tool = tools.find((t) => t.name === toolCall.name);\n\tif (!tool) {\n\t\tthrow new Error(`Tool \"${toolCall.name}\" not found`);\n\t}\n\treturn validateToolArguments(tool, toolCall);\n}\n\ninterface SerializedSchemaValidationResult {\n\tvalid: boolean;\n\tvalue: unknown;\n}\n\nfunction validateSerializedSchemaArguments(\n\targs: unknown,\n\tschema: TSchema,\n\tvalidator: Validator,\n): SerializedSchemaValidationResult {\n\tif (hasTypeBoxMetadata(schema) || !isJsonSchemaObject(schema)) return { valid: false, value: args };\n\tconst coerced = coerceWithJsonSchema(args, schema);\n\tif (coerced !== args) {\n\t\tif (isRecord(args) && isRecord(coerced)) {\n\t\t\tfor (const key of Object.keys(args)) delete args[key];\n\t\t\tObject.assign(args, coerced);\n\t\t} else if (validator.Check(coerced)) {\n\t\t\treturn { valid: true, value: coerced };\n\t\t}\n\t}\n\treturn { valid: validator.Check(args), value: args };\n}\n\n/**\n * Validates tool call arguments against the tool's TypeBox schema\n * @param tool The tool definition with TypeBox schema\n * @param toolCall The tool call from the LLM\n * @returns The validated (and potentially coerced) arguments\n * @throws Error with formatted message if validation fails\n */\nexport function validateToolArguments(tool: Tool, toolCall: ToolCall): any {\n\tconst args = structuredClone(toolCall.arguments);\n\tconst validator = getValidator(tool.parameters);\n\n\t// Serialized schemas use the compatibility coercer first so valid union arms are not changed\n\t// by TypeBox conversion before the schema-specific checks can preserve them.\n\tconst serializedValidation = validateSerializedSchemaArguments(args, tool.parameters, validator);\n\tif (serializedValidation.valid) return serializedValidation.value;\n\n\tValue.Convert(tool.parameters, args);\n\tif (validator.Check(args)) {\n\t\treturn args;\n\t}\n\n\tconst errors =\n\t\tvalidator\n\t\t\t.Errors(args)\n\t\t\t.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)\n\t\t\t.join(\"\\n\") || \"Unknown validation error\";\n\n\tconst errorMessage = `Validation failed for tool \"${toolCall.name}\":\\n${errors}\\n\\nReceived arguments:\\n${JSON.stringify(toolCall.arguments, null, 2)}`;\n\n\tthrow new Error(errorMessage);\n}\n"]}
|
package/dist/utils/validation.js
CHANGED
|
@@ -9,7 +9,8 @@ function isJsonSchemaObject(value) {
|
|
|
9
9
|
return isRecord(value);
|
|
10
10
|
}
|
|
11
11
|
function hasTypeBoxMetadata(schema) {
|
|
12
|
-
return isRecord(schema) &&
|
|
12
|
+
return (isRecord(schema) &&
|
|
13
|
+
(typeof schema["~kind"] === "string" || Object.getOwnPropertySymbols(schema).includes(TYPEBOX_KIND)));
|
|
13
14
|
}
|
|
14
15
|
function getSchemaTypes(schema) {
|
|
15
16
|
if (typeof schema.type === "string") {
|
|
@@ -54,75 +55,61 @@ function getSubSchemaValidator(schema) {
|
|
|
54
55
|
return undefined;
|
|
55
56
|
}
|
|
56
57
|
}
|
|
58
|
+
function coerceJsonNumber(value) {
|
|
59
|
+
if (value === null)
|
|
60
|
+
return 0;
|
|
61
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
62
|
+
const parsed = Number(value);
|
|
63
|
+
if (Number.isFinite(parsed))
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === "boolean")
|
|
67
|
+
return value ? 1 : 0;
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function coerceJsonInteger(value) {
|
|
71
|
+
if (value === null)
|
|
72
|
+
return 0;
|
|
73
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
74
|
+
const parsed = Number(value);
|
|
75
|
+
if (Number.isInteger(parsed))
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === "boolean")
|
|
79
|
+
return value ? 1 : 0;
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
function coerceJsonBoolean(value) {
|
|
83
|
+
if (value === null)
|
|
84
|
+
return false;
|
|
85
|
+
if (value === "true" || value === 1)
|
|
86
|
+
return true;
|
|
87
|
+
if (value === "false" || value === 0)
|
|
88
|
+
return false;
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function coerceJsonString(value) {
|
|
92
|
+
if (value === null)
|
|
93
|
+
return "";
|
|
94
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
95
|
+
return String(value);
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function coerceJsonNull(value) {
|
|
99
|
+
return value === "" || value === 0 || value === false ? null : value;
|
|
100
|
+
}
|
|
57
101
|
function coercePrimitiveByType(value, type) {
|
|
58
102
|
switch (type) {
|
|
59
|
-
case "number":
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (typeof value === "boolean") {
|
|
70
|
-
return value ? 1 : 0;
|
|
71
|
-
}
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
case "integer": {
|
|
75
|
-
if (value === null) {
|
|
76
|
-
return 0;
|
|
77
|
-
}
|
|
78
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
79
|
-
const parsed = Number(value);
|
|
80
|
-
if (Number.isInteger(parsed)) {
|
|
81
|
-
return parsed;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (typeof value === "boolean") {
|
|
85
|
-
return value ? 1 : 0;
|
|
86
|
-
}
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
case "boolean": {
|
|
90
|
-
if (value === null) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
if (typeof value === "string") {
|
|
94
|
-
if (value === "true") {
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
if (value === "false") {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (typeof value === "number") {
|
|
102
|
-
if (value === 1) {
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
if (value === 0) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return value;
|
|
110
|
-
}
|
|
111
|
-
case "string": {
|
|
112
|
-
if (value === null) {
|
|
113
|
-
return "";
|
|
114
|
-
}
|
|
115
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
116
|
-
return String(value);
|
|
117
|
-
}
|
|
118
|
-
return value;
|
|
119
|
-
}
|
|
120
|
-
case "null": {
|
|
121
|
-
if (value === "" || value === 0 || value === false) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
return value;
|
|
125
|
-
}
|
|
103
|
+
case "number":
|
|
104
|
+
return coerceJsonNumber(value);
|
|
105
|
+
case "integer":
|
|
106
|
+
return coerceJsonInteger(value);
|
|
107
|
+
case "boolean":
|
|
108
|
+
return coerceJsonBoolean(value);
|
|
109
|
+
case "string":
|
|
110
|
+
return coerceJsonString(value);
|
|
111
|
+
case "null":
|
|
112
|
+
return coerceJsonNull(value);
|
|
126
113
|
default:
|
|
127
114
|
return value;
|
|
128
115
|
}
|
|
@@ -165,6 +152,12 @@ function applySchemaArrayCoercion(value, schema) {
|
|
|
165
152
|
}
|
|
166
153
|
}
|
|
167
154
|
function coerceWithUnionSchema(value, schemas) {
|
|
155
|
+
for (const schema of schemas) {
|
|
156
|
+
const validator = getSubSchemaValidator(schema);
|
|
157
|
+
if (validator?.Check(value)) {
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
168
161
|
for (const schema of schemas) {
|
|
169
162
|
const candidate = structuredClone(value);
|
|
170
163
|
const coerced = coerceWithJsonSchema(candidate, schema);
|
|
@@ -175,30 +168,33 @@ function coerceWithUnionSchema(value, schemas) {
|
|
|
175
168
|
}
|
|
176
169
|
return value;
|
|
177
170
|
}
|
|
178
|
-
function
|
|
171
|
+
function applyCombinedSchemaCoercion(value, schema) {
|
|
179
172
|
let nextValue = value;
|
|
180
173
|
if (Array.isArray(schema.allOf)) {
|
|
181
|
-
for (const nested of schema.allOf)
|
|
174
|
+
for (const nested of schema.allOf)
|
|
182
175
|
nextValue = coerceWithJsonSchema(nextValue, nested);
|
|
183
|
-
}
|
|
184
176
|
}
|
|
185
|
-
if (Array.isArray(schema.anyOf))
|
|
177
|
+
if (Array.isArray(schema.anyOf))
|
|
186
178
|
nextValue = coerceWithUnionSchema(nextValue, schema.anyOf);
|
|
187
|
-
|
|
188
|
-
if (Array.isArray(schema.oneOf)) {
|
|
179
|
+
if (Array.isArray(schema.oneOf))
|
|
189
180
|
nextValue = coerceWithUnionSchema(nextValue, schema.oneOf);
|
|
181
|
+
return nextValue;
|
|
182
|
+
}
|
|
183
|
+
function coerceToDeclaredSchemaType(value, schemaTypes) {
|
|
184
|
+
const matchesUnionMember = schemaTypes.length > 1 && schemaTypes.some((schemaType) => matchesJsonType(value, schemaType));
|
|
185
|
+
if (schemaTypes.length === 0 || matchesUnionMember)
|
|
186
|
+
return value;
|
|
187
|
+
for (const schemaType of schemaTypes) {
|
|
188
|
+
const candidate = coercePrimitiveByType(value, schemaType);
|
|
189
|
+
if (candidate !== value)
|
|
190
|
+
return candidate;
|
|
190
191
|
}
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
function coerceWithJsonSchema(value, schema) {
|
|
195
|
+
let nextValue = applyCombinedSchemaCoercion(value, schema);
|
|
191
196
|
const schemaTypes = getSchemaTypes(schema);
|
|
192
|
-
|
|
193
|
-
if (schemaTypes.length > 0 && !matchesUnionMember) {
|
|
194
|
-
for (const schemaType of schemaTypes) {
|
|
195
|
-
const candidate = coercePrimitiveByType(nextValue, schemaType);
|
|
196
|
-
if (candidate !== nextValue) {
|
|
197
|
-
nextValue = candidate;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
197
|
+
nextValue = coerceToDeclaredSchemaType(nextValue, schemaTypes);
|
|
202
198
|
if (schemaTypes.includes("object") && isRecord(nextValue) && !Array.isArray(nextValue)) {
|
|
203
199
|
applySchemaObjectCoercion(nextValue, schema);
|
|
204
200
|
}
|
|
@@ -243,6 +239,22 @@ export function validateToolCall(tools, toolCall) {
|
|
|
243
239
|
}
|
|
244
240
|
return validateToolArguments(tool, toolCall);
|
|
245
241
|
}
|
|
242
|
+
function validateSerializedSchemaArguments(args, schema, validator) {
|
|
243
|
+
if (hasTypeBoxMetadata(schema) || !isJsonSchemaObject(schema))
|
|
244
|
+
return { valid: false, value: args };
|
|
245
|
+
const coerced = coerceWithJsonSchema(args, schema);
|
|
246
|
+
if (coerced !== args) {
|
|
247
|
+
if (isRecord(args) && isRecord(coerced)) {
|
|
248
|
+
for (const key of Object.keys(args))
|
|
249
|
+
delete args[key];
|
|
250
|
+
Object.assign(args, coerced);
|
|
251
|
+
}
|
|
252
|
+
else if (validator.Check(coerced)) {
|
|
253
|
+
return { valid: true, value: coerced };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { valid: validator.Check(args), value: args };
|
|
257
|
+
}
|
|
246
258
|
/**
|
|
247
259
|
* Validates tool call arguments against the tool's TypeBox schema
|
|
248
260
|
* @param tool The tool definition with TypeBox schema
|
|
@@ -252,22 +264,13 @@ export function validateToolCall(tools, toolCall) {
|
|
|
252
264
|
*/
|
|
253
265
|
export function validateToolArguments(tool, toolCall) {
|
|
254
266
|
const args = structuredClone(toolCall.arguments);
|
|
255
|
-
Value.Convert(tool.parameters, args);
|
|
256
267
|
const validator = getValidator(tool.parameters);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
Object.assign(args, coerced);
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
return validator.Check(coerced) ? coerced : args;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
268
|
+
// Serialized schemas use the compatibility coercer first so valid union arms are not changed
|
|
269
|
+
// by TypeBox conversion before the schema-specific checks can preserve them.
|
|
270
|
+
const serializedValidation = validateSerializedSchemaArguments(args, tool.parameters, validator);
|
|
271
|
+
if (serializedValidation.valid)
|
|
272
|
+
return serializedValidation.value;
|
|
273
|
+
Value.Convert(tool.parameters, args);
|
|
271
274
|
if (validator.Check(args)) {
|
|
272
275
|
return args;
|
|
273
276
|
}
|