@probelabs/probe 0.6.0-rc323 → 0.6.0-rc325
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/bin/binaries/{probe-v0.6.0-rc323-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc325-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc323-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc325-aarch64-unknown-linux-musl.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc323-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc325-x86_64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc323-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc325-x86_64-pc-windows-msvc.zip} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc323-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc325-x86_64-unknown-linux-musl.tar.gz} +0 -0
- package/build/agent/ProbeAgent.js +117 -87
- package/build/agent/RetryManager.js +6 -2
- package/cjs/agent/ProbeAgent.cjs +197 -130
- package/cjs/index.cjs +197 -130
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +117 -87
- package/src/agent/RetryManager.js +6 -2
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -1453,30 +1453,45 @@ export class ProbeAgent {
|
|
|
1453
1453
|
);
|
|
1454
1454
|
}
|
|
1455
1455
|
|
|
1456
|
+
/**
|
|
1457
|
+
* Create a RetryManager from the agent's retry configuration.
|
|
1458
|
+
* @param {Object} [overrides={}] - Retry option overrides
|
|
1459
|
+
* @returns {RetryManager}
|
|
1460
|
+
* @private
|
|
1461
|
+
*/
|
|
1462
|
+
_createRetryManager(overrides = {}) {
|
|
1463
|
+
return new RetryManager({
|
|
1464
|
+
maxRetries: overrides.maxRetries ?? this.retryConfig.maxRetries ?? 3,
|
|
1465
|
+
initialDelay: overrides.initialDelay ?? this.retryConfig.initialDelay ?? 1000,
|
|
1466
|
+
maxDelay: overrides.maxDelay ?? this.retryConfig.maxDelay ?? 30000,
|
|
1467
|
+
backoffFactor: overrides.backoffFactor ?? this.retryConfig.backoffFactor ?? 2,
|
|
1468
|
+
retryableErrors: overrides.retryableErrors ?? this.retryConfig.retryableErrors,
|
|
1469
|
+
jitter: overrides.jitter ?? this.retryConfig.jitter,
|
|
1470
|
+
debug: overrides.debug ?? this.debug
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1456
1474
|
/**
|
|
1457
1475
|
* Execute streamText with Vercel AI SDK using retry/fallback logic
|
|
1458
1476
|
* @param {Object} options - streamText options
|
|
1459
1477
|
* @param {AbortController} controller - Abort controller for the operation
|
|
1478
|
+
* @param {Function} [consumeResult] - Optional callback to consume the stream result inside the same retry budget
|
|
1460
1479
|
* @returns {Promise<Object>} - Stream result
|
|
1461
1480
|
* @private
|
|
1462
1481
|
*/
|
|
1463
|
-
async _executeWithVercelProvider(options, controller) {
|
|
1482
|
+
async _executeWithVercelProvider(options, controller, consumeResult) {
|
|
1464
1483
|
// Initialize retry manager if not already created
|
|
1465
1484
|
if (!this.retryManager) {
|
|
1466
|
-
this.retryManager =
|
|
1467
|
-
maxRetries: this.retryConfig.maxRetries ?? 3,
|
|
1468
|
-
initialDelay: this.retryConfig.initialDelay ?? 1000,
|
|
1469
|
-
maxDelay: this.retryConfig.maxDelay ?? 30000,
|
|
1470
|
-
backoffFactor: this.retryConfig.backoffFactor ?? 2,
|
|
1471
|
-
retryableErrors: this.retryConfig.retryableErrors,
|
|
1472
|
-
debug: this.debug
|
|
1473
|
-
});
|
|
1485
|
+
this.retryManager = this._createRetryManager();
|
|
1474
1486
|
}
|
|
1475
1487
|
|
|
1476
1488
|
// If no fallback manager, just use retry with current provider
|
|
1477
1489
|
if (!this.fallbackManager) {
|
|
1478
1490
|
return await this.retryManager.executeWithRetry(
|
|
1479
|
-
() =>
|
|
1491
|
+
async () => {
|
|
1492
|
+
const result = streamText({ ...options, abortSignal: controller.signal });
|
|
1493
|
+
return consumeResult ? await consumeResult(result) : result;
|
|
1494
|
+
},
|
|
1480
1495
|
{
|
|
1481
1496
|
provider: this.apiType,
|
|
1482
1497
|
model: this.model,
|
|
@@ -1510,17 +1525,15 @@ export class ProbeAgent {
|
|
|
1510
1525
|
}
|
|
1511
1526
|
}
|
|
1512
1527
|
|
|
1513
|
-
const providerRetryManager =
|
|
1514
|
-
maxRetries: config.maxRetries ?? this.retryConfig.maxRetries
|
|
1515
|
-
initialDelay: this.retryConfig.initialDelay ?? 1000,
|
|
1516
|
-
maxDelay: this.retryConfig.maxDelay ?? 30000,
|
|
1517
|
-
backoffFactor: this.retryConfig.backoffFactor ?? 2,
|
|
1518
|
-
retryableErrors: this.retryConfig.retryableErrors,
|
|
1519
|
-
debug: this.debug
|
|
1528
|
+
const providerRetryManager = this._createRetryManager({
|
|
1529
|
+
maxRetries: config.maxRetries ?? this.retryConfig.maxRetries
|
|
1520
1530
|
});
|
|
1521
1531
|
|
|
1522
1532
|
return await providerRetryManager.executeWithRetry(
|
|
1523
|
-
() =>
|
|
1533
|
+
async () => {
|
|
1534
|
+
const result = streamText(fallbackOptions);
|
|
1535
|
+
return consumeResult ? await consumeResult(result) : result;
|
|
1536
|
+
},
|
|
1524
1537
|
{
|
|
1525
1538
|
provider: config.provider,
|
|
1526
1539
|
model: model,
|
|
@@ -1671,10 +1684,11 @@ export class ProbeAgent {
|
|
|
1671
1684
|
/**
|
|
1672
1685
|
* Execute streamText with retry and fallback support
|
|
1673
1686
|
* @param {Object} options - streamText options
|
|
1674
|
-
* @
|
|
1687
|
+
* @param {Function} [consumeResult] - Optional callback to consume the result inside the same retry attempt
|
|
1688
|
+
* @returns {Promise<Object>} - streamText result or consumer result
|
|
1675
1689
|
* @private
|
|
1676
1690
|
*/
|
|
1677
|
-
async streamTextWithRetryAndFallback(options) {
|
|
1691
|
+
async streamTextWithRetryAndFallback(options, consumeResult) {
|
|
1678
1692
|
// Wrap the model with per-call concurrency gating if limiter is configured.
|
|
1679
1693
|
// This acquires/releases the slot around each individual LLM API call (doStream/doGenerate)
|
|
1680
1694
|
// instead of holding it for the entire multi-step agent session.
|
|
@@ -1727,6 +1741,7 @@ export class ProbeAgent {
|
|
|
1727
1741
|
const useCodex = this.clientApiProvider === 'codex' || process.env.USE_CODEX === 'true';
|
|
1728
1742
|
|
|
1729
1743
|
let result;
|
|
1744
|
+
let usedVercelProvider = false;
|
|
1730
1745
|
if (useClaudeCode || useCodex) {
|
|
1731
1746
|
try {
|
|
1732
1747
|
result = await this._tryEngineStreamPath(options, controller, timeoutState);
|
|
@@ -1747,7 +1762,12 @@ export class ProbeAgent {
|
|
|
1747
1762
|
|
|
1748
1763
|
if (!result) {
|
|
1749
1764
|
// Use Vercel AI SDK with retry/fallback
|
|
1750
|
-
|
|
1765
|
+
usedVercelProvider = true;
|
|
1766
|
+
result = await this._executeWithVercelProvider(options, controller, consumeResult);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
if (!usedVercelProvider && result && consumeResult) {
|
|
1770
|
+
return await consumeResult(result);
|
|
1751
1771
|
}
|
|
1752
1772
|
|
|
1753
1773
|
return result;
|
|
@@ -3763,8 +3783,6 @@ Follow these instructions carefully:
|
|
|
3763
3783
|
activeTools.delete(key);
|
|
3764
3784
|
}
|
|
3765
3785
|
};
|
|
3766
|
-
this.events.on('toolCall', onToolCall);
|
|
3767
|
-
|
|
3768
3786
|
// Timeout observer: separate LLM call that decides whether to extend.
|
|
3769
3787
|
// Runs independently of the main agent loop — works even when blocked by delegates.
|
|
3770
3788
|
const runTimeoutObserver = async () => {
|
|
@@ -4426,80 +4444,92 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
4426
4444
|
}
|
|
4427
4445
|
|
|
4428
4446
|
const executeAIRequest = async () => {
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
// Safety net: hard abort after 60s if wind-down doesn't complete
|
|
4443
|
-
hardAbortTimeoutId = setTimeout(() => {
|
|
4444
|
-
if (this._abortController) {
|
|
4445
|
-
this._abortController.abort();
|
|
4447
|
+
return await this.streamTextWithRetryAndFallback(streamOptions, async (result) => {
|
|
4448
|
+
this.events.on('toolCall', onToolCall);
|
|
4449
|
+
|
|
4450
|
+
// Set up timeout timer now that streamText is running.
|
|
4451
|
+
// streamText() returns immediately — the actual tool loop runs asynchronously
|
|
4452
|
+
// and completes when we await result.steps/result.text below.
|
|
4453
|
+
let gracefulTimeoutId = null;
|
|
4454
|
+
let hardAbortTimeoutId = null;
|
|
4455
|
+
if (this.timeoutBehavior === 'graceful' && gracefulTimeoutState && this.maxOperationTimeout > 0) {
|
|
4456
|
+
gracefulTimeoutId = setTimeout(() => {
|
|
4457
|
+
gracefulTimeoutState.triggered = true;
|
|
4458
|
+
if (this.debug) {
|
|
4459
|
+
console.log(`[DEBUG] Soft timeout after ${this.maxOperationTimeout}ms — entering wind-down mode (${gracefulTimeoutState.bonusStepsMax} bonus steps)`);
|
|
4446
4460
|
}
|
|
4461
|
+
// Safety net: hard abort after 60s if wind-down doesn't complete
|
|
4462
|
+
hardAbortTimeoutId = setTimeout(() => {
|
|
4463
|
+
if (this._abortController) {
|
|
4464
|
+
this._abortController.abort();
|
|
4465
|
+
}
|
|
4466
|
+
if (this.debug) {
|
|
4467
|
+
console.log(`[DEBUG] Hard abort — wind-down safety net expired after 60s`);
|
|
4468
|
+
}
|
|
4469
|
+
}, 60000);
|
|
4470
|
+
}, this.maxOperationTimeout);
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
// Negotiated timeout: run the timeout observer (separate LLM call)
|
|
4474
|
+
if (this.timeoutBehavior === 'negotiated' && this.maxOperationTimeout > 0) {
|
|
4475
|
+
negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
|
|
4447
4476
|
if (this.debug) {
|
|
4448
|
-
console.log(`[DEBUG]
|
|
4477
|
+
console.log(`[DEBUG] Soft timeout after ${this.maxOperationTimeout}ms — invoking timeout observer`);
|
|
4449
4478
|
}
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4479
|
+
runTimeoutObserver();
|
|
4480
|
+
}, this.maxOperationTimeout);
|
|
4481
|
+
}
|
|
4453
4482
|
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4483
|
+
try {
|
|
4484
|
+
// Use only the last step's text as the final answer.
|
|
4485
|
+
// result.text concatenates ALL steps (including intermediate planning text),
|
|
4486
|
+
// but the user should only see the final answer from the last step.
|
|
4487
|
+
const steps = await result.steps;
|
|
4488
|
+
let finalText;
|
|
4489
|
+
if (steps && steps.length > 1) {
|
|
4490
|
+
// Multi-step: use last step's text (the actual answer after tool calls)
|
|
4491
|
+
const lastStepText = steps[steps.length - 1].text;
|
|
4492
|
+
finalText = lastStepText || await result.text;
|
|
4493
|
+
} else {
|
|
4494
|
+
finalText = await result.text;
|
|
4459
4495
|
}
|
|
4460
|
-
runTimeoutObserver();
|
|
4461
|
-
}, this.maxOperationTimeout);
|
|
4462
|
-
}
|
|
4463
4496
|
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
finalText = lastStepText || await result.text;
|
|
4474
|
-
} else {
|
|
4475
|
-
finalText = await result.text;
|
|
4476
|
-
}
|
|
4497
|
+
// Native schema responses can legitimately carry their payload in result.output
|
|
4498
|
+
// while text and steps stay empty. Plain text answers with no steps and no
|
|
4499
|
+
// text are empty streams and should be retried by the active retry manager.
|
|
4500
|
+
if (!options.schema && (!steps || steps.length === 0) && (!finalText || !finalText.trim())) {
|
|
4501
|
+
throw Object.assign(
|
|
4502
|
+
new Error('No output generated. Check the stream for errors.'),
|
|
4503
|
+
{ name: 'AI_NoOutputGeneratedError' }
|
|
4504
|
+
);
|
|
4505
|
+
}
|
|
4477
4506
|
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4507
|
+
if (this.debug) {
|
|
4508
|
+
console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
|
|
4509
|
+
}
|
|
4481
4510
|
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4511
|
+
// Record final token usage
|
|
4512
|
+
const usage = await result.usage;
|
|
4513
|
+
if (usage) {
|
|
4514
|
+
this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
|
|
4515
|
+
}
|
|
4487
4516
|
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4517
|
+
return { finalText, result };
|
|
4518
|
+
} finally {
|
|
4519
|
+
// Clean up graceful timeout timers
|
|
4520
|
+
if (gracefulTimeoutId) clearTimeout(gracefulTimeoutId);
|
|
4521
|
+
if (hardAbortTimeoutId) clearTimeout(hardAbortTimeoutId);
|
|
4522
|
+
// Clean up negotiated timeout timer
|
|
4523
|
+
if (negotiatedTimeoutState.softTimeoutId) clearTimeout(negotiatedTimeoutState.softTimeoutId);
|
|
4524
|
+
// Clean up graceful stop hard abort timer
|
|
4525
|
+
if (this._gracefulStopHardAbortId) {
|
|
4526
|
+
clearTimeout(this._gracefulStopHardAbortId);
|
|
4527
|
+
this._gracefulStopHardAbortId = null;
|
|
4528
|
+
}
|
|
4529
|
+
// Remove in-flight tool tracker
|
|
4530
|
+
this.events.removeListener('toolCall', onToolCall);
|
|
4499
4531
|
}
|
|
4500
|
-
|
|
4501
|
-
this.events.removeListener('toolCall', onToolCall);
|
|
4502
|
-
}
|
|
4532
|
+
});
|
|
4503
4533
|
};
|
|
4504
4534
|
|
|
4505
4535
|
let aiResult;
|
|
@@ -25,7 +25,9 @@ const DEFAULT_RETRYABLE_ERRORS = [
|
|
|
25
25
|
'ECONNRESET',
|
|
26
26
|
'ETIMEDOUT',
|
|
27
27
|
'ENOTFOUND',
|
|
28
|
-
'api_error'
|
|
28
|
+
'api_error',
|
|
29
|
+
'No output generated',
|
|
30
|
+
'AI_NoOutputGeneratedError'
|
|
29
31
|
];
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -39,6 +41,7 @@ function isRetryableError(error, retryableErrors = DEFAULT_RETRYABLE_ERRORS) {
|
|
|
39
41
|
|
|
40
42
|
const errorString = error.toString().toLowerCase();
|
|
41
43
|
const errorMessage = (error.message || '').toLowerCase();
|
|
44
|
+
const errorName = (error.name || error.constructor?.name || '').toLowerCase();
|
|
42
45
|
const errorCode = (error.code || '').toLowerCase();
|
|
43
46
|
const errorType = (error.type || '').toLowerCase();
|
|
44
47
|
const statusCode = error.statusCode || error.status;
|
|
@@ -49,6 +52,7 @@ function isRetryableError(error, retryableErrors = DEFAULT_RETRYABLE_ERRORS) {
|
|
|
49
52
|
|
|
50
53
|
if (errorString.includes(lowerPattern) ||
|
|
51
54
|
errorMessage.includes(lowerPattern) ||
|
|
55
|
+
errorName.includes(lowerPattern) ||
|
|
52
56
|
errorCode.includes(lowerPattern) ||
|
|
53
57
|
errorType.includes(lowerPattern) ||
|
|
54
58
|
statusCode?.toString() === pattern) {
|
|
@@ -106,7 +110,7 @@ export class RetryManager {
|
|
|
106
110
|
this.backoffFactor = this._validateNumber(options.backoffFactor, 2, 'backoffFactor', 1, 10);
|
|
107
111
|
this.retryableErrors = options.retryableErrors || DEFAULT_RETRYABLE_ERRORS;
|
|
108
112
|
this.debug = options.debug ?? false;
|
|
109
|
-
this.jitter = options.jitter
|
|
113
|
+
this.jitter = typeof options.jitter === 'boolean' ? options.jitter : true; // Add random jitter by default
|
|
110
114
|
|
|
111
115
|
// Validate that maxDelay >= initialDelay
|
|
112
116
|
if (this.maxDelay < this.initialDelay) {
|