@probelabs/probe 0.6.0-rc226 → 0.6.0-rc227

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.
@@ -69451,7 +69451,7 @@ import path8 from "path";
69451
69451
  import os3 from "os";
69452
69452
  import { EventEmitter as EventEmitter4 } from "events";
69453
69453
  async function createEnhancedClaudeCLIEngine(options = {}) {
69454
- const { agent, systemPrompt, customPrompt, debug, sessionId, allowedTools } = options;
69454
+ const { agent, systemPrompt, customPrompt, debug, sessionId, allowedTools, timeout = 12e4 } = options;
69455
69455
  const session = new Session(
69456
69456
  sessionId || randomBytes(8).toString("hex"),
69457
69457
  debug
@@ -69550,6 +69550,30 @@ ${opts.schema}`;
69550
69550
  stdio: ["ignore", "pipe", "pipe"]
69551
69551
  // Ignore stdin since echo handles it
69552
69552
  });
69553
+ let killed = false;
69554
+ let timeoutHandle;
69555
+ let sigkillHandle;
69556
+ if (timeout > 0) {
69557
+ timeoutHandle = setTimeout(() => {
69558
+ if (!killed) {
69559
+ killed = true;
69560
+ processEnded = true;
69561
+ proc2.kill("SIGTERM");
69562
+ if (debug) {
69563
+ console.log(`[DEBUG] Process timed out after ${timeout}ms, sending SIGTERM`);
69564
+ }
69565
+ sigkillHandle = setTimeout(() => {
69566
+ if (proc2.exitCode === null) {
69567
+ proc2.kill("SIGKILL");
69568
+ if (debug) {
69569
+ console.log("[DEBUG] Process did not exit, sending SIGKILL");
69570
+ }
69571
+ }
69572
+ }, 5e3);
69573
+ emitter.emit("error", new Error(`Claude CLI process timed out after ${timeout}ms`));
69574
+ }
69575
+ }, timeout);
69576
+ }
69553
69577
  proc2.stdout.on("data", (data) => {
69554
69578
  buffer += data.toString();
69555
69579
  processJsonBuffer(buffer, emitter, session, debug, toolCollector);
@@ -69566,10 +69590,20 @@ ${opts.schema}`;
69566
69590
  }
69567
69591
  });
69568
69592
  proc2.on("close", (code) => {
69593
+ if (timeoutHandle) {
69594
+ clearTimeout(timeoutHandle);
69595
+ }
69596
+ if (sigkillHandle) {
69597
+ clearTimeout(sigkillHandle);
69598
+ }
69569
69599
  processEnded = true;
69570
69600
  if (code !== 0 && debug) {
69571
69601
  console.log(`[DEBUG] Process exited with code ${code}`);
69572
69602
  }
69603
+ if (killed) {
69604
+ emitter.emit("end");
69605
+ return;
69606
+ }
69573
69607
  if (buffer.trim()) {
69574
69608
  processJsonBuffer(buffer, emitter, session, debug, toolCollector);
69575
69609
  }
@@ -69585,6 +69619,13 @@ ${opts.schema}`;
69585
69619
  emitter.emit("end");
69586
69620
  });
69587
69621
  proc2.on("error", (error) => {
69622
+ if (timeoutHandle) {
69623
+ clearTimeout(timeoutHandle);
69624
+ }
69625
+ if (sigkillHandle) {
69626
+ clearTimeout(sigkillHandle);
69627
+ }
69628
+ processEnded = true;
69588
69629
  emitter.emit("error", error);
69589
69630
  });
69590
69631
  const messageQueue = [];
@@ -69630,7 +69671,22 @@ ${opts.schema}`;
69630
69671
  \u{1F527} Using ${msg.name}: ${JSON.stringify(msg.input)}
69631
69672
  `
69632
69673
  };
69633
- const result = await executeProbleTool(agent, msg.name, msg.input);
69674
+ const toolTimeout = 3e4;
69675
+ let toolTimeoutId;
69676
+ const timeoutPromise = new Promise((_, reject2) => {
69677
+ toolTimeoutId = setTimeout(() => reject2(new Error(`Tool ${msg.name} timed out after ${toolTimeout}ms`)), toolTimeout);
69678
+ });
69679
+ let result;
69680
+ try {
69681
+ result = await Promise.race([
69682
+ executeProbleTool(agent, msg.name, msg.input),
69683
+ timeoutPromise
69684
+ ]);
69685
+ } catch (error) {
69686
+ result = `Tool error: ${error.message}`;
69687
+ } finally {
69688
+ clearTimeout(toolTimeoutId);
69689
+ }
69634
69690
  yield { type: "text", content: `${result}
69635
69691
  ` };
69636
69692
  } else if (msg.type === "toolBatch") {
@@ -70197,6 +70253,9 @@ var init_enhanced_vercel = __esm({
70197
70253
  // src/agent/ProbeAgent.js
70198
70254
  var ProbeAgent_exports = {};
70199
70255
  __export(ProbeAgent_exports, {
70256
+ ENGINE_ACTIVITY_TIMEOUT_DEFAULT: () => ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
70257
+ ENGINE_ACTIVITY_TIMEOUT_MAX: () => ENGINE_ACTIVITY_TIMEOUT_MAX,
70258
+ ENGINE_ACTIVITY_TIMEOUT_MIN: () => ENGINE_ACTIVITY_TIMEOUT_MIN,
70200
70259
  ProbeAgent: () => ProbeAgent
70201
70260
  });
70202
70261
  import dotenv2 from "dotenv";
@@ -70231,7 +70290,7 @@ Your content here
70231
70290
 
70232
70291
  Do NOT wrap in other tags like <api_call>, <tool_name>, <function>, etc.`;
70233
70292
  }
70234
- var MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
70293
+ var ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
70235
70294
  var init_ProbeAgent = __esm({
70236
70295
  "src/agent/ProbeAgent.js"() {
70237
70296
  "use strict";
@@ -70260,6 +70319,9 @@ var init_ProbeAgent = __esm({
70260
70319
  init_delegate();
70261
70320
  init_tasks();
70262
70321
  dotenv2.config();
70322
+ ENGINE_ACTIVITY_TIMEOUT_DEFAULT = 18e4;
70323
+ ENGINE_ACTIVITY_TIMEOUT_MIN = 5e3;
70324
+ ENGINE_ACTIVITY_TIMEOUT_MAX = 6e5;
70263
70325
  MAX_TOOL_ITERATIONS = (() => {
70264
70326
  const val = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
70265
70327
  if (isNaN(val) || val < 1 || val > 200) {
@@ -70318,6 +70380,8 @@ var init_ProbeAgent = __esm({
70318
70380
  * @param {number} [options.fallback.maxTotalAttempts=10] - Maximum total attempts across all providers
70319
70381
  * @param {string} [options.completionPrompt] - Custom prompt to run after attempt_completion for validation/review (runs before mermaid/JSON validation)
70320
70382
  * @param {number} [options.maxOutputTokens] - Maximum tokens for tool output before truncation (default: 20000, can also be set via PROBE_MAX_OUTPUT_TOKENS env var)
70383
+ * @param {number} [options.requestTimeout] - Timeout in ms for AI requests (default: 120000 or REQUEST_TIMEOUT env var). Used to abort hung requests.
70384
+ * @param {number} [options.maxOperationTimeout] - Maximum timeout in ms for the entire operation including all retries and fallbacks (default: 300000 or MAX_OPERATION_TIMEOUT env var). This is the absolute maximum time for streamTextWithRetryAndFallback.
70321
70385
  */
70322
70386
  constructor(options = {}) {
70323
70387
  this.sessionId = options.sessionId || randomUUID6();
@@ -70403,6 +70467,32 @@ var init_ProbeAgent = __esm({
70403
70467
  this.enableTasks = !!options.enableTasks;
70404
70468
  this.taskManager = null;
70405
70469
  this.delegationManager = new DelegationManager();
70470
+ this.requestTimeout = options.requestTimeout ?? (() => {
70471
+ if (process.env.REQUEST_TIMEOUT) {
70472
+ const parsed = parseInt(process.env.REQUEST_TIMEOUT, 10);
70473
+ if (isNaN(parsed) || parsed < 1e3 || parsed > 36e5) {
70474
+ return 12e4;
70475
+ }
70476
+ return parsed;
70477
+ }
70478
+ return 12e4;
70479
+ })();
70480
+ if (this.debug) {
70481
+ console.log(`[DEBUG] Request timeout: ${this.requestTimeout}ms`);
70482
+ }
70483
+ this.maxOperationTimeout = options.maxOperationTimeout ?? (() => {
70484
+ if (process.env.MAX_OPERATION_TIMEOUT) {
70485
+ const parsed = parseInt(process.env.MAX_OPERATION_TIMEOUT, 10);
70486
+ if (isNaN(parsed) || parsed < 1e3 || parsed > 72e5) {
70487
+ return 3e5;
70488
+ }
70489
+ return parsed;
70490
+ }
70491
+ return 3e5;
70492
+ })();
70493
+ if (this.debug) {
70494
+ console.log(`[DEBUG] Max operation timeout: ${this.maxOperationTimeout}ms`);
70495
+ }
70406
70496
  this.retryConfig = options.retry || {};
70407
70497
  this.retryManager = null;
70408
70498
  this.fallbackConfig = options.fallback || null;
@@ -71022,88 +71112,98 @@ var init_ProbeAgent = __esm({
71022
71112
  }
71023
71113
  }
71024
71114
  /**
71025
- * Execute streamText with retry and fallback support
71026
- * @param {Object} options - streamText options
71027
- * @returns {Promise<Object>} - streamText result
71115
+ * Create a streamText-compatible result from an engine stream with timeout handling
71116
+ * @param {AsyncGenerator} engineStream - The engine's query result
71117
+ * @param {AbortSignal} abortSignal - Signal for aborting the operation
71118
+ * @param {number} requestTimeout - Per-request timeout in ms
71119
+ * @param {Object} timeoutState - Object with timeoutId property (mutable for cleanup)
71120
+ * @returns {Object} - streamText-compatible result with textStream
71028
71121
  * @private
71029
71122
  */
71030
- async streamTextWithRetryAndFallback(options) {
71031
- if (this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true") {
71123
+ _createEngineTextStreamResult(engineStream, abortSignal, requestTimeout, timeoutState) {
71124
+ const activityTimeout = (() => {
71125
+ const parsed = parseInt(process.env.ENGINE_ACTIVITY_TIMEOUT, 10);
71126
+ return isNaN(parsed) || parsed < ENGINE_ACTIVITY_TIMEOUT_MIN || parsed > ENGINE_ACTIVITY_TIMEOUT_MAX ? ENGINE_ACTIVITY_TIMEOUT_DEFAULT : parsed;
71127
+ })();
71128
+ const startTime = Date.now();
71129
+ async function* createTextStream() {
71130
+ let lastActivity = Date.now();
71032
71131
  try {
71033
- const engine = await this.getEngine();
71034
- if (engine && engine.query) {
71035
- const userMessages = options.messages.filter(
71036
- (m) => m.role === "user" && !m.content.includes("WARNING: You have reached the maximum tool iterations limit")
71037
- );
71038
- const lastUserMessage = userMessages[userMessages.length - 1];
71039
- const prompt = lastUserMessage ? lastUserMessage.content : "";
71040
- const engineOptions = {
71041
- maxTokens: options.maxTokens,
71042
- temperature: options.temperature,
71043
- messages: options.messages,
71044
- systemPrompt: options.messages.find((m) => m.role === "system")?.content
71045
- };
71046
- const engineStream = engine.query(prompt, engineOptions);
71047
- async function* createTextStream() {
71048
- for await (const message of engineStream) {
71049
- if (message.type === "text" && message.content) {
71050
- yield message.content;
71051
- } else if (typeof message === "string") {
71052
- yield message;
71053
- }
71054
- }
71132
+ for await (const message of engineStream) {
71133
+ if (abortSignal.aborted) {
71134
+ const abortError = new Error("Operation aborted");
71135
+ abortError.name = "AbortError";
71136
+ throw abortError;
71055
71137
  }
71056
- return {
71057
- textStream: createTextStream(),
71058
- usage: Promise.resolve({})
71059
- // Engine should handle its own usage tracking
71060
- // Add other streamText-compatible properties as needed
71061
- };
71062
- }
71063
- } catch (error) {
71064
- if (this.debug) {
71065
- console.log(`[DEBUG] Failed to use Claude Code engine, falling back to Vercel:`, error.message);
71066
- }
71067
- }
71068
- }
71069
- if (this.clientApiProvider === "codex" || process.env.USE_CODEX === "true") {
71070
- try {
71071
- const engine = await this.getEngine();
71072
- if (engine && engine.query) {
71073
- const userMessages = options.messages.filter(
71074
- (m) => m.role === "user" && !m.content.includes("WARNING: You have reached the maximum tool iterations limit")
71075
- );
71076
- const lastUserMessage = userMessages[userMessages.length - 1];
71077
- const prompt = lastUserMessage ? lastUserMessage.content : "";
71078
- const engineOptions = {
71079
- maxTokens: options.maxTokens,
71080
- temperature: options.temperature,
71081
- messages: options.messages,
71082
- systemPrompt: options.messages.find((m) => m.role === "system")?.content
71083
- };
71084
- const engineStream = engine.query(prompt, engineOptions);
71085
- async function* createTextStream() {
71086
- for await (const message of engineStream) {
71087
- if (message.type === "text" && message.content) {
71088
- yield message.content;
71089
- } else if (typeof message === "string") {
71090
- yield message;
71091
- }
71092
- }
71138
+ const now = Date.now();
71139
+ if (now - lastActivity > activityTimeout) {
71140
+ throw new Error(`Engine stream timeout - no activity for ${activityTimeout}ms`);
71141
+ }
71142
+ if (requestTimeout > 0 && now - startTime > requestTimeout) {
71143
+ throw new Error(`Engine stream timeout - request exceeded ${requestTimeout}ms`);
71144
+ }
71145
+ lastActivity = now;
71146
+ if (message.type === "text" && message.content) {
71147
+ yield message.content;
71148
+ } else if (typeof message === "string") {
71149
+ yield message;
71093
71150
  }
71094
- return {
71095
- textStream: createTextStream(),
71096
- usage: Promise.resolve({})
71097
- // Engine should handle its own usage tracking
71098
- // Add other streamText-compatible properties as needed
71099
- };
71100
71151
  }
71101
- } catch (error) {
71102
- if (this.debug) {
71103
- console.log(`[DEBUG] Failed to use Codex engine, falling back to Vercel:`, error.message);
71152
+ } finally {
71153
+ if (timeoutState.timeoutId) {
71154
+ clearTimeout(timeoutState.timeoutId);
71155
+ timeoutState.timeoutId = null;
71104
71156
  }
71105
71157
  }
71106
71158
  }
71159
+ return {
71160
+ textStream: createTextStream(),
71161
+ usage: Promise.resolve({})
71162
+ // Engine should handle its own usage tracking
71163
+ // Add other streamText-compatible properties as needed
71164
+ };
71165
+ }
71166
+ /**
71167
+ * Try to use an engine (claude-code or codex) for streaming
71168
+ * @param {Object} options - streamText options
71169
+ * @param {AbortController} controller - Abort controller for the operation
71170
+ * @param {Object} timeoutState - Mutable timeout state for cleanup
71171
+ * @returns {Promise<Object|null>} - Stream result or null if engine unavailable
71172
+ * @private
71173
+ */
71174
+ async _tryEngineStreamPath(options, controller, timeoutState) {
71175
+ const engine = await this.getEngine();
71176
+ if (!engine || !engine.query) {
71177
+ return null;
71178
+ }
71179
+ const userMessages = options.messages.filter(
71180
+ (m) => m.role === "user" && !m.content.includes("WARNING: You have reached the maximum tool iterations limit")
71181
+ );
71182
+ const lastUserMessage = userMessages[userMessages.length - 1];
71183
+ const prompt = lastUserMessage ? lastUserMessage.content : "";
71184
+ const engineOptions = {
71185
+ maxTokens: options.maxTokens,
71186
+ temperature: options.temperature,
71187
+ messages: options.messages,
71188
+ systemPrompt: options.messages.find((m) => m.role === "system")?.content,
71189
+ abortSignal: controller.signal
71190
+ };
71191
+ const engineStream = engine.query(prompt, engineOptions);
71192
+ return this._createEngineTextStreamResult(
71193
+ engineStream,
71194
+ controller.signal,
71195
+ this.requestTimeout,
71196
+ timeoutState
71197
+ );
71198
+ }
71199
+ /**
71200
+ * Execute streamText with Vercel AI SDK using retry/fallback logic
71201
+ * @param {Object} options - streamText options
71202
+ * @param {AbortController} controller - Abort controller for the operation
71203
+ * @returns {Promise<Object>} - Stream result
71204
+ * @private
71205
+ */
71206
+ async _executeWithVercelProvider(options, controller) {
71107
71207
  if (!this.retryManager) {
71108
71208
  this.retryManager = new RetryManager({
71109
71209
  maxRetries: this.retryConfig.maxRetries ?? 3,
@@ -71116,10 +71216,11 @@ var init_ProbeAgent = __esm({
71116
71216
  }
71117
71217
  if (!this.fallbackManager) {
71118
71218
  return await this.retryManager.executeWithRetry(
71119
- () => streamText2(options),
71219
+ () => streamText2({ ...options, abortSignal: controller.signal }),
71120
71220
  {
71121
71221
  provider: this.apiType,
71122
- model: this.model
71222
+ model: this.model,
71223
+ signal: controller.signal
71123
71224
  }
71124
71225
  );
71125
71226
  }
@@ -71127,7 +71228,8 @@ var init_ProbeAgent = __esm({
71127
71228
  async (provider, model, config) => {
71128
71229
  const fallbackOptions = {
71129
71230
  ...options,
71130
- model: provider(model)
71231
+ model: provider(model),
71232
+ abortSignal: controller.signal
71131
71233
  };
71132
71234
  const providerRetryManager = new RetryManager({
71133
71235
  maxRetries: config.maxRetries ?? this.retryConfig.maxRetries ?? 3,
@@ -71141,12 +71243,54 @@ var init_ProbeAgent = __esm({
71141
71243
  () => streamText2(fallbackOptions),
71142
71244
  {
71143
71245
  provider: config.provider,
71144
- model
71246
+ model,
71247
+ signal: controller.signal
71145
71248
  }
71146
71249
  );
71147
71250
  }
71148
71251
  );
71149
71252
  }
71253
+ /**
71254
+ * Execute streamText with retry and fallback support
71255
+ * @param {Object} options - streamText options
71256
+ * @returns {Promise<Object>} - streamText result
71257
+ * @private
71258
+ */
71259
+ async streamTextWithRetryAndFallback(options) {
71260
+ const controller = new AbortController();
71261
+ const timeoutState = { timeoutId: null };
71262
+ if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
71263
+ timeoutState.timeoutId = setTimeout(() => {
71264
+ controller.abort();
71265
+ if (this.debug) {
71266
+ console.log(`[DEBUG] Operation timed out after ${this.maxOperationTimeout}ms (max operation timeout)`);
71267
+ }
71268
+ }, this.maxOperationTimeout);
71269
+ }
71270
+ try {
71271
+ const useClaudeCode = this.clientApiProvider === "claude-code" || process.env.USE_CLAUDE_CODE === "true";
71272
+ const useCodex = this.clientApiProvider === "codex" || process.env.USE_CODEX === "true";
71273
+ if (useClaudeCode || useCodex) {
71274
+ try {
71275
+ const result = await this._tryEngineStreamPath(options, controller, timeoutState);
71276
+ if (result) {
71277
+ return result;
71278
+ }
71279
+ } catch (error) {
71280
+ if (this.debug) {
71281
+ const engineType = useClaudeCode ? "Claude Code" : "Codex";
71282
+ console.log(`[DEBUG] Failed to use ${engineType} engine, falling back to Vercel:`, error.message);
71283
+ }
71284
+ }
71285
+ }
71286
+ return await this._executeWithVercelProvider(options, controller);
71287
+ } finally {
71288
+ if (timeoutState.timeoutId) {
71289
+ clearTimeout(timeoutState.timeoutId);
71290
+ timeoutState.timeoutId = null;
71291
+ }
71292
+ }
71293
+ }
71150
71294
  /**
71151
71295
  * Initialize Anthropic model
71152
71296
  */