formagent-sdk 0.3.0 → 0.4.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 +2 -0
- package/dist/cli/index.js +290 -86
- package/dist/index.js +259 -83
- package/docs/README.md +126 -0
- package/docs/api-reference.md +677 -0
- package/docs/getting-started.md +273 -0
- package/docs/mcp-servers.md +465 -0
- package/docs/session-storage.md +320 -0
- package/docs/tools.md +501 -0
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -480,6 +480,117 @@ function getProviderEnv() {
|
|
|
480
480
|
}
|
|
481
481
|
};
|
|
482
482
|
}
|
|
483
|
+
// src/utils/retry.ts
|
|
484
|
+
var DEFAULT_RETRY_OPTIONS = {
|
|
485
|
+
maxAttempts: 3,
|
|
486
|
+
initialDelay: 1000,
|
|
487
|
+
maxDelay: 30000,
|
|
488
|
+
backoffMultiplier: 2,
|
|
489
|
+
jitter: true,
|
|
490
|
+
onRetry: () => {},
|
|
491
|
+
signal: undefined
|
|
492
|
+
};
|
|
493
|
+
function sleep(ms) {
|
|
494
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
495
|
+
}
|
|
496
|
+
function calculateDelay(attempt, initialDelay, maxDelay, backoffMultiplier, jitter) {
|
|
497
|
+
const delay = Math.min(initialDelay * Math.pow(backoffMultiplier, attempt), maxDelay);
|
|
498
|
+
if (jitter) {
|
|
499
|
+
return delay * (0.5 + Math.random() * 0.5);
|
|
500
|
+
}
|
|
501
|
+
return delay;
|
|
502
|
+
}
|
|
503
|
+
function isRetryableStatus(status) {
|
|
504
|
+
return status >= 500 || status === 429;
|
|
505
|
+
}
|
|
506
|
+
function isRetryableError(error) {
|
|
507
|
+
if (error instanceof TypeError) {
|
|
508
|
+
return error.message.includes("fetch") || error.message.includes("network") || error.message.includes("ECONNREFUSED") || error.message.includes("ETIMEDOUT") || error.message.includes("ECONNRESET");
|
|
509
|
+
}
|
|
510
|
+
if (error instanceof Error) {
|
|
511
|
+
const message = error.message.toLowerCase();
|
|
512
|
+
if (message.includes("rate_limit") || message.includes("rate limit")) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
if (message.includes("usage_limit") || message.includes("usage limit")) {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
if (message.includes("timeout") || message.includes("timed out")) {
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
if (message.includes("5") || message.includes("502") || message.includes("503") || message.includes("504")) {
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
function extractStatusCode(error) {
|
|
528
|
+
const match = error.message.match(/(\d{3})/);
|
|
529
|
+
return match ? parseInt(match[1], 10) : null;
|
|
530
|
+
}
|
|
531
|
+
async function withRetry(fn, options2 = {}) {
|
|
532
|
+
const opts = { ...DEFAULT_RETRY_OPTIONS, ...options2 };
|
|
533
|
+
let lastError = null;
|
|
534
|
+
for (let attempt = 0;attempt < opts.maxAttempts; attempt++) {
|
|
535
|
+
if (opts.signal?.aborted) {
|
|
536
|
+
throw new Error("Request aborted");
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
return await fn();
|
|
540
|
+
} catch (error) {
|
|
541
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
542
|
+
if (attempt >= opts.maxAttempts - 1) {
|
|
543
|
+
throw lastError;
|
|
544
|
+
}
|
|
545
|
+
const statusCode = extractStatusCode(lastError);
|
|
546
|
+
const isRetryable = statusCode !== null ? isRetryableStatus(statusCode) : isRetryableError(lastError);
|
|
547
|
+
if (!isRetryable) {
|
|
548
|
+
throw lastError;
|
|
549
|
+
}
|
|
550
|
+
const delay = calculateDelay(attempt, opts.initialDelay, opts.maxDelay, opts.backoffMultiplier, opts.jitter);
|
|
551
|
+
opts.onRetry(attempt + 1, lastError);
|
|
552
|
+
await sleep(delay);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
throw lastError || new Error("Max retries exceeded");
|
|
556
|
+
}
|
|
557
|
+
async function fetchWithRetry(url, init, retryOptions) {
|
|
558
|
+
return withRetry(async () => {
|
|
559
|
+
const response = await fetch(url, init);
|
|
560
|
+
if (!response.ok) {
|
|
561
|
+
const errorText = await response.text().catch(() => "");
|
|
562
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
563
|
+
try {
|
|
564
|
+
const errorJson = JSON.parse(errorText);
|
|
565
|
+
if (errorJson.error?.type) {
|
|
566
|
+
errorMessage += `: ${errorJson.error.type}`;
|
|
567
|
+
}
|
|
568
|
+
if (errorJson.error?.message) {
|
|
569
|
+
errorMessage += ` - ${errorJson.error.message}`;
|
|
570
|
+
}
|
|
571
|
+
} catch {
|
|
572
|
+
if (errorText) {
|
|
573
|
+
errorMessage += ` ${errorText}`;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const error = new Error(errorMessage);
|
|
577
|
+
error.status = response.status;
|
|
578
|
+
error.responseText = errorText;
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
return response;
|
|
582
|
+
}, {
|
|
583
|
+
signal: init.signal,
|
|
584
|
+
...retryOptions
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function getRetryOptionsFromEnv() {
|
|
588
|
+
return {
|
|
589
|
+
maxAttempts: Number.parseInt(process.env.FORMAGENT_RETRY_MAX_ATTEMPTS || "3", 10),
|
|
590
|
+
initialDelay: Number.parseInt(process.env.FORMAGENT_RETRY_INITIAL_DELAY || "1000", 10),
|
|
591
|
+
maxDelay: Number.parseInt(process.env.FORMAGENT_RETRY_MAX_DELAY || "30000", 10)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
483
594
|
// src/utils/truncation.ts
|
|
484
595
|
import * as fs from "node:fs/promises";
|
|
485
596
|
import * as path from "node:path";
|
|
@@ -2129,7 +2240,8 @@ class SessionImpl {
|
|
|
2129
2240
|
const toolResult = await tool2.execute(toolInput, context);
|
|
2130
2241
|
let content = typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content);
|
|
2131
2242
|
if (needsTruncation(content)) {
|
|
2132
|
-
|
|
2243
|
+
const truncationConfig = this.config.tempDir ? { tempDir: this.config.tempDir } : undefined;
|
|
2244
|
+
content = await truncateToolOutput(content, truncationConfig);
|
|
2133
2245
|
}
|
|
2134
2246
|
toolResponse = toolResult;
|
|
2135
2247
|
result = {
|
|
@@ -2784,6 +2896,7 @@ class AnthropicProvider {
|
|
|
2784
2896
|
/^claude-instant/
|
|
2785
2897
|
];
|
|
2786
2898
|
config;
|
|
2899
|
+
defaultRetryOptions;
|
|
2787
2900
|
constructor(config = {}) {
|
|
2788
2901
|
const apiKey = config.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
2789
2902
|
if (!apiKey) {
|
|
@@ -2793,7 +2906,15 @@ class AnthropicProvider {
|
|
|
2793
2906
|
apiKey,
|
|
2794
2907
|
baseUrl: config.baseUrl ?? process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
|
|
2795
2908
|
apiVersion: config.apiVersion ?? "2023-06-01",
|
|
2796
|
-
defaultMaxTokens: config.defaultMaxTokens ?? 4096
|
|
2909
|
+
defaultMaxTokens: config.defaultMaxTokens ?? 4096,
|
|
2910
|
+
retry: config.retry
|
|
2911
|
+
};
|
|
2912
|
+
this.defaultRetryOptions = {
|
|
2913
|
+
maxAttempts: 3,
|
|
2914
|
+
initialDelay: 1000,
|
|
2915
|
+
maxDelay: 30000,
|
|
2916
|
+
backoffMultiplier: 2,
|
|
2917
|
+
jitter: true
|
|
2797
2918
|
};
|
|
2798
2919
|
}
|
|
2799
2920
|
supportsModel(model) {
|
|
@@ -2801,31 +2922,25 @@ class AnthropicProvider {
|
|
|
2801
2922
|
}
|
|
2802
2923
|
async complete(request) {
|
|
2803
2924
|
const anthropicRequest = this.buildRequest(request, false);
|
|
2804
|
-
const
|
|
2925
|
+
const retryOptions = this.config.retry ?? this.defaultRetryOptions;
|
|
2926
|
+
const response = await fetchWithRetry(`${this.config.baseUrl}/v1/messages`, {
|
|
2805
2927
|
method: "POST",
|
|
2806
2928
|
headers: this.getHeaders(),
|
|
2807
2929
|
body: JSON.stringify(anthropicRequest),
|
|
2808
2930
|
signal: request.abortSignal
|
|
2809
|
-
});
|
|
2810
|
-
if (!response.ok) {
|
|
2811
|
-
const error = await response.text();
|
|
2812
|
-
throw new Error(`Anthropic API error: ${response.status} ${error}`);
|
|
2813
|
-
}
|
|
2931
|
+
}, retryOptions);
|
|
2814
2932
|
const data = await response.json();
|
|
2815
2933
|
return this.convertResponse(data);
|
|
2816
2934
|
}
|
|
2817
2935
|
async stream(request, options2) {
|
|
2818
2936
|
const anthropicRequest = this.buildRequest(request, true);
|
|
2819
|
-
const
|
|
2937
|
+
const retryOptions = this.config.retry ?? this.defaultRetryOptions;
|
|
2938
|
+
const response = await fetchWithRetry(`${this.config.baseUrl}/v1/messages`, {
|
|
2820
2939
|
method: "POST",
|
|
2821
2940
|
headers: this.getHeaders(),
|
|
2822
2941
|
body: JSON.stringify(anthropicRequest),
|
|
2823
2942
|
signal: request.abortSignal
|
|
2824
|
-
});
|
|
2825
|
-
if (!response.ok) {
|
|
2826
|
-
const error = await response.text();
|
|
2827
|
-
throw new Error(`Anthropic API error: ${response.status} ${error}`);
|
|
2828
|
-
}
|
|
2943
|
+
}, retryOptions);
|
|
2829
2944
|
return this.createStreamIterator(response.body, options2);
|
|
2830
2945
|
}
|
|
2831
2946
|
buildRequest(request, stream) {
|
|
@@ -3049,6 +3164,7 @@ class OpenAIProvider {
|
|
|
3049
3164
|
/^chatgpt/
|
|
3050
3165
|
];
|
|
3051
3166
|
config;
|
|
3167
|
+
defaultRetryOptions;
|
|
3052
3168
|
constructor(config = {}) {
|
|
3053
3169
|
const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
|
|
3054
3170
|
if (!apiKey) {
|
|
@@ -3058,96 +3174,88 @@ class OpenAIProvider {
|
|
|
3058
3174
|
apiKey,
|
|
3059
3175
|
baseUrl: this.normalizeBaseUrl(config.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"),
|
|
3060
3176
|
organization: config.organization,
|
|
3061
|
-
defaultMaxTokens: config.defaultMaxTokens ?? 4096
|
|
3177
|
+
defaultMaxTokens: config.defaultMaxTokens ?? 4096,
|
|
3178
|
+
retry: config.retry
|
|
3179
|
+
};
|
|
3180
|
+
this.defaultRetryOptions = {
|
|
3181
|
+
maxAttempts: 3,
|
|
3182
|
+
initialDelay: 1000,
|
|
3183
|
+
maxDelay: 30000,
|
|
3184
|
+
backoffMultiplier: 2,
|
|
3185
|
+
jitter: true
|
|
3062
3186
|
};
|
|
3063
3187
|
}
|
|
3064
3188
|
supportsModel(model) {
|
|
3065
3189
|
return this.supportedModels.some((pattern) => pattern.test(model));
|
|
3066
3190
|
}
|
|
3067
3191
|
async complete(request) {
|
|
3192
|
+
const retryOptions = this.config.retry ?? this.defaultRetryOptions;
|
|
3068
3193
|
if (this.usesResponsesApi(request.config.model)) {
|
|
3069
3194
|
const openaiRequest2 = this.buildResponsesRequest(request, false);
|
|
3070
|
-
const response2 = await
|
|
3195
|
+
const response2 = await fetchWithRetry(`${this.config.baseUrl}/responses`, {
|
|
3071
3196
|
method: "POST",
|
|
3072
3197
|
headers: this.getHeaders(),
|
|
3073
3198
|
body: JSON.stringify(openaiRequest2),
|
|
3074
3199
|
signal: request.abortSignal
|
|
3075
|
-
});
|
|
3076
|
-
if (!response2.ok) {
|
|
3077
|
-
const error = await response2.text();
|
|
3078
|
-
throw new Error(`OpenAI API error: ${response2.status} ${error}`);
|
|
3079
|
-
}
|
|
3200
|
+
}, retryOptions);
|
|
3080
3201
|
const data2 = await response2.json();
|
|
3081
3202
|
return this.convertResponsesResponse(data2);
|
|
3082
3203
|
}
|
|
3083
3204
|
const openaiRequest = this.buildRequest(request, false);
|
|
3084
|
-
|
|
3205
|
+
const response = await fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
|
|
3085
3206
|
method: "POST",
|
|
3086
3207
|
headers: this.getHeaders(),
|
|
3087
3208
|
body: JSON.stringify(openaiRequest),
|
|
3088
3209
|
signal: request.abortSignal
|
|
3089
|
-
});
|
|
3090
|
-
if (
|
|
3091
|
-
const
|
|
3092
|
-
if (this.shouldFallbackToResponses(
|
|
3210
|
+
}, retryOptions);
|
|
3211
|
+
if (response.status === 404) {
|
|
3212
|
+
const errorText = await response.clone().text();
|
|
3213
|
+
if (this.shouldFallbackToResponses(404, errorText)) {
|
|
3093
3214
|
const fallbackRequest = this.buildResponsesRequest(request, false);
|
|
3094
|
-
|
|
3215
|
+
const fallbackResponse = await fetchWithRetry(`${this.config.baseUrl}/responses`, {
|
|
3095
3216
|
method: "POST",
|
|
3096
3217
|
headers: this.getHeaders(),
|
|
3097
3218
|
body: JSON.stringify(fallbackRequest),
|
|
3098
3219
|
signal: request.abortSignal
|
|
3099
|
-
});
|
|
3100
|
-
|
|
3101
|
-
const fallbackError = await response.text();
|
|
3102
|
-
throw new Error(`OpenAI API error: ${response.status} ${fallbackError}`);
|
|
3103
|
-
}
|
|
3104
|
-
const data2 = await response.json();
|
|
3220
|
+
}, retryOptions);
|
|
3221
|
+
const data2 = await fallbackResponse.json();
|
|
3105
3222
|
return this.convertResponsesResponse(data2);
|
|
3106
3223
|
}
|
|
3107
|
-
throw new Error(`OpenAI API error: ${response.status} ${error}`);
|
|
3108
3224
|
}
|
|
3109
3225
|
const data = await response.json();
|
|
3110
3226
|
return this.convertResponse(data);
|
|
3111
3227
|
}
|
|
3112
3228
|
async stream(request, options2) {
|
|
3229
|
+
const retryOptions = this.config.retry ?? this.defaultRetryOptions;
|
|
3113
3230
|
if (this.usesResponsesApi(request.config.model)) {
|
|
3114
3231
|
const openaiRequest2 = this.buildResponsesRequest(request, true);
|
|
3115
|
-
const response2 = await
|
|
3232
|
+
const response2 = await fetchWithRetry(`${this.config.baseUrl}/responses`, {
|
|
3116
3233
|
method: "POST",
|
|
3117
3234
|
headers: this.getHeaders(),
|
|
3118
3235
|
body: JSON.stringify(openaiRequest2),
|
|
3119
3236
|
signal: request.abortSignal
|
|
3120
|
-
});
|
|
3121
|
-
if (!response2.ok) {
|
|
3122
|
-
const error = await response2.text();
|
|
3123
|
-
throw new Error(`OpenAI API error: ${response2.status} ${error}`);
|
|
3124
|
-
}
|
|
3237
|
+
}, retryOptions);
|
|
3125
3238
|
return this.createResponsesStreamIterator(response2.body, options2);
|
|
3126
3239
|
}
|
|
3127
3240
|
const openaiRequest = this.buildRequest(request, true);
|
|
3128
|
-
|
|
3241
|
+
const response = await fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
|
|
3129
3242
|
method: "POST",
|
|
3130
3243
|
headers: this.getHeaders(),
|
|
3131
3244
|
body: JSON.stringify(openaiRequest),
|
|
3132
3245
|
signal: request.abortSignal
|
|
3133
|
-
});
|
|
3134
|
-
if (
|
|
3135
|
-
const
|
|
3136
|
-
if (this.shouldFallbackToResponses(
|
|
3246
|
+
}, retryOptions);
|
|
3247
|
+
if (response.status === 404) {
|
|
3248
|
+
const errorText = await response.clone().text();
|
|
3249
|
+
if (this.shouldFallbackToResponses(404, errorText)) {
|
|
3137
3250
|
const fallbackRequest = this.buildResponsesRequest(request, true);
|
|
3138
|
-
|
|
3251
|
+
const fallbackResponse = await fetchWithRetry(`${this.config.baseUrl}/responses`, {
|
|
3139
3252
|
method: "POST",
|
|
3140
3253
|
headers: this.getHeaders(),
|
|
3141
3254
|
body: JSON.stringify(fallbackRequest),
|
|
3142
3255
|
signal: request.abortSignal
|
|
3143
|
-
});
|
|
3144
|
-
|
|
3145
|
-
const fallbackError = await response.text();
|
|
3146
|
-
throw new Error(`OpenAI API error: ${response.status} ${fallbackError}`);
|
|
3147
|
-
}
|
|
3148
|
-
return this.createResponsesStreamIterator(response.body, options2);
|
|
3256
|
+
}, retryOptions);
|
|
3257
|
+
return this.createResponsesStreamIterator(fallbackResponse.body, options2);
|
|
3149
3258
|
}
|
|
3150
|
-
throw new Error(`OpenAI API error: ${response.status} ${error}`);
|
|
3151
3259
|
}
|
|
3152
3260
|
return this.createStreamIterator(response.body, options2);
|
|
3153
3261
|
}
|
|
@@ -3865,6 +3973,7 @@ class GeminiProvider {
|
|
|
3865
3973
|
name = "Gemini";
|
|
3866
3974
|
supportedModels = [/^gemini-/, /^models\/gemini-/];
|
|
3867
3975
|
config;
|
|
3976
|
+
defaultRetryOptions;
|
|
3868
3977
|
constructor(config = {}) {
|
|
3869
3978
|
const apiKey = config.apiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
|
|
3870
3979
|
if (!apiKey) {
|
|
@@ -3873,7 +3982,15 @@ class GeminiProvider {
|
|
|
3873
3982
|
this.config = {
|
|
3874
3983
|
apiKey,
|
|
3875
3984
|
baseUrl: config.baseUrl ?? process.env.GEMINI_BASE_URL ?? "https://generativelanguage.googleapis.com/v1beta",
|
|
3876
|
-
defaultMaxTokens: config.defaultMaxTokens ?? 4096
|
|
3985
|
+
defaultMaxTokens: config.defaultMaxTokens ?? 4096,
|
|
3986
|
+
retry: config.retry
|
|
3987
|
+
};
|
|
3988
|
+
this.defaultRetryOptions = {
|
|
3989
|
+
maxAttempts: 3,
|
|
3990
|
+
initialDelay: 1000,
|
|
3991
|
+
maxDelay: 30000,
|
|
3992
|
+
backoffMultiplier: 2,
|
|
3993
|
+
jitter: true
|
|
3877
3994
|
};
|
|
3878
3995
|
}
|
|
3879
3996
|
supportsModel(model) {
|
|
@@ -3882,16 +3999,13 @@ class GeminiProvider {
|
|
|
3882
3999
|
async complete(request) {
|
|
3883
4000
|
const geminiRequest = this.buildRequest(request);
|
|
3884
4001
|
const url = this.buildUrl(this.getModelPath(request.config.model) + ":generateContent");
|
|
3885
|
-
const
|
|
4002
|
+
const retryOptions = this.config.retry ?? this.defaultRetryOptions;
|
|
4003
|
+
const response = await fetchWithRetry(url, {
|
|
3886
4004
|
method: "POST",
|
|
3887
4005
|
headers: this.getHeaders(),
|
|
3888
4006
|
body: JSON.stringify(geminiRequest),
|
|
3889
4007
|
signal: request.abortSignal
|
|
3890
|
-
});
|
|
3891
|
-
if (!response.ok) {
|
|
3892
|
-
const error = await response.text();
|
|
3893
|
-
throw new Error(`Gemini API error: ${response.status} ${error}`);
|
|
3894
|
-
}
|
|
4008
|
+
}, retryOptions);
|
|
3895
4009
|
const data = await response.json();
|
|
3896
4010
|
return this.convertResponse(data, request.config.model);
|
|
3897
4011
|
}
|
|
@@ -3900,16 +4014,13 @@ class GeminiProvider {
|
|
|
3900
4014
|
const url = this.buildUrl(this.getModelPath(request.config.model) + ":streamGenerateContent", {
|
|
3901
4015
|
alt: "sse"
|
|
3902
4016
|
});
|
|
3903
|
-
const
|
|
4017
|
+
const retryOptions = this.config.retry ?? this.defaultRetryOptions;
|
|
4018
|
+
const response = await fetchWithRetry(url, {
|
|
3904
4019
|
method: "POST",
|
|
3905
4020
|
headers: this.getHeaders(),
|
|
3906
4021
|
body: JSON.stringify(geminiRequest),
|
|
3907
4022
|
signal: request.abortSignal
|
|
3908
|
-
});
|
|
3909
|
-
if (!response.ok) {
|
|
3910
|
-
const error = await response.text();
|
|
3911
|
-
throw new Error(`Gemini API error: ${response.status} ${error}`);
|
|
3912
|
-
}
|
|
4023
|
+
}, retryOptions);
|
|
3913
4024
|
const contentType = response.headers.get("content-type") ?? "";
|
|
3914
4025
|
if (!contentType.includes("text/event-stream")) {
|
|
3915
4026
|
const data = await response.json();
|
|
@@ -4777,10 +4888,13 @@ function isZodSchema(schema) {
|
|
|
4777
4888
|
return typeof schema === "object" && schema !== null && "parse" in schema && "safeParse" in schema && typeof schema.parse === "function" && typeof schema.safeParse === "function";
|
|
4778
4889
|
}
|
|
4779
4890
|
function zodToJsonSchema(zodSchema) {
|
|
4780
|
-
const
|
|
4781
|
-
const typeName =
|
|
4782
|
-
if (typeName === "ZodObject"
|
|
4783
|
-
|
|
4891
|
+
const def = zodSchema._def;
|
|
4892
|
+
const typeName = def?.typeName;
|
|
4893
|
+
if (typeName === "ZodObject") {
|
|
4894
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
4895
|
+
if (shape) {
|
|
4896
|
+
return objectShapeToJsonSchema(shape);
|
|
4897
|
+
}
|
|
4784
4898
|
}
|
|
4785
4899
|
return {
|
|
4786
4900
|
type: "object",
|
|
@@ -4793,13 +4907,35 @@ function objectShapeToJsonSchema(shape) {
|
|
|
4793
4907
|
const required = [];
|
|
4794
4908
|
for (const [key, value] of Object.entries(shape)) {
|
|
4795
4909
|
const def = value?._def;
|
|
4796
|
-
|
|
4797
|
-
|
|
4910
|
+
let typeName = def?.typeName;
|
|
4911
|
+
let description = def?.description;
|
|
4798
4912
|
let innerDef = def;
|
|
4799
4913
|
let isOptional = false;
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4914
|
+
while (innerDef) {
|
|
4915
|
+
const innerTypeName = innerDef.typeName;
|
|
4916
|
+
if (innerTypeName === "ZodOptional") {
|
|
4917
|
+
isOptional = true;
|
|
4918
|
+
if (!description && innerDef.description) {
|
|
4919
|
+
description = innerDef.description;
|
|
4920
|
+
}
|
|
4921
|
+
innerDef = innerDef.innerType?._def;
|
|
4922
|
+
} else if (innerTypeName === "ZodDefault") {
|
|
4923
|
+
isOptional = true;
|
|
4924
|
+
if (!description && innerDef.description) {
|
|
4925
|
+
description = innerDef.description;
|
|
4926
|
+
}
|
|
4927
|
+
innerDef = innerDef.innerType?._def;
|
|
4928
|
+
} else if (innerTypeName === "ZodNullable") {
|
|
4929
|
+
if (!description && innerDef.description) {
|
|
4930
|
+
description = innerDef.description;
|
|
4931
|
+
}
|
|
4932
|
+
innerDef = innerDef.innerType?._def;
|
|
4933
|
+
} else {
|
|
4934
|
+
if (!description && innerDef.description) {
|
|
4935
|
+
description = innerDef.description;
|
|
4936
|
+
}
|
|
4937
|
+
break;
|
|
4938
|
+
}
|
|
4803
4939
|
}
|
|
4804
4940
|
const prop = zodDefToJsonSchema(innerDef);
|
|
4805
4941
|
if (description) {
|
|
@@ -4832,8 +4968,10 @@ function zodDefToJsonSchema(def) {
|
|
|
4832
4968
|
type: "array",
|
|
4833
4969
|
items: zodDefToJsonSchema(def.type?._def)
|
|
4834
4970
|
};
|
|
4835
|
-
case "ZodObject":
|
|
4836
|
-
|
|
4971
|
+
case "ZodObject": {
|
|
4972
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
4973
|
+
return objectShapeToJsonSchema(shape);
|
|
4974
|
+
}
|
|
4837
4975
|
case "ZodLiteral":
|
|
4838
4976
|
return { type: typeof def.value, enum: [def.value] };
|
|
4839
4977
|
case "ZodUnion":
|
|
@@ -5312,6 +5450,7 @@ function checkDirAccess(dirPath, options2) {
|
|
|
5312
5450
|
|
|
5313
5451
|
// src/tools/builtin/bash.ts
|
|
5314
5452
|
var DEFAULT_TIMEOUT = 120000;
|
|
5453
|
+
var DEFAULT_IDLE_TIMEOUT = 30000;
|
|
5315
5454
|
var MAX_OUTPUT_LENGTH = 1e5;
|
|
5316
5455
|
var DEFAULT_BLOCKED_PATTERNS = [
|
|
5317
5456
|
"\\bsudo\\b",
|
|
@@ -5378,21 +5517,39 @@ function createBashTool(options2 = {}) {
|
|
|
5378
5517
|
}
|
|
5379
5518
|
}
|
|
5380
5519
|
const actualTimeout = Math.min(timeout, 600000);
|
|
5520
|
+
const idleTimeout = options2.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
|
|
5381
5521
|
return new Promise((resolve2) => {
|
|
5382
5522
|
let stdout = "";
|
|
5383
5523
|
let stderr = "";
|
|
5384
5524
|
let killed = false;
|
|
5525
|
+
let killedReason = null;
|
|
5526
|
+
let lastOutputTime = Date.now();
|
|
5385
5527
|
const proc = spawn("bash", ["-c", command], {
|
|
5386
5528
|
cwd: cwdAccess.resolved,
|
|
5387
5529
|
env: process.env,
|
|
5388
|
-
shell: false
|
|
5530
|
+
shell: false,
|
|
5531
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5389
5532
|
});
|
|
5390
5533
|
const timer = setTimeout(() => {
|
|
5391
5534
|
killed = true;
|
|
5535
|
+
killedReason = "timeout";
|
|
5392
5536
|
proc.kill("SIGTERM");
|
|
5393
5537
|
setTimeout(() => proc.kill("SIGKILL"), 1000);
|
|
5394
5538
|
}, actualTimeout);
|
|
5539
|
+
const idleChecker = setInterval(() => {
|
|
5540
|
+
const idleTime = Date.now() - lastOutputTime;
|
|
5541
|
+
if (idleTime >= idleTimeout && !killed) {
|
|
5542
|
+
killed = true;
|
|
5543
|
+
killedReason = "idle";
|
|
5544
|
+
proc.kill("SIGTERM");
|
|
5545
|
+
setTimeout(() => proc.kill("SIGKILL"), 1000);
|
|
5546
|
+
}
|
|
5547
|
+
}, 1000);
|
|
5548
|
+
const updateLastOutputTime = () => {
|
|
5549
|
+
lastOutputTime = Date.now();
|
|
5550
|
+
};
|
|
5395
5551
|
proc.stdout?.on("data", (data) => {
|
|
5552
|
+
updateLastOutputTime();
|
|
5396
5553
|
stdout += data.toString();
|
|
5397
5554
|
if (stdout.length > MAX_OUTPUT_LENGTH) {
|
|
5398
5555
|
stdout = stdout.slice(0, MAX_OUTPUT_LENGTH) + `
|
|
@@ -5401,6 +5558,7 @@ function createBashTool(options2 = {}) {
|
|
|
5401
5558
|
}
|
|
5402
5559
|
});
|
|
5403
5560
|
proc.stderr?.on("data", (data) => {
|
|
5561
|
+
updateLastOutputTime();
|
|
5404
5562
|
stderr += data.toString();
|
|
5405
5563
|
if (stderr.length > MAX_OUTPUT_LENGTH) {
|
|
5406
5564
|
stderr = stderr.slice(0, MAX_OUTPUT_LENGTH) + `
|
|
@@ -5409,17 +5567,31 @@ function createBashTool(options2 = {}) {
|
|
|
5409
5567
|
});
|
|
5410
5568
|
proc.on("close", (code) => {
|
|
5411
5569
|
clearTimeout(timer);
|
|
5570
|
+
clearInterval(idleChecker);
|
|
5412
5571
|
if (killed) {
|
|
5413
|
-
|
|
5414
|
-
|
|
5572
|
+
if (killedReason === "idle") {
|
|
5573
|
+
resolve2({
|
|
5574
|
+
content: `Command terminated: no output for ${idleTimeout / 1000} seconds (likely waiting for input)
|
|
5415
5575
|
|
|
5416
5576
|
Partial output:
|
|
5417
5577
|
${stdout}
|
|
5418
5578
|
|
|
5419
5579
|
Stderr:
|
|
5420
5580
|
${stderr}`,
|
|
5421
|
-
|
|
5422
|
-
|
|
5581
|
+
isError: true
|
|
5582
|
+
});
|
|
5583
|
+
} else {
|
|
5584
|
+
resolve2({
|
|
5585
|
+
content: `Command timed out after ${actualTimeout}ms
|
|
5586
|
+
|
|
5587
|
+
Partial output:
|
|
5588
|
+
${stdout}
|
|
5589
|
+
|
|
5590
|
+
Stderr:
|
|
5591
|
+
${stderr}`,
|
|
5592
|
+
isError: true
|
|
5593
|
+
});
|
|
5594
|
+
}
|
|
5423
5595
|
return;
|
|
5424
5596
|
}
|
|
5425
5597
|
const output = stdout + (stderr ? `
|
|
@@ -5440,6 +5612,7 @@ ${output}`,
|
|
|
5440
5612
|
});
|
|
5441
5613
|
proc.on("error", (error) => {
|
|
5442
5614
|
clearTimeout(timer);
|
|
5615
|
+
clearInterval(idleChecker);
|
|
5443
5616
|
resolve2({
|
|
5444
5617
|
content: `Failed to execute command: ${error.message}`,
|
|
5445
5618
|
isError: true
|
|
@@ -7091,7 +7264,7 @@ ${responseText}`;
|
|
|
7091
7264
|
metadata: {
|
|
7092
7265
|
status: response.status,
|
|
7093
7266
|
statusText: response.statusText,
|
|
7094
|
-
headers: Object.fromEntries(response.headers
|
|
7267
|
+
headers: Object.fromEntries(response.headers),
|
|
7095
7268
|
body: responseBody
|
|
7096
7269
|
}
|
|
7097
7270
|
};
|
|
@@ -7902,6 +8075,7 @@ function createAgent(config) {
|
|
|
7902
8075
|
}
|
|
7903
8076
|
export {
|
|
7904
8077
|
zodToJsonSchema,
|
|
8078
|
+
withRetry,
|
|
7905
8079
|
webTools,
|
|
7906
8080
|
waitForEvent,
|
|
7907
8081
|
utilityTools,
|
|
@@ -7933,6 +8107,7 @@ export {
|
|
|
7933
8107
|
interactiveTools,
|
|
7934
8108
|
globalCostTracker,
|
|
7935
8109
|
getTodos,
|
|
8110
|
+
getRetryOptionsFromEnv,
|
|
7936
8111
|
getProviderEnv,
|
|
7937
8112
|
getProjectSkillsPath,
|
|
7938
8113
|
getPreset,
|
|
@@ -7949,6 +8124,7 @@ export {
|
|
|
7949
8124
|
generateEnvContext,
|
|
7950
8125
|
forkSession,
|
|
7951
8126
|
fileTools,
|
|
8127
|
+
fetchWithRetry,
|
|
7952
8128
|
extractTitle,
|
|
7953
8129
|
extractDescription,
|
|
7954
8130
|
estimateTokens,
|