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/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
- content = await truncateToolOutput(content);
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 response = await fetch(`${this.config.baseUrl}/v1/messages`, {
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 response = await fetch(`${this.config.baseUrl}/v1/messages`, {
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 fetch(`${this.config.baseUrl}/responses`, {
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
- let response = await fetch(`${this.config.baseUrl}/chat/completions`, {
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 (!response.ok) {
3091
- const error = await response.text();
3092
- if (this.shouldFallbackToResponses(response.status, error)) {
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
- response = await fetch(`${this.config.baseUrl}/responses`, {
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
- if (!response.ok) {
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 fetch(`${this.config.baseUrl}/responses`, {
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
- let response = await fetch(`${this.config.baseUrl}/chat/completions`, {
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 (!response.ok) {
3135
- const error = await response.text();
3136
- if (this.shouldFallbackToResponses(response.status, error)) {
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
- response = await fetch(`${this.config.baseUrl}/responses`, {
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
- if (!response.ok) {
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 response = await fetch(url, {
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 response = await fetch(url, {
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 shape = zodSchema._def?.shape;
4781
- const typeName = zodSchema._def?.typeName;
4782
- if (typeName === "ZodObject" && shape) {
4783
- return objectShapeToJsonSchema(shape);
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
- const typeName = def?.typeName;
4797
- const description = def?.description;
4910
+ let typeName = def?.typeName;
4911
+ let description = def?.description;
4798
4912
  let innerDef = def;
4799
4913
  let isOptional = false;
4800
- if (typeName === "ZodOptional") {
4801
- isOptional = true;
4802
- innerDef = def.innerType?._def;
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
- return objectShapeToJsonSchema(def.shape);
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
- resolve2({
5414
- content: `Command timed out after ${actualTimeout}ms
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
- isError: true
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.entries()),
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,