@ljoukov/llm 3.0.3 → 3.0.6

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
@@ -1582,6 +1582,123 @@ function parseEventBlock(raw) {
1582
1582
  }
1583
1583
  }
1584
1584
 
1585
+ // src/utils/modelConcurrency.ts
1586
+ var MIN_MODEL_CONCURRENCY_CAP = 1;
1587
+ var MAX_MODEL_CONCURRENCY_CAP = 64;
1588
+ var DEFAULT_MODEL_CONCURRENCY_CAP = 3;
1589
+ var DEFAULT_OPENAI_MODEL_CONCURRENCY_CAP = 12;
1590
+ var DEFAULT_GOOGLE_MODEL_CONCURRENCY_CAP = 4;
1591
+ var DEFAULT_GOOGLE_PREVIEW_MODEL_CONCURRENCY_CAP = 2;
1592
+ var DEFAULT_FIREWORKS_MODEL_CONCURRENCY_CAP = 6;
1593
+ var MODEL_CONCURRENCY_PROVIDERS = [
1594
+ "openai",
1595
+ "google",
1596
+ "fireworks"
1597
+ ];
1598
+ var configuredModelConcurrency = normalizeModelConcurrencyConfig({});
1599
+ function clampModelConcurrencyCap(value) {
1600
+ if (!Number.isFinite(value)) {
1601
+ return DEFAULT_MODEL_CONCURRENCY_CAP;
1602
+ }
1603
+ const rounded = Math.floor(value);
1604
+ if (rounded < MIN_MODEL_CONCURRENCY_CAP) {
1605
+ return MIN_MODEL_CONCURRENCY_CAP;
1606
+ }
1607
+ if (rounded > MAX_MODEL_CONCURRENCY_CAP) {
1608
+ return MAX_MODEL_CONCURRENCY_CAP;
1609
+ }
1610
+ return rounded;
1611
+ }
1612
+ function normalizeModelIdForConfig(modelId) {
1613
+ return modelId.trim().toLowerCase();
1614
+ }
1615
+ function normalizeCap(value) {
1616
+ if (value === void 0 || !Number.isFinite(value)) {
1617
+ return void 0;
1618
+ }
1619
+ return clampModelConcurrencyCap(value);
1620
+ }
1621
+ function normalizeModelCapMap(caps) {
1622
+ const normalized = /* @__PURE__ */ new Map();
1623
+ if (!caps) {
1624
+ return normalized;
1625
+ }
1626
+ for (const [modelId, cap] of Object.entries(caps)) {
1627
+ const modelKey = normalizeModelIdForConfig(modelId);
1628
+ if (!modelKey) {
1629
+ continue;
1630
+ }
1631
+ const normalizedCap = normalizeCap(cap);
1632
+ if (normalizedCap === void 0) {
1633
+ continue;
1634
+ }
1635
+ normalized.set(modelKey, normalizedCap);
1636
+ }
1637
+ return normalized;
1638
+ }
1639
+ function normalizeModelConcurrencyConfig(config) {
1640
+ const providerCaps = {};
1641
+ const providerModelCaps = {
1642
+ openai: /* @__PURE__ */ new Map(),
1643
+ google: /* @__PURE__ */ new Map(),
1644
+ fireworks: /* @__PURE__ */ new Map()
1645
+ };
1646
+ for (const provider of MODEL_CONCURRENCY_PROVIDERS) {
1647
+ const providerCap = normalizeCap(config.providerCaps?.[provider]);
1648
+ if (providerCap !== void 0) {
1649
+ providerCaps[provider] = providerCap;
1650
+ }
1651
+ providerModelCaps[provider] = new Map(
1652
+ normalizeModelCapMap(config.providerModelCaps?.[provider])
1653
+ );
1654
+ }
1655
+ return {
1656
+ globalCap: normalizeCap(config.globalCap),
1657
+ providerCaps,
1658
+ modelCaps: normalizeModelCapMap(config.modelCaps),
1659
+ providerModelCaps
1660
+ };
1661
+ }
1662
+ function resolveDefaultProviderCap(provider, modelId) {
1663
+ if (provider === "openai") {
1664
+ return DEFAULT_OPENAI_MODEL_CONCURRENCY_CAP;
1665
+ }
1666
+ if (provider === "google") {
1667
+ return modelId?.includes("preview") ? DEFAULT_GOOGLE_PREVIEW_MODEL_CONCURRENCY_CAP : DEFAULT_GOOGLE_MODEL_CONCURRENCY_CAP;
1668
+ }
1669
+ return DEFAULT_FIREWORKS_MODEL_CONCURRENCY_CAP;
1670
+ }
1671
+ function configureModelConcurrency(config = {}) {
1672
+ configuredModelConcurrency = normalizeModelConcurrencyConfig(config);
1673
+ }
1674
+ function resetModelConcurrencyConfig() {
1675
+ configuredModelConcurrency = normalizeModelConcurrencyConfig({});
1676
+ }
1677
+ function resolveModelConcurrencyCap(options) {
1678
+ const modelId = options.modelId ? normalizeModelIdForConfig(options.modelId) : void 0;
1679
+ const config = options.config ? normalizeModelConcurrencyConfig(options.config) : configuredModelConcurrency;
1680
+ const providerModelCap = modelId ? config.providerModelCaps[options.provider].get(modelId) : void 0;
1681
+ if (providerModelCap !== void 0) {
1682
+ return providerModelCap;
1683
+ }
1684
+ const modelCap = modelId ? config.modelCaps.get(modelId) : void 0;
1685
+ if (modelCap !== void 0) {
1686
+ return modelCap;
1687
+ }
1688
+ const providerCap = config.providerCaps[options.provider];
1689
+ if (providerCap !== void 0) {
1690
+ return providerCap;
1691
+ }
1692
+ if (config.globalCap !== void 0) {
1693
+ return config.globalCap;
1694
+ }
1695
+ const defaultCap = normalizeCap(options.defaultCap);
1696
+ if (defaultCap !== void 0) {
1697
+ return defaultCap;
1698
+ }
1699
+ return resolveDefaultProviderCap(options.provider, modelId);
1700
+ }
1701
+
1585
1702
  // src/utils/scheduler.ts
1586
1703
  function sleep(ms) {
1587
1704
  return new Promise((resolve) => {
@@ -1597,13 +1714,72 @@ function toError(value) {
1597
1714
  }
1598
1715
  return new Error("Unknown error");
1599
1716
  }
1717
+ function getStatusCode(error) {
1718
+ if (!error || typeof error !== "object") {
1719
+ return void 0;
1720
+ }
1721
+ const maybe = error;
1722
+ const candidates = [maybe.status, maybe.statusCode];
1723
+ for (const candidate of candidates) {
1724
+ if (typeof candidate === "number") {
1725
+ return candidate;
1726
+ }
1727
+ if (typeof candidate === "string") {
1728
+ const parsed = Number.parseInt(candidate, 10);
1729
+ if (Number.isFinite(parsed)) {
1730
+ return parsed;
1731
+ }
1732
+ }
1733
+ }
1734
+ if (typeof maybe.code === "number") {
1735
+ return maybe.code;
1736
+ }
1737
+ return void 0;
1738
+ }
1739
+ function getErrorText(error) {
1740
+ if (error instanceof Error) {
1741
+ return error.message.toLowerCase();
1742
+ }
1743
+ if (typeof error === "string") {
1744
+ return error.toLowerCase();
1745
+ }
1746
+ if (error && typeof error === "object") {
1747
+ const maybe = error;
1748
+ const code = typeof maybe.code === "string" ? maybe.code : "";
1749
+ const message = typeof maybe.message === "string" ? maybe.message : "";
1750
+ return `${code} ${message}`.trim().toLowerCase();
1751
+ }
1752
+ return "";
1753
+ }
1754
+ function defaultIsOverloadError(error) {
1755
+ const status = getStatusCode(error);
1756
+ if (status === 429 || status === 503 || status === 529) {
1757
+ return true;
1758
+ }
1759
+ const text = getErrorText(error);
1760
+ if (!text) {
1761
+ return false;
1762
+ }
1763
+ return text.includes("rate limit") || text.includes("too many requests") || text.includes("resource exhausted") || text.includes("resource_exhausted") || text.includes("overload");
1764
+ }
1600
1765
  function createCallScheduler(options = {}) {
1601
1766
  const maxParallelRequests = Math.max(1, Math.floor(options.maxParallelRequests ?? 3));
1767
+ const initialParallelRequests = Math.min(
1768
+ maxParallelRequests,
1769
+ Math.max(1, Math.floor(options.initialParallelRequests ?? Math.min(3, maxParallelRequests)))
1770
+ );
1771
+ const increaseAfterConsecutiveSuccesses = Math.max(
1772
+ 1,
1773
+ Math.floor(options.increaseAfterConsecutiveSuccesses ?? 8)
1774
+ );
1602
1775
  const minIntervalBetweenStartMs = Math.max(0, Math.floor(options.minIntervalBetweenStartMs ?? 0));
1603
1776
  const startJitterMs = Math.max(0, Math.floor(options.startJitterMs ?? 0));
1604
1777
  const retryPolicy = options.retry;
1778
+ const isOverloadError2 = options.isOverloadError ?? defaultIsOverloadError;
1605
1779
  let activeCount = 0;
1606
1780
  let lastStartTime = 0;
1781
+ let currentParallelLimit = initialParallelRequests;
1782
+ let consecutiveSuccesses = 0;
1607
1783
  let startSpacingChain = Promise.resolve();
1608
1784
  const queue = [];
1609
1785
  async function applyStartSpacing() {
@@ -1629,11 +1805,23 @@ function createCallScheduler(options = {}) {
1629
1805
  release?.();
1630
1806
  }
1631
1807
  }
1632
- async function attemptWithRetries(fn, attempt) {
1808
+ async function attemptWithRetries(fn, attempt, state) {
1633
1809
  try {
1810
+ const spacingStartedAtMs = Date.now();
1634
1811
  await applyStartSpacing();
1812
+ const callStartedAtMs = Date.now();
1813
+ state.schedulerDelayMs += Math.max(0, callStartedAtMs - spacingStartedAtMs);
1814
+ if (state.startedAtMs === void 0) {
1815
+ state.startedAtMs = callStartedAtMs;
1816
+ }
1817
+ state.attempts = Math.max(state.attempts, attempt);
1635
1818
  return await fn();
1636
1819
  } catch (error) {
1820
+ if (isOverloadError2(error)) {
1821
+ state.overloadCount += 1;
1822
+ consecutiveSuccesses = 0;
1823
+ currentParallelLimit = Math.max(1, Math.ceil(currentParallelLimit / 2));
1824
+ }
1637
1825
  const err = toError(error);
1638
1826
  if (!retryPolicy || attempt >= retryPolicy.maxAttempts) {
1639
1827
  throw err;
@@ -1647,13 +1835,14 @@ function createCallScheduler(options = {}) {
1647
1835
  }
1648
1836
  const normalizedDelay = Math.max(0, delay);
1649
1837
  if (normalizedDelay > 0) {
1838
+ state.retryDelayMs += normalizedDelay;
1650
1839
  await sleep(normalizedDelay);
1651
1840
  }
1652
- return attemptWithRetries(fn, attempt + 1);
1841
+ return attemptWithRetries(fn, attempt + 1, state);
1653
1842
  }
1654
1843
  }
1655
1844
  function drainQueue() {
1656
- while (activeCount < maxParallelRequests && queue.length > 0) {
1845
+ while (activeCount < currentParallelLimit && queue.length > 0) {
1657
1846
  const task = queue.shift();
1658
1847
  if (!task) {
1659
1848
  continue;
@@ -1662,15 +1851,49 @@ function createCallScheduler(options = {}) {
1662
1851
  void task();
1663
1852
  }
1664
1853
  }
1665
- function run(fn) {
1854
+ function run(fn, runOptions = {}) {
1666
1855
  return new Promise((resolve, reject) => {
1856
+ const enqueuedAtMs = Date.now();
1667
1857
  const job = async () => {
1858
+ const dequeuedAtMs = Date.now();
1859
+ const state = {
1860
+ enqueuedAtMs,
1861
+ dequeuedAtMs,
1862
+ schedulerDelayMs: 0,
1863
+ retryDelayMs: 0,
1864
+ attempts: 0,
1865
+ overloadCount: 0
1866
+ };
1668
1867
  try {
1669
- const result = await attemptWithRetries(fn, 1);
1868
+ const result = await attemptWithRetries(fn, 1, state);
1869
+ state.completedAtMs = Date.now();
1870
+ consecutiveSuccesses += 1;
1871
+ if (currentParallelLimit < maxParallelRequests && consecutiveSuccesses >= increaseAfterConsecutiveSuccesses) {
1872
+ currentParallelLimit += 1;
1873
+ consecutiveSuccesses = 0;
1874
+ }
1670
1875
  resolve(result);
1671
1876
  } catch (error) {
1877
+ state.completedAtMs = Date.now();
1672
1878
  reject(toError(error));
1673
1879
  } finally {
1880
+ const startedAtMs = state.startedAtMs ?? state.dequeuedAtMs;
1881
+ const completedAtMs = state.completedAtMs ?? Date.now();
1882
+ const metrics = {
1883
+ enqueuedAtMs: state.enqueuedAtMs,
1884
+ dequeuedAtMs: state.dequeuedAtMs,
1885
+ startedAtMs,
1886
+ completedAtMs,
1887
+ queueWaitMs: Math.max(0, state.dequeuedAtMs - state.enqueuedAtMs),
1888
+ schedulerDelayMs: Math.max(0, state.schedulerDelayMs),
1889
+ retryDelayMs: Math.max(0, state.retryDelayMs),
1890
+ attempts: Math.max(1, state.attempts),
1891
+ overloadCount: Math.max(0, state.overloadCount)
1892
+ };
1893
+ try {
1894
+ runOptions.onSettled?.(metrics);
1895
+ } catch {
1896
+ }
1674
1897
  activeCount -= 1;
1675
1898
  queueMicrotask(drainQueue);
1676
1899
  }
@@ -1756,13 +1979,28 @@ function getFireworksClient() {
1756
1979
  }
1757
1980
 
1758
1981
  // src/fireworks/calls.ts
1759
- var scheduler = createCallScheduler({
1760
- maxParallelRequests: 3,
1761
- minIntervalBetweenStartMs: 200,
1762
- startJitterMs: 200
1763
- });
1764
- async function runFireworksCall(fn) {
1765
- return scheduler.run(async () => fn(getFireworksClient()));
1982
+ var DEFAULT_SCHEDULER_KEY = "__default__";
1983
+ var schedulerByModel = /* @__PURE__ */ new Map();
1984
+ function getSchedulerForModel(modelId) {
1985
+ const normalizedModelId = modelId?.trim();
1986
+ const schedulerKey = normalizedModelId && normalizedModelId.length > 0 ? normalizedModelId : DEFAULT_SCHEDULER_KEY;
1987
+ const existing = schedulerByModel.get(schedulerKey);
1988
+ if (existing) {
1989
+ return existing;
1990
+ }
1991
+ const created = createCallScheduler({
1992
+ maxParallelRequests: resolveModelConcurrencyCap({
1993
+ provider: "fireworks",
1994
+ modelId: normalizedModelId
1995
+ }),
1996
+ minIntervalBetweenStartMs: 200,
1997
+ startJitterMs: 200
1998
+ });
1999
+ schedulerByModel.set(schedulerKey, created);
2000
+ return created;
2001
+ }
2002
+ async function runFireworksCall(fn, modelId, runOptions) {
2003
+ return getSchedulerForModel(modelId).run(async () => fn(getFireworksClient()), runOptions);
1766
2004
  }
1767
2005
 
1768
2006
  // src/fireworks/models.ts
@@ -2098,6 +2336,18 @@ function shouldRetry(error) {
2098
2336
  }
2099
2337
  return false;
2100
2338
  }
2339
+ function isOverloadError(error) {
2340
+ const status = getStatus(error);
2341
+ if (status === 429 || status === 503 || status === 529) {
2342
+ return true;
2343
+ }
2344
+ const reason = getErrorReason(error);
2345
+ if (reason && RATE_LIMIT_REASONS.has(reason)) {
2346
+ return true;
2347
+ }
2348
+ const message = getErrorMessage(error).toLowerCase();
2349
+ return message.includes("rate limit") || message.includes("too many requests") || message.includes("resource exhausted") || message.includes("resource_exhausted");
2350
+ }
2101
2351
  function retryDelayMs(attempt) {
2102
2352
  const baseRetryDelayMs = 500;
2103
2353
  const maxRetryDelayMs = 4e3;
@@ -2105,23 +2355,39 @@ function retryDelayMs(attempt) {
2105
2355
  const jitter = Math.floor(Math.random() * 200);
2106
2356
  return base + jitter;
2107
2357
  }
2108
- var scheduler2 = createCallScheduler({
2109
- maxParallelRequests: 3,
2110
- minIntervalBetweenStartMs: 200,
2111
- startJitterMs: 200,
2112
- retry: {
2113
- maxAttempts: 3,
2114
- getDelayMs: (attempt, error) => {
2115
- if (!shouldRetry(error)) {
2116
- return null;
2358
+ var DEFAULT_SCHEDULER_KEY2 = "__default__";
2359
+ var schedulerByModel2 = /* @__PURE__ */ new Map();
2360
+ function getSchedulerForModel2(modelId) {
2361
+ const normalizedModelId = modelId?.trim();
2362
+ const schedulerKey = normalizedModelId && normalizedModelId.length > 0 ? normalizedModelId : DEFAULT_SCHEDULER_KEY2;
2363
+ const existing = schedulerByModel2.get(schedulerKey);
2364
+ if (existing) {
2365
+ return existing;
2366
+ }
2367
+ const created = createCallScheduler({
2368
+ maxParallelRequests: resolveModelConcurrencyCap({
2369
+ provider: "google",
2370
+ modelId: normalizedModelId
2371
+ }),
2372
+ minIntervalBetweenStartMs: 200,
2373
+ startJitterMs: 200,
2374
+ isOverloadError,
2375
+ retry: {
2376
+ maxAttempts: 3,
2377
+ getDelayMs: (attempt, error) => {
2378
+ if (!shouldRetry(error)) {
2379
+ return null;
2380
+ }
2381
+ const hintedDelay = getRetryAfterMs(error);
2382
+ return hintedDelay ?? retryDelayMs(attempt);
2117
2383
  }
2118
- const hintedDelay = getRetryAfterMs(error);
2119
- return hintedDelay ?? retryDelayMs(attempt);
2120
2384
  }
2121
- }
2122
- });
2123
- async function runGeminiCall(fn) {
2124
- return scheduler2.run(async () => fn(await getGeminiClient()));
2385
+ });
2386
+ schedulerByModel2.set(schedulerKey, created);
2387
+ return created;
2388
+ }
2389
+ async function runGeminiCall(fn, modelId, runOptions) {
2390
+ return getSchedulerForModel2(modelId).run(async () => fn(await getGeminiClient()), runOptions);
2125
2391
  }
2126
2392
 
2127
2393
  // src/openai/client.ts
@@ -2282,13 +2548,28 @@ function getOpenAiClient() {
2282
2548
 
2283
2549
  // src/openai/calls.ts
2284
2550
  var DEFAULT_OPENAI_REASONING_EFFORT = "medium";
2285
- var scheduler3 = createCallScheduler({
2286
- maxParallelRequests: 3,
2287
- minIntervalBetweenStartMs: 200,
2288
- startJitterMs: 200
2289
- });
2290
- async function runOpenAiCall(fn) {
2291
- return scheduler3.run(async () => fn(getOpenAiClient()));
2551
+ var DEFAULT_SCHEDULER_KEY3 = "__default__";
2552
+ var schedulerByModel3 = /* @__PURE__ */ new Map();
2553
+ function getSchedulerForModel3(modelId) {
2554
+ const normalizedModelId = modelId?.trim();
2555
+ const schedulerKey = normalizedModelId && normalizedModelId.length > 0 ? normalizedModelId : DEFAULT_SCHEDULER_KEY3;
2556
+ const existing = schedulerByModel3.get(schedulerKey);
2557
+ if (existing) {
2558
+ return existing;
2559
+ }
2560
+ const created = createCallScheduler({
2561
+ maxParallelRequests: resolveModelConcurrencyCap({
2562
+ provider: "openai",
2563
+ modelId: normalizedModelId
2564
+ }),
2565
+ minIntervalBetweenStartMs: 200,
2566
+ startJitterMs: 200
2567
+ });
2568
+ schedulerByModel3.set(schedulerKey, created);
2569
+ return created;
2570
+ }
2571
+ async function runOpenAiCall(fn, modelId, runOptions) {
2572
+ return getSchedulerForModel3(modelId).run(async () => fn(getOpenAiClient()), runOptions);
2292
2573
  }
2293
2574
 
2294
2575
  // src/openai/models.ts
@@ -2742,9 +3023,9 @@ function isRetryableChatGptTransportError(error) {
2742
3023
  return false;
2743
3024
  }
2744
3025
  const message = error.message.toLowerCase();
2745
- return message === "terminated" || message.includes("socket hang up") || message.includes("fetch failed") || message.includes("network");
3026
+ return message === "terminated" || message.includes("socket hang up") || message.includes("fetch failed") || message.includes("network") || message.includes("responses websocket");
2746
3027
  }
2747
- async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 2) {
3028
+ async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 3) {
2748
3029
  let attempt = 1;
2749
3030
  while (true) {
2750
3031
  try {
@@ -3649,77 +3930,153 @@ function buildToolErrorOutput(message, issues) {
3649
3930
  }
3650
3931
  return output;
3651
3932
  }
3933
+ var SUBAGENT_WAIT_TOOL_NAME = "wait";
3934
+ function toIsoTimestamp(ms) {
3935
+ return new Date(ms).toISOString();
3936
+ }
3937
+ function toToolResultDuration(result) {
3938
+ return typeof result.durationMs === "number" && Number.isFinite(result.durationMs) ? Math.max(0, result.durationMs) : 0;
3939
+ }
3940
+ function schedulerMetricsOrDefault(metrics) {
3941
+ if (!metrics) {
3942
+ return {
3943
+ queueWaitMs: 0,
3944
+ schedulerDelayMs: 0,
3945
+ providerRetryDelayMs: 0,
3946
+ providerAttempts: 1
3947
+ };
3948
+ }
3949
+ return {
3950
+ queueWaitMs: Math.max(0, metrics.queueWaitMs),
3951
+ schedulerDelayMs: Math.max(0, metrics.schedulerDelayMs),
3952
+ providerRetryDelayMs: Math.max(0, metrics.retryDelayMs),
3953
+ providerAttempts: Math.max(1, metrics.attempts),
3954
+ modelCallStartedAtMs: metrics.startedAtMs
3955
+ };
3956
+ }
3957
+ function buildStepTiming(params) {
3958
+ const scheduler = schedulerMetricsOrDefault(params.schedulerMetrics);
3959
+ const modelCallStartedAtMs = scheduler.modelCallStartedAtMs ?? params.stepStartedAtMs;
3960
+ const firstModelEventAtMs = params.firstModelEventAtMs;
3961
+ const effectiveFirstEventAtMs = firstModelEventAtMs !== void 0 ? Math.max(modelCallStartedAtMs, firstModelEventAtMs) : params.modelCompletedAtMs;
3962
+ const connectionSetupMs = Math.max(0, effectiveFirstEventAtMs - modelCallStartedAtMs);
3963
+ const activeGenerationMs = Math.max(0, params.modelCompletedAtMs - effectiveFirstEventAtMs);
3964
+ return {
3965
+ startedAt: toIsoTimestamp(params.stepStartedAtMs),
3966
+ completedAt: toIsoTimestamp(params.stepCompletedAtMs),
3967
+ totalMs: Math.max(0, params.stepCompletedAtMs - params.stepStartedAtMs),
3968
+ queueWaitMs: scheduler.queueWaitMs,
3969
+ connectionSetupMs,
3970
+ activeGenerationMs,
3971
+ toolExecutionMs: Math.max(0, params.toolExecutionMs),
3972
+ waitToolMs: Math.max(0, params.waitToolMs),
3973
+ schedulerDelayMs: scheduler.schedulerDelayMs,
3974
+ providerRetryDelayMs: scheduler.providerRetryDelayMs,
3975
+ providerAttempts: scheduler.providerAttempts
3976
+ };
3977
+ }
3978
+ function extractSpawnStartupMetrics(outputPayload) {
3979
+ if (!outputPayload || typeof outputPayload !== "object") {
3980
+ return void 0;
3981
+ }
3982
+ const outputRecord = outputPayload;
3983
+ const notification = typeof outputRecord.notification === "string" ? outputRecord.notification : "";
3984
+ if (notification !== "spawned") {
3985
+ return void 0;
3986
+ }
3987
+ const agent = outputRecord.agent;
3988
+ if (!agent || typeof agent !== "object") {
3989
+ return void 0;
3990
+ }
3991
+ const agentRecord = agent;
3992
+ const startupLatencyMs = agentRecord.spawn_startup_latency_ms;
3993
+ if (typeof startupLatencyMs !== "number" || !Number.isFinite(startupLatencyMs)) {
3994
+ return void 0;
3995
+ }
3996
+ return {
3997
+ spawnStartupLatencyMs: Math.max(0, startupLatencyMs)
3998
+ };
3999
+ }
3652
4000
  async function executeToolCall(params) {
3653
4001
  const { callKind, toolName, tool: tool2, rawInput, parseError } = params;
3654
- if (!tool2) {
3655
- const message = `Unknown tool: ${toolName}`;
4002
+ const startedAtMs = Date.now();
4003
+ const finalize = (base, outputPayload, metrics) => {
4004
+ const completedAtMs = Date.now();
3656
4005
  return {
3657
- result: { toolName, input: rawInput, output: { error: message }, error: message },
3658
- outputPayload: buildToolErrorOutput(message)
4006
+ result: {
4007
+ ...base,
4008
+ startedAt: toIsoTimestamp(startedAtMs),
4009
+ completedAt: toIsoTimestamp(completedAtMs),
4010
+ durationMs: Math.max(0, completedAtMs - startedAtMs),
4011
+ ...metrics ? { metrics } : {}
4012
+ },
4013
+ outputPayload
3659
4014
  };
4015
+ };
4016
+ if (!tool2) {
4017
+ const message = `Unknown tool: ${toolName}`;
4018
+ const outputPayload = buildToolErrorOutput(message);
4019
+ return finalize(
4020
+ { toolName, input: rawInput, output: outputPayload, error: message },
4021
+ outputPayload
4022
+ );
3660
4023
  }
3661
4024
  if (callKind === "custom") {
3662
4025
  if (!isCustomTool(tool2)) {
3663
4026
  const message = `Tool ${toolName} was called as custom_tool_call but is declared as function.`;
3664
4027
  const outputPayload = buildToolErrorOutput(message);
3665
- return {
3666
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4028
+ return finalize(
4029
+ { toolName, input: rawInput, output: outputPayload, error: message },
3667
4030
  outputPayload
3668
- };
4031
+ );
3669
4032
  }
3670
4033
  const input = typeof rawInput === "string" ? rawInput : String(rawInput ?? "");
3671
4034
  try {
3672
4035
  const output = await tool2.execute(input);
3673
- return {
3674
- result: { toolName, input, output },
3675
- outputPayload: output
3676
- };
4036
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4037
+ return finalize({ toolName, input, output }, output, metrics);
3677
4038
  } catch (error) {
3678
4039
  const message = error instanceof Error ? error.message : String(error);
3679
4040
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
3680
- return {
3681
- result: { toolName, input, output: outputPayload, error: message },
3682
- outputPayload
3683
- };
4041
+ return finalize({ toolName, input, output: outputPayload, error: message }, outputPayload);
3684
4042
  }
3685
4043
  }
3686
4044
  if (isCustomTool(tool2)) {
3687
4045
  const message = `Tool ${toolName} was called as function_call but is declared as custom.`;
3688
4046
  const outputPayload = buildToolErrorOutput(message);
3689
- return {
3690
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4047
+ return finalize(
4048
+ { toolName, input: rawInput, output: outputPayload, error: message },
3691
4049
  outputPayload
3692
- };
4050
+ );
3693
4051
  }
3694
4052
  if (parseError) {
3695
4053
  const message = `Invalid JSON for tool ${toolName}: ${parseError}`;
3696
- return {
3697
- result: { toolName, input: rawInput, output: { error: message }, error: message },
3698
- outputPayload: buildToolErrorOutput(message)
3699
- };
4054
+ const outputPayload = buildToolErrorOutput(message);
4055
+ return finalize(
4056
+ { toolName, input: rawInput, output: outputPayload, error: message },
4057
+ outputPayload
4058
+ );
3700
4059
  }
3701
4060
  const parsed = tool2.inputSchema.safeParse(rawInput);
3702
4061
  if (!parsed.success) {
3703
4062
  const message = `Invalid tool arguments for ${toolName}: ${formatZodIssues(parsed.error.issues)}`;
3704
4063
  const outputPayload = buildToolErrorOutput(message, parsed.error.issues);
3705
- return {
3706
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4064
+ return finalize(
4065
+ { toolName, input: rawInput, output: outputPayload, error: message },
3707
4066
  outputPayload
3708
- };
4067
+ );
3709
4068
  }
3710
4069
  try {
3711
4070
  const output = await tool2.execute(parsed.data);
3712
- return {
3713
- result: { toolName, input: parsed.data, output },
3714
- outputPayload: output
3715
- };
4071
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4072
+ return finalize({ toolName, input: parsed.data, output }, output, metrics);
3716
4073
  } catch (error) {
3717
4074
  const message = error instanceof Error ? error.message : String(error);
3718
4075
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
3719
- return {
3720
- result: { toolName, input: parsed.data, output: outputPayload, error: message },
4076
+ return finalize(
4077
+ { toolName, input: parsed.data, output: outputPayload, error: message },
3721
4078
  outputPayload
3722
- };
4079
+ );
3723
4080
  }
3724
4081
  }
3725
4082
  function buildToolLogId(turn, toolIndex) {
@@ -4050,7 +4407,7 @@ async function runTextCall(params) {
4050
4407
  }
4051
4408
  }
4052
4409
  }
4053
- });
4410
+ }, modelForProvider);
4054
4411
  } else if (provider === "chatgpt") {
4055
4412
  const chatGptInput = toChatGptInput(contents);
4056
4413
  const reasoningEffort = resolveOpenAiReasoningEffort(
@@ -4145,7 +4502,7 @@ async function runTextCall(params) {
4145
4502
  pushDelta("response", textOutput);
4146
4503
  }
4147
4504
  latestUsage = extractFireworksUsageTokens(response.usage);
4148
- });
4505
+ }, modelForProvider);
4149
4506
  } else {
4150
4507
  const geminiContents = contents.map(convertLlmContentToGeminiContent);
4151
4508
  const config = {
@@ -4213,7 +4570,7 @@ async function runTextCall(params) {
4213
4570
  }
4214
4571
  }
4215
4572
  grounding = latestGrounding;
4216
- });
4573
+ }, modelForProvider);
4217
4574
  }
4218
4575
  const mergedParts = mergeConsecutiveTextParts(responseParts);
4219
4576
  const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
@@ -4584,6 +4941,9 @@ async function runToolLoop(request) {
4584
4941
  let input = toOpenAiInput(contents);
4585
4942
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
4586
4943
  const turn = stepIndex + 1;
4944
+ const stepStartedAtMs = Date.now();
4945
+ let firstModelEventAtMs;
4946
+ let schedulerMetrics;
4587
4947
  const abortController = new AbortController();
4588
4948
  if (request.signal) {
4589
4949
  if (request.signal.aborted) {
@@ -4602,45 +4962,59 @@ async function runToolLoop(request) {
4602
4962
  const emitEvent = (ev) => {
4603
4963
  onEvent?.(ev);
4604
4964
  };
4605
- const finalResponse = await runOpenAiCall(async (client) => {
4606
- const stream = client.responses.stream(
4607
- {
4608
- model: providerInfo.model,
4609
- input,
4610
- ...previousResponseId ? { previous_response_id: previousResponseId } : {},
4611
- ...openAiTools.length > 0 ? { tools: openAiTools } : {},
4612
- ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
4613
- reasoning,
4614
- text: textConfig,
4615
- include: ["reasoning.encrypted_content"]
4616
- },
4617
- { signal: abortController.signal }
4618
- );
4619
- for await (const event of stream) {
4620
- switch (event.type) {
4621
- case "response.output_text.delta":
4622
- emitEvent({
4623
- type: "delta",
4624
- channel: "response",
4625
- text: typeof event.delta === "string" ? event.delta : ""
4626
- });
4627
- break;
4628
- case "response.reasoning_summary_text.delta":
4629
- emitEvent({
4630
- type: "delta",
4631
- channel: "thought",
4632
- text: typeof event.delta === "string" ? event.delta : ""
4633
- });
4634
- break;
4635
- case "response.refusal.delta":
4636
- emitEvent({ type: "blocked" });
4637
- break;
4638
- default:
4639
- break;
4965
+ const markFirstModelEvent = () => {
4966
+ if (firstModelEventAtMs === void 0) {
4967
+ firstModelEventAtMs = Date.now();
4968
+ }
4969
+ };
4970
+ const finalResponse = await runOpenAiCall(
4971
+ async (client) => {
4972
+ const stream = client.responses.stream(
4973
+ {
4974
+ model: providerInfo.model,
4975
+ input,
4976
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
4977
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
4978
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
4979
+ reasoning,
4980
+ text: textConfig,
4981
+ include: ["reasoning.encrypted_content"]
4982
+ },
4983
+ { signal: abortController.signal }
4984
+ );
4985
+ for await (const event of stream) {
4986
+ markFirstModelEvent();
4987
+ switch (event.type) {
4988
+ case "response.output_text.delta":
4989
+ emitEvent({
4990
+ type: "delta",
4991
+ channel: "response",
4992
+ text: typeof event.delta === "string" ? event.delta : ""
4993
+ });
4994
+ break;
4995
+ case "response.reasoning_summary_text.delta":
4996
+ emitEvent({
4997
+ type: "delta",
4998
+ channel: "thought",
4999
+ text: typeof event.delta === "string" ? event.delta : ""
5000
+ });
5001
+ break;
5002
+ case "response.refusal.delta":
5003
+ emitEvent({ type: "blocked" });
5004
+ break;
5005
+ default:
5006
+ break;
5007
+ }
5008
+ }
5009
+ return await stream.finalResponse();
5010
+ },
5011
+ providerInfo.model,
5012
+ {
5013
+ onSettled: (metrics) => {
5014
+ schedulerMetrics = metrics;
4640
5015
  }
4641
5016
  }
4642
- return await stream.finalResponse();
4643
- });
5017
+ );
4644
5018
  modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
4645
5019
  emitEvent({ type: "model", modelVersion });
4646
5020
  if (finalResponse.error) {
@@ -4650,6 +5024,7 @@ async function runToolLoop(request) {
4650
5024
  usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
4651
5025
  const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
4652
5026
  const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5027
+ const modelCompletedAtMs = Date.now();
4653
5028
  const stepCostUsd = estimateCallCostUsd({
4654
5029
  modelId: modelVersion,
4655
5030
  tokens: usageTokens,
@@ -4664,6 +5039,16 @@ async function runToolLoop(request) {
4664
5039
  if (responseToolCalls.length === 0) {
4665
5040
  finalText = responseText;
4666
5041
  finalThoughts = reasoningSummary;
5042
+ const stepCompletedAtMs2 = Date.now();
5043
+ const timing2 = buildStepTiming({
5044
+ stepStartedAtMs,
5045
+ stepCompletedAtMs: stepCompletedAtMs2,
5046
+ modelCompletedAtMs,
5047
+ firstModelEventAtMs,
5048
+ schedulerMetrics,
5049
+ toolExecutionMs: 0,
5050
+ waitToolMs: 0
5051
+ });
4667
5052
  steps.push({
4668
5053
  step: steps.length + 1,
4669
5054
  modelVersion,
@@ -4671,7 +5056,8 @@ async function runToolLoop(request) {
4671
5056
  thoughts: reasoningSummary || void 0,
4672
5057
  toolCalls: [],
4673
5058
  usage: usageTokens,
4674
- costUsd: stepCostUsd
5059
+ costUsd: stepCostUsd,
5060
+ timing: timing2
4675
5061
  });
4676
5062
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4677
5063
  }
@@ -4716,8 +5102,15 @@ async function runToolLoop(request) {
4716
5102
  })
4717
5103
  );
4718
5104
  const toolOutputs = [];
5105
+ let toolExecutionMs = 0;
5106
+ let waitToolMs = 0;
4719
5107
  for (const { entry, result, outputPayload } of callResults) {
4720
5108
  stepToolCalls.push({ ...result, callId: entry.call.call_id });
5109
+ const callDurationMs = toToolResultDuration(result);
5110
+ toolExecutionMs += callDurationMs;
5111
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5112
+ waitToolMs += callDurationMs;
5113
+ }
4721
5114
  if (entry.call.kind === "custom") {
4722
5115
  toolOutputs.push({
4723
5116
  type: "custom_tool_call_output",
@@ -4732,6 +5125,16 @@ async function runToolLoop(request) {
4732
5125
  });
4733
5126
  }
4734
5127
  }
5128
+ const stepCompletedAtMs = Date.now();
5129
+ const timing = buildStepTiming({
5130
+ stepStartedAtMs,
5131
+ stepCompletedAtMs,
5132
+ modelCompletedAtMs,
5133
+ firstModelEventAtMs,
5134
+ schedulerMetrics,
5135
+ toolExecutionMs,
5136
+ waitToolMs
5137
+ });
4735
5138
  steps.push({
4736
5139
  step: steps.length + 1,
4737
5140
  modelVersion,
@@ -4739,7 +5142,8 @@ async function runToolLoop(request) {
4739
5142
  thoughts: reasoningSummary || void 0,
4740
5143
  toolCalls: stepToolCalls,
4741
5144
  usage: usageTokens,
4742
- costUsd: stepCostUsd
5145
+ costUsd: stepCostUsd,
5146
+ timing
4743
5147
  });
4744
5148
  previousResponseId = finalResponse.id;
4745
5149
  input = toolOutputs;
@@ -4760,6 +5164,13 @@ async function runToolLoop(request) {
4760
5164
  let input = [...toolLoopInput.input];
4761
5165
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
4762
5166
  const turn = stepIndex + 1;
5167
+ const stepStartedAtMs = Date.now();
5168
+ let firstModelEventAtMs;
5169
+ const markFirstModelEvent = () => {
5170
+ if (firstModelEventAtMs === void 0) {
5171
+ firstModelEventAtMs = Date.now();
5172
+ }
5173
+ };
4763
5174
  const response = await collectChatGptCodexResponseWithRetry({
4764
5175
  sessionId: conversationId,
4765
5176
  request: {
@@ -4782,13 +5193,16 @@ async function runToolLoop(request) {
4782
5193
  signal: request.signal,
4783
5194
  onDelta: (delta) => {
4784
5195
  if (delta.thoughtDelta) {
5196
+ markFirstModelEvent();
4785
5197
  request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
4786
5198
  }
4787
5199
  if (delta.textDelta) {
5200
+ markFirstModelEvent();
4788
5201
  request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
4789
5202
  }
4790
5203
  }
4791
5204
  });
5205
+ const modelCompletedAtMs = Date.now();
4792
5206
  const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
4793
5207
  const usageTokens = extractChatGptUsageTokens(response.usage);
4794
5208
  const stepCostUsd = estimateCallCostUsd({
@@ -4803,6 +5217,15 @@ async function runToolLoop(request) {
4803
5217
  if (responseToolCalls.length === 0) {
4804
5218
  finalText = responseText;
4805
5219
  finalThoughts = reasoningSummaryText;
5220
+ const stepCompletedAtMs2 = Date.now();
5221
+ const timing2 = buildStepTiming({
5222
+ stepStartedAtMs,
5223
+ stepCompletedAtMs: stepCompletedAtMs2,
5224
+ modelCompletedAtMs,
5225
+ firstModelEventAtMs,
5226
+ toolExecutionMs: 0,
5227
+ waitToolMs: 0
5228
+ });
4806
5229
  steps.push({
4807
5230
  step: steps.length + 1,
4808
5231
  modelVersion,
@@ -4810,7 +5233,8 @@ async function runToolLoop(request) {
4810
5233
  thoughts: reasoningSummaryText || void 0,
4811
5234
  toolCalls: [],
4812
5235
  usage: usageTokens,
4813
- costUsd: stepCostUsd
5236
+ costUsd: stepCostUsd,
5237
+ timing: timing2
4814
5238
  });
4815
5239
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4816
5240
  }
@@ -4850,8 +5274,15 @@ async function runToolLoop(request) {
4850
5274
  );
4851
5275
  })
4852
5276
  );
5277
+ let toolExecutionMs = 0;
5278
+ let waitToolMs = 0;
4853
5279
  for (const { entry, result, outputPayload } of callResults) {
4854
5280
  toolCalls.push({ ...result, callId: entry.ids.callId });
5281
+ const callDurationMs = toToolResultDuration(result);
5282
+ toolExecutionMs += callDurationMs;
5283
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5284
+ waitToolMs += callDurationMs;
5285
+ }
4855
5286
  if (entry.call.kind === "custom") {
4856
5287
  toolOutputs.push({
4857
5288
  type: "custom_tool_call",
@@ -4882,6 +5313,15 @@ async function runToolLoop(request) {
4882
5313
  });
4883
5314
  }
4884
5315
  }
5316
+ const stepCompletedAtMs = Date.now();
5317
+ const timing = buildStepTiming({
5318
+ stepStartedAtMs,
5319
+ stepCompletedAtMs,
5320
+ modelCompletedAtMs,
5321
+ firstModelEventAtMs,
5322
+ toolExecutionMs,
5323
+ waitToolMs
5324
+ });
4885
5325
  steps.push({
4886
5326
  step: steps.length + 1,
4887
5327
  modelVersion,
@@ -4889,7 +5329,8 @@ async function runToolLoop(request) {
4889
5329
  thoughts: reasoningSummaryText || void 0,
4890
5330
  toolCalls,
4891
5331
  usage: usageTokens,
4892
- costUsd: stepCostUsd
5332
+ costUsd: stepCostUsd,
5333
+ timing
4893
5334
  });
4894
5335
  input = input.concat(toolOutputs);
4895
5336
  }
@@ -4905,18 +5346,29 @@ async function runToolLoop(request) {
4905
5346
  const messages = toFireworksMessages(contents);
4906
5347
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
4907
5348
  const turn = stepIndex + 1;
4908
- const response = await runFireworksCall(async (client) => {
4909
- return await client.chat.completions.create(
4910
- {
4911
- model: providerInfo.model,
4912
- messages,
4913
- tools: fireworksTools,
4914
- tool_choice: "auto",
4915
- parallel_tool_calls: true
4916
- },
4917
- { signal: request.signal }
4918
- );
4919
- });
5349
+ const stepStartedAtMs = Date.now();
5350
+ let schedulerMetrics;
5351
+ const response = await runFireworksCall(
5352
+ async (client) => {
5353
+ return await client.chat.completions.create(
5354
+ {
5355
+ model: providerInfo.model,
5356
+ messages,
5357
+ tools: fireworksTools,
5358
+ tool_choice: "auto",
5359
+ parallel_tool_calls: true
5360
+ },
5361
+ { signal: request.signal }
5362
+ );
5363
+ },
5364
+ providerInfo.model,
5365
+ {
5366
+ onSettled: (metrics) => {
5367
+ schedulerMetrics = metrics;
5368
+ }
5369
+ }
5370
+ );
5371
+ const modelCompletedAtMs = Date.now();
4920
5372
  const modelVersion = typeof response.model === "string" ? response.model : request.model;
4921
5373
  request.onEvent?.({ type: "model", modelVersion });
4922
5374
  const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
@@ -4947,6 +5399,15 @@ async function runToolLoop(request) {
4947
5399
  if (responseToolCalls.length === 0) {
4948
5400
  finalText = responseText;
4949
5401
  finalThoughts = "";
5402
+ const stepCompletedAtMs2 = Date.now();
5403
+ const timing2 = buildStepTiming({
5404
+ stepStartedAtMs,
5405
+ stepCompletedAtMs: stepCompletedAtMs2,
5406
+ modelCompletedAtMs,
5407
+ schedulerMetrics,
5408
+ toolExecutionMs: 0,
5409
+ waitToolMs: 0
5410
+ });
4950
5411
  steps.push({
4951
5412
  step: steps.length + 1,
4952
5413
  modelVersion,
@@ -4954,7 +5415,8 @@ async function runToolLoop(request) {
4954
5415
  thoughts: void 0,
4955
5416
  toolCalls: [],
4956
5417
  usage: usageTokens,
4957
- costUsd: stepCostUsd
5418
+ costUsd: stepCostUsd,
5419
+ timing: timing2
4958
5420
  });
4959
5421
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4960
5422
  }
@@ -4989,8 +5451,15 @@ async function runToolLoop(request) {
4989
5451
  );
4990
5452
  const assistantToolCalls = [];
4991
5453
  const toolMessages = [];
5454
+ let toolExecutionMs = 0;
5455
+ let waitToolMs = 0;
4992
5456
  for (const { entry, result, outputPayload } of callResults) {
4993
5457
  stepToolCalls.push({ ...result, callId: entry.call.id });
5458
+ const callDurationMs = toToolResultDuration(result);
5459
+ toolExecutionMs += callDurationMs;
5460
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5461
+ waitToolMs += callDurationMs;
5462
+ }
4994
5463
  assistantToolCalls.push({
4995
5464
  id: entry.call.id,
4996
5465
  type: "function",
@@ -5005,6 +5474,15 @@ async function runToolLoop(request) {
5005
5474
  content: mergeToolOutput(outputPayload)
5006
5475
  });
5007
5476
  }
5477
+ const stepCompletedAtMs = Date.now();
5478
+ const timing = buildStepTiming({
5479
+ stepStartedAtMs,
5480
+ stepCompletedAtMs,
5481
+ modelCompletedAtMs,
5482
+ schedulerMetrics,
5483
+ toolExecutionMs,
5484
+ waitToolMs
5485
+ });
5008
5486
  steps.push({
5009
5487
  step: steps.length + 1,
5010
5488
  modelVersion,
@@ -5012,7 +5490,8 @@ async function runToolLoop(request) {
5012
5490
  thoughts: void 0,
5013
5491
  toolCalls: stepToolCalls,
5014
5492
  usage: usageTokens,
5015
- costUsd: stepCostUsd
5493
+ costUsd: stepCostUsd,
5494
+ timing
5016
5495
  });
5017
5496
  messages.push({
5018
5497
  role: "assistant",
@@ -5028,6 +5507,14 @@ async function runToolLoop(request) {
5028
5507
  const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
5029
5508
  const geminiContents = contents.map(convertLlmContentToGeminiContent);
5030
5509
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5510
+ const stepStartedAtMs = Date.now();
5511
+ let firstModelEventAtMs;
5512
+ let schedulerMetrics;
5513
+ const markFirstModelEvent = () => {
5514
+ if (firstModelEventAtMs === void 0) {
5515
+ firstModelEventAtMs = Date.now();
5516
+ }
5517
+ };
5031
5518
  const config = {
5032
5519
  maxOutputTokens: 32e3,
5033
5520
  tools: geminiTools,
@@ -5039,81 +5526,91 @@ async function runToolLoop(request) {
5039
5526
  thinkingConfig: resolveGeminiThinkingConfig(request.model)
5040
5527
  };
5041
5528
  const onEvent = request.onEvent;
5042
- const response = await runGeminiCall(async (client) => {
5043
- const stream = await client.models.generateContentStream({
5044
- model: request.model,
5045
- contents: geminiContents,
5046
- config
5047
- });
5048
- let responseText = "";
5049
- let thoughtsText = "";
5050
- const modelParts = [];
5051
- const functionCalls = [];
5052
- const seenFunctionCallIds = /* @__PURE__ */ new Set();
5053
- const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5054
- let latestUsageMetadata;
5055
- let resolvedModelVersion;
5056
- for await (const chunk of stream) {
5057
- if (chunk.modelVersion) {
5058
- resolvedModelVersion = chunk.modelVersion;
5059
- onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5060
- }
5061
- if (chunk.usageMetadata) {
5062
- latestUsageMetadata = chunk.usageMetadata;
5063
- }
5064
- const candidates = chunk.candidates;
5065
- if (!candidates || candidates.length === 0) {
5066
- continue;
5067
- }
5068
- const primary = candidates[0];
5069
- const parts = primary?.content?.parts;
5070
- if (!parts || parts.length === 0) {
5071
- continue;
5072
- }
5073
- for (const part of parts) {
5074
- modelParts.push(part);
5075
- const call = part.functionCall;
5076
- if (call) {
5077
- const id = typeof call.id === "string" ? call.id : "";
5078
- const shouldAdd = (() => {
5079
- if (id.length > 0) {
5080
- if (seenFunctionCallIds.has(id)) {
5529
+ const response = await runGeminiCall(
5530
+ async (client) => {
5531
+ const stream = await client.models.generateContentStream({
5532
+ model: request.model,
5533
+ contents: geminiContents,
5534
+ config
5535
+ });
5536
+ let responseText = "";
5537
+ let thoughtsText = "";
5538
+ const modelParts = [];
5539
+ const functionCalls = [];
5540
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
5541
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5542
+ let latestUsageMetadata;
5543
+ let resolvedModelVersion;
5544
+ for await (const chunk of stream) {
5545
+ markFirstModelEvent();
5546
+ if (chunk.modelVersion) {
5547
+ resolvedModelVersion = chunk.modelVersion;
5548
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5549
+ }
5550
+ if (chunk.usageMetadata) {
5551
+ latestUsageMetadata = chunk.usageMetadata;
5552
+ }
5553
+ const candidates = chunk.candidates;
5554
+ if (!candidates || candidates.length === 0) {
5555
+ continue;
5556
+ }
5557
+ const primary = candidates[0];
5558
+ const parts = primary?.content?.parts;
5559
+ if (!parts || parts.length === 0) {
5560
+ continue;
5561
+ }
5562
+ for (const part of parts) {
5563
+ modelParts.push(part);
5564
+ const call = part.functionCall;
5565
+ if (call) {
5566
+ const id = typeof call.id === "string" ? call.id : "";
5567
+ const shouldAdd = (() => {
5568
+ if (id.length > 0) {
5569
+ if (seenFunctionCallIds.has(id)) {
5570
+ return false;
5571
+ }
5572
+ seenFunctionCallIds.add(id);
5573
+ return true;
5574
+ }
5575
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
5576
+ if (seenFunctionCallKeys.has(key)) {
5081
5577
  return false;
5082
5578
  }
5083
- seenFunctionCallIds.add(id);
5579
+ seenFunctionCallKeys.add(key);
5084
5580
  return true;
5581
+ })();
5582
+ if (shouldAdd) {
5583
+ functionCalls.push(call);
5085
5584
  }
5086
- const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
5087
- if (seenFunctionCallKeys.has(key)) {
5088
- return false;
5089
- }
5090
- seenFunctionCallKeys.add(key);
5091
- return true;
5092
- })();
5093
- if (shouldAdd) {
5094
- functionCalls.push(call);
5095
5585
  }
5096
- }
5097
- if (typeof part.text === "string" && part.text.length > 0) {
5098
- if (part.thought) {
5099
- thoughtsText += part.text;
5100
- onEvent?.({ type: "delta", channel: "thought", text: part.text });
5101
- } else {
5102
- responseText += part.text;
5103
- onEvent?.({ type: "delta", channel: "response", text: part.text });
5586
+ if (typeof part.text === "string" && part.text.length > 0) {
5587
+ if (part.thought) {
5588
+ thoughtsText += part.text;
5589
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
5590
+ } else {
5591
+ responseText += part.text;
5592
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
5593
+ }
5104
5594
  }
5105
5595
  }
5106
5596
  }
5597
+ return {
5598
+ responseText,
5599
+ thoughtsText,
5600
+ functionCalls,
5601
+ modelParts,
5602
+ usageMetadata: latestUsageMetadata,
5603
+ modelVersion: resolvedModelVersion ?? request.model
5604
+ };
5605
+ },
5606
+ request.model,
5607
+ {
5608
+ onSettled: (metrics) => {
5609
+ schedulerMetrics = metrics;
5610
+ }
5107
5611
  }
5108
- return {
5109
- responseText,
5110
- thoughtsText,
5111
- functionCalls,
5112
- modelParts,
5113
- usageMetadata: latestUsageMetadata,
5114
- modelVersion: resolvedModelVersion ?? request.model
5115
- };
5116
- });
5612
+ );
5613
+ const modelCompletedAtMs = Date.now();
5117
5614
  const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
5118
5615
  const modelVersion = response.modelVersion ?? request.model;
5119
5616
  const stepCostUsd = estimateCallCostUsd({
@@ -5125,6 +5622,16 @@ async function runToolLoop(request) {
5125
5622
  if (response.functionCalls.length === 0) {
5126
5623
  finalText = response.responseText.trim();
5127
5624
  finalThoughts = response.thoughtsText.trim();
5625
+ const stepCompletedAtMs2 = Date.now();
5626
+ const timing2 = buildStepTiming({
5627
+ stepStartedAtMs,
5628
+ stepCompletedAtMs: stepCompletedAtMs2,
5629
+ modelCompletedAtMs,
5630
+ firstModelEventAtMs,
5631
+ schedulerMetrics,
5632
+ toolExecutionMs: 0,
5633
+ waitToolMs: 0
5634
+ });
5128
5635
  steps.push({
5129
5636
  step: steps.length + 1,
5130
5637
  modelVersion,
@@ -5132,7 +5639,8 @@ async function runToolLoop(request) {
5132
5639
  thoughts: finalThoughts || void 0,
5133
5640
  toolCalls: [],
5134
5641
  usage: usageTokens,
5135
- costUsd: stepCostUsd
5642
+ costUsd: stepCostUsd,
5643
+ timing: timing2
5136
5644
  });
5137
5645
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5138
5646
  }
@@ -5182,8 +5690,15 @@ async function runToolLoop(request) {
5182
5690
  );
5183
5691
  })
5184
5692
  );
5693
+ let toolExecutionMs = 0;
5694
+ let waitToolMs = 0;
5185
5695
  for (const { entry, result, outputPayload } of callResults) {
5186
5696
  toolCalls.push({ ...result, callId: entry.call.id });
5697
+ const callDurationMs = toToolResultDuration(result);
5698
+ toolExecutionMs += callDurationMs;
5699
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5700
+ waitToolMs += callDurationMs;
5701
+ }
5187
5702
  const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
5188
5703
  responseParts.push({
5189
5704
  functionResponse: {
@@ -5193,6 +5708,16 @@ async function runToolLoop(request) {
5193
5708
  }
5194
5709
  });
5195
5710
  }
5711
+ const stepCompletedAtMs = Date.now();
5712
+ const timing = buildStepTiming({
5713
+ stepStartedAtMs,
5714
+ stepCompletedAtMs,
5715
+ modelCompletedAtMs,
5716
+ firstModelEventAtMs,
5717
+ schedulerMetrics,
5718
+ toolExecutionMs,
5719
+ waitToolMs
5720
+ });
5196
5721
  steps.push({
5197
5722
  step: steps.length + 1,
5198
5723
  modelVersion,
@@ -5200,7 +5725,8 @@ async function runToolLoop(request) {
5200
5725
  thoughts: response.thoughtsText.trim() || void 0,
5201
5726
  toolCalls,
5202
5727
  usage: usageTokens,
5203
- costUsd: stepCostUsd
5728
+ costUsd: stepCostUsd,
5729
+ timing
5204
5730
  });
5205
5731
  geminiContents.push({ role: "user", parts: responseParts });
5206
5732
  }
@@ -5451,13 +5977,648 @@ function appendMarkdownSourcesSection(value, sources) {
5451
5977
  ${lines}`;
5452
5978
  }
5453
5979
 
5980
+ // src/agent.ts
5981
+ import { randomBytes as randomBytes3 } from "crypto";
5982
+
5983
+ // src/agent/subagents.ts
5984
+ import { randomBytes as randomBytes2 } from "crypto";
5985
+ import { z as z4 } from "zod";
5986
+ var DEFAULT_SUBAGENT_MAX_AGENTS = 4;
5987
+ var DEFAULT_SUBAGENT_MAX_DEPTH = 2;
5988
+ var DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS = 1500;
5989
+ var DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS = 9e4;
5990
+ var MAX_SUBAGENT_MAX_AGENTS = 64;
5991
+ var MAX_SUBAGENT_MAX_DEPTH = 12;
5992
+ var MAX_SUBAGENT_MAX_STEPS = 64;
5993
+ var MAX_SUBAGENT_WAIT_TIMEOUT_MS = 6e5;
5994
+ var SUBAGENT_CONTROL_TOOL_NAMES = ["send_input", "resume_agent", "wait", "close_agent"];
5995
+ var subagentInputItemSchema = z4.object({
5996
+ text: z4.string().optional(),
5997
+ image_url: z4.string().optional(),
5998
+ name: z4.string().optional(),
5999
+ path: z4.string().optional(),
6000
+ type: z4.string().optional()
6001
+ }).passthrough();
6002
+ var spawnAgentInputSchema = z4.object({
6003
+ prompt: z4.string().optional().describe("Initial prompt for the subagent."),
6004
+ message: z4.string().optional().describe("Codex-style alias for prompt."),
6005
+ items: z4.array(subagentInputItemSchema).optional().describe("Optional Codex-style input items."),
6006
+ agent_type: z4.string().optional().describe("Codex-style agent type hint."),
6007
+ instructions: z4.string().optional().describe("Optional extra instructions for this subagent instance."),
6008
+ model: z4.string().optional().describe("Optional model override. Must be one of this package's supported text model ids."),
6009
+ max_steps: z4.number().int().min(1).max(MAX_SUBAGENT_MAX_STEPS).optional().describe("Optional max step budget for each subagent run.")
6010
+ }).refine((value) => Boolean(resolvePromptValue(value.prompt, value.message, value.items)), {
6011
+ message: "Either prompt, message, or items must contain non-empty input."
6012
+ });
6013
+ var sendInputSchema = z4.object({
6014
+ agent_id: z4.string().optional().describe("Target subagent id."),
6015
+ id: z4.string().optional().describe("Codex-style alias for agent_id."),
6016
+ input: z4.string().optional().describe("New user input queued for the subagent."),
6017
+ message: z4.string().optional().describe("Codex-style alias for input."),
6018
+ items: z4.array(subagentInputItemSchema).optional().describe("Optional Codex-style input items."),
6019
+ interrupt: z4.boolean().optional().describe("If true and currently running, aborts active run before queuing input.")
6020
+ }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
6021
+ message: "agent_id (or id) is required."
6022
+ }).refine((value) => Boolean(resolvePromptValue(value.input, value.message, value.items)), {
6023
+ message: "input (or message/items) is required."
6024
+ });
6025
+ var resumeAgentSchema = z4.object({
6026
+ agent_id: z4.string().optional().describe("Target subagent id."),
6027
+ id: z4.string().optional().describe("Codex-style alias for agent_id.")
6028
+ }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
6029
+ message: "agent_id (or id) is required."
6030
+ });
6031
+ var waitSchema = z4.object({
6032
+ agent_id: z4.string().optional().describe("Target subagent id."),
6033
+ id: z4.string().optional().describe("Codex-style alias for agent_id."),
6034
+ ids: z4.array(z4.string().min(1)).optional().describe("Codex-style list of agent ids."),
6035
+ timeout_ms: z4.number().int().min(1).optional().describe("Optional wait timeout in milliseconds.")
6036
+ }).refine(
6037
+ (value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)) || Array.isArray(value.ids) && value.ids.length > 0,
6038
+ {
6039
+ message: "agent_id/id or ids is required."
6040
+ }
6041
+ );
6042
+ var closeSchema = z4.object({
6043
+ agent_id: z4.string().optional().describe("Target subagent id."),
6044
+ id: z4.string().optional().describe("Codex-style alias for agent_id.")
6045
+ }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
6046
+ message: "agent_id (or id) is required."
6047
+ });
6048
+ function resolveSubagentToolConfig(selection, currentDepth) {
6049
+ const defaults = {
6050
+ maxAgents: DEFAULT_SUBAGENT_MAX_AGENTS,
6051
+ maxDepth: DEFAULT_SUBAGENT_MAX_DEPTH,
6052
+ defaultWaitTimeoutMs: DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS,
6053
+ maxWaitTimeoutMs: DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS,
6054
+ promptPattern: "codex",
6055
+ inheritTools: true,
6056
+ inheritFilesystemTool: true
6057
+ };
6058
+ if (selection === void 0 || selection === false) {
6059
+ return {
6060
+ enabled: false,
6061
+ ...defaults
6062
+ };
6063
+ }
6064
+ const config = selection === true ? {} : selection;
6065
+ const maxAgents = normalizeInteger(
6066
+ config.maxAgents,
6067
+ defaults.maxAgents,
6068
+ 1,
6069
+ MAX_SUBAGENT_MAX_AGENTS
6070
+ );
6071
+ const maxDepth = normalizeInteger(config.maxDepth, defaults.maxDepth, 1, MAX_SUBAGENT_MAX_DEPTH);
6072
+ const defaultWaitTimeoutMs = normalizeInteger(
6073
+ config.defaultWaitTimeoutMs,
6074
+ defaults.defaultWaitTimeoutMs,
6075
+ 1,
6076
+ MAX_SUBAGENT_WAIT_TIMEOUT_MS
6077
+ );
6078
+ const maxWaitTimeoutMs = normalizeInteger(
6079
+ config.maxWaitTimeoutMs,
6080
+ defaults.maxWaitTimeoutMs,
6081
+ defaultWaitTimeoutMs,
6082
+ MAX_SUBAGENT_WAIT_TIMEOUT_MS
6083
+ );
6084
+ const promptPattern = config.promptPattern ?? defaults.promptPattern;
6085
+ const instructions = trimToUndefined(config.instructions);
6086
+ const maxSteps = normalizeOptionalInteger(config.maxSteps, 1, MAX_SUBAGENT_MAX_STEPS);
6087
+ const enabled = config.enabled !== false && currentDepth < maxDepth;
6088
+ return {
6089
+ enabled,
6090
+ maxAgents,
6091
+ maxDepth,
6092
+ defaultWaitTimeoutMs,
6093
+ maxWaitTimeoutMs,
6094
+ promptPattern,
6095
+ ...instructions ? { instructions } : {},
6096
+ ...config.model ? { model: config.model } : {},
6097
+ ...maxSteps ? { maxSteps } : {},
6098
+ inheritTools: config.inheritTools !== false,
6099
+ inheritFilesystemTool: config.inheritFilesystemTool !== false
6100
+ };
6101
+ }
6102
+ function buildCodexSubagentOrchestratorInstructions(params) {
6103
+ return [
6104
+ "Subagent orchestration tools are available: spawn_agent, send_input, resume_agent, wait, close_agent.",
6105
+ "Use this control pattern:",
6106
+ "1. spawn_agent with a focused prompt.",
6107
+ "2. wait on that agent_id until it is no longer running.",
6108
+ "3. For follow-up turns, send_input then resume_agent.",
6109
+ "4. close_agent when delegation is complete.",
6110
+ `Limits: max active subagents ${params.maxAgents}, max depth ${params.maxDepth}, current depth ${params.currentDepth}.`
6111
+ ].join("\n");
6112
+ }
6113
+ function buildCodexSubagentWorkerInstructions(params) {
6114
+ return [
6115
+ `You are a delegated subagent at depth ${params.depth}/${params.maxDepth}.`,
6116
+ "Focus on the delegated task, use available tools when needed, and return concise actionable output.",
6117
+ "If blocked, report the blocker explicitly."
6118
+ ].join("\n");
6119
+ }
6120
+ function createSubagentToolController(options) {
6121
+ if (!options.config.enabled) {
6122
+ return {
6123
+ tools: {},
6124
+ closeAll: async () => {
6125
+ }
6126
+ };
6127
+ }
6128
+ const agents = /* @__PURE__ */ new Map();
6129
+ const tools = {
6130
+ spawn_agent: tool({
6131
+ description: "Spawns a subagent asynchronously. Returns immediately with agent status and id.",
6132
+ inputSchema: spawnAgentInputSchema,
6133
+ execute: async (input) => {
6134
+ if (countActiveAgents(agents) >= options.config.maxAgents) {
6135
+ throw new Error(
6136
+ `Subagent limit reached (${options.config.maxAgents}). Close existing agents before spawning new ones.`
6137
+ );
6138
+ }
6139
+ const childDepth = options.parentDepth + 1;
6140
+ if (childDepth > options.config.maxDepth) {
6141
+ throw new Error(
6142
+ `Subagent depth limit reached (${options.config.maxDepth}). Cannot spawn at depth ${childDepth}.`
6143
+ );
6144
+ }
6145
+ let model = options.config.model ?? options.parentModel;
6146
+ if (input.model) {
6147
+ if (!isLlmTextModelId(input.model)) {
6148
+ throw new Error(`Unsupported subagent model id: ${input.model}`);
6149
+ }
6150
+ model = input.model;
6151
+ }
6152
+ const id = `agent_${randomBytes2(6).toString("hex")}`;
6153
+ const now = Date.now();
6154
+ const initialPrompt = resolvePromptValue(input.prompt, input.message, input.items);
6155
+ if (!initialPrompt) {
6156
+ throw new Error("spawn_agent requires prompt/message/items with non-empty text.");
6157
+ }
6158
+ const agent = {
6159
+ id,
6160
+ depth: childDepth,
6161
+ model,
6162
+ status: "idle",
6163
+ createdAtMs: now,
6164
+ updatedAtMs: now,
6165
+ pendingInputs: [initialPrompt],
6166
+ history: [],
6167
+ ...options.buildChildInstructions ? {
6168
+ instructions: trimToUndefined(
6169
+ options.buildChildInstructions(input.instructions, childDepth)
6170
+ )
6171
+ } : input.instructions ? { instructions: trimToUndefined(input.instructions) } : {},
6172
+ ...input.max_steps ? { maxSteps: input.max_steps } : options.config.maxSteps ? { maxSteps: options.config.maxSteps } : {},
6173
+ turns: 0,
6174
+ notification: "spawned",
6175
+ notificationMessage: `Spawned subagent ${id}.`,
6176
+ version: 1,
6177
+ waiters: /* @__PURE__ */ new Set()
6178
+ };
6179
+ agents.set(id, agent);
6180
+ startRun(agent, options);
6181
+ return buildToolResponse(agent, {
6182
+ notification: "spawned",
6183
+ message: `Spawned subagent ${id}.`
6184
+ });
6185
+ }
6186
+ }),
6187
+ send_input: tool({
6188
+ description: "Queues new input for an existing subagent.",
6189
+ inputSchema: sendInputSchema,
6190
+ execute: async (input) => {
6191
+ const agentId = resolveAgentIdValue(input.agent_id, input.id);
6192
+ if (!agentId) {
6193
+ throw new Error("send_input requires agent_id or id.");
6194
+ }
6195
+ const agent = requireAgent(agents, agentId);
6196
+ const nextInput = resolvePromptValue(input.input, input.message, input.items);
6197
+ if (!nextInput) {
6198
+ throw new Error("send_input requires input/message/items with non-empty text.");
6199
+ }
6200
+ if (agent.status === "closed") {
6201
+ throw new Error(`Subagent ${agent.id} is closed.`);
6202
+ }
6203
+ if (input.interrupt && agent.abortController) {
6204
+ agent.abortController.abort("send_input_interrupt");
6205
+ agent.pendingInputs.unshift(nextInput);
6206
+ setNotification(agent, "input_queued", `Interrupted ${agent.id} and queued new input.`);
6207
+ return buildToolResponse(agent);
6208
+ }
6209
+ agent.pendingInputs.push(nextInput);
6210
+ setNotification(agent, "input_queued", `Queued input for ${agent.id}.`);
6211
+ return buildToolResponse(agent);
6212
+ }
6213
+ }),
6214
+ resume_agent: tool({
6215
+ description: "Resumes a subagent run when queued input is available.",
6216
+ inputSchema: resumeAgentSchema,
6217
+ execute: async (input) => {
6218
+ const agentId = resolveAgentIdValue(input.agent_id, input.id);
6219
+ if (!agentId) {
6220
+ throw new Error("resume_agent requires agent_id or id.");
6221
+ }
6222
+ const agent = requireAgent(agents, agentId);
6223
+ if (agent.status === "closed") {
6224
+ setNotification(agent, "already_closed", `Subagent ${agent.id} is already closed.`);
6225
+ return buildToolResponse(agent, {
6226
+ notification: "already_closed",
6227
+ message: `Subagent ${agent.id} is already closed.`
6228
+ });
6229
+ }
6230
+ const outcome = startRun(agent, options);
6231
+ if (outcome === "started") {
6232
+ return buildToolResponse(agent, {
6233
+ notification: "run_started",
6234
+ message: `Started subagent ${agent.id}.`
6235
+ });
6236
+ }
6237
+ if (outcome === "already_running") {
6238
+ setNotification(agent, "already_running", `Subagent ${agent.id} is already running.`);
6239
+ return buildToolResponse(agent);
6240
+ }
6241
+ setNotification(agent, "no_pending_input", `Subagent ${agent.id} has no queued input.`);
6242
+ return buildToolResponse(agent);
6243
+ }
6244
+ }),
6245
+ wait: tool({
6246
+ description: "Waits for a running subagent to change state or until timeout. Returns current status.",
6247
+ inputSchema: waitSchema,
6248
+ execute: async (input) => {
6249
+ const usesIdsArray = Array.isArray(input.ids) && input.ids.length > 0;
6250
+ const ids = resolveAgentIdList(input.agent_id, input.id, input.ids);
6251
+ if (ids.length === 0) {
6252
+ throw new Error("wait requires agent_id/id or ids.");
6253
+ }
6254
+ const timeoutMs = normalizeInteger(
6255
+ input.timeout_ms,
6256
+ options.config.defaultWaitTimeoutMs,
6257
+ 1,
6258
+ options.config.maxWaitTimeoutMs
6259
+ );
6260
+ if (usesIdsArray) {
6261
+ const status = await waitForAnyAgentStatus(agents, ids, timeoutMs);
6262
+ return { status, timed_out: Object.keys(status).length === 0, timeout_ms: timeoutMs };
6263
+ }
6264
+ const agent = requireAgent(agents, ids[0]);
6265
+ if (agent.status === "running") {
6266
+ const completed = await waitUntilNotRunning(agent, timeoutMs);
6267
+ if (!completed) {
6268
+ setNotification(
6269
+ agent,
6270
+ "timeout",
6271
+ `Timed out after ${timeoutMs}ms while waiting for ${agent.id}.`
6272
+ );
6273
+ return buildToolResponse(agent, void 0, { timed_out: true, timeout_ms: timeoutMs });
6274
+ }
6275
+ }
6276
+ return buildToolResponse(agent, void 0, { timed_out: false, timeout_ms: timeoutMs });
6277
+ }
6278
+ }),
6279
+ close_agent: tool({
6280
+ description: "Closes a subagent and aborts its current run if it is still running.",
6281
+ inputSchema: closeSchema,
6282
+ execute: async (input) => {
6283
+ const agentId = resolveAgentIdValue(input.agent_id, input.id);
6284
+ if (!agentId) {
6285
+ throw new Error("close_agent requires agent_id or id.");
6286
+ }
6287
+ const agent = requireAgent(agents, agentId);
6288
+ if (agent.status === "closed") {
6289
+ setNotification(agent, "already_closed", `Subagent ${agent.id} is already closed.`);
6290
+ return buildToolResponse(agent, void 0, { cancelled: false });
6291
+ }
6292
+ const cancelled = closeSubagent(agent, `Closed ${agent.id}.`);
6293
+ return buildToolResponse(
6294
+ agent,
6295
+ { notification: "closed", message: `Closed ${agent.id}.` },
6296
+ { cancelled }
6297
+ );
6298
+ }
6299
+ })
6300
+ };
6301
+ return {
6302
+ tools,
6303
+ closeAll: async () => {
6304
+ const running = [];
6305
+ for (const agent of agents.values()) {
6306
+ if (agent.status !== "closed") {
6307
+ closeSubagent(agent, `Parent agent loop closed ${agent.id}.`);
6308
+ }
6309
+ if (agent.runningPromise) {
6310
+ running.push(agent.runningPromise);
6311
+ }
6312
+ }
6313
+ if (running.length > 0) {
6314
+ await Promise.race([Promise.allSettled(running), sleep2(2e3)]);
6315
+ }
6316
+ }
6317
+ };
6318
+ }
6319
+ function requireAgent(agents, id) {
6320
+ const agent = agents.get(id);
6321
+ if (!agent) {
6322
+ throw new Error(`Unknown subagent id: ${id}`);
6323
+ }
6324
+ return agent;
6325
+ }
6326
+ function resolveAgentIdValue(agentId, idAlias) {
6327
+ const preferred = agentId?.trim();
6328
+ if (preferred) {
6329
+ return preferred;
6330
+ }
6331
+ const alias = idAlias?.trim();
6332
+ return alias ?? "";
6333
+ }
6334
+ function resolveAgentIdList(agentId, idAlias, ids) {
6335
+ if (Array.isArray(ids) && ids.length > 0) {
6336
+ return [...new Set(ids.map((value) => value.trim()).filter(Boolean))];
6337
+ }
6338
+ const single = resolveAgentIdValue(agentId, idAlias);
6339
+ return single ? [single] : [];
6340
+ }
6341
+ function resolvePromptValue(prompt, message, items) {
6342
+ const promptValue = prompt?.trim();
6343
+ if (promptValue) {
6344
+ return promptValue;
6345
+ }
6346
+ const messageValue = message?.trim();
6347
+ if (messageValue) {
6348
+ return messageValue;
6349
+ }
6350
+ const itemText = resolveInputItemsText(items);
6351
+ return itemText ?? "";
6352
+ }
6353
+ function resolveInputItemsText(items) {
6354
+ if (!items || items.length === 0) {
6355
+ return void 0;
6356
+ }
6357
+ const lines = [];
6358
+ for (const item of items) {
6359
+ if (typeof item.text === "string" && item.text.trim().length > 0) {
6360
+ lines.push(item.text.trim());
6361
+ continue;
6362
+ }
6363
+ const itemType = typeof item.type === "string" ? item.type.trim() : "";
6364
+ const name = typeof item.name === "string" ? item.name.trim() : "";
6365
+ const path6 = typeof item.path === "string" ? item.path.trim() : "";
6366
+ const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
6367
+ const compact = [itemType, name, path6 || imageUrl].filter(Boolean).join(" ");
6368
+ if (compact) {
6369
+ lines.push(compact);
6370
+ }
6371
+ }
6372
+ if (lines.length === 0) {
6373
+ return void 0;
6374
+ }
6375
+ return lines.join("\n");
6376
+ }
6377
+ function countActiveAgents(agents) {
6378
+ let count = 0;
6379
+ for (const agent of agents.values()) {
6380
+ if (agent.status !== "closed") {
6381
+ count += 1;
6382
+ }
6383
+ }
6384
+ return count;
6385
+ }
6386
+ async function waitForAnyAgentStatus(agents, ids, timeoutMs) {
6387
+ const requested = ids.map((id) => requireAgent(agents, id));
6388
+ const deadline = Date.now() + timeoutMs;
6389
+ while (true) {
6390
+ const status = {};
6391
+ for (const agent of requested) {
6392
+ if (agent.status !== "running") {
6393
+ status[agent.id] = buildSnapshot(agent);
6394
+ }
6395
+ }
6396
+ if (Object.keys(status).length > 0) {
6397
+ return status;
6398
+ }
6399
+ const remaining = deadline - Date.now();
6400
+ if (remaining <= 0) {
6401
+ return {};
6402
+ }
6403
+ await Promise.race(
6404
+ requested.map(async (agent) => {
6405
+ const changed = await waitForVersionChange(agent, agent.version, remaining);
6406
+ if (!changed) {
6407
+ return;
6408
+ }
6409
+ })
6410
+ );
6411
+ }
6412
+ }
6413
+ function setNotification(agent, notification, message) {
6414
+ agent.notification = notification;
6415
+ agent.notificationMessage = message;
6416
+ agent.updatedAtMs = Date.now();
6417
+ agent.version += 1;
6418
+ notifyWaiters(agent);
6419
+ }
6420
+ function setLifecycle(agent, status, notification, message) {
6421
+ agent.status = status;
6422
+ setNotification(agent, notification, message);
6423
+ }
6424
+ function notifyWaiters(agent) {
6425
+ if (agent.waiters.size === 0) {
6426
+ return;
6427
+ }
6428
+ const waiters = [...agent.waiters];
6429
+ agent.waiters.clear();
6430
+ for (const notify of waiters) {
6431
+ notify();
6432
+ }
6433
+ }
6434
+ function startRun(agent, options) {
6435
+ if (agent.runningPromise) {
6436
+ return "already_running";
6437
+ }
6438
+ const nextInput = agent.pendingInputs.shift();
6439
+ if (!nextInput) {
6440
+ return "no_pending_input";
6441
+ }
6442
+ const input = [...agent.history, { role: "user", content: nextInput }];
6443
+ const abortController = new AbortController();
6444
+ const runStartedAtMs = Date.now();
6445
+ agent.abortController = abortController;
6446
+ if (agent.firstRunStartedAtMs === void 0) {
6447
+ agent.firstRunStartedAtMs = runStartedAtMs;
6448
+ }
6449
+ agent.lastRunStartedAtMs = runStartedAtMs;
6450
+ agent.lastError = void 0;
6451
+ setLifecycle(
6452
+ agent,
6453
+ "running",
6454
+ "run_started",
6455
+ `Subagent ${agent.id} started run ${agent.turns + 1}.`
6456
+ );
6457
+ const runPromise = (async () => {
6458
+ try {
6459
+ const result = await options.runSubagent({
6460
+ agentId: agent.id,
6461
+ depth: agent.depth,
6462
+ model: agent.model,
6463
+ input,
6464
+ instructions: agent.instructions,
6465
+ maxSteps: agent.maxSteps,
6466
+ signal: abortController.signal
6467
+ });
6468
+ if (agent.status === "closed") {
6469
+ return;
6470
+ }
6471
+ agent.lastResult = result;
6472
+ agent.lastError = void 0;
6473
+ agent.turns += 1;
6474
+ agent.history = [...input, { role: "assistant", content: result.text }];
6475
+ setLifecycle(
6476
+ agent,
6477
+ "idle",
6478
+ "run_completed",
6479
+ `Subagent ${agent.id} completed run ${agent.turns}.`
6480
+ );
6481
+ } catch (error) {
6482
+ if (agent.status === "closed") {
6483
+ return;
6484
+ }
6485
+ if (abortController.signal.aborted) {
6486
+ setLifecycle(agent, "idle", "input_queued", `Subagent ${agent.id} run interrupted.`);
6487
+ return;
6488
+ }
6489
+ const message = toErrorMessage(error);
6490
+ agent.lastError = message;
6491
+ setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
6492
+ } finally {
6493
+ const runCompletedAtMs = Date.now();
6494
+ agent.lastRunCompletedAtMs = runCompletedAtMs;
6495
+ agent.lastRunDurationMs = Math.max(0, runCompletedAtMs - runStartedAtMs);
6496
+ agent.runningPromise = void 0;
6497
+ agent.abortController = void 0;
6498
+ }
6499
+ })();
6500
+ agent.runningPromise = runPromise;
6501
+ return "started";
6502
+ }
6503
+ function closeSubagent(agent, message) {
6504
+ const cancelled = Boolean(agent.runningPromise);
6505
+ agent.pendingInputs = [];
6506
+ if (agent.abortController) {
6507
+ agent.abortController.abort("close_agent");
6508
+ }
6509
+ setLifecycle(agent, "closed", "closed", message);
6510
+ return cancelled;
6511
+ }
6512
+ async function waitUntilNotRunning(agent, timeoutMs) {
6513
+ const deadline = Date.now() + timeoutMs;
6514
+ while (agent.status === "running") {
6515
+ const remaining = deadline - Date.now();
6516
+ if (remaining <= 0) {
6517
+ return false;
6518
+ }
6519
+ const currentVersion = agent.version;
6520
+ const changed = await waitForVersionChange(agent, currentVersion, remaining);
6521
+ if (!changed) {
6522
+ return false;
6523
+ }
6524
+ }
6525
+ return true;
6526
+ }
6527
+ async function waitForVersionChange(agent, version, timeoutMs) {
6528
+ if (agent.version !== version) {
6529
+ return true;
6530
+ }
6531
+ return await new Promise((resolve) => {
6532
+ const waiter = () => {
6533
+ cleanup();
6534
+ resolve(true);
6535
+ };
6536
+ const timeout = setTimeout(() => {
6537
+ cleanup();
6538
+ resolve(false);
6539
+ }, timeoutMs);
6540
+ const cleanup = () => {
6541
+ clearTimeout(timeout);
6542
+ agent.waiters.delete(waiter);
6543
+ };
6544
+ agent.waiters.add(waiter);
6545
+ });
6546
+ }
6547
+ function buildToolResponse(agent, override, extra = {}) {
6548
+ const notification = override?.notification ?? agent.notification;
6549
+ const message = override?.message ?? agent.notificationMessage;
6550
+ const snapshot = buildSnapshot(agent);
6551
+ return {
6552
+ agent_id: snapshot.agent_id,
6553
+ notification,
6554
+ message,
6555
+ status: snapshot.status,
6556
+ agent: snapshot,
6557
+ tool_availability: snapshot.status === "closed" ? [] : [...SUBAGENT_CONTROL_TOOL_NAMES],
6558
+ ...extra
6559
+ };
6560
+ }
6561
+ function buildSnapshot(agent) {
6562
+ return {
6563
+ agent_id: agent.id,
6564
+ status: agent.status,
6565
+ depth: agent.depth,
6566
+ model: agent.model,
6567
+ pending_inputs: agent.pendingInputs.length,
6568
+ turns: agent.turns,
6569
+ created_at: new Date(agent.createdAtMs).toISOString(),
6570
+ updated_at: new Date(agent.updatedAtMs).toISOString(),
6571
+ ...agent.firstRunStartedAtMs ? {
6572
+ first_run_started_at: new Date(agent.firstRunStartedAtMs).toISOString(),
6573
+ spawn_startup_latency_ms: Math.max(0, agent.firstRunStartedAtMs - agent.createdAtMs)
6574
+ } : {},
6575
+ ...agent.lastRunStartedAtMs ? { last_run_started_at: new Date(agent.lastRunStartedAtMs).toISOString() } : {},
6576
+ ...agent.lastRunCompletedAtMs ? { last_run_completed_at: new Date(agent.lastRunCompletedAtMs).toISOString() } : {},
6577
+ ...typeof agent.lastRunDurationMs === "number" ? { last_run_duration_ms: Math.max(0, agent.lastRunDurationMs) } : {},
6578
+ ...agent.lastError ? { last_error: agent.lastError } : {},
6579
+ ...agent.lastResult ? {
6580
+ last_result: {
6581
+ text: agent.lastResult.text,
6582
+ thoughts: agent.lastResult.thoughts,
6583
+ step_count: agent.lastResult.steps.length,
6584
+ total_cost_usd: agent.lastResult.totalCostUsd
6585
+ }
6586
+ } : {}
6587
+ };
6588
+ }
6589
+ function normalizeInteger(value, fallback, min, max) {
6590
+ const parsed = Number.isFinite(value) ? Math.floor(value) : fallback;
6591
+ return Math.max(min, Math.min(max, parsed));
6592
+ }
6593
+ function normalizeOptionalInteger(value, min, max) {
6594
+ if (!Number.isFinite(value)) {
6595
+ return void 0;
6596
+ }
6597
+ return Math.max(min, Math.min(max, Math.floor(value)));
6598
+ }
6599
+ function trimToUndefined(value) {
6600
+ const trimmed = value?.trim();
6601
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
6602
+ }
6603
+ function toErrorMessage(error) {
6604
+ if (error instanceof Error) {
6605
+ return error.message;
6606
+ }
6607
+ return String(error);
6608
+ }
6609
+ function sleep2(ms) {
6610
+ return new Promise((resolve) => {
6611
+ setTimeout(resolve, ms);
6612
+ });
6613
+ }
6614
+
5454
6615
  // src/tools/filesystemTools.ts
5455
6616
  import path5 from "path";
5456
- import { z as z5 } from "zod";
6617
+ import { z as z6 } from "zod";
5457
6618
 
5458
6619
  // src/tools/applyPatch.ts
5459
6620
  import path4 from "path";
5460
- import { z as z4 } from "zod";
6621
+ import { z as z5 } from "zod";
5461
6622
 
5462
6623
  // src/tools/filesystem.ts
5463
6624
  import { promises as fs3 } from "fs";
@@ -5753,8 +6914,8 @@ var CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION = [
5753
6914
  "- You must prefix new lines with `+` even when creating a new file",
5754
6915
  "- File references can only be relative, NEVER ABSOLUTE."
5755
6916
  ].join("\n");
5756
- var applyPatchToolInputSchema = z4.object({
5757
- input: z4.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
6917
+ var applyPatchToolInputSchema = z5.object({
6918
+ input: z5.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
5758
6919
  });
5759
6920
  function createApplyPatchTool(options = {}) {
5760
6921
  return tool({
@@ -6162,100 +7323,100 @@ var MAX_GREP_LIMIT = 2e3;
6162
7323
  var DEFAULT_MAX_LINE_LENGTH = 500;
6163
7324
  var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
6164
7325
  var DEFAULT_TAB_WIDTH = 4;
6165
- var codexReadFileInputSchema = z5.object({
6166
- file_path: z5.string().min(1).describe("Absolute path to the file"),
6167
- offset: z5.number().int().min(1).optional().describe("The line number to start reading from. Must be 1 or greater."),
6168
- limit: z5.number().int().min(1).optional().describe("The maximum number of lines to return."),
6169
- mode: z5.enum(["slice", "indentation"]).optional().describe('Optional mode selector: "slice" (default) or "indentation".'),
6170
- indentation: z5.object({
6171
- anchor_line: z5.number().int().min(1).optional(),
6172
- max_levels: z5.number().int().min(0).optional(),
6173
- include_siblings: z5.boolean().optional(),
6174
- include_header: z5.boolean().optional(),
6175
- max_lines: z5.number().int().min(1).optional()
7326
+ var codexReadFileInputSchema = z6.object({
7327
+ file_path: z6.string().min(1).describe("Absolute path to the file"),
7328
+ offset: z6.number().int().min(1).optional().describe("The line number to start reading from. Must be 1 or greater."),
7329
+ limit: z6.number().int().min(1).optional().describe("The maximum number of lines to return."),
7330
+ mode: z6.enum(["slice", "indentation"]).optional().describe('Optional mode selector: "slice" (default) or "indentation".'),
7331
+ indentation: z6.object({
7332
+ anchor_line: z6.number().int().min(1).optional(),
7333
+ max_levels: z6.number().int().min(0).optional(),
7334
+ include_siblings: z6.boolean().optional(),
7335
+ include_header: z6.boolean().optional(),
7336
+ max_lines: z6.number().int().min(1).optional()
6176
7337
  }).optional()
6177
7338
  });
6178
- var codexListDirInputSchema = z5.object({
6179
- dir_path: z5.string().min(1).describe("Absolute path to the directory to list."),
6180
- offset: z5.number().int().min(1).optional().describe("The entry number to start listing from. Must be 1 or greater."),
6181
- limit: z5.number().int().min(1).optional().describe("The maximum number of entries to return."),
6182
- depth: z5.number().int().min(1).optional().describe("The maximum directory depth to traverse. Must be 1 or greater.")
7339
+ var codexListDirInputSchema = z6.object({
7340
+ dir_path: z6.string().min(1).describe("Absolute path to the directory to list."),
7341
+ offset: z6.number().int().min(1).optional().describe("The entry number to start listing from. Must be 1 or greater."),
7342
+ limit: z6.number().int().min(1).optional().describe("The maximum number of entries to return."),
7343
+ depth: z6.number().int().min(1).optional().describe("The maximum directory depth to traverse. Must be 1 or greater.")
6183
7344
  });
6184
- var codexGrepFilesInputSchema = z5.object({
6185
- pattern: z5.string().min(1).describe("Regular expression pattern to search for."),
6186
- include: z5.string().optional().describe('Optional glob limiting searched files (for example "*.rs").'),
6187
- path: z5.string().optional().describe("Directory or file path to search. Defaults to cwd."),
6188
- limit: z5.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
7345
+ var codexGrepFilesInputSchema = z6.object({
7346
+ pattern: z6.string().min(1).describe("Regular expression pattern to search for."),
7347
+ include: z6.string().optional().describe('Optional glob limiting searched files (for example "*.rs").'),
7348
+ path: z6.string().optional().describe("Directory or file path to search. Defaults to cwd."),
7349
+ limit: z6.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
6189
7350
  });
6190
- var applyPatchInputSchema = z5.object({
6191
- input: z5.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
7351
+ var applyPatchInputSchema = z6.object({
7352
+ input: z6.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
6192
7353
  });
6193
- var geminiReadFileInputSchema = z5.object({
6194
- file_path: z5.string().min(1),
6195
- offset: z5.number().int().min(0).nullish(),
6196
- limit: z5.number().int().min(1).nullish()
7354
+ var geminiReadFileInputSchema = z6.object({
7355
+ file_path: z6.string().min(1),
7356
+ offset: z6.number().int().min(0).nullish(),
7357
+ limit: z6.number().int().min(1).nullish()
6197
7358
  });
6198
- var geminiReadFilesInputSchema = z5.object({
6199
- paths: z5.array(z5.string().min(1)).min(1),
6200
- line_offset: z5.number().int().min(0).nullish(),
6201
- line_limit: z5.number().int().min(1).nullish(),
6202
- char_offset: z5.number().int().min(0).nullish(),
6203
- char_limit: z5.number().int().min(1).nullish(),
6204
- include_line_numbers: z5.boolean().nullish()
7359
+ var geminiReadFilesInputSchema = z6.object({
7360
+ paths: z6.array(z6.string().min(1)).min(1),
7361
+ line_offset: z6.number().int().min(0).nullish(),
7362
+ line_limit: z6.number().int().min(1).nullish(),
7363
+ char_offset: z6.number().int().min(0).nullish(),
7364
+ char_limit: z6.number().int().min(1).nullish(),
7365
+ include_line_numbers: z6.boolean().nullish()
6205
7366
  }).superRefine((value, context) => {
6206
7367
  const hasLineWindow = value.line_offset !== void 0 || value.line_limit !== void 0;
6207
7368
  const hasCharWindow = value.char_offset !== void 0 || value.char_limit !== void 0;
6208
7369
  if (hasLineWindow && hasCharWindow) {
6209
7370
  context.addIssue({
6210
- code: z5.ZodIssueCode.custom,
7371
+ code: z6.ZodIssueCode.custom,
6211
7372
  message: "Use either line_* or char_* window arguments, not both."
6212
7373
  });
6213
7374
  }
6214
7375
  });
6215
- var geminiWriteFileInputSchema = z5.object({
6216
- file_path: z5.string().min(1),
6217
- content: z5.string()
7376
+ var geminiWriteFileInputSchema = z6.object({
7377
+ file_path: z6.string().min(1),
7378
+ content: z6.string()
6218
7379
  });
6219
- var geminiReplaceInputSchema = z5.object({
6220
- file_path: z5.string().min(1),
6221
- instruction: z5.string().min(1),
6222
- old_string: z5.string(),
6223
- new_string: z5.string(),
6224
- expected_replacements: z5.number().int().min(1).nullish()
7380
+ var geminiReplaceInputSchema = z6.object({
7381
+ file_path: z6.string().min(1),
7382
+ instruction: z6.string().min(1),
7383
+ old_string: z6.string(),
7384
+ new_string: z6.string(),
7385
+ expected_replacements: z6.number().int().min(1).nullish()
6225
7386
  });
6226
- var geminiListDirectoryInputSchema = z5.object({
6227
- dir_path: z5.string().min(1),
6228
- ignore: z5.array(z5.string()).nullish(),
6229
- file_filtering_options: z5.object({
6230
- respect_git_ignore: z5.boolean().nullish(),
6231
- respect_gemini_ignore: z5.boolean().nullish()
7387
+ var geminiListDirectoryInputSchema = z6.object({
7388
+ dir_path: z6.string().min(1),
7389
+ ignore: z6.array(z6.string()).nullish(),
7390
+ file_filtering_options: z6.object({
7391
+ respect_git_ignore: z6.boolean().nullish(),
7392
+ respect_gemini_ignore: z6.boolean().nullish()
6232
7393
  }).nullish()
6233
7394
  });
6234
- var geminiRgSearchInputSchema = z5.object({
6235
- pattern: z5.string().min(1),
6236
- path: z5.string().nullish(),
6237
- glob: z5.string().nullish(),
6238
- case_sensitive: z5.boolean().nullish(),
6239
- exclude_pattern: z5.string().nullish(),
6240
- names_only: z5.boolean().nullish(),
6241
- max_matches_per_file: z5.number().int().min(1).nullish(),
6242
- max_results: z5.number().int().min(1).nullish()
7395
+ var geminiRgSearchInputSchema = z6.object({
7396
+ pattern: z6.string().min(1),
7397
+ path: z6.string().nullish(),
7398
+ glob: z6.string().nullish(),
7399
+ case_sensitive: z6.boolean().nullish(),
7400
+ exclude_pattern: z6.string().nullish(),
7401
+ names_only: z6.boolean().nullish(),
7402
+ max_matches_per_file: z6.number().int().min(1).nullish(),
7403
+ max_results: z6.number().int().min(1).nullish()
6243
7404
  });
6244
- var geminiGrepSearchInputSchema = z5.object({
6245
- pattern: z5.string().min(1),
6246
- dir_path: z5.string().nullish(),
6247
- include: z5.string().nullish(),
6248
- exclude_pattern: z5.string().nullish(),
6249
- names_only: z5.boolean().nullish(),
6250
- max_matches_per_file: z5.number().int().min(1).nullish(),
6251
- total_max_matches: z5.number().int().min(1).nullish()
7405
+ var geminiGrepSearchInputSchema = z6.object({
7406
+ pattern: z6.string().min(1),
7407
+ dir_path: z6.string().nullish(),
7408
+ include: z6.string().nullish(),
7409
+ exclude_pattern: z6.string().nullish(),
7410
+ names_only: z6.boolean().nullish(),
7411
+ max_matches_per_file: z6.number().int().min(1).nullish(),
7412
+ total_max_matches: z6.number().int().min(1).nullish()
6252
7413
  });
6253
- var geminiGlobInputSchema = z5.object({
6254
- pattern: z5.string().min(1),
6255
- dir_path: z5.string().nullish(),
6256
- case_sensitive: z5.boolean().nullish(),
6257
- respect_git_ignore: z5.boolean().nullish(),
6258
- respect_gemini_ignore: z5.boolean().nullish()
7414
+ var geminiGlobInputSchema = z6.object({
7415
+ pattern: z6.string().min(1),
7416
+ dir_path: z6.string().nullish(),
7417
+ case_sensitive: z6.boolean().nullish(),
7418
+ respect_git_ignore: z6.boolean().nullish(),
7419
+ respect_gemini_ignore: z6.boolean().nullish()
6259
7420
  });
6260
7421
  function resolveFilesystemToolProfile(model, profile = "auto") {
6261
7422
  if (profile !== "auto") {
@@ -7243,19 +8404,107 @@ function isNoEntError(error) {
7243
8404
 
7244
8405
  // src/agent.ts
7245
8406
  async function runAgentLoop(request) {
7246
- const { tools: customTools, filesystemTool, filesystem_tool, ...toolLoopRequest } = request;
8407
+ const telemetry = createAgentTelemetrySession(request.telemetry);
8408
+ try {
8409
+ return await runAgentLoopInternal(request, { depth: 0, telemetry });
8410
+ } finally {
8411
+ await telemetry?.flush();
8412
+ }
8413
+ }
8414
+ async function runAgentLoopInternal(request, context) {
8415
+ const {
8416
+ tools: customTools,
8417
+ filesystemTool,
8418
+ filesystem_tool,
8419
+ subagentTool,
8420
+ subagent_tool,
8421
+ subagents,
8422
+ telemetry,
8423
+ ...toolLoopRequest
8424
+ } = request;
8425
+ const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
8426
+ const runId = randomRunId();
8427
+ const startedAtMs = Date.now();
7247
8428
  const filesystemSelection = filesystemTool ?? filesystem_tool;
8429
+ const subagentSelection = subagentTool ?? subagent_tool ?? subagents;
7248
8430
  const filesystemTools = resolveFilesystemTools(request.model, filesystemSelection);
7249
- const mergedTools = mergeToolSets(filesystemTools, customTools ?? {});
8431
+ const resolvedSubagentConfig = resolveSubagentToolConfig(subagentSelection, context.depth);
8432
+ const subagentController = createSubagentController({
8433
+ runId,
8434
+ model: request.model,
8435
+ depth: context.depth,
8436
+ telemetry: telemetrySession,
8437
+ customTools: customTools ?? {},
8438
+ filesystemSelection,
8439
+ subagentSelection,
8440
+ toolLoopRequest,
8441
+ resolvedSubagentConfig
8442
+ });
8443
+ const mergedTools = mergeToolSets(
8444
+ mergeToolSets(filesystemTools, subagentController?.tools ?? {}),
8445
+ customTools ?? {}
8446
+ );
7250
8447
  if (Object.keys(mergedTools).length === 0) {
7251
8448
  throw new Error(
7252
- "runAgentLoop requires at least one tool. Provide `tools` or enable `filesystemTool`."
8449
+ "runAgentLoop requires at least one tool. Provide `tools`, enable `filesystemTool`, or enable `subagentTool`."
7253
8450
  );
7254
8451
  }
7255
- return runToolLoop({
7256
- ...toolLoopRequest,
7257
- tools: mergedTools
8452
+ const instructions = buildLoopInstructions(
8453
+ toolLoopRequest.instructions,
8454
+ resolvedSubagentConfig,
8455
+ context.depth
8456
+ );
8457
+ const emitTelemetry = createAgentTelemetryEmitter({
8458
+ session: telemetrySession,
8459
+ runId,
8460
+ parentRunId: context.parentRunId,
8461
+ depth: context.depth,
8462
+ model: request.model
8463
+ });
8464
+ emitTelemetry({
8465
+ type: "agent.run.started",
8466
+ inputMode: typeof request.input === "string" ? "string" : "messages",
8467
+ customToolCount: Object.keys(customTools ?? {}).length,
8468
+ mergedToolCount: Object.keys(mergedTools).length,
8469
+ filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
8470
+ subagentToolsEnabled: resolvedSubagentConfig.enabled
7258
8471
  });
8472
+ const sourceOnEvent = toolLoopRequest.onEvent;
8473
+ const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
8474
+ const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
8475
+ sourceOnEvent?.(event);
8476
+ if (includeLlmStreamEvents) {
8477
+ emitTelemetry({ type: "agent.run.stream", event });
8478
+ }
8479
+ } : void 0;
8480
+ try {
8481
+ const result = await runToolLoop({
8482
+ ...toolLoopRequest,
8483
+ ...instructions ? { instructions } : {},
8484
+ ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
8485
+ tools: mergedTools
8486
+ });
8487
+ emitTelemetry({
8488
+ type: "agent.run.completed",
8489
+ success: true,
8490
+ durationMs: Math.max(0, Date.now() - startedAtMs),
8491
+ stepCount: result.steps.length,
8492
+ toolCallCount: countToolCalls(result),
8493
+ totalCostUsd: result.totalCostUsd,
8494
+ usage: summarizeResultUsage(result)
8495
+ });
8496
+ return result;
8497
+ } catch (error) {
8498
+ emitTelemetry({
8499
+ type: "agent.run.completed",
8500
+ success: false,
8501
+ durationMs: Math.max(0, Date.now() - startedAtMs),
8502
+ error: toErrorMessage2(error)
8503
+ });
8504
+ throw error;
8505
+ } finally {
8506
+ await subagentController?.closeAll();
8507
+ }
7259
8508
  }
7260
8509
  function resolveFilesystemTools(model, selection) {
7261
8510
  if (selection === void 0 || selection === false) {
@@ -7283,13 +8532,216 @@ function mergeToolSets(base, extra) {
7283
8532
  for (const [toolName, toolSpec] of Object.entries(extra)) {
7284
8533
  if (Object.hasOwn(merged, toolName)) {
7285
8534
  throw new Error(
7286
- `Duplicate tool name "${toolName}" in runAgentLoop. Rename the custom tool or disable that filesystem tool.`
8535
+ `Duplicate tool name "${toolName}" in runAgentLoop. Rename one of the conflicting tools or disable an overlapping built-in tool.`
7287
8536
  );
7288
8537
  }
7289
8538
  merged[toolName] = toolSpec;
7290
8539
  }
7291
8540
  return merged;
7292
8541
  }
8542
+ function createSubagentController(params) {
8543
+ if (!params.resolvedSubagentConfig.enabled) {
8544
+ return null;
8545
+ }
8546
+ return createSubagentToolController({
8547
+ config: params.resolvedSubagentConfig,
8548
+ parentDepth: params.depth,
8549
+ parentModel: params.resolvedSubagentConfig.model ?? params.model,
8550
+ buildChildInstructions: (spawnInstructions, childDepth) => buildChildInstructions(spawnInstructions, params.resolvedSubagentConfig, childDepth),
8551
+ runSubagent: async (subagentRequest) => {
8552
+ const childCustomTools = params.resolvedSubagentConfig.inheritTools ? params.customTools : {};
8553
+ const childFilesystemSelection = params.resolvedSubagentConfig.inheritFilesystemTool ? params.filesystemSelection : false;
8554
+ return await runAgentLoopInternal(
8555
+ {
8556
+ model: subagentRequest.model,
8557
+ input: subagentRequest.input,
8558
+ instructions: subagentRequest.instructions,
8559
+ tools: childCustomTools,
8560
+ filesystemTool: childFilesystemSelection,
8561
+ subagentTool: params.subagentSelection,
8562
+ modelTools: params.toolLoopRequest.modelTools,
8563
+ maxSteps: subagentRequest.maxSteps,
8564
+ openAiReasoningEffort: params.toolLoopRequest.openAiReasoningEffort,
8565
+ signal: subagentRequest.signal
8566
+ },
8567
+ {
8568
+ depth: params.depth + 1,
8569
+ parentRunId: params.runId,
8570
+ telemetry: params.telemetry
8571
+ }
8572
+ );
8573
+ }
8574
+ });
8575
+ }
8576
+ function buildLoopInstructions(baseInstructions, config, depth) {
8577
+ if (!config.enabled) {
8578
+ return trimToUndefined2(baseInstructions);
8579
+ }
8580
+ const blocks = [];
8581
+ const base = trimToUndefined2(baseInstructions);
8582
+ if (base) {
8583
+ blocks.push(base);
8584
+ }
8585
+ if (config.promptPattern === "codex") {
8586
+ blocks.push(
8587
+ buildCodexSubagentOrchestratorInstructions({
8588
+ currentDepth: depth,
8589
+ maxDepth: config.maxDepth,
8590
+ maxAgents: config.maxAgents
8591
+ })
8592
+ );
8593
+ }
8594
+ if (config.instructions) {
8595
+ blocks.push(config.instructions);
8596
+ }
8597
+ return blocks.length > 0 ? blocks.join("\n\n") : void 0;
8598
+ }
8599
+ function buildChildInstructions(spawnInstructions, config, childDepth) {
8600
+ const blocks = [];
8601
+ if (config.promptPattern === "codex") {
8602
+ blocks.push(
8603
+ buildCodexSubagentWorkerInstructions({
8604
+ depth: childDepth,
8605
+ maxDepth: config.maxDepth
8606
+ })
8607
+ );
8608
+ }
8609
+ if (config.instructions) {
8610
+ blocks.push(config.instructions);
8611
+ }
8612
+ const perSpawn = trimToUndefined2(spawnInstructions);
8613
+ if (perSpawn) {
8614
+ blocks.push(perSpawn);
8615
+ }
8616
+ return blocks.length > 0 ? blocks.join("\n\n") : void 0;
8617
+ }
8618
+ function trimToUndefined2(value) {
8619
+ const trimmed = value?.trim();
8620
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
8621
+ }
8622
+ function randomRunId() {
8623
+ return randomBytes3(8).toString("hex");
8624
+ }
8625
+ function toIsoNow() {
8626
+ return (/* @__PURE__ */ new Date()).toISOString();
8627
+ }
8628
+ function toErrorMessage2(error) {
8629
+ if (error instanceof Error && error.message) {
8630
+ return error.message;
8631
+ }
8632
+ if (typeof error === "string") {
8633
+ return error;
8634
+ }
8635
+ return "Unknown error";
8636
+ }
8637
+ function countToolCalls(result) {
8638
+ let count = 0;
8639
+ for (const step of result.steps) {
8640
+ count += step.toolCalls.length;
8641
+ }
8642
+ return count;
8643
+ }
8644
+ function sumUsageValue(current, next) {
8645
+ if (typeof next !== "number" || !Number.isFinite(next)) {
8646
+ return current;
8647
+ }
8648
+ const normalizedNext = Math.max(0, next);
8649
+ if (typeof current !== "number" || !Number.isFinite(current)) {
8650
+ return normalizedNext;
8651
+ }
8652
+ return Math.max(0, current) + normalizedNext;
8653
+ }
8654
+ function summarizeResultUsage(result) {
8655
+ let summary;
8656
+ for (const step of result.steps) {
8657
+ const usage = step.usage;
8658
+ if (!usage) {
8659
+ continue;
8660
+ }
8661
+ summary = {
8662
+ promptTokens: sumUsageValue(summary?.promptTokens, usage.promptTokens),
8663
+ cachedTokens: sumUsageValue(summary?.cachedTokens, usage.cachedTokens),
8664
+ responseTokens: sumUsageValue(summary?.responseTokens, usage.responseTokens),
8665
+ responseImageTokens: sumUsageValue(summary?.responseImageTokens, usage.responseImageTokens),
8666
+ thinkingTokens: sumUsageValue(summary?.thinkingTokens, usage.thinkingTokens),
8667
+ totalTokens: sumUsageValue(summary?.totalTokens, usage.totalTokens),
8668
+ toolUsePromptTokens: sumUsageValue(summary?.toolUsePromptTokens, usage.toolUsePromptTokens)
8669
+ };
8670
+ }
8671
+ return summary;
8672
+ }
8673
+ function isPromiseLike(value) {
8674
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
8675
+ }
8676
+ function isAgentTelemetrySink(value) {
8677
+ return typeof value === "object" && value !== null && typeof value.emit === "function";
8678
+ }
8679
+ function resolveTelemetrySelection(telemetry) {
8680
+ if (!telemetry) {
8681
+ return void 0;
8682
+ }
8683
+ if (isAgentTelemetrySink(telemetry)) {
8684
+ return { sink: telemetry };
8685
+ }
8686
+ if (isAgentTelemetrySink(telemetry.sink)) {
8687
+ return telemetry;
8688
+ }
8689
+ throw new Error("Invalid runAgentLoop telemetry config: expected a sink with emit(event).");
8690
+ }
8691
+ function createAgentTelemetrySession(telemetry) {
8692
+ const config = resolveTelemetrySelection(telemetry);
8693
+ if (!config) {
8694
+ return void 0;
8695
+ }
8696
+ const pending = /* @__PURE__ */ new Set();
8697
+ const trackPromise = (promise) => {
8698
+ pending.add(promise);
8699
+ promise.finally(() => {
8700
+ pending.delete(promise);
8701
+ });
8702
+ };
8703
+ const emit = (event) => {
8704
+ try {
8705
+ const output = config.sink.emit(event);
8706
+ if (isPromiseLike(output)) {
8707
+ const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
8708
+ trackPromise(task);
8709
+ }
8710
+ } catch {
8711
+ }
8712
+ };
8713
+ const flush = async () => {
8714
+ while (pending.size > 0) {
8715
+ await Promise.allSettled([...pending]);
8716
+ }
8717
+ if (typeof config.sink.flush === "function") {
8718
+ try {
8719
+ await config.sink.flush();
8720
+ } catch {
8721
+ }
8722
+ }
8723
+ };
8724
+ return {
8725
+ includeLlmStreamEvents: config.includeLlmStreamEvents === true,
8726
+ emit,
8727
+ flush
8728
+ };
8729
+ }
8730
+ function createAgentTelemetryEmitter(params) {
8731
+ return (event) => {
8732
+ if (!params.session) {
8733
+ return;
8734
+ }
8735
+ params.session.emit({
8736
+ ...event,
8737
+ timestamp: toIsoNow(),
8738
+ runId: params.runId,
8739
+ ...params.parentRunId ? { parentRunId: params.parentRunId } : {},
8740
+ depth: params.depth,
8741
+ model: params.model
8742
+ });
8743
+ };
8744
+ }
7293
8745
  export {
7294
8746
  CHATGPT_MODEL_IDS,
7295
8747
  CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
@@ -7312,6 +8764,7 @@ export {
7312
8764
  appendMarkdownSourcesSection,
7313
8765
  applyPatch,
7314
8766
  configureGemini,
8767
+ configureModelConcurrency,
7315
8768
  convertGooglePartsToLlmParts,
7316
8769
  createApplyPatchTool,
7317
8770
  createCodexApplyPatchTool,
@@ -7356,6 +8809,7 @@ export {
7356
8809
  loadLocalEnv,
7357
8810
  parseJsonFromLlmText,
7358
8811
  refreshChatGptOauthToken,
8812
+ resetModelConcurrencyConfig,
7359
8813
  resolveFilesystemToolProfile,
7360
8814
  resolveFireworksModelId,
7361
8815
  runAgentLoop,