@bike4mind/cli 0.2.21-fix-cli-subagent-model-alias-resolution.18205 → 0.2.21-fix-idle-timeout-error-message.18217
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/{chunk-NAIQUTDD.js → chunk-FGNCVUBR.js} +1 -1
- package/dist/{chunk-3NN5PD22.js → chunk-KUGWFEGL.js} +1 -1
- package/dist/{chunk-HXWD4UGK.js → chunk-RQRJNE4K.js} +275 -32
- package/dist/{chunk-4UPFBOWT.js → chunk-ZXXSKLZA.js} +1 -1
- package/dist/{create-EZHKFCZQ.js → create-QDEJLD6X.js} +2 -2
- package/dist/index.js +11 -151
- package/dist/{mementoService-C34LNDCN.js → mementoService-KSDOJKMT.js} +2 -2
- package/dist/{src-2ENQ4HWZ.js → src-XY56MWV2.js} +1 -1
- package/dist/{subtractCredits-5SDCKUXU.js → subtractCredits-3VRXFXK3.js} +2 -2
- package/package.json +6 -6
|
@@ -1949,6 +1949,7 @@ var TEMPERATURE_ONLY_MODELS = [
|
|
|
1949
1949
|
var INITIAL_TIMEOUT_MS = 3e4;
|
|
1950
1950
|
var DEFAULT_IDLE_TIMEOUT_MS = 9e4;
|
|
1951
1951
|
var THINKING_IDLE_TIMEOUT_MS = 18e4;
|
|
1952
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
1952
1953
|
var AnthropicBackend = class {
|
|
1953
1954
|
_api;
|
|
1954
1955
|
logger;
|
|
@@ -1964,8 +1965,10 @@ var AnthropicBackend = class {
|
|
|
1964
1965
|
/**
|
|
1965
1966
|
* Emit a CloudWatch metric when a rate limit error occurs.
|
|
1966
1967
|
* This helps monitor and alert on Anthropic API rate limiting issues.
|
|
1968
|
+
* @param model - The model that triggered the rate limit
|
|
1969
|
+
* @param featureArea - The feature area (mcp-tools, chat, built-in-tools, etc.) for filtering (#6413)
|
|
1967
1970
|
*/
|
|
1968
|
-
async emitRateLimitMetric(model) {
|
|
1971
|
+
async emitRateLimitMetric(model, featureArea = "unknown") {
|
|
1969
1972
|
try {
|
|
1970
1973
|
const client = new CloudWatchClient({
|
|
1971
1974
|
region: process.env.AWS_REGION || "us-east-2"
|
|
@@ -1983,13 +1986,15 @@ var AnthropicBackend = class {
|
|
|
1983
1986
|
// SEED_STAGE_NAME is set via DEFAULT_LAMBDA_ENVIRONMENT in infra/constants.ts
|
|
1984
1987
|
// and maps to $app.stage. The alarm in infra/alarms.ts filters by Stage dimension.
|
|
1985
1988
|
// Fallback to 'local' for non-Lambda contexts (local dev, tests).
|
|
1986
|
-
{ Name: "Stage", Value: process.env.SEED_STAGE_NAME || "local" }
|
|
1989
|
+
{ Name: "Stage", Value: process.env.SEED_STAGE_NAME || "local" },
|
|
1990
|
+
// Feature area helps identify which feature is causing rate limits (#6413)
|
|
1991
|
+
{ Name: "FeatureArea", Value: featureArea }
|
|
1987
1992
|
]
|
|
1988
1993
|
}
|
|
1989
1994
|
]
|
|
1990
1995
|
});
|
|
1991
1996
|
await client.send(command);
|
|
1992
|
-
this.logger.info("[AnthropicBackend] Emitted RateLimitError metric to CloudWatch", { model });
|
|
1997
|
+
this.logger.info("[AnthropicBackend] Emitted RateLimitError metric to CloudWatch", { model, featureArea });
|
|
1993
1998
|
} catch (metricsError) {
|
|
1994
1999
|
this.logger.warn("[AnthropicBackend] Failed to emit CloudWatch metric", {
|
|
1995
2000
|
error: metricsError instanceof Error ? metricsError.message : String(metricsError)
|
|
@@ -2289,9 +2294,29 @@ var AnthropicBackend = class {
|
|
|
2289
2294
|
...options
|
|
2290
2295
|
};
|
|
2291
2296
|
const toolCallCount = options._internal?.toolCallCount ?? 0;
|
|
2292
|
-
if (toolCallCount >= DEFAULT_MAX_TOOL_CALLS) {
|
|
2293
|
-
|
|
2294
|
-
|
|
2297
|
+
if (toolCallCount >= DEFAULT_MAX_TOOL_CALLS && options.tools?.length) {
|
|
2298
|
+
const mcpTools = options.tools?.filter((t) => t._isMcpTool) || [];
|
|
2299
|
+
const builtInTools = options.tools?.filter((t) => !t._isMcpTool) || [];
|
|
2300
|
+
this.logger.warn(`\u26A0\uFE0F Max tool calls limit (${DEFAULT_MAX_TOOL_CALLS}) reached. Disabling tools to prevent infinite loops.`, {
|
|
2301
|
+
model,
|
|
2302
|
+
toolCallCount,
|
|
2303
|
+
toolsUsedSoFar: toolsUsed.map((t) => t.name),
|
|
2304
|
+
availableToolCount: options.tools?.length || 0,
|
|
2305
|
+
mcpToolCount: mcpTools.length,
|
|
2306
|
+
mcpToolNames: mcpTools.map((t) => t.toolSchema.name).slice(0, 10),
|
|
2307
|
+
builtInToolCount: builtInTools.length,
|
|
2308
|
+
builtInToolNames: builtInTools.map((t) => t.toolSchema.name).slice(0, 10),
|
|
2309
|
+
messageCount: messages.length
|
|
2310
|
+
});
|
|
2311
|
+
await this.complete(model, messages, {
|
|
2312
|
+
...options,
|
|
2313
|
+
tools: void 0,
|
|
2314
|
+
_internal: {
|
|
2315
|
+
...options._internal
|
|
2316
|
+
// Preserve enableIdleTimeout, idleTimeoutMs
|
|
2317
|
+
// Keep toolCallCount at the limit - no need to increment when tools are removed
|
|
2318
|
+
}
|
|
2319
|
+
}, cb, toolsUsed);
|
|
2295
2320
|
return;
|
|
2296
2321
|
}
|
|
2297
2322
|
const rawTools = options.tools;
|
|
@@ -2366,8 +2391,44 @@ var AnthropicBackend = class {
|
|
|
2366
2391
|
const func = [];
|
|
2367
2392
|
if (options.stream) {
|
|
2368
2393
|
await new Promise(async (resolve, reject) => {
|
|
2394
|
+
const enableRequestTimeout = options._internal?.enableRequestTimeout ?? false;
|
|
2395
|
+
const requestAbortController = new AbortController();
|
|
2396
|
+
let requestTimeout;
|
|
2397
|
+
if (enableRequestTimeout) {
|
|
2398
|
+
requestTimeout = setTimeout(() => {
|
|
2399
|
+
this.logger.error("[AnthropicBackend] Request timeout - API call did not start streaming", {
|
|
2400
|
+
model,
|
|
2401
|
+
toolCount: options.tools?.length || 0,
|
|
2402
|
+
mcpToolCount: options.tools?.filter((t) => t._isMcpTool).length || 0,
|
|
2403
|
+
timeoutMs: REQUEST_TIMEOUT_MS
|
|
2404
|
+
});
|
|
2405
|
+
this.emitIdleTimeoutMetric(model, options.tools?.length || 0, 0).catch(() => {
|
|
2406
|
+
});
|
|
2407
|
+
requestAbortController.abort();
|
|
2408
|
+
}, REQUEST_TIMEOUT_MS);
|
|
2409
|
+
}
|
|
2410
|
+
const combinedSignal = enableRequestTimeout ? options.abortSignal ? AbortSignal.any([options.abortSignal, requestAbortController.signal]) : requestAbortController.signal : options.abortSignal;
|
|
2411
|
+
let isIdleTimeout = false;
|
|
2412
|
+
let idleTimeoutMsForError = 0;
|
|
2369
2413
|
try {
|
|
2370
|
-
const
|
|
2414
|
+
const payloadForSize = { ...apiParams, stream: true };
|
|
2415
|
+
const payloadSizeBytes = Buffer.byteLength(JSON.stringify(payloadForSize), "utf8");
|
|
2416
|
+
const toolsSizeBytes = apiParams.tools ? Buffer.byteLength(JSON.stringify(apiParams.tools), "utf8") : 0;
|
|
2417
|
+
const messagesSizeBytes = apiParams.messages ? Buffer.byteLength(JSON.stringify(apiParams.messages), "utf8") : 0;
|
|
2418
|
+
this.logger.info("[AnthropicBackend] API request payload diagnostics", {
|
|
2419
|
+
model,
|
|
2420
|
+
totalPayloadSizeKB: Math.round(payloadSizeBytes / 1024),
|
|
2421
|
+
toolsSizeKB: Math.round(toolsSizeBytes / 1024),
|
|
2422
|
+
messagesSizeKB: Math.round(messagesSizeBytes / 1024),
|
|
2423
|
+
toolCount: apiParams.tools?.length || 0,
|
|
2424
|
+
messageCount: apiParams.messages?.length || 0,
|
|
2425
|
+
mcpToolCount: options.tools?.filter((t) => t._isMcpTool).length || 0,
|
|
2426
|
+
hasThinking: !!apiParams.thinking,
|
|
2427
|
+
thinkingBudget: apiParams.thinking?.budget_tokens
|
|
2428
|
+
});
|
|
2429
|
+
const stream = await this._api.messages.create({ ...apiParams, stream: true }, { signal: combinedSignal });
|
|
2430
|
+
if (requestTimeout)
|
|
2431
|
+
clearTimeout(requestTimeout);
|
|
2371
2432
|
let isInThinkingBlock = false;
|
|
2372
2433
|
const collectedContent = [];
|
|
2373
2434
|
const enableIdleTimeout = options._internal?.enableIdleTimeout ?? false;
|
|
@@ -2383,6 +2444,8 @@ var AnthropicBackend = class {
|
|
|
2383
2444
|
clearTimeout(idleTimer);
|
|
2384
2445
|
const timeoutMs = eventCount === 0 ? INITIAL_TIMEOUT_MS : idleTimeoutMs;
|
|
2385
2446
|
idleTimer = setTimeout(() => {
|
|
2447
|
+
isIdleTimeout = true;
|
|
2448
|
+
idleTimeoutMsForError = timeoutMs;
|
|
2386
2449
|
this.logger.error("[AnthropicBackend] Stream idle timeout - no events received", {
|
|
2387
2450
|
model,
|
|
2388
2451
|
eventCount,
|
|
@@ -2394,7 +2457,6 @@ var AnthropicBackend = class {
|
|
|
2394
2457
|
this.emitIdleTimeoutMetric(model, options.tools?.length || 0, eventCount).catch(() => {
|
|
2395
2458
|
});
|
|
2396
2459
|
stream.controller?.abort?.();
|
|
2397
|
-
reject(new Error(`Stream idle timeout after ${timeoutMs}ms - no events received`));
|
|
2398
2460
|
}, timeoutMs);
|
|
2399
2461
|
};
|
|
2400
2462
|
resetIdleTimer();
|
|
@@ -2492,16 +2554,42 @@ var AnthropicBackend = class {
|
|
|
2492
2554
|
}
|
|
2493
2555
|
resolve();
|
|
2494
2556
|
} catch (error) {
|
|
2557
|
+
if (requestTimeout)
|
|
2558
|
+
clearTimeout(requestTimeout);
|
|
2495
2559
|
const isAbortError = error instanceof Error && (error.message.includes("aborted") || error.message.includes("AbortError") || error.name === "AbortError");
|
|
2560
|
+
const isRequestTimeout = requestAbortController.signal.aborted;
|
|
2496
2561
|
if (isAbortError) {
|
|
2497
|
-
|
|
2498
|
-
|
|
2562
|
+
if (isRequestTimeout) {
|
|
2563
|
+
this.logger.error("[AnthropicBackend] Request aborted due to timeout", {
|
|
2564
|
+
model,
|
|
2565
|
+
toolCount: options.tools?.length || 0
|
|
2566
|
+
});
|
|
2567
|
+
reject(new Error(`Anthropic API request timeout after ${REQUEST_TIMEOUT_MS}ms - no streaming response received`));
|
|
2568
|
+
} else if (isIdleTimeout) {
|
|
2569
|
+
this.logger.error("[AnthropicBackend] Stream aborted due to idle timeout", {
|
|
2570
|
+
model,
|
|
2571
|
+
toolCount: options.tools?.length || 0,
|
|
2572
|
+
idleTimeoutMs: idleTimeoutMsForError
|
|
2573
|
+
});
|
|
2574
|
+
reject(new Error(`Anthropic API stream timeout - no response received within ${idleTimeoutMsForError / 1e3} seconds. The model may be overloaded. Try simplifying your request or using fewer tools.`));
|
|
2575
|
+
} else {
|
|
2576
|
+
this.logger.debug("Anthropic request was aborted (likely client disconnect)");
|
|
2577
|
+
resolve();
|
|
2578
|
+
}
|
|
2499
2579
|
} else {
|
|
2500
2580
|
reject(error);
|
|
2501
2581
|
}
|
|
2502
2582
|
}
|
|
2503
2583
|
});
|
|
2504
2584
|
if (func.some((f) => f && f.name)) {
|
|
2585
|
+
const toolCallNames = func.filter((t) => t?.name).map((t) => t.name);
|
|
2586
|
+
this.logger.info("[Tool Execution] Model requested tool calls", {
|
|
2587
|
+
model,
|
|
2588
|
+
toolCallCount,
|
|
2589
|
+
toolsRequested: toolCallNames,
|
|
2590
|
+
toolCount: toolCallNames.length,
|
|
2591
|
+
messageCountBefore: messages.length
|
|
2592
|
+
});
|
|
2505
2593
|
for (const tool of func) {
|
|
2506
2594
|
if (!tool || !tool.name || !tool.parameters)
|
|
2507
2595
|
continue;
|
|
@@ -2515,14 +2603,31 @@ var AnthropicBackend = class {
|
|
|
2515
2603
|
if (!tool || !tool.name || !tool.parameters)
|
|
2516
2604
|
continue;
|
|
2517
2605
|
const { id, name, parameters } = tool;
|
|
2518
|
-
const
|
|
2606
|
+
const toolDef = options.tools?.find((tool2) => tool2.toolSchema.name === name);
|
|
2607
|
+
const toolFn = toolDef?.toolFn;
|
|
2608
|
+
const isMcpTool = toolDef?._isMcpTool ?? false;
|
|
2519
2609
|
if (name && parameters && toolFn) {
|
|
2520
|
-
this.logger.
|
|
2521
|
-
|
|
2610
|
+
this.logger.info("[Tool Execution] Executing tool", {
|
|
2611
|
+
model,
|
|
2612
|
+
toolName: name,
|
|
2613
|
+
isMcpTool,
|
|
2614
|
+
toolCallIteration: toolCallCount + 1,
|
|
2615
|
+
parameterKeys: Object.keys(JSON.parse(parameters || "{}"))
|
|
2616
|
+
});
|
|
2617
|
+
const toolStartTime = Date.now();
|
|
2522
2618
|
try {
|
|
2523
2619
|
const parsedParams = JSON.parse(parameters);
|
|
2524
2620
|
const result = await toolFn(parsedParams);
|
|
2525
|
-
|
|
2621
|
+
const resultStr = result.toString();
|
|
2622
|
+
const toolDuration = Date.now() - toolStartTime;
|
|
2623
|
+
this.logger.info("[Tool Execution] Tool completed successfully", {
|
|
2624
|
+
model,
|
|
2625
|
+
toolName: name,
|
|
2626
|
+
isMcpTool,
|
|
2627
|
+
durationMs: toolDuration,
|
|
2628
|
+
resultLength: resultStr.length,
|
|
2629
|
+
resultPreview: resultStr.substring(0, 100) + (resultStr.length > 100 ? "..." : "")
|
|
2630
|
+
});
|
|
2526
2631
|
await handleToolResultStreaming(name, result, async (results) => {
|
|
2527
2632
|
await cb(results, {
|
|
2528
2633
|
inputTokens: 0,
|
|
@@ -2531,21 +2636,63 @@ var AnthropicBackend = class {
|
|
|
2531
2636
|
});
|
|
2532
2637
|
});
|
|
2533
2638
|
const toolId = id || `tool_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2534
|
-
this.pushToolMessages(messages, { id: toolId, name, parameters },
|
|
2639
|
+
this.pushToolMessages(messages, { id: toolId, name, parameters }, resultStr);
|
|
2535
2640
|
} catch (error) {
|
|
2641
|
+
const toolDuration = Date.now() - toolStartTime;
|
|
2536
2642
|
if (error instanceof PermissionDeniedError) {
|
|
2537
2643
|
throw error;
|
|
2538
2644
|
}
|
|
2539
|
-
this.logger.error(
|
|
2645
|
+
this.logger.error("[Tool Execution] Tool failed", {
|
|
2646
|
+
model,
|
|
2647
|
+
toolName: name,
|
|
2648
|
+
isMcpTool,
|
|
2649
|
+
durationMs: toolDuration,
|
|
2650
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2651
|
+
});
|
|
2540
2652
|
const toolId = id || `tool_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2541
2653
|
this.pushToolMessages(messages, { id: toolId, name, parameters }, `Error processing ${name} tool: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2542
2654
|
}
|
|
2655
|
+
} else if (name && parameters && !toolFn) {
|
|
2656
|
+
this.logger.warn("[Tool Execution] Tool function not found", {
|
|
2657
|
+
model,
|
|
2658
|
+
toolName: name,
|
|
2659
|
+
availableTools: options.tools?.map((t) => t.toolSchema.name) || []
|
|
2660
|
+
});
|
|
2543
2661
|
}
|
|
2544
2662
|
}
|
|
2545
2663
|
await cb(["\n\n"], { toolsUsed });
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2664
|
+
const hasMcpTool = func.some((tool) => {
|
|
2665
|
+
if (!tool?.name)
|
|
2666
|
+
return false;
|
|
2667
|
+
return options.tools?.find((t) => t.toolSchema.name === tool.name)?._isMcpTool;
|
|
2668
|
+
});
|
|
2669
|
+
this.logger.info("[Tool Execution] Making recursive call after tool execution", {
|
|
2670
|
+
model,
|
|
2671
|
+
toolCallIteration: toolCallCount + 1,
|
|
2672
|
+
messageCountAfter: messages.length,
|
|
2673
|
+
toolsExecuted: func.filter((t) => t?.name).map((t) => t.name),
|
|
2674
|
+
hasMcpTool,
|
|
2675
|
+
willKeepTools: hasMcpTool,
|
|
2676
|
+
totalToolsUsed: toolsUsed.length
|
|
2677
|
+
});
|
|
2678
|
+
if (hasMcpTool) {
|
|
2679
|
+
await this.complete(model, messages, {
|
|
2680
|
+
...options,
|
|
2681
|
+
_internal: {
|
|
2682
|
+
...options._internal,
|
|
2683
|
+
toolCallCount: toolCallCount + 1
|
|
2684
|
+
}
|
|
2685
|
+
}, cb, toolsUsed);
|
|
2686
|
+
} else {
|
|
2687
|
+
await this.complete(model, messages, {
|
|
2688
|
+
...options,
|
|
2689
|
+
tools: void 0,
|
|
2690
|
+
_internal: {
|
|
2691
|
+
...options._internal,
|
|
2692
|
+
toolCallCount: toolCallCount + 1
|
|
2693
|
+
}
|
|
2694
|
+
}, cb, toolsUsed);
|
|
2695
|
+
}
|
|
2549
2696
|
} else {
|
|
2550
2697
|
const thinkingBlocks = this.getThinkingBlocks();
|
|
2551
2698
|
this.logger.debug(`[Tool Execution] executeTools=false, passing tool calls to callback with ${thinkingBlocks?.length || 0} thinking blocks`);
|
|
@@ -2587,32 +2734,70 @@ var AnthropicBackend = class {
|
|
|
2587
2734
|
}
|
|
2588
2735
|
await cb(streamedText, { toolsUsed });
|
|
2589
2736
|
if (func.some((f) => f && f.name)) {
|
|
2737
|
+
const toolCallNames = func.filter((t) => t?.name).map((t) => t.name);
|
|
2738
|
+
this.logger.info("[Tool Execution] Model requested tool calls (non-streaming)", {
|
|
2739
|
+
model,
|
|
2740
|
+
toolCallCount,
|
|
2741
|
+
toolsRequested: toolCallNames,
|
|
2742
|
+
toolCount: toolCallNames.length,
|
|
2743
|
+
messageCountBefore: messages.length
|
|
2744
|
+
});
|
|
2590
2745
|
if (options.executeTools !== false) {
|
|
2591
2746
|
for (const tool of func) {
|
|
2592
2747
|
if (!tool || !tool.name || !tool.parameters)
|
|
2593
2748
|
continue;
|
|
2594
2749
|
const { id, name, parameters } = tool;
|
|
2595
|
-
const
|
|
2750
|
+
const toolDef = options.tools?.find((tool2) => tool2.toolSchema.name === name);
|
|
2751
|
+
const toolFn = toolDef?.toolFn;
|
|
2752
|
+
const isMcpTool = toolDef?._isMcpTool ?? false;
|
|
2596
2753
|
if (name && parameters && toolFn) {
|
|
2597
|
-
this.logger.
|
|
2598
|
-
|
|
2754
|
+
this.logger.info("[Tool Execution] Executing tool (non-streaming)", {
|
|
2755
|
+
model,
|
|
2756
|
+
toolName: name,
|
|
2757
|
+
isMcpTool,
|
|
2758
|
+
toolCallIteration: toolCallCount + 1,
|
|
2759
|
+
parameterKeys: Object.keys(JSON.parse(parameters || "{}"))
|
|
2760
|
+
});
|
|
2761
|
+
const toolStartTime = Date.now();
|
|
2599
2762
|
try {
|
|
2600
2763
|
const parsedParams = JSON.parse(parameters);
|
|
2601
2764
|
const result = await toolFn(parsedParams);
|
|
2602
|
-
|
|
2765
|
+
const resultStr = result.toString();
|
|
2766
|
+
const toolDuration = Date.now() - toolStartTime;
|
|
2767
|
+
this.logger.info("[Tool Execution] Tool completed successfully (non-streaming)", {
|
|
2768
|
+
model,
|
|
2769
|
+
toolName: name,
|
|
2770
|
+
isMcpTool,
|
|
2771
|
+
durationMs: toolDuration,
|
|
2772
|
+
resultLength: resultStr.length,
|
|
2773
|
+
resultPreview: resultStr.substring(0, 100) + (resultStr.length > 100 ? "..." : "")
|
|
2774
|
+
});
|
|
2603
2775
|
await handleToolResultStreaming(name, result, async (results) => {
|
|
2604
2776
|
await cb(results, { toolsUsed });
|
|
2605
2777
|
});
|
|
2606
2778
|
const toolId = id || `tool_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2607
|
-
this.pushToolMessages(messages, { id: toolId, name, parameters },
|
|
2779
|
+
this.pushToolMessages(messages, { id: toolId, name, parameters }, resultStr);
|
|
2608
2780
|
} catch (error) {
|
|
2781
|
+
const toolDuration = Date.now() - toolStartTime;
|
|
2609
2782
|
if (error instanceof PermissionDeniedError) {
|
|
2610
2783
|
throw error;
|
|
2611
2784
|
}
|
|
2612
|
-
this.logger.error(
|
|
2785
|
+
this.logger.error("[Tool Execution] Tool failed (non-streaming)", {
|
|
2786
|
+
model,
|
|
2787
|
+
toolName: name,
|
|
2788
|
+
isMcpTool,
|
|
2789
|
+
durationMs: toolDuration,
|
|
2790
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2791
|
+
});
|
|
2613
2792
|
const toolId = id || `tool_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2614
2793
|
this.pushToolMessages(messages, { id: toolId, name, parameters }, `Error processing ${name} tool: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2615
2794
|
}
|
|
2795
|
+
} else if (name && parameters && !toolFn) {
|
|
2796
|
+
this.logger.warn("[Tool Execution] Tool function not found (non-streaming)", {
|
|
2797
|
+
model,
|
|
2798
|
+
toolName: name,
|
|
2799
|
+
availableTools: options.tools?.map((t) => t.toolSchema.name) || []
|
|
2800
|
+
});
|
|
2616
2801
|
}
|
|
2617
2802
|
}
|
|
2618
2803
|
const hasMcpTool = func.some((tool) => {
|
|
@@ -2620,16 +2805,35 @@ var AnthropicBackend = class {
|
|
|
2620
2805
|
return false;
|
|
2621
2806
|
return options.tools?.find((t) => t.toolSchema.name === tool.name)?._isMcpTool;
|
|
2622
2807
|
});
|
|
2808
|
+
this.logger.info("[Tool Execution] Making recursive call after tool execution (non-streaming)", {
|
|
2809
|
+
model,
|
|
2810
|
+
toolCallIteration: toolCallCount + 1,
|
|
2811
|
+
messageCountAfter: messages.length,
|
|
2812
|
+
toolsExecuted: func.filter((t) => t?.name).map((t) => t.name),
|
|
2813
|
+
hasMcpTool,
|
|
2814
|
+
willKeepTools: hasMcpTool,
|
|
2815
|
+
totalToolsUsed: toolsUsed.length
|
|
2816
|
+
});
|
|
2623
2817
|
await cb(["\n\n"], { toolsUsed });
|
|
2624
2818
|
if (hasMcpTool) {
|
|
2625
2819
|
await this.complete(model, messages, {
|
|
2626
2820
|
...options,
|
|
2627
2821
|
_internal: {
|
|
2822
|
+
...options._internal,
|
|
2823
|
+
// Preserve enableIdleTimeout, idleTimeoutMs
|
|
2628
2824
|
toolCallCount: toolCallCount + 1
|
|
2629
2825
|
}
|
|
2630
2826
|
}, cb, toolsUsed);
|
|
2631
2827
|
} else {
|
|
2632
|
-
await this.complete(model, messages, {
|
|
2828
|
+
await this.complete(model, messages, {
|
|
2829
|
+
...options,
|
|
2830
|
+
tools: void 0,
|
|
2831
|
+
_internal: {
|
|
2832
|
+
...options._internal,
|
|
2833
|
+
// Preserve enableIdleTimeout, idleTimeoutMs
|
|
2834
|
+
toolCallCount: toolCallCount + 1
|
|
2835
|
+
}
|
|
2836
|
+
}, cb, toolsUsed);
|
|
2633
2837
|
}
|
|
2634
2838
|
} else {
|
|
2635
2839
|
const thinkingBlocks = this.getThinkingBlocks();
|
|
@@ -2644,12 +2848,41 @@ var AnthropicBackend = class {
|
|
|
2644
2848
|
}
|
|
2645
2849
|
} catch (error) {
|
|
2646
2850
|
if (error instanceof RateLimitError) {
|
|
2851
|
+
const mcpTools = options.tools?.filter((t) => t._isMcpTool) || [];
|
|
2852
|
+
const builtInTools = options.tools?.filter((t) => !t._isMcpTool) || [];
|
|
2853
|
+
const hasMcpTools = mcpTools.length > 0;
|
|
2854
|
+
const hasBuiltInTools = builtInTools.length > 0;
|
|
2855
|
+
let featureArea = "chat";
|
|
2856
|
+
if (hasMcpTools && !hasBuiltInTools) {
|
|
2857
|
+
featureArea = "mcp-tools";
|
|
2858
|
+
} else if (hasMcpTools && hasBuiltInTools) {
|
|
2859
|
+
featureArea = "mcp-tools+built-in";
|
|
2860
|
+
} else if (hasBuiltInTools) {
|
|
2861
|
+
featureArea = "built-in-tools";
|
|
2862
|
+
}
|
|
2863
|
+
let requestType = "initial-chat";
|
|
2864
|
+
if (toolCallCount > 0) {
|
|
2865
|
+
requestType = `tool-continuation-${toolCallCount}`;
|
|
2866
|
+
} else if (options.tools?.length) {
|
|
2867
|
+
requestType = "initial-with-tools";
|
|
2868
|
+
}
|
|
2647
2869
|
this.logger.error("[AnthropicBackend] Rate limit error after all retries exhausted", {
|
|
2648
2870
|
model,
|
|
2649
2871
|
status: error.status,
|
|
2650
|
-
message: error.message
|
|
2872
|
+
message: error.message,
|
|
2873
|
+
// Context for ops (#6413)
|
|
2874
|
+
featureArea,
|
|
2875
|
+
requestType,
|
|
2876
|
+
toolCallIteration: toolCallCount,
|
|
2877
|
+
toolsUsedSoFar: toolsUsed.map((t) => t.name),
|
|
2878
|
+
availableToolCount: options.tools?.length || 0,
|
|
2879
|
+
mcpToolCount: mcpTools.length,
|
|
2880
|
+
mcpToolNames: mcpTools.map((t) => t.toolSchema.name).slice(0, 10),
|
|
2881
|
+
builtInToolCount: builtInTools.length,
|
|
2882
|
+
builtInToolNames: builtInTools.map((t) => t.toolSchema.name).slice(0, 10),
|
|
2883
|
+
messageCount: messages.length
|
|
2651
2884
|
});
|
|
2652
|
-
this.emitRateLimitMetric(model).catch(() => {
|
|
2885
|
+
this.emitRateLimitMetric(model, featureArea).catch(() => {
|
|
2653
2886
|
});
|
|
2654
2887
|
} else {
|
|
2655
2888
|
this.logger.debug("Error in complete:", error);
|
|
@@ -5975,9 +6208,14 @@ var OpenAIBackend = class {
|
|
|
5975
6208
|
...options
|
|
5976
6209
|
};
|
|
5977
6210
|
const toolCallCount = options._internal?.toolCallCount ?? 0;
|
|
5978
|
-
if (toolCallCount >= DEFAULT_MAX_TOOL_CALLS) {
|
|
6211
|
+
if (toolCallCount >= DEFAULT_MAX_TOOL_CALLS && options.tools?.length) {
|
|
5979
6212
|
this.logger.warn(`\u26A0\uFE0F Max tool calls limit (${DEFAULT_MAX_TOOL_CALLS}) reached. Disabling tools to prevent infinite loops.`);
|
|
5980
|
-
await this.complete(model, messages, {
|
|
6213
|
+
await this.complete(model, messages, {
|
|
6214
|
+
...options,
|
|
6215
|
+
tools: void 0,
|
|
6216
|
+
_internal: options._internal
|
|
6217
|
+
// Preserve any internal settings
|
|
6218
|
+
}, callback, toolsUsed);
|
|
5981
6219
|
return;
|
|
5982
6220
|
}
|
|
5983
6221
|
const rawTools = options.tools;
|
|
@@ -6484,9 +6722,14 @@ var XAIBackend = class {
|
|
|
6484
6722
|
...options
|
|
6485
6723
|
};
|
|
6486
6724
|
const toolCallCount = options._internal?.toolCallCount ?? 0;
|
|
6487
|
-
if (toolCallCount >= DEFAULT_MAX_TOOL_CALLS) {
|
|
6725
|
+
if (toolCallCount >= DEFAULT_MAX_TOOL_CALLS && options.tools?.length) {
|
|
6488
6726
|
this.logger.warn(`\u26A0\uFE0F Max tool calls limit (${DEFAULT_MAX_TOOL_CALLS}) reached. Disabling tools to prevent infinite loops.`);
|
|
6489
|
-
await this.complete(model, messages, {
|
|
6727
|
+
await this.complete(model, messages, {
|
|
6728
|
+
...options,
|
|
6729
|
+
tools: void 0,
|
|
6730
|
+
_internal: options._internal
|
|
6731
|
+
// Preserve any internal settings
|
|
6732
|
+
}, callback, toolsUsed);
|
|
6490
6733
|
return;
|
|
6491
6734
|
}
|
|
6492
6735
|
const rawTools = options.tools;
|
package/dist/index.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
getEffectiveApiKey,
|
|
5
5
|
getOpenWeatherKey,
|
|
6
6
|
getSerperKey
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-FGNCVUBR.js";
|
|
8
8
|
import {
|
|
9
9
|
ConfigStore
|
|
10
10
|
} from "./chunk-VFQF2JIT.js";
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-ZXXSKLZA.js";
|
|
12
|
+
import "./chunk-KUGWFEGL.js";
|
|
13
13
|
import {
|
|
14
14
|
BFLImageService,
|
|
15
15
|
BaseStorage,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
OpenAIBackend,
|
|
22
22
|
OpenAIImageService,
|
|
23
23
|
XAIImageService
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-RQRJNE4K.js";
|
|
25
25
|
import {
|
|
26
26
|
AiEvents,
|
|
27
27
|
ApiKeyEvents,
|
|
@@ -12262,7 +12262,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
12262
12262
|
// package.json
|
|
12263
12263
|
var package_default = {
|
|
12264
12264
|
name: "@bike4mind/cli",
|
|
12265
|
-
version: "0.2.21-fix-
|
|
12265
|
+
version: "0.2.21-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
12266
12266
|
type: "module",
|
|
12267
12267
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
12268
12268
|
license: "UNLICENSED",
|
|
@@ -12369,10 +12369,10 @@ var package_default = {
|
|
|
12369
12369
|
},
|
|
12370
12370
|
devDependencies: {
|
|
12371
12371
|
"@bike4mind/agents": "0.1.0",
|
|
12372
|
-
"@bike4mind/common": "2.45.1-fix-
|
|
12373
|
-
"@bike4mind/mcp": "1.25.1-fix-
|
|
12374
|
-
"@bike4mind/services": "2.43.1-fix-
|
|
12375
|
-
"@bike4mind/utils": "2.3.1-fix-
|
|
12372
|
+
"@bike4mind/common": "2.45.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
12373
|
+
"@bike4mind/mcp": "1.25.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
12374
|
+
"@bike4mind/services": "2.43.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
12375
|
+
"@bike4mind/utils": "2.3.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
12376
12376
|
"@types/better-sqlite3": "^7.6.13",
|
|
12377
12377
|
"@types/diff": "^5.0.9",
|
|
12378
12378
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -12389,7 +12389,7 @@ var package_default = {
|
|
|
12389
12389
|
optionalDependencies: {
|
|
12390
12390
|
"@vscode/ripgrep": "^1.17.0"
|
|
12391
12391
|
},
|
|
12392
|
-
gitHead: "
|
|
12392
|
+
gitHead: "987e50b8a7e8d0a0e3b9ef708138d3c6182560be"
|
|
12393
12393
|
};
|
|
12394
12394
|
|
|
12395
12395
|
// src/config/constants.ts
|
|
@@ -12623,139 +12623,6 @@ import fs13 from "fs/promises";
|
|
|
12623
12623
|
import path16 from "path";
|
|
12624
12624
|
import os3 from "os";
|
|
12625
12625
|
import matter2 from "gray-matter";
|
|
12626
|
-
var FULL_MODEL_ID_PREFIXES = [
|
|
12627
|
-
"claude-",
|
|
12628
|
-
"gpt-",
|
|
12629
|
-
"gemini-",
|
|
12630
|
-
"grok-",
|
|
12631
|
-
"meta.",
|
|
12632
|
-
"anthropic.",
|
|
12633
|
-
"us.anthropic.",
|
|
12634
|
-
"global.anthropic.",
|
|
12635
|
-
"amazon.",
|
|
12636
|
-
"ai21.",
|
|
12637
|
-
"deepseek",
|
|
12638
|
-
"whisper",
|
|
12639
|
-
"flux-",
|
|
12640
|
-
"sora-"
|
|
12641
|
-
];
|
|
12642
|
-
var MODEL_ALIASES = {
|
|
12643
|
-
// ===================
|
|
12644
|
-
// Anthropic/Claude Models
|
|
12645
|
-
// ===================
|
|
12646
|
-
// Short aliases (most common)
|
|
12647
|
-
opus: "claude-opus-4-5-20251101",
|
|
12648
|
-
sonnet: "claude-sonnet-4-5-20250929",
|
|
12649
|
-
haiku: "claude-3-5-haiku-20241022",
|
|
12650
|
-
// Claude-prefixed aliases
|
|
12651
|
-
"claude-opus": "claude-opus-4-5-20251101",
|
|
12652
|
-
"claude-sonnet": "claude-sonnet-4-5-20250929",
|
|
12653
|
-
"claude-haiku": "claude-3-5-haiku-20241022",
|
|
12654
|
-
// Version-specific Claude aliases
|
|
12655
|
-
"claude-4.5-opus": "claude-opus-4-5-20251101",
|
|
12656
|
-
"claude-4.5-sonnet": "claude-sonnet-4-5-20250929",
|
|
12657
|
-
"claude-4.5-haiku": "claude-haiku-4-5-20251001",
|
|
12658
|
-
"claude-4-opus": "claude-opus-4-20250514",
|
|
12659
|
-
"claude-4-sonnet": "claude-sonnet-4-20250514",
|
|
12660
|
-
"claude-4.1-opus": "claude-opus-4-1-20250805",
|
|
12661
|
-
"claude-3.7-sonnet": "claude-3-7-sonnet-20250219",
|
|
12662
|
-
"claude-3.5-sonnet": "claude-3-5-sonnet-20241022",
|
|
12663
|
-
"claude-3.5-haiku": "claude-3-5-haiku-20241022",
|
|
12664
|
-
"claude-3-opus": "claude-3-opus-20240229",
|
|
12665
|
-
// ===================
|
|
12666
|
-
// OpenAI Models
|
|
12667
|
-
// ===================
|
|
12668
|
-
// GPT-4 family
|
|
12669
|
-
"gpt-4": "gpt-4",
|
|
12670
|
-
"gpt-4o": "gpt-4o",
|
|
12671
|
-
"gpt-4o-mini": "gpt-4o-mini",
|
|
12672
|
-
"gpt-4-turbo": "gpt-4-turbo",
|
|
12673
|
-
"gpt-4.1": "gpt-4.1-2025-04-14",
|
|
12674
|
-
"gpt-4.1-mini": "gpt-4.1-mini-2025-04-14",
|
|
12675
|
-
"gpt-4.1-nano": "gpt-4.1-nano-2025-04-14",
|
|
12676
|
-
"gpt-4.5": "gpt-4.5-preview-2025-02-27",
|
|
12677
|
-
// GPT-5 family
|
|
12678
|
-
"gpt-5": "gpt-5",
|
|
12679
|
-
"gpt-5-mini": "gpt-5-mini",
|
|
12680
|
-
"gpt-5-nano": "gpt-5-nano",
|
|
12681
|
-
"gpt-5.1": "gpt-5.1",
|
|
12682
|
-
"gpt-5.2": "gpt-5.2",
|
|
12683
|
-
// OpenAI reasoning models (o-series)
|
|
12684
|
-
o1: "o1-2024-12-17",
|
|
12685
|
-
"o1-preview": "o1-preview-2024-09-12",
|
|
12686
|
-
"o1-mini": "o1-mini-2024-09-12",
|
|
12687
|
-
o3: "o3-2025-04-16",
|
|
12688
|
-
"o3-mini": "o3-mini-2025-01-31",
|
|
12689
|
-
"o4-mini": "o4-mini-2025-04-16",
|
|
12690
|
-
// ===================
|
|
12691
|
-
// Google Gemini Models
|
|
12692
|
-
// ===================
|
|
12693
|
-
gemini: "gemini-2.5-pro",
|
|
12694
|
-
"gemini-pro": "gemini-2.5-pro",
|
|
12695
|
-
"gemini-flash": "gemini-2.5-flash-preview-09-2025",
|
|
12696
|
-
"gemini-flash-lite": "gemini-2.5-flash-lite-preview-09-2025",
|
|
12697
|
-
// Gemini 3 (preview)
|
|
12698
|
-
"gemini-3": "gemini-3-pro-preview",
|
|
12699
|
-
"gemini-3-pro": "gemini-3-pro-preview",
|
|
12700
|
-
"gemini-3-flash": "gemini-3-flash-preview",
|
|
12701
|
-
// Gemini 2.5
|
|
12702
|
-
"gemini-2.5": "gemini-2.5-pro",
|
|
12703
|
-
"gemini-2.5-pro": "gemini-2.5-pro",
|
|
12704
|
-
"gemini-2.5-flash": "gemini-2.5-flash-preview-09-2025",
|
|
12705
|
-
// Gemini 2.0
|
|
12706
|
-
"gemini-2.0-flash": "gemini-2.0-flash-exp",
|
|
12707
|
-
// Gemini 1.5 (legacy)
|
|
12708
|
-
"gemini-1.5-pro": "gemini-1.5-pro",
|
|
12709
|
-
"gemini-1.5-flash": "gemini-1.5-flash",
|
|
12710
|
-
"gemini-1.5-flash-8b": "gemini-1.5-flash-8b",
|
|
12711
|
-
// ===================
|
|
12712
|
-
// xAI Grok Models
|
|
12713
|
-
// ===================
|
|
12714
|
-
grok: "grok-3",
|
|
12715
|
-
"grok-3": "grok-3",
|
|
12716
|
-
"grok-3-fast": "grok-3-fast",
|
|
12717
|
-
"grok-3-mini": "grok-3-mini",
|
|
12718
|
-
"grok-3-mini-fast": "grok-3-mini-fast",
|
|
12719
|
-
"grok-2": "grok-2-1212",
|
|
12720
|
-
"grok-2-vision": "grok-2-vision-1212",
|
|
12721
|
-
// ===================
|
|
12722
|
-
// DeepSeek Models
|
|
12723
|
-
// ===================
|
|
12724
|
-
deepseek: "deepseek-r1:latest",
|
|
12725
|
-
"deepseek-r1": "deepseek-r1:latest",
|
|
12726
|
-
// ===================
|
|
12727
|
-
// Llama Models (Ollama local)
|
|
12728
|
-
// ===================
|
|
12729
|
-
llama: "llama3.3",
|
|
12730
|
-
llama3: "llama3.3",
|
|
12731
|
-
"llama3.3": "llama3.3",
|
|
12732
|
-
tinyllama: "tinyllama"
|
|
12733
|
-
};
|
|
12734
|
-
function getAvailableModelAliases() {
|
|
12735
|
-
return Object.keys(MODEL_ALIASES).sort();
|
|
12736
|
-
}
|
|
12737
|
-
function resolveModelAlias(modelInput, agentName, filePath) {
|
|
12738
|
-
const normalizedInput = modelInput.toLowerCase();
|
|
12739
|
-
if (MODEL_ALIASES[normalizedInput]) {
|
|
12740
|
-
return { model: MODEL_ALIASES[normalizedInput], resolved: true };
|
|
12741
|
-
}
|
|
12742
|
-
const hasDatePattern = /\d{8}|\d{4}-\d{2}-\d{2}/.test(modelInput);
|
|
12743
|
-
const hasBedrockSuffix = modelInput.includes(":0");
|
|
12744
|
-
const hasKnownPrefix = FULL_MODEL_ID_PREFIXES.some((prefix) => modelInput.startsWith(prefix));
|
|
12745
|
-
if (hasDatePattern || hasBedrockSuffix || hasKnownPrefix) {
|
|
12746
|
-
return { model: modelInput, resolved: true };
|
|
12747
|
-
}
|
|
12748
|
-
const availableAliases = getAvailableModelAliases();
|
|
12749
|
-
const suggestions = availableAliases.filter((alias) => alias.includes(normalizedInput) || normalizedInput.includes(alias)).slice(0, 5);
|
|
12750
|
-
let warning = `Unknown model "${modelInput}" in agent "${agentName}" (${filePath}). Using inherited model instead.
|
|
12751
|
-
`;
|
|
12752
|
-
if (suggestions.length > 0) {
|
|
12753
|
-
warning += `Did you mean: ${suggestions.join(", ")}?
|
|
12754
|
-
`;
|
|
12755
|
-
}
|
|
12756
|
-
warning += `Available aliases: opus, sonnet, haiku, gpt-4o, gemini, grok, etc. Run with --verbose for full list.`;
|
|
12757
|
-
return { model: DEFAULT_AGENT_MODEL, resolved: false, warning };
|
|
12758
|
-
}
|
|
12759
12626
|
var AgentStore = class {
|
|
12760
12627
|
/**
|
|
12761
12628
|
* Creates a new AgentStore
|
|
@@ -12846,17 +12713,10 @@ var AgentStore = class {
|
|
|
12846
12713
|
const { data: frontmatter, content: body } = matter2(content);
|
|
12847
12714
|
const parsed = AgentFrontmatterSchema.parse(frontmatter);
|
|
12848
12715
|
const name = path16.basename(filePath, ".md");
|
|
12849
|
-
const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
|
|
12850
|
-
const resolution = resolveModelAlias(modelInput, name, filePath);
|
|
12851
|
-
if (!resolution.resolved && resolution.warning) {
|
|
12852
|
-
console.warn(`
|
|
12853
|
-
\u26A0\uFE0F ${resolution.warning}
|
|
12854
|
-
`);
|
|
12855
|
-
}
|
|
12856
12716
|
return {
|
|
12857
12717
|
name,
|
|
12858
12718
|
description: parsed.description,
|
|
12859
|
-
model:
|
|
12719
|
+
model: parsed.model || DEFAULT_AGENT_MODEL,
|
|
12860
12720
|
systemPrompt: body.trim(),
|
|
12861
12721
|
allowedTools: parsed["allowed-tools"],
|
|
12862
12722
|
deniedTools: parsed["denied-tools"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bike4mind/cli",
|
|
3
|
-
"version": "0.2.21-fix-
|
|
3
|
+
"version": "0.2.21-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -107,10 +107,10 @@
|
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@bike4mind/agents": "0.1.0",
|
|
110
|
-
"@bike4mind/common": "2.45.1-fix-
|
|
111
|
-
"@bike4mind/mcp": "1.25.1-fix-
|
|
112
|
-
"@bike4mind/services": "2.43.1-fix-
|
|
113
|
-
"@bike4mind/utils": "2.3.1-fix-
|
|
110
|
+
"@bike4mind/common": "2.45.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
111
|
+
"@bike4mind/mcp": "1.25.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
112
|
+
"@bike4mind/services": "2.43.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
113
|
+
"@bike4mind/utils": "2.3.1-fix-idle-timeout-error-message.18217+987e50b8a",
|
|
114
114
|
"@types/better-sqlite3": "^7.6.13",
|
|
115
115
|
"@types/diff": "^5.0.9",
|
|
116
116
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -127,5 +127,5 @@
|
|
|
127
127
|
"optionalDependencies": {
|
|
128
128
|
"@vscode/ripgrep": "^1.17.0"
|
|
129
129
|
},
|
|
130
|
-
"gitHead": "
|
|
130
|
+
"gitHead": "987e50b8a7e8d0a0e3b9ef708138d3c6182560be"
|
|
131
131
|
}
|