@ljoukov/llm 3.0.4 → 3.0.8

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
@@ -1586,23 +1586,16 @@ function parseEventBlock(raw) {
1586
1586
  var MIN_MODEL_CONCURRENCY_CAP = 1;
1587
1587
  var MAX_MODEL_CONCURRENCY_CAP = 64;
1588
1588
  var DEFAULT_MODEL_CONCURRENCY_CAP = 3;
1589
- function parsePositiveInteger(raw) {
1590
- if (raw === void 0) {
1591
- return void 0;
1592
- }
1593
- const normalized = raw.trim();
1594
- if (!normalized) {
1595
- return void 0;
1596
- }
1597
- if (!/^-?\d+$/u.test(normalized)) {
1598
- return void 0;
1599
- }
1600
- const parsed = Number.parseInt(normalized, 10);
1601
- if (!Number.isFinite(parsed)) {
1602
- return void 0;
1603
- }
1604
- return parsed;
1605
- }
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({});
1606
1599
  function clampModelConcurrencyCap(value) {
1607
1600
  if (!Number.isFinite(value)) {
1608
1601
  return DEFAULT_MODEL_CONCURRENCY_CAP;
@@ -1616,30 +1609,94 @@ function clampModelConcurrencyCap(value) {
1616
1609
  }
1617
1610
  return rounded;
1618
1611
  }
1619
- function normalizeModelIdForEnv(modelId) {
1620
- return modelId.trim().replace(/[^A-Za-z0-9]+/gu, "_").replace(/^_+|_+$/gu, "").toUpperCase();
1612
+ function normalizeModelIdForConfig(modelId) {
1613
+ return modelId.trim().toLowerCase();
1621
1614
  }
1622
- function resolveModelConcurrencyCap(options) {
1623
- const env = options.env ?? process.env;
1624
- const providerPrefix = options.providerEnvPrefix;
1625
- const defaultCap = clampModelConcurrencyCap(options.defaultCap ?? DEFAULT_MODEL_CONCURRENCY_CAP);
1626
- const normalizedModelId = options.modelId ? normalizeModelIdForEnv(options.modelId) : "";
1627
- const candidateKeys = [
1628
- ...normalizedModelId ? [
1629
- `${providerPrefix}_MAX_PARALLEL_REQUESTS_MODEL_${normalizedModelId}`,
1630
- `LLM_MAX_PARALLEL_REQUESTS_MODEL_${normalizedModelId}`
1631
- ] : [],
1632
- `${providerPrefix}_MAX_PARALLEL_REQUESTS_PER_MODEL`,
1633
- "LLM_MAX_PARALLEL_REQUESTS_PER_MODEL"
1634
- ];
1635
- for (const key of candidateKeys) {
1636
- const parsed = parsePositiveInteger(env[key]);
1637
- if (parsed === void 0) {
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) {
1638
1629
  continue;
1639
1630
  }
1640
- return clampModelConcurrencyCap(parsed);
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;
1641
1687
  }
1642
- return defaultCap;
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);
1643
1700
  }
1644
1701
 
1645
1702
  // src/utils/scheduler.ts
@@ -1748,12 +1805,20 @@ function createCallScheduler(options = {}) {
1748
1805
  release?.();
1749
1806
  }
1750
1807
  }
1751
- async function attemptWithRetries(fn, attempt) {
1808
+ async function attemptWithRetries(fn, attempt, state) {
1752
1809
  try {
1810
+ const spacingStartedAtMs = Date.now();
1753
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);
1754
1818
  return await fn();
1755
1819
  } catch (error) {
1756
1820
  if (isOverloadError2(error)) {
1821
+ state.overloadCount += 1;
1757
1822
  consecutiveSuccesses = 0;
1758
1823
  currentParallelLimit = Math.max(1, Math.ceil(currentParallelLimit / 2));
1759
1824
  }
@@ -1770,9 +1835,10 @@ function createCallScheduler(options = {}) {
1770
1835
  }
1771
1836
  const normalizedDelay = Math.max(0, delay);
1772
1837
  if (normalizedDelay > 0) {
1838
+ state.retryDelayMs += normalizedDelay;
1773
1839
  await sleep(normalizedDelay);
1774
1840
  }
1775
- return attemptWithRetries(fn, attempt + 1);
1841
+ return attemptWithRetries(fn, attempt + 1, state);
1776
1842
  }
1777
1843
  }
1778
1844
  function drainQueue() {
@@ -1785,11 +1851,22 @@ function createCallScheduler(options = {}) {
1785
1851
  void task();
1786
1852
  }
1787
1853
  }
1788
- function run(fn) {
1854
+ function run(fn, runOptions = {}) {
1789
1855
  return new Promise((resolve, reject) => {
1856
+ const enqueuedAtMs = Date.now();
1790
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
+ };
1791
1867
  try {
1792
- const result = await attemptWithRetries(fn, 1);
1868
+ const result = await attemptWithRetries(fn, 1, state);
1869
+ state.completedAtMs = Date.now();
1793
1870
  consecutiveSuccesses += 1;
1794
1871
  if (currentParallelLimit < maxParallelRequests && consecutiveSuccesses >= increaseAfterConsecutiveSuccesses) {
1795
1872
  currentParallelLimit += 1;
@@ -1797,8 +1874,26 @@ function createCallScheduler(options = {}) {
1797
1874
  }
1798
1875
  resolve(result);
1799
1876
  } catch (error) {
1877
+ state.completedAtMs = Date.now();
1800
1878
  reject(toError(error));
1801
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
+ }
1802
1897
  activeCount -= 1;
1803
1898
  queueMicrotask(drainQueue);
1804
1899
  }
@@ -1895,7 +1990,7 @@ function getSchedulerForModel(modelId) {
1895
1990
  }
1896
1991
  const created = createCallScheduler({
1897
1992
  maxParallelRequests: resolveModelConcurrencyCap({
1898
- providerEnvPrefix: "FIREWORKS",
1993
+ provider: "fireworks",
1899
1994
  modelId: normalizedModelId
1900
1995
  }),
1901
1996
  minIntervalBetweenStartMs: 200,
@@ -1904,8 +1999,8 @@ function getSchedulerForModel(modelId) {
1904
1999
  schedulerByModel.set(schedulerKey, created);
1905
2000
  return created;
1906
2001
  }
1907
- async function runFireworksCall(fn, modelId) {
1908
- return getSchedulerForModel(modelId).run(async () => fn(getFireworksClient()));
2002
+ async function runFireworksCall(fn, modelId, runOptions) {
2003
+ return getSchedulerForModel(modelId).run(async () => fn(getFireworksClient()), runOptions);
1909
2004
  }
1910
2005
 
1911
2006
  // src/fireworks/models.ts
@@ -2271,7 +2366,7 @@ function getSchedulerForModel2(modelId) {
2271
2366
  }
2272
2367
  const created = createCallScheduler({
2273
2368
  maxParallelRequests: resolveModelConcurrencyCap({
2274
- providerEnvPrefix: "GOOGLE",
2369
+ provider: "google",
2275
2370
  modelId: normalizedModelId
2276
2371
  }),
2277
2372
  minIntervalBetweenStartMs: 200,
@@ -2291,8 +2386,8 @@ function getSchedulerForModel2(modelId) {
2291
2386
  schedulerByModel2.set(schedulerKey, created);
2292
2387
  return created;
2293
2388
  }
2294
- async function runGeminiCall(fn, modelId) {
2295
- return getSchedulerForModel2(modelId).run(async () => fn(await getGeminiClient()));
2389
+ async function runGeminiCall(fn, modelId, runOptions) {
2390
+ return getSchedulerForModel2(modelId).run(async () => fn(await getGeminiClient()), runOptions);
2296
2391
  }
2297
2392
 
2298
2393
  // src/openai/client.ts
@@ -2464,7 +2559,7 @@ function getSchedulerForModel3(modelId) {
2464
2559
  }
2465
2560
  const created = createCallScheduler({
2466
2561
  maxParallelRequests: resolveModelConcurrencyCap({
2467
- providerEnvPrefix: "OPENAI",
2562
+ provider: "openai",
2468
2563
  modelId: normalizedModelId
2469
2564
  }),
2470
2565
  minIntervalBetweenStartMs: 200,
@@ -2473,8 +2568,8 @@ function getSchedulerForModel3(modelId) {
2473
2568
  schedulerByModel3.set(schedulerKey, created);
2474
2569
  return created;
2475
2570
  }
2476
- async function runOpenAiCall(fn, modelId) {
2477
- return getSchedulerForModel3(modelId).run(async () => fn(getOpenAiClient()));
2571
+ async function runOpenAiCall(fn, modelId, runOptions) {
2572
+ return getSchedulerForModel3(modelId).run(async () => fn(getOpenAiClient()), runOptions);
2478
2573
  }
2479
2574
 
2480
2575
  // src/openai/models.ts
@@ -2928,9 +3023,9 @@ function isRetryableChatGptTransportError(error) {
2928
3023
  return false;
2929
3024
  }
2930
3025
  const message = error.message.toLowerCase();
2931
- 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");
2932
3027
  }
2933
- async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 2) {
3028
+ async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 3) {
2934
3029
  let attempt = 1;
2935
3030
  while (true) {
2936
3031
  try {
@@ -3835,77 +3930,153 @@ function buildToolErrorOutput(message, issues) {
3835
3930
  }
3836
3931
  return output;
3837
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
+ }
3838
4000
  async function executeToolCall(params) {
3839
4001
  const { callKind, toolName, tool: tool2, rawInput, parseError } = params;
3840
- if (!tool2) {
3841
- const message = `Unknown tool: ${toolName}`;
4002
+ const startedAtMs = Date.now();
4003
+ const finalize = (base, outputPayload, metrics) => {
4004
+ const completedAtMs = Date.now();
3842
4005
  return {
3843
- result: { toolName, input: rawInput, output: { error: message }, error: message },
3844
- 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
3845
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
+ );
3846
4023
  }
3847
4024
  if (callKind === "custom") {
3848
4025
  if (!isCustomTool(tool2)) {
3849
4026
  const message = `Tool ${toolName} was called as custom_tool_call but is declared as function.`;
3850
4027
  const outputPayload = buildToolErrorOutput(message);
3851
- return {
3852
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4028
+ return finalize(
4029
+ { toolName, input: rawInput, output: outputPayload, error: message },
3853
4030
  outputPayload
3854
- };
4031
+ );
3855
4032
  }
3856
4033
  const input = typeof rawInput === "string" ? rawInput : String(rawInput ?? "");
3857
4034
  try {
3858
4035
  const output = await tool2.execute(input);
3859
- return {
3860
- result: { toolName, input, output },
3861
- outputPayload: output
3862
- };
4036
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4037
+ return finalize({ toolName, input, output }, output, metrics);
3863
4038
  } catch (error) {
3864
4039
  const message = error instanceof Error ? error.message : String(error);
3865
4040
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
3866
- return {
3867
- result: { toolName, input, output: outputPayload, error: message },
3868
- outputPayload
3869
- };
4041
+ return finalize({ toolName, input, output: outputPayload, error: message }, outputPayload);
3870
4042
  }
3871
4043
  }
3872
4044
  if (isCustomTool(tool2)) {
3873
4045
  const message = `Tool ${toolName} was called as function_call but is declared as custom.`;
3874
4046
  const outputPayload = buildToolErrorOutput(message);
3875
- return {
3876
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4047
+ return finalize(
4048
+ { toolName, input: rawInput, output: outputPayload, error: message },
3877
4049
  outputPayload
3878
- };
4050
+ );
3879
4051
  }
3880
4052
  if (parseError) {
3881
4053
  const message = `Invalid JSON for tool ${toolName}: ${parseError}`;
3882
- return {
3883
- result: { toolName, input: rawInput, output: { error: message }, error: message },
3884
- outputPayload: buildToolErrorOutput(message)
3885
- };
4054
+ const outputPayload = buildToolErrorOutput(message);
4055
+ return finalize(
4056
+ { toolName, input: rawInput, output: outputPayload, error: message },
4057
+ outputPayload
4058
+ );
3886
4059
  }
3887
4060
  const parsed = tool2.inputSchema.safeParse(rawInput);
3888
4061
  if (!parsed.success) {
3889
4062
  const message = `Invalid tool arguments for ${toolName}: ${formatZodIssues(parsed.error.issues)}`;
3890
4063
  const outputPayload = buildToolErrorOutput(message, parsed.error.issues);
3891
- return {
3892
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4064
+ return finalize(
4065
+ { toolName, input: rawInput, output: outputPayload, error: message },
3893
4066
  outputPayload
3894
- };
4067
+ );
3895
4068
  }
3896
4069
  try {
3897
4070
  const output = await tool2.execute(parsed.data);
3898
- return {
3899
- result: { toolName, input: parsed.data, output },
3900
- outputPayload: output
3901
- };
4071
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4072
+ return finalize({ toolName, input: parsed.data, output }, output, metrics);
3902
4073
  } catch (error) {
3903
4074
  const message = error instanceof Error ? error.message : String(error);
3904
4075
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
3905
- return {
3906
- result: { toolName, input: parsed.data, output: outputPayload, error: message },
4076
+ return finalize(
4077
+ { toolName, input: parsed.data, output: outputPayload, error: message },
3907
4078
  outputPayload
3908
- };
4079
+ );
3909
4080
  }
3910
4081
  }
3911
4082
  function buildToolLogId(turn, toolIndex) {
@@ -4628,6 +4799,102 @@ var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
4628
4799
  function resolveToolLoopContents(input) {
4629
4800
  return resolveTextContents(input);
4630
4801
  }
4802
+ var toolLoopSteeringInternals = /* @__PURE__ */ new WeakMap();
4803
+ function createToolLoopSteeringChannel() {
4804
+ const pending = [];
4805
+ let closed = false;
4806
+ const channel = {
4807
+ append: (input) => {
4808
+ if (closed) {
4809
+ return { accepted: false, queuedCount: pending.length };
4810
+ }
4811
+ const normalized = normalizeToolLoopSteeringInput(input);
4812
+ if (normalized.length === 0) {
4813
+ return { accepted: false, queuedCount: pending.length };
4814
+ }
4815
+ pending.push(...normalized);
4816
+ return { accepted: true, queuedCount: pending.length };
4817
+ },
4818
+ steer: (input) => channel.append(input),
4819
+ pendingCount: () => pending.length,
4820
+ close: () => {
4821
+ if (closed) {
4822
+ return;
4823
+ }
4824
+ closed = true;
4825
+ pending.length = 0;
4826
+ }
4827
+ };
4828
+ const internalState = {
4829
+ drainPendingContents: () => {
4830
+ if (pending.length === 0) {
4831
+ return [];
4832
+ }
4833
+ return pending.splice(0, pending.length);
4834
+ },
4835
+ close: channel.close
4836
+ };
4837
+ toolLoopSteeringInternals.set(channel, internalState);
4838
+ return channel;
4839
+ }
4840
+ function resolveToolLoopSteeringInternal(steering) {
4841
+ if (!steering) {
4842
+ return void 0;
4843
+ }
4844
+ const internal = toolLoopSteeringInternals.get(steering);
4845
+ if (!internal) {
4846
+ throw new Error(
4847
+ "Invalid tool loop steering channel. Use createToolLoopSteeringChannel() to construct one."
4848
+ );
4849
+ }
4850
+ return internal;
4851
+ }
4852
+ function normalizeToolLoopSteeringInput(input) {
4853
+ const messages = typeof input === "string" ? [{ role: "user", content: input }] : Array.isArray(input) ? input : [input];
4854
+ const normalized = [];
4855
+ for (const message of messages) {
4856
+ const role = message.role ?? "user";
4857
+ if (role !== "user") {
4858
+ throw new Error("Tool loop steering only accepts role='user' messages.");
4859
+ }
4860
+ if (typeof message.content === "string") {
4861
+ if (message.content.length === 0) {
4862
+ continue;
4863
+ }
4864
+ normalized.push({
4865
+ role: "user",
4866
+ parts: [{ type: "text", text: message.content }]
4867
+ });
4868
+ continue;
4869
+ }
4870
+ if (!Array.isArray(message.content) || message.content.length === 0) {
4871
+ continue;
4872
+ }
4873
+ const parts = [];
4874
+ for (const part of message.content) {
4875
+ if (part.type === "text") {
4876
+ parts.push({ type: "text", text: part.text });
4877
+ } else {
4878
+ parts.push({ type: "inlineData", data: part.data, mimeType: part.mimeType });
4879
+ }
4880
+ }
4881
+ if (parts.length > 0) {
4882
+ normalized.push({ role: "user", parts });
4883
+ }
4884
+ }
4885
+ return normalized;
4886
+ }
4887
+ function toChatGptAssistantMessage(text) {
4888
+ if (!text) {
4889
+ return void 0;
4890
+ }
4891
+ return {
4892
+ type: "message",
4893
+ role: "assistant",
4894
+ status: "completed",
4895
+ content: [{ type: "output_text", text }]
4896
+ };
4897
+ }
4631
4898
  function isCustomTool(toolDef) {
4632
4899
  return toolDef.type === "custom";
4633
4900
  }
@@ -4746,411 +5013,908 @@ async function runToolLoop(request) {
4746
5013
  }
4747
5014
  const maxSteps = Math.max(1, Math.floor(request.maxSteps ?? DEFAULT_TOOL_LOOP_MAX_STEPS));
4748
5015
  const providerInfo = resolveProvider(request.model);
5016
+ const steeringInternal = resolveToolLoopSteeringInternal(request.steering);
4749
5017
  const steps = [];
4750
5018
  let totalCostUsd = 0;
4751
5019
  let finalText = "";
4752
5020
  let finalThoughts = "";
4753
- if (providerInfo.provider === "openai") {
4754
- const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
4755
- const openAiNativeTools = toOpenAiTools(request.modelTools);
4756
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
4757
- const reasoningEffort = resolveOpenAiReasoningEffort(
4758
- providerInfo.model,
4759
- request.openAiReasoningEffort
4760
- );
4761
- const textConfig = {
4762
- format: { type: "text" },
4763
- verbosity: resolveOpenAiVerbosity(providerInfo.model)
4764
- };
4765
- const reasoning = {
4766
- effort: toOpenAiReasoningEffort(reasoningEffort),
4767
- summary: "detailed"
4768
- };
4769
- let previousResponseId;
4770
- let input = toOpenAiInput(contents);
4771
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
4772
- const turn = stepIndex + 1;
4773
- const abortController = new AbortController();
4774
- if (request.signal) {
4775
- if (request.signal.aborted) {
4776
- abortController.abort(request.signal.reason);
4777
- } else {
4778
- request.signal.addEventListener(
4779
- "abort",
4780
- () => abortController.abort(request.signal?.reason),
4781
- { once: true }
4782
- );
4783
- }
4784
- }
4785
- const onEvent = request.onEvent;
4786
- let modelVersion = request.model;
4787
- let usageTokens;
4788
- const emitEvent = (ev) => {
4789
- onEvent?.(ev);
5021
+ try {
5022
+ if (providerInfo.provider === "openai") {
5023
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
5024
+ const openAiNativeTools = toOpenAiTools(request.modelTools);
5025
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
5026
+ const reasoningEffort = resolveOpenAiReasoningEffort(
5027
+ providerInfo.model,
5028
+ request.openAiReasoningEffort
5029
+ );
5030
+ const textConfig = {
5031
+ format: { type: "text" },
5032
+ verbosity: resolveOpenAiVerbosity(providerInfo.model)
4790
5033
  };
4791
- const finalResponse = await runOpenAiCall(async (client) => {
4792
- const stream = client.responses.stream(
4793
- {
4794
- model: providerInfo.model,
4795
- input,
4796
- ...previousResponseId ? { previous_response_id: previousResponseId } : {},
4797
- ...openAiTools.length > 0 ? { tools: openAiTools } : {},
4798
- ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
4799
- reasoning,
4800
- text: textConfig,
4801
- include: ["reasoning.encrypted_content"]
5034
+ const reasoning = {
5035
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5036
+ summary: "detailed"
5037
+ };
5038
+ let previousResponseId;
5039
+ let input = toOpenAiInput(contents);
5040
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5041
+ const turn = stepIndex + 1;
5042
+ const stepStartedAtMs = Date.now();
5043
+ let firstModelEventAtMs;
5044
+ let schedulerMetrics;
5045
+ const abortController = new AbortController();
5046
+ if (request.signal) {
5047
+ if (request.signal.aborted) {
5048
+ abortController.abort(request.signal.reason);
5049
+ } else {
5050
+ request.signal.addEventListener(
5051
+ "abort",
5052
+ () => abortController.abort(request.signal?.reason),
5053
+ { once: true }
5054
+ );
5055
+ }
5056
+ }
5057
+ const onEvent = request.onEvent;
5058
+ let modelVersion = request.model;
5059
+ let usageTokens;
5060
+ let thoughtDeltaEmitted = false;
5061
+ const emitEvent = (ev) => {
5062
+ onEvent?.(ev);
5063
+ };
5064
+ const markFirstModelEvent = () => {
5065
+ if (firstModelEventAtMs === void 0) {
5066
+ firstModelEventAtMs = Date.now();
5067
+ }
5068
+ };
5069
+ const finalResponse = await runOpenAiCall(
5070
+ async (client) => {
5071
+ const stream = client.responses.stream(
5072
+ {
5073
+ model: providerInfo.model,
5074
+ input,
5075
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5076
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5077
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5078
+ reasoning,
5079
+ text: textConfig,
5080
+ include: ["reasoning.encrypted_content"]
5081
+ },
5082
+ { signal: abortController.signal }
5083
+ );
5084
+ for await (const event of stream) {
5085
+ markFirstModelEvent();
5086
+ switch (event.type) {
5087
+ case "response.output_text.delta":
5088
+ emitEvent({
5089
+ type: "delta",
5090
+ channel: "response",
5091
+ text: typeof event.delta === "string" ? event.delta : ""
5092
+ });
5093
+ break;
5094
+ case "response.reasoning_summary_text.delta":
5095
+ thoughtDeltaEmitted = true;
5096
+ emitEvent({
5097
+ type: "delta",
5098
+ channel: "thought",
5099
+ text: typeof event.delta === "string" ? event.delta : ""
5100
+ });
5101
+ break;
5102
+ case "response.refusal.delta":
5103
+ emitEvent({ type: "blocked" });
5104
+ break;
5105
+ default:
5106
+ break;
5107
+ }
5108
+ }
5109
+ return await stream.finalResponse();
4802
5110
  },
4803
- { signal: abortController.signal }
5111
+ providerInfo.model,
5112
+ {
5113
+ onSettled: (metrics) => {
5114
+ schedulerMetrics = metrics;
5115
+ }
5116
+ }
4804
5117
  );
4805
- for await (const event of stream) {
4806
- switch (event.type) {
4807
- case "response.output_text.delta":
4808
- emitEvent({
4809
- type: "delta",
4810
- channel: "response",
4811
- text: typeof event.delta === "string" ? event.delta : ""
4812
- });
4813
- break;
4814
- case "response.reasoning_summary_text.delta":
4815
- emitEvent({
4816
- type: "delta",
4817
- channel: "thought",
4818
- text: typeof event.delta === "string" ? event.delta : ""
4819
- });
4820
- break;
4821
- case "response.refusal.delta":
4822
- emitEvent({ type: "blocked" });
4823
- break;
4824
- default:
4825
- break;
5118
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5119
+ emitEvent({ type: "model", modelVersion });
5120
+ if (finalResponse.error) {
5121
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5122
+ throw new Error(message);
5123
+ }
5124
+ usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5125
+ const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5126
+ const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5127
+ if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5128
+ emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
5129
+ }
5130
+ const modelCompletedAtMs = Date.now();
5131
+ const stepCostUsd = estimateCallCostUsd({
5132
+ modelId: modelVersion,
5133
+ tokens: usageTokens,
5134
+ responseImages: 0
5135
+ });
5136
+ totalCostUsd += stepCostUsd;
5137
+ if (usageTokens) {
5138
+ emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5139
+ }
5140
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
5141
+ const stepToolCalls = [];
5142
+ if (responseToolCalls.length === 0) {
5143
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5144
+ const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
5145
+ finalText = responseText;
5146
+ finalThoughts = reasoningSummary;
5147
+ const stepCompletedAtMs2 = Date.now();
5148
+ const timing2 = buildStepTiming({
5149
+ stepStartedAtMs,
5150
+ stepCompletedAtMs: stepCompletedAtMs2,
5151
+ modelCompletedAtMs,
5152
+ firstModelEventAtMs,
5153
+ schedulerMetrics,
5154
+ toolExecutionMs: 0,
5155
+ waitToolMs: 0
5156
+ });
5157
+ steps.push({
5158
+ step: steps.length + 1,
5159
+ modelVersion,
5160
+ text: responseText || void 0,
5161
+ thoughts: reasoningSummary || void 0,
5162
+ toolCalls: [],
5163
+ usage: usageTokens,
5164
+ costUsd: stepCostUsd,
5165
+ timing: timing2
5166
+ });
5167
+ if (steeringItems2.length === 0) {
5168
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4826
5169
  }
5170
+ previousResponseId = finalResponse.id;
5171
+ input = steeringItems2;
5172
+ continue;
4827
5173
  }
4828
- return await stream.finalResponse();
4829
- }, providerInfo.model);
4830
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
4831
- emitEvent({ type: "model", modelVersion });
4832
- if (finalResponse.error) {
4833
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
4834
- throw new Error(message);
4835
- }
4836
- usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
4837
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
4838
- const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
4839
- const stepCostUsd = estimateCallCostUsd({
4840
- modelId: modelVersion,
4841
- tokens: usageTokens,
4842
- responseImages: 0
4843
- });
4844
- totalCostUsd += stepCostUsd;
4845
- if (usageTokens) {
4846
- emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
4847
- }
4848
- const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
4849
- const stepToolCalls = [];
4850
- if (responseToolCalls.length === 0) {
4851
- finalText = responseText;
4852
- finalThoughts = reasoningSummary;
5174
+ const callInputs = responseToolCalls.map((call, index) => {
5175
+ const toolIndex = index + 1;
5176
+ const toolId = buildToolLogId(turn, toolIndex);
5177
+ const toolName = call.name;
5178
+ if (call.kind === "custom") {
5179
+ return {
5180
+ call,
5181
+ toolName,
5182
+ value: call.input,
5183
+ parseError: void 0,
5184
+ toolId,
5185
+ turn,
5186
+ toolIndex
5187
+ };
5188
+ }
5189
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5190
+ return { call, toolName, value, parseError, toolId, turn, toolIndex };
5191
+ });
5192
+ for (const entry of callInputs) {
5193
+ emitEvent({
5194
+ type: "tool_call",
5195
+ phase: "started",
5196
+ turn: entry.turn,
5197
+ toolIndex: entry.toolIndex,
5198
+ toolName: entry.toolName,
5199
+ toolId: entry.toolId,
5200
+ callKind: entry.call.kind,
5201
+ callId: entry.call.call_id,
5202
+ input: entry.value
5203
+ });
5204
+ }
5205
+ const callResults = await Promise.all(
5206
+ callInputs.map(async (entry) => {
5207
+ return await toolCallContextStorage.run(
5208
+ {
5209
+ toolName: entry.toolName,
5210
+ toolId: entry.toolId,
5211
+ turn: entry.turn,
5212
+ toolIndex: entry.toolIndex
5213
+ },
5214
+ async () => {
5215
+ const { result, outputPayload } = await executeToolCall({
5216
+ callKind: entry.call.kind,
5217
+ toolName: entry.toolName,
5218
+ tool: request.tools[entry.toolName],
5219
+ rawInput: entry.value,
5220
+ parseError: entry.parseError
5221
+ });
5222
+ return { entry, result, outputPayload };
5223
+ }
5224
+ );
5225
+ })
5226
+ );
5227
+ const toolOutputs = [];
5228
+ let toolExecutionMs = 0;
5229
+ let waitToolMs = 0;
5230
+ for (const { entry, result, outputPayload } of callResults) {
5231
+ stepToolCalls.push({ ...result, callId: entry.call.call_id });
5232
+ const callDurationMs = toToolResultDuration(result);
5233
+ toolExecutionMs += callDurationMs;
5234
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5235
+ waitToolMs += callDurationMs;
5236
+ }
5237
+ emitEvent({
5238
+ type: "tool_call",
5239
+ phase: "completed",
5240
+ turn: entry.turn,
5241
+ toolIndex: entry.toolIndex,
5242
+ toolName: entry.toolName,
5243
+ toolId: entry.toolId,
5244
+ callKind: entry.call.kind,
5245
+ callId: entry.call.call_id,
5246
+ input: entry.value,
5247
+ output: result.output,
5248
+ error: result.error,
5249
+ durationMs: result.durationMs
5250
+ });
5251
+ if (entry.call.kind === "custom") {
5252
+ toolOutputs.push({
5253
+ type: "custom_tool_call_output",
5254
+ call_id: entry.call.call_id,
5255
+ output: mergeToolOutput(outputPayload)
5256
+ });
5257
+ } else {
5258
+ toolOutputs.push({
5259
+ type: "function_call_output",
5260
+ call_id: entry.call.call_id,
5261
+ output: mergeToolOutput(outputPayload)
5262
+ });
5263
+ }
5264
+ }
5265
+ const stepCompletedAtMs = Date.now();
5266
+ const timing = buildStepTiming({
5267
+ stepStartedAtMs,
5268
+ stepCompletedAtMs,
5269
+ modelCompletedAtMs,
5270
+ firstModelEventAtMs,
5271
+ schedulerMetrics,
5272
+ toolExecutionMs,
5273
+ waitToolMs
5274
+ });
4853
5275
  steps.push({
4854
5276
  step: steps.length + 1,
4855
5277
  modelVersion,
4856
5278
  text: responseText || void 0,
4857
5279
  thoughts: reasoningSummary || void 0,
4858
- toolCalls: [],
5280
+ toolCalls: stepToolCalls,
4859
5281
  usage: usageTokens,
4860
- costUsd: stepCostUsd
5282
+ costUsd: stepCostUsd,
5283
+ timing
4861
5284
  });
4862
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4863
- }
4864
- const callInputs = responseToolCalls.map((call, index) => {
4865
- const toolIndex = index + 1;
4866
- const toolId = buildToolLogId(turn, toolIndex);
4867
- const toolName = call.name;
4868
- if (call.kind === "custom") {
4869
- return {
4870
- call,
4871
- toolName,
4872
- value: call.input,
4873
- parseError: void 0,
4874
- toolId,
4875
- turn,
4876
- toolIndex
4877
- };
4878
- }
4879
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
4880
- return { call, toolName, value, parseError, toolId, turn, toolIndex };
4881
- });
4882
- const callResults = await Promise.all(
4883
- callInputs.map(async (entry) => {
4884
- return await toolCallContextStorage.run(
4885
- {
4886
- toolName: entry.toolName,
4887
- toolId: entry.toolId,
4888
- turn: entry.turn,
4889
- toolIndex: entry.toolIndex
5285
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5286
+ const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
5287
+ previousResponseId = finalResponse.id;
5288
+ input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
5289
+ }
5290
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5291
+ }
5292
+ if (providerInfo.provider === "chatgpt") {
5293
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
5294
+ const openAiNativeTools = toOpenAiTools(request.modelTools);
5295
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
5296
+ const reasoningEffort = resolveOpenAiReasoningEffort(
5297
+ request.model,
5298
+ request.openAiReasoningEffort
5299
+ );
5300
+ const toolLoopInput = toChatGptInput(contents);
5301
+ const conversationId = `tool-loop-${randomBytes(8).toString("hex")}`;
5302
+ const promptCacheKey = conversationId;
5303
+ let input = [...toolLoopInput.input];
5304
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5305
+ const turn = stepIndex + 1;
5306
+ const stepStartedAtMs = Date.now();
5307
+ let firstModelEventAtMs;
5308
+ let thoughtDeltaEmitted = false;
5309
+ const markFirstModelEvent = () => {
5310
+ if (firstModelEventAtMs === void 0) {
5311
+ firstModelEventAtMs = Date.now();
5312
+ }
5313
+ };
5314
+ const response = await collectChatGptCodexResponseWithRetry({
5315
+ sessionId: conversationId,
5316
+ request: {
5317
+ model: providerInfo.model,
5318
+ store: false,
5319
+ stream: true,
5320
+ instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
5321
+ input,
5322
+ prompt_cache_key: promptCacheKey,
5323
+ include: ["reasoning.encrypted_content"],
5324
+ tools: openAiTools,
5325
+ tool_choice: "auto",
5326
+ parallel_tool_calls: true,
5327
+ reasoning: {
5328
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5329
+ summary: "detailed"
4890
5330
  },
4891
- async () => {
4892
- const { result, outputPayload } = await executeToolCall({
4893
- callKind: entry.call.kind,
4894
- toolName: entry.toolName,
4895
- tool: request.tools[entry.toolName],
4896
- rawInput: entry.value,
4897
- parseError: entry.parseError
4898
- });
4899
- return { entry, result, outputPayload };
5331
+ text: { verbosity: resolveOpenAiVerbosity(request.model) }
5332
+ },
5333
+ signal: request.signal,
5334
+ onDelta: (delta) => {
5335
+ if (delta.thoughtDelta) {
5336
+ markFirstModelEvent();
5337
+ thoughtDeltaEmitted = true;
5338
+ request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
4900
5339
  }
4901
- );
4902
- })
4903
- );
4904
- const toolOutputs = [];
4905
- for (const { entry, result, outputPayload } of callResults) {
4906
- stepToolCalls.push({ ...result, callId: entry.call.call_id });
4907
- if (entry.call.kind === "custom") {
4908
- toolOutputs.push({
4909
- type: "custom_tool_call_output",
4910
- call_id: entry.call.call_id,
4911
- output: mergeToolOutput(outputPayload)
4912
- });
4913
- } else {
4914
- toolOutputs.push({
4915
- type: "function_call_output",
4916
- call_id: entry.call.call_id,
4917
- output: mergeToolOutput(outputPayload)
5340
+ if (delta.textDelta) {
5341
+ markFirstModelEvent();
5342
+ request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
5343
+ }
5344
+ }
5345
+ });
5346
+ const modelCompletedAtMs = Date.now();
5347
+ const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
5348
+ const usageTokens = extractChatGptUsageTokens(response.usage);
5349
+ const stepCostUsd = estimateCallCostUsd({
5350
+ modelId: modelVersion,
5351
+ tokens: usageTokens,
5352
+ responseImages: 0
5353
+ });
5354
+ totalCostUsd += stepCostUsd;
5355
+ const responseText = (response.text ?? "").trim();
5356
+ const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
5357
+ if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
5358
+ request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
5359
+ }
5360
+ const responseToolCalls = response.toolCalls ?? [];
5361
+ if (responseToolCalls.length === 0) {
5362
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5363
+ const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
5364
+ finalText = responseText;
5365
+ finalThoughts = reasoningSummaryText;
5366
+ const stepCompletedAtMs2 = Date.now();
5367
+ const timing2 = buildStepTiming({
5368
+ stepStartedAtMs,
5369
+ stepCompletedAtMs: stepCompletedAtMs2,
5370
+ modelCompletedAtMs,
5371
+ firstModelEventAtMs,
5372
+ toolExecutionMs: 0,
5373
+ waitToolMs: 0
4918
5374
  });
5375
+ steps.push({
5376
+ step: steps.length + 1,
5377
+ modelVersion,
5378
+ text: responseText || void 0,
5379
+ thoughts: reasoningSummaryText || void 0,
5380
+ toolCalls: [],
5381
+ usage: usageTokens,
5382
+ costUsd: stepCostUsd,
5383
+ timing: timing2
5384
+ });
5385
+ if (steeringItems2.length === 0) {
5386
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5387
+ }
5388
+ const assistantItem = toChatGptAssistantMessage(responseText);
5389
+ input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
5390
+ continue;
4919
5391
  }
4920
- }
4921
- steps.push({
4922
- step: steps.length + 1,
4923
- modelVersion,
4924
- text: responseText || void 0,
4925
- thoughts: reasoningSummary || void 0,
4926
- toolCalls: stepToolCalls,
4927
- usage: usageTokens,
4928
- costUsd: stepCostUsd
4929
- });
4930
- previousResponseId = finalResponse.id;
4931
- input = toolOutputs;
4932
- }
4933
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
4934
- }
4935
- if (providerInfo.provider === "chatgpt") {
4936
- const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
4937
- const openAiNativeTools = toOpenAiTools(request.modelTools);
4938
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
4939
- const reasoningEffort = resolveOpenAiReasoningEffort(
4940
- request.model,
4941
- request.openAiReasoningEffort
4942
- );
4943
- const toolLoopInput = toChatGptInput(contents);
4944
- const conversationId = `tool-loop-${randomBytes(8).toString("hex")}`;
4945
- const promptCacheKey = conversationId;
4946
- let input = [...toolLoopInput.input];
4947
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
4948
- const turn = stepIndex + 1;
4949
- const response = await collectChatGptCodexResponseWithRetry({
4950
- sessionId: conversationId,
4951
- request: {
4952
- model: providerInfo.model,
4953
- store: false,
4954
- stream: true,
4955
- instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
4956
- input,
4957
- prompt_cache_key: promptCacheKey,
4958
- include: ["reasoning.encrypted_content"],
4959
- tools: openAiTools,
4960
- tool_choice: "auto",
4961
- parallel_tool_calls: true,
4962
- reasoning: {
4963
- effort: toOpenAiReasoningEffort(reasoningEffort),
4964
- summary: "detailed"
4965
- },
4966
- text: { verbosity: resolveOpenAiVerbosity(request.model) }
4967
- },
4968
- signal: request.signal,
4969
- onDelta: (delta) => {
4970
- if (delta.thoughtDelta) {
4971
- request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
5392
+ const toolCalls = [];
5393
+ const toolOutputs = [];
5394
+ const callInputs = responseToolCalls.map((call, index) => {
5395
+ const toolIndex = index + 1;
5396
+ const toolId = buildToolLogId(turn, toolIndex);
5397
+ const toolName = call.name;
5398
+ const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
5399
+ const ids = normalizeChatGptToolIds({
5400
+ callKind: call.kind,
5401
+ callId: call.callId,
5402
+ itemId: call.id
5403
+ });
5404
+ return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
5405
+ });
5406
+ for (const entry of callInputs) {
5407
+ request.onEvent?.({
5408
+ type: "tool_call",
5409
+ phase: "started",
5410
+ turn: entry.turn,
5411
+ toolIndex: entry.toolIndex,
5412
+ toolName: entry.toolName,
5413
+ toolId: entry.toolId,
5414
+ callKind: entry.call.kind,
5415
+ callId: entry.ids.callId,
5416
+ input: entry.value
5417
+ });
5418
+ }
5419
+ const callResults = await Promise.all(
5420
+ callInputs.map(async (entry) => {
5421
+ return await toolCallContextStorage.run(
5422
+ {
5423
+ toolName: entry.toolName,
5424
+ toolId: entry.toolId,
5425
+ turn: entry.turn,
5426
+ toolIndex: entry.toolIndex
5427
+ },
5428
+ async () => {
5429
+ const { result, outputPayload } = await executeToolCall({
5430
+ callKind: entry.call.kind,
5431
+ toolName: entry.toolName,
5432
+ tool: request.tools[entry.toolName],
5433
+ rawInput: entry.value,
5434
+ parseError: entry.parseError
5435
+ });
5436
+ return { entry, result, outputPayload };
5437
+ }
5438
+ );
5439
+ })
5440
+ );
5441
+ let toolExecutionMs = 0;
5442
+ let waitToolMs = 0;
5443
+ for (const { entry, result, outputPayload } of callResults) {
5444
+ toolCalls.push({ ...result, callId: entry.ids.callId });
5445
+ const callDurationMs = toToolResultDuration(result);
5446
+ toolExecutionMs += callDurationMs;
5447
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5448
+ waitToolMs += callDurationMs;
4972
5449
  }
4973
- if (delta.textDelta) {
4974
- request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
5450
+ request.onEvent?.({
5451
+ type: "tool_call",
5452
+ phase: "completed",
5453
+ turn: entry.turn,
5454
+ toolIndex: entry.toolIndex,
5455
+ toolName: entry.toolName,
5456
+ toolId: entry.toolId,
5457
+ callKind: entry.call.kind,
5458
+ callId: entry.ids.callId,
5459
+ input: entry.value,
5460
+ output: result.output,
5461
+ error: result.error,
5462
+ durationMs: result.durationMs
5463
+ });
5464
+ if (entry.call.kind === "custom") {
5465
+ toolOutputs.push({
5466
+ type: "custom_tool_call",
5467
+ id: entry.ids.itemId,
5468
+ call_id: entry.ids.callId,
5469
+ name: entry.toolName,
5470
+ input: entry.call.input,
5471
+ status: "completed"
5472
+ });
5473
+ toolOutputs.push({
5474
+ type: "custom_tool_call_output",
5475
+ call_id: entry.ids.callId,
5476
+ output: mergeToolOutput(outputPayload)
5477
+ });
5478
+ } else {
5479
+ toolOutputs.push({
5480
+ type: "function_call",
5481
+ id: entry.ids.itemId,
5482
+ call_id: entry.ids.callId,
5483
+ name: entry.toolName,
5484
+ arguments: entry.call.arguments,
5485
+ status: "completed"
5486
+ });
5487
+ toolOutputs.push({
5488
+ type: "function_call_output",
5489
+ call_id: entry.ids.callId,
5490
+ output: mergeToolOutput(outputPayload)
5491
+ });
4975
5492
  }
4976
5493
  }
4977
- });
4978
- const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
4979
- const usageTokens = extractChatGptUsageTokens(response.usage);
4980
- const stepCostUsd = estimateCallCostUsd({
4981
- modelId: modelVersion,
4982
- tokens: usageTokens,
4983
- responseImages: 0
4984
- });
4985
- totalCostUsd += stepCostUsd;
4986
- const responseText = (response.text ?? "").trim();
4987
- const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
4988
- const responseToolCalls = response.toolCalls ?? [];
4989
- if (responseToolCalls.length === 0) {
4990
- finalText = responseText;
4991
- finalThoughts = reasoningSummaryText;
5494
+ const stepCompletedAtMs = Date.now();
5495
+ const timing = buildStepTiming({
5496
+ stepStartedAtMs,
5497
+ stepCompletedAtMs,
5498
+ modelCompletedAtMs,
5499
+ firstModelEventAtMs,
5500
+ toolExecutionMs,
5501
+ waitToolMs
5502
+ });
4992
5503
  steps.push({
4993
5504
  step: steps.length + 1,
4994
5505
  modelVersion,
4995
5506
  text: responseText || void 0,
4996
5507
  thoughts: reasoningSummaryText || void 0,
4997
- toolCalls: [],
5508
+ toolCalls,
4998
5509
  usage: usageTokens,
4999
- costUsd: stepCostUsd
5510
+ costUsd: stepCostUsd,
5511
+ timing
5000
5512
  });
5001
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5513
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5514
+ const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
5515
+ input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
5002
5516
  }
5003
- const toolCalls = [];
5004
- const toolOutputs = [];
5005
- const callInputs = responseToolCalls.map((call, index) => {
5006
- const toolIndex = index + 1;
5007
- const toolId = buildToolLogId(turn, toolIndex);
5008
- const toolName = call.name;
5009
- const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
5010
- const ids = normalizeChatGptToolIds({
5011
- callKind: call.kind,
5012
- callId: call.callId,
5013
- itemId: call.id
5014
- });
5015
- return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
5016
- });
5017
- const callResults = await Promise.all(
5018
- callInputs.map(async (entry) => {
5019
- return await toolCallContextStorage.run(
5020
- {
5021
- toolName: entry.toolName,
5022
- toolId: entry.toolId,
5023
- turn: entry.turn,
5024
- toolIndex: entry.toolIndex
5025
- },
5026
- async () => {
5027
- const { result, outputPayload } = await executeToolCall({
5028
- callKind: entry.call.kind,
5029
- toolName: entry.toolName,
5030
- tool: request.tools[entry.toolName],
5031
- rawInput: entry.value,
5032
- parseError: entry.parseError
5033
- });
5034
- return { entry, result, outputPayload };
5517
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5518
+ }
5519
+ if (providerInfo.provider === "fireworks") {
5520
+ if (request.modelTools && request.modelTools.length > 0) {
5521
+ throw new Error(
5522
+ "Fireworks provider does not support provider-native modelTools in runToolLoop."
5523
+ );
5524
+ }
5525
+ const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
5526
+ const messages = toFireworksMessages(contents);
5527
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5528
+ const turn = stepIndex + 1;
5529
+ const stepStartedAtMs = Date.now();
5530
+ let schedulerMetrics;
5531
+ const response = await runFireworksCall(
5532
+ async (client) => {
5533
+ return await client.chat.completions.create(
5534
+ {
5535
+ model: providerInfo.model,
5536
+ messages,
5537
+ tools: fireworksTools,
5538
+ tool_choice: "auto",
5539
+ parallel_tool_calls: true
5540
+ },
5541
+ { signal: request.signal }
5542
+ );
5543
+ },
5544
+ providerInfo.model,
5545
+ {
5546
+ onSettled: (metrics) => {
5547
+ schedulerMetrics = metrics;
5035
5548
  }
5036
- );
5037
- })
5038
- );
5039
- for (const { entry, result, outputPayload } of callResults) {
5040
- toolCalls.push({ ...result, callId: entry.ids.callId });
5041
- if (entry.call.kind === "custom") {
5042
- toolOutputs.push({
5043
- type: "custom_tool_call",
5044
- id: entry.ids.itemId,
5045
- call_id: entry.ids.callId,
5046
- name: entry.toolName,
5047
- input: entry.call.input,
5048
- status: "completed"
5549
+ }
5550
+ );
5551
+ const modelCompletedAtMs = Date.now();
5552
+ const modelVersion = typeof response.model === "string" ? response.model : request.model;
5553
+ request.onEvent?.({ type: "model", modelVersion });
5554
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5555
+ if (choice?.finish_reason === "content_filter") {
5556
+ request.onEvent?.({ type: "blocked" });
5557
+ }
5558
+ const message = choice?.message;
5559
+ const responseText = extractFireworksMessageText(message).trim();
5560
+ if (responseText.length > 0) {
5561
+ request.onEvent?.({ type: "delta", channel: "response", text: responseText });
5562
+ }
5563
+ const usageTokens = extractFireworksUsageTokens(response.usage);
5564
+ const stepCostUsd = estimateCallCostUsd({
5565
+ modelId: modelVersion,
5566
+ tokens: usageTokens,
5567
+ responseImages: 0
5568
+ });
5569
+ totalCostUsd += stepCostUsd;
5570
+ if (usageTokens) {
5571
+ request.onEvent?.({
5572
+ type: "usage",
5573
+ usage: usageTokens,
5574
+ costUsd: stepCostUsd,
5575
+ modelVersion
5576
+ });
5577
+ }
5578
+ const responseToolCalls = extractFireworksToolCalls(message);
5579
+ if (responseToolCalls.length === 0) {
5580
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5581
+ const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
5582
+ finalText = responseText;
5583
+ finalThoughts = "";
5584
+ const stepCompletedAtMs2 = Date.now();
5585
+ const timing2 = buildStepTiming({
5586
+ stepStartedAtMs,
5587
+ stepCompletedAtMs: stepCompletedAtMs2,
5588
+ modelCompletedAtMs,
5589
+ schedulerMetrics,
5590
+ toolExecutionMs: 0,
5591
+ waitToolMs: 0
5049
5592
  });
5050
- toolOutputs.push({
5051
- type: "custom_tool_call_output",
5052
- call_id: entry.ids.callId,
5053
- output: mergeToolOutput(outputPayload)
5593
+ steps.push({
5594
+ step: steps.length + 1,
5595
+ modelVersion,
5596
+ text: responseText || void 0,
5597
+ thoughts: void 0,
5598
+ toolCalls: [],
5599
+ usage: usageTokens,
5600
+ costUsd: stepCostUsd,
5601
+ timing: timing2
5054
5602
  });
5055
- } else {
5056
- toolOutputs.push({
5057
- type: "function_call",
5058
- id: entry.ids.itemId,
5059
- call_id: entry.ids.callId,
5060
- name: entry.toolName,
5061
- arguments: entry.call.arguments,
5062
- status: "completed"
5603
+ if (steeringMessages.length === 0) {
5604
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5605
+ }
5606
+ if (responseText.length > 0) {
5607
+ messages.push({ role: "assistant", content: responseText });
5608
+ }
5609
+ messages.push(...steeringMessages);
5610
+ continue;
5611
+ }
5612
+ const stepToolCalls = [];
5613
+ const callInputs = responseToolCalls.map((call, index) => {
5614
+ const toolIndex = index + 1;
5615
+ const toolId = buildToolLogId(turn, toolIndex);
5616
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5617
+ return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
5618
+ });
5619
+ for (const entry of callInputs) {
5620
+ request.onEvent?.({
5621
+ type: "tool_call",
5622
+ phase: "started",
5623
+ turn: entry.turn,
5624
+ toolIndex: entry.toolIndex,
5625
+ toolName: entry.toolName,
5626
+ toolId: entry.toolId,
5627
+ callKind: "function",
5628
+ callId: entry.call.id,
5629
+ input: entry.value
5063
5630
  });
5064
- toolOutputs.push({
5065
- type: "function_call_output",
5066
- call_id: entry.ids.callId,
5067
- output: mergeToolOutput(outputPayload)
5631
+ }
5632
+ const callResults = await Promise.all(
5633
+ callInputs.map(async (entry) => {
5634
+ return await toolCallContextStorage.run(
5635
+ {
5636
+ toolName: entry.toolName,
5637
+ toolId: entry.toolId,
5638
+ turn: entry.turn,
5639
+ toolIndex: entry.toolIndex
5640
+ },
5641
+ async () => {
5642
+ const { result, outputPayload } = await executeToolCall({
5643
+ callKind: "function",
5644
+ toolName: entry.toolName,
5645
+ tool: request.tools[entry.toolName],
5646
+ rawInput: entry.value,
5647
+ parseError: entry.parseError
5648
+ });
5649
+ return { entry, result, outputPayload };
5650
+ }
5651
+ );
5652
+ })
5653
+ );
5654
+ const assistantToolCalls = [];
5655
+ const toolMessages = [];
5656
+ let toolExecutionMs = 0;
5657
+ let waitToolMs = 0;
5658
+ for (const { entry, result, outputPayload } of callResults) {
5659
+ stepToolCalls.push({ ...result, callId: entry.call.id });
5660
+ const callDurationMs = toToolResultDuration(result);
5661
+ toolExecutionMs += callDurationMs;
5662
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5663
+ waitToolMs += callDurationMs;
5664
+ }
5665
+ request.onEvent?.({
5666
+ type: "tool_call",
5667
+ phase: "completed",
5668
+ turn: entry.turn,
5669
+ toolIndex: entry.toolIndex,
5670
+ toolName: entry.toolName,
5671
+ toolId: entry.toolId,
5672
+ callKind: "function",
5673
+ callId: entry.call.id,
5674
+ input: entry.value,
5675
+ output: result.output,
5676
+ error: result.error,
5677
+ durationMs: result.durationMs
5678
+ });
5679
+ assistantToolCalls.push({
5680
+ id: entry.call.id,
5681
+ type: "function",
5682
+ function: {
5683
+ name: entry.toolName,
5684
+ arguments: entry.call.arguments
5685
+ }
5068
5686
  });
5687
+ toolMessages.push({
5688
+ role: "tool",
5689
+ tool_call_id: entry.call.id,
5690
+ content: mergeToolOutput(outputPayload)
5691
+ });
5692
+ }
5693
+ const stepCompletedAtMs = Date.now();
5694
+ const timing = buildStepTiming({
5695
+ stepStartedAtMs,
5696
+ stepCompletedAtMs,
5697
+ modelCompletedAtMs,
5698
+ schedulerMetrics,
5699
+ toolExecutionMs,
5700
+ waitToolMs
5701
+ });
5702
+ steps.push({
5703
+ step: steps.length + 1,
5704
+ modelVersion,
5705
+ text: responseText || void 0,
5706
+ thoughts: void 0,
5707
+ toolCalls: stepToolCalls,
5708
+ usage: usageTokens,
5709
+ costUsd: stepCostUsd,
5710
+ timing
5711
+ });
5712
+ messages.push({
5713
+ role: "assistant",
5714
+ ...responseText.length > 0 ? { content: responseText } : {},
5715
+ tool_calls: assistantToolCalls
5716
+ });
5717
+ messages.push(...toolMessages);
5718
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5719
+ if (steeringInput.length > 0) {
5720
+ messages.push(...toFireworksMessages(steeringInput));
5069
5721
  }
5070
5722
  }
5071
- steps.push({
5072
- step: steps.length + 1,
5073
- modelVersion,
5074
- text: responseText || void 0,
5075
- thoughts: reasoningSummaryText || void 0,
5076
- toolCalls,
5077
- usage: usageTokens,
5078
- costUsd: stepCostUsd
5079
- });
5080
- input = input.concat(toolOutputs);
5723
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5081
5724
  }
5082
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5083
- }
5084
- if (providerInfo.provider === "fireworks") {
5085
- if (request.modelTools && request.modelTools.length > 0) {
5086
- throw new Error(
5087
- "Fireworks provider does not support provider-native modelTools in runToolLoop."
5088
- );
5089
- }
5090
- const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
5091
- const messages = toFireworksMessages(contents);
5725
+ const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
5726
+ const geminiNativeTools = toGeminiTools(request.modelTools);
5727
+ const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
5728
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
5092
5729
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5093
- const turn = stepIndex + 1;
5094
- const response = await runFireworksCall(async (client) => {
5095
- return await client.chat.completions.create(
5096
- {
5097
- model: providerInfo.model,
5098
- messages,
5099
- tools: fireworksTools,
5100
- tool_choice: "auto",
5101
- parallel_tool_calls: true
5102
- },
5103
- { signal: request.signal }
5104
- );
5105
- }, providerInfo.model);
5106
- const modelVersion = typeof response.model === "string" ? response.model : request.model;
5107
- request.onEvent?.({ type: "model", modelVersion });
5108
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5109
- if (choice?.finish_reason === "content_filter") {
5110
- request.onEvent?.({ type: "blocked" });
5111
- }
5112
- const message = choice?.message;
5113
- const responseText = extractFireworksMessageText(message).trim();
5114
- if (responseText.length > 0) {
5115
- request.onEvent?.({ type: "delta", channel: "response", text: responseText });
5116
- }
5117
- const usageTokens = extractFireworksUsageTokens(response.usage);
5730
+ const stepStartedAtMs = Date.now();
5731
+ let firstModelEventAtMs;
5732
+ let schedulerMetrics;
5733
+ const markFirstModelEvent = () => {
5734
+ if (firstModelEventAtMs === void 0) {
5735
+ firstModelEventAtMs = Date.now();
5736
+ }
5737
+ };
5738
+ const config = {
5739
+ maxOutputTokens: 32e3,
5740
+ tools: geminiTools,
5741
+ toolConfig: {
5742
+ functionCallingConfig: {
5743
+ mode: FunctionCallingConfigMode.VALIDATED
5744
+ }
5745
+ },
5746
+ thinkingConfig: resolveGeminiThinkingConfig(request.model)
5747
+ };
5748
+ const onEvent = request.onEvent;
5749
+ const response = await runGeminiCall(
5750
+ async (client) => {
5751
+ const stream = await client.models.generateContentStream({
5752
+ model: request.model,
5753
+ contents: geminiContents,
5754
+ config
5755
+ });
5756
+ let responseText = "";
5757
+ let thoughtsText = "";
5758
+ const modelParts = [];
5759
+ const functionCalls = [];
5760
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
5761
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5762
+ let latestUsageMetadata;
5763
+ let resolvedModelVersion;
5764
+ for await (const chunk of stream) {
5765
+ markFirstModelEvent();
5766
+ if (chunk.modelVersion) {
5767
+ resolvedModelVersion = chunk.modelVersion;
5768
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5769
+ }
5770
+ if (chunk.usageMetadata) {
5771
+ latestUsageMetadata = chunk.usageMetadata;
5772
+ }
5773
+ const candidates = chunk.candidates;
5774
+ if (!candidates || candidates.length === 0) {
5775
+ continue;
5776
+ }
5777
+ const primary = candidates[0];
5778
+ const parts = primary?.content?.parts;
5779
+ if (!parts || parts.length === 0) {
5780
+ continue;
5781
+ }
5782
+ for (const part of parts) {
5783
+ modelParts.push(part);
5784
+ const call = part.functionCall;
5785
+ if (call) {
5786
+ const id = typeof call.id === "string" ? call.id : "";
5787
+ const shouldAdd = (() => {
5788
+ if (id.length > 0) {
5789
+ if (seenFunctionCallIds.has(id)) {
5790
+ return false;
5791
+ }
5792
+ seenFunctionCallIds.add(id);
5793
+ return true;
5794
+ }
5795
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
5796
+ if (seenFunctionCallKeys.has(key)) {
5797
+ return false;
5798
+ }
5799
+ seenFunctionCallKeys.add(key);
5800
+ return true;
5801
+ })();
5802
+ if (shouldAdd) {
5803
+ functionCalls.push(call);
5804
+ }
5805
+ }
5806
+ if (typeof part.text === "string" && part.text.length > 0) {
5807
+ if (part.thought) {
5808
+ thoughtsText += part.text;
5809
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
5810
+ } else {
5811
+ responseText += part.text;
5812
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
5813
+ }
5814
+ }
5815
+ }
5816
+ }
5817
+ return {
5818
+ responseText,
5819
+ thoughtsText,
5820
+ functionCalls,
5821
+ modelParts,
5822
+ usageMetadata: latestUsageMetadata,
5823
+ modelVersion: resolvedModelVersion ?? request.model
5824
+ };
5825
+ },
5826
+ request.model,
5827
+ {
5828
+ onSettled: (metrics) => {
5829
+ schedulerMetrics = metrics;
5830
+ }
5831
+ }
5832
+ );
5833
+ const modelCompletedAtMs = Date.now();
5834
+ const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
5835
+ const modelVersion = response.modelVersion ?? request.model;
5118
5836
  const stepCostUsd = estimateCallCostUsd({
5119
5837
  modelId: modelVersion,
5120
5838
  tokens: usageTokens,
5121
5839
  responseImages: 0
5122
5840
  });
5123
5841
  totalCostUsd += stepCostUsd;
5124
- if (usageTokens) {
5125
- request.onEvent?.({
5126
- type: "usage",
5127
- usage: usageTokens,
5128
- costUsd: stepCostUsd,
5129
- modelVersion
5842
+ if (response.functionCalls.length === 0) {
5843
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5844
+ finalText = response.responseText.trim();
5845
+ finalThoughts = response.thoughtsText.trim();
5846
+ const stepCompletedAtMs2 = Date.now();
5847
+ const timing2 = buildStepTiming({
5848
+ stepStartedAtMs,
5849
+ stepCompletedAtMs: stepCompletedAtMs2,
5850
+ modelCompletedAtMs,
5851
+ firstModelEventAtMs,
5852
+ schedulerMetrics,
5853
+ toolExecutionMs: 0,
5854
+ waitToolMs: 0
5130
5855
  });
5131
- }
5132
- const responseToolCalls = extractFireworksToolCalls(message);
5133
- if (responseToolCalls.length === 0) {
5134
- finalText = responseText;
5135
- finalThoughts = "";
5136
5856
  steps.push({
5137
5857
  step: steps.length + 1,
5138
5858
  modelVersion,
5139
- text: responseText || void 0,
5140
- thoughts: void 0,
5859
+ text: finalText || void 0,
5860
+ thoughts: finalThoughts || void 0,
5141
5861
  toolCalls: [],
5142
5862
  usage: usageTokens,
5143
- costUsd: stepCostUsd
5863
+ costUsd: stepCostUsd,
5864
+ timing: timing2
5144
5865
  });
5145
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5866
+ if (steeringInput2.length === 0) {
5867
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5868
+ }
5869
+ const modelPartsForHistory2 = response.modelParts.filter(
5870
+ (part) => !(typeof part.text === "string" && part.thought === true)
5871
+ );
5872
+ if (modelPartsForHistory2.length > 0) {
5873
+ geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
5874
+ } else if (response.responseText.length > 0) {
5875
+ geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
5876
+ }
5877
+ geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
5878
+ continue;
5146
5879
  }
5147
- const stepToolCalls = [];
5148
- const callInputs = responseToolCalls.map((call, index) => {
5880
+ const toolCalls = [];
5881
+ const modelPartsForHistory = response.modelParts.filter(
5882
+ (part) => !(typeof part.text === "string" && part.thought === true)
5883
+ );
5884
+ if (modelPartsForHistory.length > 0) {
5885
+ geminiContents.push({ role: "model", parts: modelPartsForHistory });
5886
+ } else {
5887
+ const parts = [];
5888
+ if (response.responseText) {
5889
+ parts.push({ text: response.responseText });
5890
+ }
5891
+ for (const call of response.functionCalls) {
5892
+ parts.push({ functionCall: call });
5893
+ }
5894
+ geminiContents.push({ role: "model", parts });
5895
+ }
5896
+ const responseParts = [];
5897
+ const callInputs = response.functionCalls.map((call, index) => {
5898
+ const turn = stepIndex + 1;
5149
5899
  const toolIndex = index + 1;
5150
5900
  const toolId = buildToolLogId(turn, toolIndex);
5151
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5152
- return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
5901
+ const toolName = call.name ?? "unknown";
5902
+ const rawInput = call.args ?? {};
5903
+ return { call, toolName, rawInput, toolId, turn, toolIndex };
5153
5904
  });
5905
+ for (const entry of callInputs) {
5906
+ onEvent?.({
5907
+ type: "tool_call",
5908
+ phase: "started",
5909
+ turn: entry.turn,
5910
+ toolIndex: entry.toolIndex,
5911
+ toolName: entry.toolName,
5912
+ toolId: entry.toolId,
5913
+ callKind: "function",
5914
+ callId: entry.call.id,
5915
+ input: entry.rawInput
5916
+ });
5917
+ }
5154
5918
  const callResults = await Promise.all(
5155
5919
  callInputs.map(async (entry) => {
5156
5920
  return await toolCallContextStorage.run(
@@ -5165,232 +5929,134 @@ async function runToolLoop(request) {
5165
5929
  callKind: "function",
5166
5930
  toolName: entry.toolName,
5167
5931
  tool: request.tools[entry.toolName],
5168
- rawInput: entry.value,
5169
- parseError: entry.parseError
5932
+ rawInput: entry.rawInput
5170
5933
  });
5171
5934
  return { entry, result, outputPayload };
5172
5935
  }
5173
5936
  );
5174
5937
  })
5175
5938
  );
5176
- const assistantToolCalls = [];
5177
- const toolMessages = [];
5939
+ let toolExecutionMs = 0;
5940
+ let waitToolMs = 0;
5178
5941
  for (const { entry, result, outputPayload } of callResults) {
5179
- stepToolCalls.push({ ...result, callId: entry.call.id });
5180
- assistantToolCalls.push({
5181
- id: entry.call.id,
5182
- type: "function",
5183
- function: {
5942
+ toolCalls.push({ ...result, callId: entry.call.id });
5943
+ const callDurationMs = toToolResultDuration(result);
5944
+ toolExecutionMs += callDurationMs;
5945
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5946
+ waitToolMs += callDurationMs;
5947
+ }
5948
+ onEvent?.({
5949
+ type: "tool_call",
5950
+ phase: "completed",
5951
+ turn: entry.turn,
5952
+ toolIndex: entry.toolIndex,
5953
+ toolName: entry.toolName,
5954
+ toolId: entry.toolId,
5955
+ callKind: "function",
5956
+ callId: entry.call.id,
5957
+ input: entry.rawInput,
5958
+ output: result.output,
5959
+ error: result.error,
5960
+ durationMs: result.durationMs
5961
+ });
5962
+ const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
5963
+ responseParts.push({
5964
+ functionResponse: {
5184
5965
  name: entry.toolName,
5185
- arguments: entry.call.arguments
5966
+ response: responsePayload,
5967
+ ...entry.call.id ? { id: entry.call.id } : {}
5186
5968
  }
5187
5969
  });
5188
- toolMessages.push({
5189
- role: "tool",
5190
- tool_call_id: entry.call.id,
5191
- content: mergeToolOutput(outputPayload)
5192
- });
5193
5970
  }
5971
+ const stepCompletedAtMs = Date.now();
5972
+ const timing = buildStepTiming({
5973
+ stepStartedAtMs,
5974
+ stepCompletedAtMs,
5975
+ modelCompletedAtMs,
5976
+ firstModelEventAtMs,
5977
+ schedulerMetrics,
5978
+ toolExecutionMs,
5979
+ waitToolMs
5980
+ });
5194
5981
  steps.push({
5195
5982
  step: steps.length + 1,
5196
5983
  modelVersion,
5197
- text: responseText || void 0,
5198
- thoughts: void 0,
5199
- toolCalls: stepToolCalls,
5984
+ text: response.responseText.trim() || void 0,
5985
+ thoughts: response.thoughtsText.trim() || void 0,
5986
+ toolCalls,
5200
5987
  usage: usageTokens,
5201
- costUsd: stepCostUsd
5202
- });
5203
- messages.push({
5204
- role: "assistant",
5205
- ...responseText.length > 0 ? { content: responseText } : {},
5206
- tool_calls: assistantToolCalls
5988
+ costUsd: stepCostUsd,
5989
+ timing
5207
5990
  });
5208
- messages.push(...toolMessages);
5991
+ geminiContents.push({ role: "user", parts: responseParts });
5992
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5993
+ if (steeringInput.length > 0) {
5994
+ geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
5995
+ }
5209
5996
  }
5210
5997
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5998
+ } finally {
5999
+ steeringInternal?.close();
5211
6000
  }
5212
- const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
5213
- const geminiNativeTools = toGeminiTools(request.modelTools);
5214
- const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
5215
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
5216
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5217
- const config = {
5218
- maxOutputTokens: 32e3,
5219
- tools: geminiTools,
5220
- toolConfig: {
5221
- functionCallingConfig: {
5222
- mode: FunctionCallingConfigMode.VALIDATED
5223
- }
5224
- },
5225
- thinkingConfig: resolveGeminiThinkingConfig(request.model)
5226
- };
5227
- const onEvent = request.onEvent;
5228
- const response = await runGeminiCall(async (client) => {
5229
- const stream = await client.models.generateContentStream({
5230
- model: request.model,
5231
- contents: geminiContents,
5232
- config
5233
- });
5234
- let responseText = "";
5235
- let thoughtsText = "";
5236
- const modelParts = [];
5237
- const functionCalls = [];
5238
- const seenFunctionCallIds = /* @__PURE__ */ new Set();
5239
- const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5240
- let latestUsageMetadata;
5241
- let resolvedModelVersion;
5242
- for await (const chunk of stream) {
5243
- if (chunk.modelVersion) {
5244
- resolvedModelVersion = chunk.modelVersion;
5245
- onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5246
- }
5247
- if (chunk.usageMetadata) {
5248
- latestUsageMetadata = chunk.usageMetadata;
5249
- }
5250
- const candidates = chunk.candidates;
5251
- if (!candidates || candidates.length === 0) {
5252
- continue;
5253
- }
5254
- const primary = candidates[0];
5255
- const parts = primary?.content?.parts;
5256
- if (!parts || parts.length === 0) {
5257
- continue;
5258
- }
5259
- for (const part of parts) {
5260
- modelParts.push(part);
5261
- const call = part.functionCall;
5262
- if (call) {
5263
- const id = typeof call.id === "string" ? call.id : "";
5264
- const shouldAdd = (() => {
5265
- if (id.length > 0) {
5266
- if (seenFunctionCallIds.has(id)) {
5267
- return false;
5268
- }
5269
- seenFunctionCallIds.add(id);
5270
- return true;
5271
- }
5272
- const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
5273
- if (seenFunctionCallKeys.has(key)) {
5274
- return false;
5275
- }
5276
- seenFunctionCallKeys.add(key);
5277
- return true;
5278
- })();
5279
- if (shouldAdd) {
5280
- functionCalls.push(call);
5281
- }
5282
- }
5283
- if (typeof part.text === "string" && part.text.length > 0) {
5284
- if (part.thought) {
5285
- thoughtsText += part.text;
5286
- onEvent?.({ type: "delta", channel: "thought", text: part.text });
5287
- } else {
5288
- responseText += part.text;
5289
- onEvent?.({ type: "delta", channel: "response", text: part.text });
5290
- }
5291
- }
5292
- }
5293
- }
5294
- return {
5295
- responseText,
5296
- thoughtsText,
5297
- functionCalls,
5298
- modelParts,
5299
- usageMetadata: latestUsageMetadata,
5300
- modelVersion: resolvedModelVersion ?? request.model
5301
- };
5302
- }, request.model);
5303
- const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
5304
- const modelVersion = response.modelVersion ?? request.model;
5305
- const stepCostUsd = estimateCallCostUsd({
5306
- modelId: modelVersion,
5307
- tokens: usageTokens,
5308
- responseImages: 0
5309
- });
5310
- totalCostUsd += stepCostUsd;
5311
- if (response.functionCalls.length === 0) {
5312
- finalText = response.responseText.trim();
5313
- finalThoughts = response.thoughtsText.trim();
5314
- steps.push({
5315
- step: steps.length + 1,
5316
- modelVersion,
5317
- text: finalText || void 0,
5318
- thoughts: finalThoughts || void 0,
5319
- toolCalls: [],
5320
- usage: usageTokens,
5321
- costUsd: stepCostUsd
5322
- });
5323
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
6001
+ }
6002
+ function mergeAbortSignals(first, second) {
6003
+ if (!first) {
6004
+ return second;
6005
+ }
6006
+ if (!second) {
6007
+ return first;
6008
+ }
6009
+ const controller = new AbortController();
6010
+ const abortFrom = (signal) => {
6011
+ if (!controller.signal.aborted) {
6012
+ controller.abort(signal.reason);
5324
6013
  }
5325
- const toolCalls = [];
5326
- const modelPartsForHistory = response.modelParts.filter(
5327
- (part) => !(typeof part.text === "string" && part.thought === true)
5328
- );
5329
- if (modelPartsForHistory.length > 0) {
5330
- geminiContents.push({ role: "model", parts: modelPartsForHistory });
5331
- } else {
5332
- const parts = [];
5333
- if (response.responseText) {
5334
- parts.push({ text: response.responseText });
5335
- }
5336
- for (const call of response.functionCalls) {
5337
- parts.push({ functionCall: call });
5338
- }
5339
- geminiContents.push({ role: "model", parts });
5340
- }
5341
- const responseParts = [];
5342
- const callInputs = response.functionCalls.map((call, index) => {
5343
- const turn = stepIndex + 1;
5344
- const toolIndex = index + 1;
5345
- const toolId = buildToolLogId(turn, toolIndex);
5346
- const toolName = call.name ?? "unknown";
5347
- const rawInput = call.args ?? {};
5348
- return { call, toolName, rawInput, toolId, turn, toolIndex };
5349
- });
5350
- const callResults = await Promise.all(
5351
- callInputs.map(async (entry) => {
5352
- return await toolCallContextStorage.run(
5353
- {
5354
- toolName: entry.toolName,
5355
- toolId: entry.toolId,
5356
- turn: entry.turn,
5357
- toolIndex: entry.toolIndex
5358
- },
5359
- async () => {
5360
- const { result, outputPayload } = await executeToolCall({
5361
- callKind: "function",
5362
- toolName: entry.toolName,
5363
- tool: request.tools[entry.toolName],
5364
- rawInput: entry.rawInput
5365
- });
5366
- return { entry, result, outputPayload };
5367
- }
5368
- );
5369
- })
5370
- );
5371
- for (const { entry, result, outputPayload } of callResults) {
5372
- toolCalls.push({ ...result, callId: entry.call.id });
5373
- const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
5374
- responseParts.push({
5375
- functionResponse: {
5376
- name: entry.toolName,
5377
- response: responsePayload,
5378
- ...entry.call.id ? { id: entry.call.id } : {}
6014
+ };
6015
+ if (first.aborted) {
6016
+ abortFrom(first);
6017
+ } else {
6018
+ first.addEventListener("abort", () => abortFrom(first), { once: true });
6019
+ }
6020
+ if (second.aborted) {
6021
+ abortFrom(second);
6022
+ } else {
6023
+ second.addEventListener("abort", () => abortFrom(second), { once: true });
6024
+ }
6025
+ return controller.signal;
6026
+ }
6027
+ function streamToolLoop(request) {
6028
+ const queue = createAsyncQueue();
6029
+ const abortController = new AbortController();
6030
+ const steering = request.steering ?? createToolLoopSteeringChannel();
6031
+ const signal = mergeAbortSignals(request.signal, abortController.signal);
6032
+ const sourceOnEvent = request.onEvent;
6033
+ const result = (async () => {
6034
+ try {
6035
+ const output = await runToolLoop({
6036
+ ...request,
6037
+ steering,
6038
+ ...signal ? { signal } : {},
6039
+ onEvent: (event) => {
6040
+ sourceOnEvent?.(event);
6041
+ queue.push(event);
5379
6042
  }
5380
6043
  });
6044
+ queue.close();
6045
+ return output;
6046
+ } catch (error) {
6047
+ const err = error instanceof Error ? error : new Error(String(error));
6048
+ queue.fail(err);
6049
+ throw err;
5381
6050
  }
5382
- steps.push({
5383
- step: steps.length + 1,
5384
- modelVersion,
5385
- text: response.responseText.trim() || void 0,
5386
- thoughts: response.thoughtsText.trim() || void 0,
5387
- toolCalls,
5388
- usage: usageTokens,
5389
- costUsd: stepCostUsd
5390
- });
5391
- geminiContents.push({ role: "user", parts: responseParts });
5392
- }
5393
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
6051
+ })();
6052
+ return {
6053
+ events: queue.iterable,
6054
+ result,
6055
+ append: steering.append,
6056
+ steer: steering.steer,
6057
+ pendingSteeringCount: steering.pendingCount,
6058
+ abort: () => abortController.abort()
6059
+ };
5394
6060
  }
5395
6061
  var IMAGE_GRADE_SCHEMA = z3.enum(["pass", "fail"]);
5396
6062
  async function gradeGeneratedImage(params) {
@@ -5637,68 +6303,112 @@ function appendMarkdownSourcesSection(value, sources) {
5637
6303
  ${lines}`;
5638
6304
  }
5639
6305
 
6306
+ // src/agent.ts
6307
+ import { randomBytes as randomBytes3 } from "crypto";
6308
+
5640
6309
  // src/agent/subagents.ts
5641
6310
  import { randomBytes as randomBytes2 } from "crypto";
5642
6311
  import { z as z4 } from "zod";
5643
- var DEFAULT_SUBAGENT_MAX_AGENTS = 4;
5644
- var DEFAULT_SUBAGENT_MAX_DEPTH = 2;
5645
- var DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS = 1500;
5646
- var DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS = 9e4;
6312
+ var DEFAULT_SUBAGENT_MAX_AGENTS = 6;
6313
+ var DEFAULT_SUBAGENT_MAX_DEPTH = 1;
6314
+ var DEFAULT_SUBAGENT_MIN_WAIT_TIMEOUT_MS = 1e4;
6315
+ var DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS = 3e4;
6316
+ var DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS = 36e5;
5647
6317
  var MAX_SUBAGENT_MAX_AGENTS = 64;
5648
6318
  var MAX_SUBAGENT_MAX_DEPTH = 12;
5649
6319
  var MAX_SUBAGENT_MAX_STEPS = 64;
5650
- var MAX_SUBAGENT_WAIT_TIMEOUT_MS = 6e5;
6320
+ var MAX_SUBAGENT_WAIT_TIMEOUT_MS = 36e5;
5651
6321
  var SUBAGENT_CONTROL_TOOL_NAMES = ["send_input", "resume_agent", "wait", "close_agent"];
6322
+ var DEFAULT_AGENT_TYPE = "default";
6323
+ var BUILT_IN_AGENT_TYPES = ["default", "researcher", "worker", "reviewer"];
6324
+ var RESEARCHER_ROLE_DESCRIPTION = `Use \`researcher\` for focused discovery and fact-finding work.
6325
+ Researchers are fast and authoritative.
6326
+ They should be used for specific, well-scoped research questions.
6327
+ Rules:
6328
+ - Do not repeat searches they have already completed.
6329
+ - Trust researcher findings unless there is a clear contradiction.
6330
+ - Run researchers in parallel when useful.
6331
+ - Reuse existing researchers for related follow-up questions.`;
6332
+ var WORKER_ROLE_DESCRIPTION = `Use for execution and production work across domains.
6333
+ Typical tasks:
6334
+ - Build part of a deliverable
6335
+ - Implement requested changes
6336
+ - Produce concrete outputs (documents, plans, analyses, artifacts)
6337
+ Rules:
6338
+ - Explicitly assign **ownership** of the task (scope / responsibility).
6339
+ - Always tell workers they are **not alone in the workspace**, and they should ignore edits made by others without touching them unless asked.`;
6340
+ var REVIEWER_ROLE_DESCRIPTION = `Use \`reviewer\` to evaluate completed work and provide feedback.
6341
+ Reviewers focus on quality, correctness, risk, and clarity.
6342
+ Rules:
6343
+ - Review critically and prioritize issues by severity.
6344
+ - Call out gaps, assumptions, and edge cases explicitly.
6345
+ - Provide actionable, concrete feedback to improve the result.
6346
+ - Do not redo the entire task unless explicitly requested; evaluate first.`;
6347
+ var BUILT_IN_AGENT_TYPE_DESCRIPTIONS = {
6348
+ default: "Default agent.",
6349
+ researcher: RESEARCHER_ROLE_DESCRIPTION,
6350
+ worker: WORKER_ROLE_DESCRIPTION,
6351
+ reviewer: REVIEWER_ROLE_DESCRIPTION
6352
+ };
6353
+ var BUILT_IN_AGENT_TYPE_INSTRUCTIONS = {
6354
+ default: void 0,
6355
+ researcher: RESEARCHER_ROLE_DESCRIPTION,
6356
+ worker: WORKER_ROLE_DESCRIPTION,
6357
+ reviewer: REVIEWER_ROLE_DESCRIPTION
6358
+ };
6359
+ var SUBAGENT_NOTIFICATION_OPEN_TAG = "<subagent_notification>";
6360
+ var SUBAGENT_NOTIFICATION_CLOSE_TAG = "</subagent_notification>";
6361
+ var SPAWN_AGENT_TYPE_DESCRIPTION = buildSpawnAgentTypeDescription();
5652
6362
  var subagentInputItemSchema = z4.object({
5653
- text: z4.string().optional(),
5654
- image_url: z4.string().optional(),
5655
- name: z4.string().optional(),
5656
- path: z4.string().optional(),
5657
- type: z4.string().optional()
6363
+ text: z4.string().nullish(),
6364
+ image_url: z4.string().nullish(),
6365
+ name: z4.string().nullish(),
6366
+ path: z4.string().nullish(),
6367
+ type: z4.string().nullish()
5658
6368
  }).passthrough();
5659
6369
  var spawnAgentInputSchema = z4.object({
5660
- prompt: z4.string().optional().describe("Initial prompt for the subagent."),
5661
- message: z4.string().optional().describe("Codex-style alias for prompt."),
5662
- items: z4.array(subagentInputItemSchema).optional().describe("Optional Codex-style input items."),
5663
- agent_type: z4.string().optional().describe("Codex-style agent type hint."),
5664
- instructions: z4.string().optional().describe("Optional extra instructions for this subagent instance."),
5665
- model: z4.string().optional().describe("Optional model override. Must be one of this package's supported text model ids."),
5666
- max_steps: z4.number().int().min(1).max(MAX_SUBAGENT_MAX_STEPS).optional().describe("Optional max step budget for each subagent run.")
5667
- }).refine((value) => Boolean(resolvePromptValue(value.prompt, value.message, value.items)), {
5668
- message: "Either prompt, message, or items must contain non-empty input."
6370
+ prompt: z4.string().nullish().describe("Alias for message. Initial plain-text task for the new agent."),
6371
+ message: z4.string().nullish().describe("Initial plain-text task for the new agent. Use either message or items."),
6372
+ items: z4.array(subagentInputItemSchema).nullish().describe(
6373
+ "Structured input items. Use this to pass explicit mentions (for example app:// connector paths)."
6374
+ ),
6375
+ agent_type: z4.string().nullish().describe(SPAWN_AGENT_TYPE_DESCRIPTION),
6376
+ fork_context: z4.boolean().nullish().describe(
6377
+ "When true, fork the current thread history into the new agent before sending the initial prompt. This must be used when you want the new agent to have exactly the same context as you."
6378
+ ),
6379
+ instructions: z4.string().nullish().describe("Optional extra instructions for this subagent instance."),
6380
+ model: z4.string().nullish().describe("Optional model override. Must be one of this package's supported text model ids."),
6381
+ max_steps: z4.number().int().min(1).max(MAX_SUBAGENT_MAX_STEPS).nullish().describe("Optional max step budget for each subagent run.")
5669
6382
  });
5670
6383
  var sendInputSchema = z4.object({
5671
- agent_id: z4.string().optional().describe("Target subagent id."),
5672
- id: z4.string().optional().describe("Codex-style alias for agent_id."),
5673
- input: z4.string().optional().describe("New user input queued for the subagent."),
5674
- message: z4.string().optional().describe("Codex-style alias for input."),
5675
- items: z4.array(subagentInputItemSchema).optional().describe("Optional Codex-style input items."),
5676
- interrupt: z4.boolean().optional().describe("If true and currently running, aborts active run before queuing input.")
6384
+ agent_id: z4.string().nullish().describe("Target subagent id."),
6385
+ id: z4.string().nullish().describe("Agent id to message (from spawn_agent)."),
6386
+ input: z4.string().nullish().describe("New user input queued for the subagent."),
6387
+ message: z4.string().nullish().describe("Legacy plain-text message to send to the agent. Use either message or items."),
6388
+ items: z4.array(subagentInputItemSchema).nullish().describe(
6389
+ "Structured input items. Use this to pass explicit mentions (for example app:// connector paths)."
6390
+ ),
6391
+ interrupt: z4.boolean().nullish().describe("If true and currently running, aborts active run before queuing input.")
5677
6392
  }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
5678
6393
  message: "agent_id (or id) is required."
5679
- }).refine((value) => Boolean(resolvePromptValue(value.input, value.message, value.items)), {
5680
- message: "input (or message/items) is required."
5681
6394
  });
5682
6395
  var resumeAgentSchema = z4.object({
5683
- agent_id: z4.string().optional().describe("Target subagent id."),
5684
- id: z4.string().optional().describe("Codex-style alias for agent_id.")
6396
+ agent_id: z4.string().nullish().describe("Target subagent id."),
6397
+ id: z4.string().nullish().describe("Agent id to resume.")
5685
6398
  }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
5686
6399
  message: "agent_id (or id) is required."
5687
6400
  });
5688
6401
  var waitSchema = z4.object({
5689
- agent_id: z4.string().optional().describe("Target subagent id."),
5690
- id: z4.string().optional().describe("Codex-style alias for agent_id."),
5691
- ids: z4.array(z4.string().min(1)).optional().describe("Codex-style list of agent ids."),
5692
- timeout_ms: z4.number().int().min(1).optional().describe("Optional wait timeout in milliseconds.")
5693
- }).refine(
5694
- (value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)) || Array.isArray(value.ids) && value.ids.length > 0,
5695
- {
5696
- message: "agent_id/id or ids is required."
5697
- }
5698
- );
6402
+ agent_id: z4.string().nullish().describe("Target subagent id."),
6403
+ id: z4.string().nullish().describe("Codex-style alias for agent_id."),
6404
+ ids: z4.array(z4.string().min(1)).nullish().describe("Agent ids to wait on. Pass multiple ids to wait for whichever finishes first."),
6405
+ timeout_ms: z4.number().int().nullish().describe(
6406
+ "Optional timeout in milliseconds. Defaults to 30000, min 10000, max 3600000. Prefer longer waits (minutes) to avoid busy polling."
6407
+ )
6408
+ });
5699
6409
  var closeSchema = z4.object({
5700
- agent_id: z4.string().optional().describe("Target subagent id."),
5701
- id: z4.string().optional().describe("Codex-style alias for agent_id.")
6410
+ agent_id: z4.string().nullish().describe("Target subagent id."),
6411
+ id: z4.string().nullish().describe("Agent id to close (from spawn_agent).")
5702
6412
  }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
5703
6413
  message: "agent_id (or id) is required."
5704
6414
  });
@@ -5706,6 +6416,7 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5706
6416
  const defaults = {
5707
6417
  maxAgents: DEFAULT_SUBAGENT_MAX_AGENTS,
5708
6418
  maxDepth: DEFAULT_SUBAGENT_MAX_DEPTH,
6419
+ minWaitTimeoutMs: DEFAULT_SUBAGENT_MIN_WAIT_TIMEOUT_MS,
5709
6420
  defaultWaitTimeoutMs: DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS,
5710
6421
  maxWaitTimeoutMs: DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS,
5711
6422
  promptPattern: "codex",
@@ -5726,10 +6437,16 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5726
6437
  MAX_SUBAGENT_MAX_AGENTS
5727
6438
  );
5728
6439
  const maxDepth = normalizeInteger(config.maxDepth, defaults.maxDepth, 1, MAX_SUBAGENT_MAX_DEPTH);
6440
+ const minWaitTimeoutMs = normalizeInteger(
6441
+ config.minWaitTimeoutMs,
6442
+ defaults.minWaitTimeoutMs,
6443
+ 1,
6444
+ MAX_SUBAGENT_WAIT_TIMEOUT_MS
6445
+ );
5729
6446
  const defaultWaitTimeoutMs = normalizeInteger(
5730
6447
  config.defaultWaitTimeoutMs,
5731
6448
  defaults.defaultWaitTimeoutMs,
5732
- 1,
6449
+ minWaitTimeoutMs,
5733
6450
  MAX_SUBAGENT_WAIT_TIMEOUT_MS
5734
6451
  );
5735
6452
  const maxWaitTimeoutMs = normalizeInteger(
@@ -5746,6 +6463,7 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5746
6463
  enabled,
5747
6464
  maxAgents,
5748
6465
  maxDepth,
6466
+ minWaitTimeoutMs,
5749
6467
  defaultWaitTimeoutMs,
5750
6468
  maxWaitTimeoutMs,
5751
6469
  promptPattern,
@@ -5759,9 +6477,11 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5759
6477
  function buildCodexSubagentOrchestratorInstructions(params) {
5760
6478
  return [
5761
6479
  "Subagent orchestration tools are available: spawn_agent, send_input, resume_agent, wait, close_agent.",
6480
+ "Background updates may appear as <subagent_notification>{...}</subagent_notification>; treat them as status updates, not new user intent.",
6481
+ "Available spawn_agent agent_type values: default, researcher, worker, reviewer.",
5762
6482
  "Use this control pattern:",
5763
6483
  "1. spawn_agent with a focused prompt.",
5764
- "2. wait on that agent_id until it is no longer running.",
6484
+ "2. wait with ids=[agent_id] until the agent reaches a non-running state. Prefer long waits (minutes).",
5765
6485
  "3. For follow-up turns, send_input then resume_agent.",
5766
6486
  "4. close_agent when delegation is complete.",
5767
6487
  `Limits: max active subagents ${params.maxAgents}, max depth ${params.maxDepth}, current depth ${params.currentDepth}.`
@@ -5783,9 +6503,10 @@ function createSubagentToolController(options) {
5783
6503
  };
5784
6504
  }
5785
6505
  const agents = /* @__PURE__ */ new Map();
6506
+ const roleNicknameCounts = /* @__PURE__ */ new Map();
5786
6507
  const tools = {
5787
6508
  spawn_agent: tool({
5788
- description: "Spawns a subagent asynchronously. Returns immediately with agent status and id.",
6509
+ description: "Spawn a sub-agent for a well-scoped task. Returns the agent id (and user-facing nickname when available) to use to communicate with this agent.",
5789
6510
  inputSchema: spawnAgentInputSchema,
5790
6511
  execute: async (input) => {
5791
6512
  if (countActiveAgents(agents) >= options.config.maxAgents) {
@@ -5808,24 +6529,36 @@ function createSubagentToolController(options) {
5808
6529
  }
5809
6530
  const id = `agent_${randomBytes2(6).toString("hex")}`;
5810
6531
  const now = Date.now();
5811
- const initialPrompt = resolvePromptValue(input.prompt, input.message, input.items);
5812
- if (!initialPrompt) {
5813
- throw new Error("spawn_agent requires prompt/message/items with non-empty text.");
5814
- }
6532
+ const { roleName, roleInstructions } = resolveAgentType(input.agent_type);
6533
+ const nickname = reserveAgentNickname(roleName, roleNicknameCounts);
6534
+ const perSpawnInstructions = joinInstructionBlocks(
6535
+ roleInstructions,
6536
+ trimToUndefined(input.instructions)
6537
+ );
6538
+ const initialPrompt = resolveCollabInputText({
6539
+ textCandidates: [{ value: input.prompt }, { value: input.message }],
6540
+ items: input.items,
6541
+ bothError: "Provide either prompt/message or items, but not both.",
6542
+ missingError: "Provide one of: prompt/message or items.",
6543
+ emptyTextError: "Empty message can't be sent to an agent.",
6544
+ emptyItemsError: "Items can't be empty."
6545
+ });
5815
6546
  const agent = {
5816
6547
  id,
5817
6548
  depth: childDepth,
5818
6549
  model,
6550
+ ...nickname ? { nickname } : {},
6551
+ agentRole: roleName,
5819
6552
  status: "idle",
5820
6553
  createdAtMs: now,
5821
6554
  updatedAtMs: now,
5822
6555
  pendingInputs: [initialPrompt],
5823
- history: [],
6556
+ history: input.fork_context && options.forkContextMessages ? [...options.forkContextMessages] : [],
5824
6557
  ...options.buildChildInstructions ? {
5825
6558
  instructions: trimToUndefined(
5826
- options.buildChildInstructions(input.instructions, childDepth)
6559
+ options.buildChildInstructions(perSpawnInstructions, childDepth)
5827
6560
  )
5828
- } : input.instructions ? { instructions: trimToUndefined(input.instructions) } : {},
6561
+ } : perSpawnInstructions ? { instructions: perSpawnInstructions } : {},
5829
6562
  ...input.max_steps ? { maxSteps: input.max_steps } : options.config.maxSteps ? { maxSteps: options.config.maxSteps } : {},
5830
6563
  turns: 0,
5831
6564
  notification: "spawned",
@@ -5835,41 +6568,50 @@ function createSubagentToolController(options) {
5835
6568
  };
5836
6569
  agents.set(id, agent);
5837
6570
  startRun(agent, options);
5838
- return buildToolResponse(agent, {
5839
- notification: "spawned",
5840
- message: `Spawned subagent ${id}.`
5841
- });
6571
+ return buildToolResponse(
6572
+ agent,
6573
+ {
6574
+ notification: "spawned",
6575
+ message: `Spawned subagent ${id}.`
6576
+ },
6577
+ { nickname: agent.nickname }
6578
+ );
5842
6579
  }
5843
6580
  }),
5844
6581
  send_input: tool({
5845
- description: "Queues new input for an existing subagent.",
6582
+ description: "Send a message to an existing agent. Use interrupt=true to redirect work immediately.",
5846
6583
  inputSchema: sendInputSchema,
5847
6584
  execute: async (input) => {
6585
+ const submissionId = randomSubmissionId();
5848
6586
  const agentId = resolveAgentIdValue(input.agent_id, input.id);
5849
6587
  if (!agentId) {
5850
6588
  throw new Error("send_input requires agent_id or id.");
5851
6589
  }
5852
6590
  const agent = requireAgent(agents, agentId);
5853
- const nextInput = resolvePromptValue(input.input, input.message, input.items);
5854
- if (!nextInput) {
5855
- throw new Error("send_input requires input/message/items with non-empty text.");
5856
- }
6591
+ const nextInput = resolveCollabInputText({
6592
+ textCandidates: [{ value: input.input }, { value: input.message }],
6593
+ items: input.items,
6594
+ bothError: "Provide either input/message or items, but not both.",
6595
+ missingError: "Provide one of: input/message or items.",
6596
+ emptyTextError: "Empty message can't be sent to an agent.",
6597
+ emptyItemsError: "Items can't be empty."
6598
+ });
5857
6599
  if (agent.status === "closed") {
5858
- throw new Error(`Subagent ${agent.id} is closed.`);
6600
+ throw new Error(`agent with id ${agent.id} is closed`);
5859
6601
  }
5860
6602
  if (input.interrupt && agent.abortController) {
5861
6603
  agent.abortController.abort("send_input_interrupt");
5862
6604
  agent.pendingInputs.unshift(nextInput);
5863
6605
  setNotification(agent, "input_queued", `Interrupted ${agent.id} and queued new input.`);
5864
- return buildToolResponse(agent);
6606
+ return buildToolResponse(agent, void 0, { submission_id: submissionId });
5865
6607
  }
5866
6608
  agent.pendingInputs.push(nextInput);
5867
6609
  setNotification(agent, "input_queued", `Queued input for ${agent.id}.`);
5868
- return buildToolResponse(agent);
6610
+ return buildToolResponse(agent, void 0, { submission_id: submissionId });
5869
6611
  }
5870
6612
  }),
5871
6613
  resume_agent: tool({
5872
- description: "Resumes a subagent run when queued input is available.",
6614
+ description: "Resume a previously closed agent by id so it can receive send_input and wait calls.",
5873
6615
  inputSchema: resumeAgentSchema,
5874
6616
  execute: async (input) => {
5875
6617
  const agentId = resolveAgentIdValue(input.agent_id, input.id);
@@ -5878,10 +6620,11 @@ function createSubagentToolController(options) {
5878
6620
  }
5879
6621
  const agent = requireAgent(agents, agentId);
5880
6622
  if (agent.status === "closed") {
5881
- setNotification(agent, "already_closed", `Subagent ${agent.id} is already closed.`);
6623
+ agent.status = "idle";
6624
+ setNotification(agent, "resumed", `Resumed subagent ${agent.id}.`);
5882
6625
  return buildToolResponse(agent, {
5883
- notification: "already_closed",
5884
- message: `Subagent ${agent.id} is already closed.`
6626
+ notification: "resumed",
6627
+ message: `Resumed subagent ${agent.id}.`
5885
6628
  });
5886
6629
  }
5887
6630
  const outcome = startRun(agent, options);
@@ -5900,41 +6643,42 @@ function createSubagentToolController(options) {
5900
6643
  }
5901
6644
  }),
5902
6645
  wait: tool({
5903
- description: "Waits for a running subagent to change state or until timeout. Returns current status.",
6646
+ description: "Wait for agents to reach a final status. Completed statuses may include the agent's final message. Returns empty status when timed out. Once the agent reaches a final status, a notification message will be received containing the same completed status.",
5904
6647
  inputSchema: waitSchema,
5905
6648
  execute: async (input) => {
5906
- const usesIdsArray = Array.isArray(input.ids) && input.ids.length > 0;
5907
6649
  const ids = resolveAgentIdList(input.agent_id, input.id, input.ids);
5908
6650
  if (ids.length === 0) {
5909
- throw new Error("wait requires agent_id/id or ids.");
6651
+ throw new Error("ids must be non-empty");
6652
+ }
6653
+ if (typeof input.timeout_ms === "number" && input.timeout_ms <= 0) {
6654
+ throw new Error("timeout_ms must be greater than zero");
5910
6655
  }
5911
6656
  const timeoutMs = normalizeInteger(
5912
6657
  input.timeout_ms,
5913
6658
  options.config.defaultWaitTimeoutMs,
5914
- 1,
6659
+ options.config.minWaitTimeoutMs,
5915
6660
  options.config.maxWaitTimeoutMs
5916
6661
  );
5917
- if (usesIdsArray) {
5918
- const status = await waitForAnyAgentStatus(agents, ids, timeoutMs);
5919
- return { status, timed_out: Object.keys(status).length === 0, timeout_ms: timeoutMs };
5920
- }
5921
- const agent = requireAgent(agents, ids[0]);
5922
- if (agent.status === "running") {
5923
- const completed = await waitUntilNotRunning(agent, timeoutMs);
5924
- if (!completed) {
5925
- setNotification(
5926
- agent,
5927
- "timeout",
5928
- `Timed out after ${timeoutMs}ms while waiting for ${agent.id}.`
5929
- );
5930
- return buildToolResponse(agent, void 0, { timed_out: true, timeout_ms: timeoutMs });
5931
- }
6662
+ const status = await waitForAnyAgentStatus(agents, ids, timeoutMs);
6663
+ const timedOut = Object.keys(status).length === 0;
6664
+ if (timedOut && ids.length === 1) {
6665
+ const agent = requireAgent(agents, ids[0]);
6666
+ setNotification(
6667
+ agent,
6668
+ "timeout",
6669
+ `Timed out after ${timeoutMs}ms while waiting for ${agent.id}.`
6670
+ );
5932
6671
  }
5933
- return buildToolResponse(agent, void 0, { timed_out: false, timeout_ms: timeoutMs });
6672
+ return {
6673
+ status,
6674
+ status_summary: summarizeAgentStatuses(status),
6675
+ timed_out: timedOut,
6676
+ timeout_ms: timeoutMs
6677
+ };
5934
6678
  }
5935
6679
  }),
5936
6680
  close_agent: tool({
5937
- description: "Closes a subagent and aborts its current run if it is still running.",
6681
+ description: "Close an agent when it is no longer needed and return its last known status.",
5938
6682
  inputSchema: closeSchema,
5939
6683
  execute: async (input) => {
5940
6684
  const agentId = resolveAgentIdValue(input.agent_id, input.id);
@@ -5946,7 +6690,7 @@ function createSubagentToolController(options) {
5946
6690
  setNotification(agent, "already_closed", `Subagent ${agent.id} is already closed.`);
5947
6691
  return buildToolResponse(agent, void 0, { cancelled: false });
5948
6692
  }
5949
- const cancelled = closeSubagent(agent, `Closed ${agent.id}.`);
6693
+ const cancelled = closeSubagent(agent, `Closed ${agent.id}.`, options);
5950
6694
  return buildToolResponse(
5951
6695
  agent,
5952
6696
  { notification: "closed", message: `Closed ${agent.id}.` },
@@ -5961,7 +6705,7 @@ function createSubagentToolController(options) {
5961
6705
  const running = [];
5962
6706
  for (const agent of agents.values()) {
5963
6707
  if (agent.status !== "closed") {
5964
- closeSubagent(agent, `Parent agent loop closed ${agent.id}.`);
6708
+ closeSubagent(agent, `Parent agent loop closed ${agent.id}.`, options);
5965
6709
  }
5966
6710
  if (agent.runningPromise) {
5967
6711
  running.push(agent.runningPromise);
@@ -5976,7 +6720,7 @@ function createSubagentToolController(options) {
5976
6720
  function requireAgent(agents, id) {
5977
6721
  const agent = agents.get(id);
5978
6722
  if (!agent) {
5979
- throw new Error(`Unknown subagent id: ${id}`);
6723
+ throw new Error(`agent with id ${id} not found`);
5980
6724
  }
5981
6725
  return agent;
5982
6726
  }
@@ -5995,17 +6739,33 @@ function resolveAgentIdList(agentId, idAlias, ids) {
5995
6739
  const single = resolveAgentIdValue(agentId, idAlias);
5996
6740
  return single ? [single] : [];
5997
6741
  }
5998
- function resolvePromptValue(prompt, message, items) {
5999
- const promptValue = prompt?.trim();
6000
- if (promptValue) {
6001
- return promptValue;
6742
+ function resolveCollabInputText(params) {
6743
+ const textCandidate = params.textCandidates.find(
6744
+ (candidate) => candidate.value !== void 0 && candidate.value !== null
6745
+ );
6746
+ const hasText = Boolean(textCandidate);
6747
+ const hasItems = params.items !== void 0 && params.items !== null;
6748
+ if (hasText && hasItems) {
6749
+ throw new Error(params.bothError);
6002
6750
  }
6003
- const messageValue = message?.trim();
6004
- if (messageValue) {
6005
- return messageValue;
6751
+ if (!hasText && !hasItems) {
6752
+ throw new Error(params.missingError);
6006
6753
  }
6007
- const itemText = resolveInputItemsText(items);
6008
- return itemText ?? "";
6754
+ if (hasText) {
6755
+ const value = textCandidate?.value?.trim();
6756
+ if (!value) {
6757
+ throw new Error(params.emptyTextError);
6758
+ }
6759
+ return value;
6760
+ }
6761
+ if (!params.items || params.items.length === 0) {
6762
+ throw new Error(params.emptyItemsError);
6763
+ }
6764
+ const itemText = resolveInputItemsText(params.items);
6765
+ if (!itemText) {
6766
+ throw new Error(params.emptyItemsError);
6767
+ }
6768
+ return itemText;
6009
6769
  }
6010
6770
  function resolveInputItemsText(items) {
6011
6771
  if (!items || items.length === 0) {
@@ -6021,9 +6781,28 @@ function resolveInputItemsText(items) {
6021
6781
  const name = typeof item.name === "string" ? item.name.trim() : "";
6022
6782
  const path6 = typeof item.path === "string" ? item.path.trim() : "";
6023
6783
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
6024
- const compact = [itemType, name, path6 || imageUrl].filter(Boolean).join(" ");
6025
- if (compact) {
6026
- lines.push(compact);
6784
+ if (itemType === "image") {
6785
+ lines.push("[image]");
6786
+ continue;
6787
+ }
6788
+ if (itemType === "local_image" && path6) {
6789
+ lines.push(`[local_image:${path6}]`);
6790
+ continue;
6791
+ }
6792
+ if (itemType === "skill" && name && path6) {
6793
+ lines.push(`[skill:$${name}](${path6})`);
6794
+ continue;
6795
+ }
6796
+ if (itemType === "mention" && name && path6) {
6797
+ lines.push(`[mention:$${name}](${path6})`);
6798
+ continue;
6799
+ }
6800
+ if (path6 || imageUrl) {
6801
+ lines.push(`[${itemType || "input"}:${path6 || imageUrl}]`);
6802
+ continue;
6803
+ }
6804
+ if (name) {
6805
+ lines.push(`[${itemType || "input"}:${name}]`);
6027
6806
  }
6028
6807
  }
6029
6808
  if (lines.length === 0) {
@@ -6098,7 +6877,12 @@ function startRun(agent, options) {
6098
6877
  }
6099
6878
  const input = [...agent.history, { role: "user", content: nextInput }];
6100
6879
  const abortController = new AbortController();
6880
+ const runStartedAtMs = Date.now();
6101
6881
  agent.abortController = abortController;
6882
+ if (agent.firstRunStartedAtMs === void 0) {
6883
+ agent.firstRunStartedAtMs = runStartedAtMs;
6884
+ }
6885
+ agent.lastRunStartedAtMs = runStartedAtMs;
6102
6886
  agent.lastError = void 0;
6103
6887
  setLifecycle(
6104
6888
  agent,
@@ -6130,6 +6914,7 @@ function startRun(agent, options) {
6130
6914
  "run_completed",
6131
6915
  `Subagent ${agent.id} completed run ${agent.turns}.`
6132
6916
  );
6917
+ emitBackgroundNotification(agent, options);
6133
6918
  } catch (error) {
6134
6919
  if (agent.status === "closed") {
6135
6920
  return;
@@ -6141,7 +6926,11 @@ function startRun(agent, options) {
6141
6926
  const message = toErrorMessage(error);
6142
6927
  agent.lastError = message;
6143
6928
  setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
6929
+ emitBackgroundNotification(agent, options);
6144
6930
  } finally {
6931
+ const runCompletedAtMs = Date.now();
6932
+ agent.lastRunCompletedAtMs = runCompletedAtMs;
6933
+ agent.lastRunDurationMs = Math.max(0, runCompletedAtMs - runStartedAtMs);
6145
6934
  agent.runningPromise = void 0;
6146
6935
  agent.abortController = void 0;
6147
6936
  }
@@ -6149,30 +6938,16 @@ function startRun(agent, options) {
6149
6938
  agent.runningPromise = runPromise;
6150
6939
  return "started";
6151
6940
  }
6152
- function closeSubagent(agent, message) {
6941
+ function closeSubagent(agent, message, options) {
6153
6942
  const cancelled = Boolean(agent.runningPromise);
6154
6943
  agent.pendingInputs = [];
6155
6944
  if (agent.abortController) {
6156
6945
  agent.abortController.abort("close_agent");
6157
6946
  }
6158
6947
  setLifecycle(agent, "closed", "closed", message);
6948
+ emitBackgroundNotification(agent, options);
6159
6949
  return cancelled;
6160
6950
  }
6161
- async function waitUntilNotRunning(agent, timeoutMs) {
6162
- const deadline = Date.now() + timeoutMs;
6163
- while (agent.status === "running") {
6164
- const remaining = deadline - Date.now();
6165
- if (remaining <= 0) {
6166
- return false;
6167
- }
6168
- const currentVersion = agent.version;
6169
- const changed = await waitForVersionChange(agent, currentVersion, remaining);
6170
- if (!changed) {
6171
- return false;
6172
- }
6173
- }
6174
- return true;
6175
- }
6176
6951
  async function waitForVersionChange(agent, version, timeoutMs) {
6177
6952
  if (agent.version !== version) {
6178
6953
  return true;
@@ -6210,6 +6985,8 @@ function buildToolResponse(agent, override, extra = {}) {
6210
6985
  function buildSnapshot(agent) {
6211
6986
  return {
6212
6987
  agent_id: agent.id,
6988
+ ...agent.nickname ? { nickname: agent.nickname } : {},
6989
+ agent_role: agent.agentRole,
6213
6990
  status: agent.status,
6214
6991
  depth: agent.depth,
6215
6992
  model: agent.model,
@@ -6217,6 +6994,13 @@ function buildSnapshot(agent) {
6217
6994
  turns: agent.turns,
6218
6995
  created_at: new Date(agent.createdAtMs).toISOString(),
6219
6996
  updated_at: new Date(agent.updatedAtMs).toISOString(),
6997
+ ...agent.firstRunStartedAtMs ? {
6998
+ first_run_started_at: new Date(agent.firstRunStartedAtMs).toISOString(),
6999
+ spawn_startup_latency_ms: Math.max(0, agent.firstRunStartedAtMs - agent.createdAtMs)
7000
+ } : {},
7001
+ ...agent.lastRunStartedAtMs ? { last_run_started_at: new Date(agent.lastRunStartedAtMs).toISOString() } : {},
7002
+ ...agent.lastRunCompletedAtMs ? { last_run_completed_at: new Date(agent.lastRunCompletedAtMs).toISOString() } : {},
7003
+ ...typeof agent.lastRunDurationMs === "number" ? { last_run_duration_ms: Math.max(0, agent.lastRunDurationMs) } : {},
6220
7004
  ...agent.lastError ? { last_error: agent.lastError } : {},
6221
7005
  ...agent.lastResult ? {
6222
7006
  last_result: {
@@ -6228,6 +7012,83 @@ function buildSnapshot(agent) {
6228
7012
  } : {}
6229
7013
  };
6230
7014
  }
7015
+ function emitBackgroundNotification(agent, options) {
7016
+ if (!options?.onBackgroundMessage) {
7017
+ return;
7018
+ }
7019
+ if (!isBackgroundNotification(agent.notification)) {
7020
+ return;
7021
+ }
7022
+ const payload = {
7023
+ agent_id: agent.id,
7024
+ status: buildSnapshot(agent)
7025
+ };
7026
+ const body = JSON.stringify(payload);
7027
+ try {
7028
+ options.onBackgroundMessage(
7029
+ `${SUBAGENT_NOTIFICATION_OPEN_TAG}${body}${SUBAGENT_NOTIFICATION_CLOSE_TAG}`
7030
+ );
7031
+ } catch {
7032
+ }
7033
+ }
7034
+ function isBackgroundNotification(notification) {
7035
+ return notification === "run_completed" || notification === "run_failed" || notification === "closed";
7036
+ }
7037
+ function summarizeAgentStatuses(status) {
7038
+ const summary = {};
7039
+ for (const [agentId, snapshot] of Object.entries(status)) {
7040
+ const value = snapshot.status;
7041
+ summary[agentId] = typeof value === "string" ? value : "unknown";
7042
+ }
7043
+ return summary;
7044
+ }
7045
+ function buildSpawnAgentTypeDescription() {
7046
+ const sections = BUILT_IN_AGENT_TYPES.map((name) => {
7047
+ const description = BUILT_IN_AGENT_TYPE_DESCRIPTIONS[name];
7048
+ return `${name}: {
7049
+ ${description}
7050
+ }`;
7051
+ });
7052
+ return [
7053
+ `Optional type name for the new agent. If omitted, \`${DEFAULT_AGENT_TYPE}\` is used.`,
7054
+ "Available roles:",
7055
+ ...sections
7056
+ ].join("\n");
7057
+ }
7058
+ function resolveAgentType(agentType) {
7059
+ const requestedRoleName = trimToUndefined(agentType) ?? DEFAULT_AGENT_TYPE;
7060
+ const roleName = requestedRoleName;
7061
+ const description = BUILT_IN_AGENT_TYPE_DESCRIPTIONS[roleName];
7062
+ if (!description) {
7063
+ throw new Error(`unknown agent_type '${requestedRoleName}'`);
7064
+ }
7065
+ return {
7066
+ roleName,
7067
+ roleInstructions: BUILT_IN_AGENT_TYPE_INSTRUCTIONS[roleName]
7068
+ };
7069
+ }
7070
+ function reserveAgentNickname(roleName, counts) {
7071
+ const prefixByRole = {
7072
+ default: "Agent",
7073
+ researcher: "Researcher",
7074
+ worker: "Worker",
7075
+ reviewer: "Reviewer"
7076
+ };
7077
+ const prefix = prefixByRole[roleName] ?? "Agent";
7078
+ const next = (counts.get(prefix) ?? 0) + 1;
7079
+ counts.set(prefix, next);
7080
+ return `${prefix}_${next}`;
7081
+ }
7082
+ function joinInstructionBlocks(...blocks) {
7083
+ const parts = blocks.map(trimToUndefined).filter((value) => Boolean(value));
7084
+ if (parts.length === 0) {
7085
+ return void 0;
7086
+ }
7087
+ return parts.join("\n\n");
7088
+ }
7089
+ function randomSubmissionId() {
7090
+ return `sub_${randomBytes2(6).toString("hex")}`;
7091
+ }
6231
7092
  function normalizeInteger(value, fallback, min, max) {
6232
7093
  const parsed = Number.isFinite(value) ? Math.floor(value) : fallback;
6233
7094
  return Math.max(min, Math.min(max, parsed));
@@ -6966,29 +7827,33 @@ var DEFAULT_MAX_LINE_LENGTH = 500;
6966
7827
  var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
6967
7828
  var DEFAULT_TAB_WIDTH = 4;
6968
7829
  var codexReadFileInputSchema = z6.object({
6969
- file_path: z6.string().min(1).describe("Absolute path to the file"),
6970
- offset: z6.number().int().min(1).optional().describe("The line number to start reading from. Must be 1 or greater."),
6971
- limit: z6.number().int().min(1).optional().describe("The maximum number of lines to return."),
6972
- mode: z6.enum(["slice", "indentation"]).optional().describe('Optional mode selector: "slice" (default) or "indentation".'),
7830
+ file_path: z6.string().min(1).describe(
7831
+ "Path to the file (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7832
+ ),
7833
+ offset: z6.number().int().min(1).nullish().describe("The line number to start reading from. Must be 1 or greater."),
7834
+ limit: z6.number().int().min(1).nullish().describe("The maximum number of lines to return."),
7835
+ mode: z6.enum(["slice", "indentation"]).nullish().describe('Optional mode selector: "slice" (default) or "indentation".'),
6973
7836
  indentation: z6.object({
6974
- anchor_line: z6.number().int().min(1).optional(),
6975
- max_levels: z6.number().int().min(0).optional(),
6976
- include_siblings: z6.boolean().optional(),
6977
- include_header: z6.boolean().optional(),
6978
- max_lines: z6.number().int().min(1).optional()
6979
- }).optional()
7837
+ anchor_line: z6.number().int().min(1).nullish(),
7838
+ max_levels: z6.number().int().min(0).nullish(),
7839
+ include_siblings: z6.boolean().nullish(),
7840
+ include_header: z6.boolean().nullish(),
7841
+ max_lines: z6.number().int().min(1).nullish()
7842
+ }).nullish()
6980
7843
  });
6981
7844
  var codexListDirInputSchema = z6.object({
6982
- dir_path: z6.string().min(1).describe("Absolute path to the directory to list."),
6983
- offset: z6.number().int().min(1).optional().describe("The entry number to start listing from. Must be 1 or greater."),
6984
- limit: z6.number().int().min(1).optional().describe("The maximum number of entries to return."),
6985
- depth: z6.number().int().min(1).optional().describe("The maximum directory depth to traverse. Must be 1 or greater.")
7845
+ dir_path: z6.string().min(1).describe(
7846
+ "Path to the directory to list (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7847
+ ),
7848
+ offset: z6.number().int().min(1).nullish().describe("The entry number to start listing from. Must be 1 or greater."),
7849
+ limit: z6.number().int().min(1).nullish().describe("The maximum number of entries to return."),
7850
+ depth: z6.number().int().min(1).nullish().describe("The maximum directory depth to traverse. Must be 1 or greater.")
6986
7851
  });
6987
7852
  var codexGrepFilesInputSchema = z6.object({
6988
7853
  pattern: z6.string().min(1).describe("Regular expression pattern to search for."),
6989
- include: z6.string().optional().describe('Optional glob limiting searched files (for example "*.rs").'),
6990
- path: z6.string().optional().describe("Directory or file path to search. Defaults to cwd."),
6991
- limit: z6.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
7854
+ include: z6.string().nullish().describe('Optional glob limiting searched files (for example "*.rs").'),
7855
+ path: z6.string().nullish().describe("Directory or file path to search. Defaults to cwd."),
7856
+ limit: z6.number().int().min(1).nullish().describe("Maximum number of file paths to return (defaults to 100).")
6992
7857
  });
6993
7858
  var applyPatchInputSchema = z6.object({
6994
7859
  input: z6.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
@@ -7223,9 +8088,6 @@ function createGlobTool(options = {}) {
7223
8088
  }
7224
8089
  async function readFileCodex(input, options) {
7225
8090
  const runtime = resolveRuntime(options);
7226
- if (!path5.isAbsolute(input.file_path)) {
7227
- throw new Error("file_path must be an absolute path");
7228
- }
7229
8091
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
7230
8092
  await runAccessHook2(runtime, {
7231
8093
  cwd: runtime.cwd,
@@ -7268,15 +8130,12 @@ async function readFileCodex(input, options) {
7268
8130
  maxLevels: indentation.max_levels ?? 0,
7269
8131
  includeSiblings: indentation.include_siblings ?? false,
7270
8132
  includeHeader: indentation.include_header ?? true,
7271
- maxLines: indentation.max_lines
8133
+ maxLines: indentation.max_lines ?? void 0
7272
8134
  });
7273
8135
  return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
7274
8136
  }
7275
8137
  async function listDirectoryCodex(input, options) {
7276
8138
  const runtime = resolveRuntime(options);
7277
- if (!path5.isAbsolute(input.dir_path)) {
7278
- throw new Error("dir_path must be an absolute path");
7279
- }
7280
8139
  const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
7281
8140
  await runAccessHook2(runtime, {
7282
8141
  cwd: runtime.cwd,
@@ -7304,7 +8163,7 @@ async function listDirectoryCodex(input, options) {
7304
8163
  const remaining = entries.length - startIndex;
7305
8164
  const cappedLimit = Math.min(limit, remaining);
7306
8165
  const selected = entries.slice(startIndex, startIndex + cappedLimit);
7307
- const output = [`Absolute path: ${dirPath}`];
8166
+ const output = [`Absolute path: ${toSandboxDisplayPath(dirPath, runtime.cwd)}`];
7308
8167
  for (const entry of selected) {
7309
8168
  output.push(formatListEntry(entry));
7310
8169
  }
@@ -7690,10 +8549,17 @@ function mapApplyPatchAction(action) {
7690
8549
  }
7691
8550
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
7692
8551
  const absolutePath = path5.isAbsolute(inputPath) ? path5.resolve(inputPath) : path5.resolve(cwd, inputPath);
7693
- if (!allowOutsideCwd && !isPathInsideCwd2(absolutePath, cwd)) {
7694
- throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
8552
+ if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
8553
+ return absolutePath;
7695
8554
  }
7696
- return absolutePath;
8555
+ if (path5.isAbsolute(inputPath)) {
8556
+ const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
8557
+ const sandboxRootedPath = path5.resolve(cwd, sandboxRelativePath);
8558
+ if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
8559
+ return sandboxRootedPath;
8560
+ }
8561
+ }
8562
+ throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
7697
8563
  }
7698
8564
  function isPathInsideCwd2(candidatePath, cwd) {
7699
8565
  const relative = path5.relative(cwd, candidatePath);
@@ -7709,6 +8575,16 @@ function toDisplayPath2(absolutePath, cwd) {
7709
8575
  }
7710
8576
  return absolutePath;
7711
8577
  }
8578
+ function toSandboxDisplayPath(absolutePath, cwd) {
8579
+ const relative = path5.relative(cwd, absolutePath);
8580
+ if (relative === "") {
8581
+ return "/";
8582
+ }
8583
+ if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
8584
+ return `/${normalizeSlashes(relative)}`;
8585
+ }
8586
+ return normalizeSlashes(absolutePath);
8587
+ }
7712
8588
  function splitLines(content) {
7713
8589
  const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7714
8590
  const lines = normalized.split("\n");
@@ -8046,7 +8922,71 @@ function isNoEntError(error) {
8046
8922
 
8047
8923
  // src/agent.ts
8048
8924
  async function runAgentLoop(request) {
8049
- return await runAgentLoopInternal(request, { depth: 0 });
8925
+ const telemetry = createAgentTelemetrySession(request.telemetry);
8926
+ try {
8927
+ return await runAgentLoopInternal(request, { depth: 0, telemetry });
8928
+ } finally {
8929
+ await telemetry?.flush();
8930
+ }
8931
+ }
8932
+ function mergeAbortSignals2(first, second) {
8933
+ if (!first) {
8934
+ return second;
8935
+ }
8936
+ if (!second) {
8937
+ return first;
8938
+ }
8939
+ const controller = new AbortController();
8940
+ const abortFrom = (signal) => {
8941
+ if (!controller.signal.aborted) {
8942
+ controller.abort(signal.reason);
8943
+ }
8944
+ };
8945
+ if (first.aborted) {
8946
+ abortFrom(first);
8947
+ } else {
8948
+ first.addEventListener("abort", () => abortFrom(first), { once: true });
8949
+ }
8950
+ if (second.aborted) {
8951
+ abortFrom(second);
8952
+ } else {
8953
+ second.addEventListener("abort", () => abortFrom(second), { once: true });
8954
+ }
8955
+ return controller.signal;
8956
+ }
8957
+ function streamAgentLoop(request) {
8958
+ const queue = createAsyncQueue();
8959
+ const abortController = new AbortController();
8960
+ const steering = request.steering ?? createToolLoopSteeringChannel();
8961
+ const signal = mergeAbortSignals2(request.signal, abortController.signal);
8962
+ const sourceOnEvent = request.onEvent;
8963
+ const result = (async () => {
8964
+ try {
8965
+ const output = await runAgentLoop({
8966
+ ...request,
8967
+ steering,
8968
+ ...signal ? { signal } : {},
8969
+ onEvent: (event) => {
8970
+ sourceOnEvent?.(event);
8971
+ queue.push(event);
8972
+ }
8973
+ });
8974
+ queue.close();
8975
+ return output;
8976
+ } catch (error) {
8977
+ const err = error instanceof Error ? error : new Error(String(error));
8978
+ queue.fail(err);
8979
+ throw err;
8980
+ }
8981
+ })();
8982
+ return {
8983
+ events: queue.iterable,
8984
+ result,
8985
+ append: steering.append,
8986
+ steer: steering.steer,
8987
+ pendingSteeringCount: steering.pendingCount,
8988
+ abort: () => abortController.abort()
8989
+ };
8050
8990
  }
8051
8991
  async function runAgentLoopInternal(request, context) {
8052
8992
  const {
@@ -8056,19 +8996,28 @@ async function runAgentLoopInternal(request, context) {
8056
8996
  subagentTool,
8057
8997
  subagent_tool,
8058
8998
  subagents,
8999
+ telemetry,
8059
9000
  ...toolLoopRequest
8060
9001
  } = request;
9002
+ const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
9003
+ const runId = randomRunId();
9004
+ const startedAtMs = Date.now();
9005
+ const steeringChannel = toolLoopRequest.steering ?? createToolLoopSteeringChannel();
9006
+ const toolLoopRequestWithSteering = toolLoopRequest.steering === steeringChannel ? toolLoopRequest : { ...toolLoopRequest, steering: steeringChannel };
8061
9007
  const filesystemSelection = filesystemTool ?? filesystem_tool;
8062
9008
  const subagentSelection = subagentTool ?? subagent_tool ?? subagents;
8063
9009
  const filesystemTools = resolveFilesystemTools(request.model, filesystemSelection);
8064
9010
  const resolvedSubagentConfig = resolveSubagentToolConfig(subagentSelection, context.depth);
8065
9011
  const subagentController = createSubagentController({
9012
+ runId,
8066
9013
  model: request.model,
8067
9014
  depth: context.depth,
9015
+ telemetry: telemetrySession,
8068
9016
  customTools: customTools ?? {},
8069
9017
  filesystemSelection,
8070
9018
  subagentSelection,
8071
- toolLoopRequest,
9019
+ toolLoopRequest: toolLoopRequestWithSteering,
9020
+ steering: steeringChannel,
8072
9021
  resolvedSubagentConfig
8073
9022
  });
8074
9023
  const mergedTools = mergeToolSets(
@@ -8081,16 +9030,58 @@ async function runAgentLoopInternal(request, context) {
8081
9030
  );
8082
9031
  }
8083
9032
  const instructions = buildLoopInstructions(
8084
- toolLoopRequest.instructions,
9033
+ toolLoopRequestWithSteering.instructions,
8085
9034
  resolvedSubagentConfig,
8086
9035
  context.depth
8087
9036
  );
9037
+ const emitTelemetry = createAgentTelemetryEmitter({
9038
+ session: telemetrySession,
9039
+ runId,
9040
+ parentRunId: context.parentRunId,
9041
+ depth: context.depth,
9042
+ model: request.model
9043
+ });
9044
+ emitTelemetry({
9045
+ type: "agent.run.started",
9046
+ inputMode: typeof request.input === "string" ? "string" : "messages",
9047
+ customToolCount: Object.keys(customTools ?? {}).length,
9048
+ mergedToolCount: Object.keys(mergedTools).length,
9049
+ filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
9050
+ subagentToolsEnabled: resolvedSubagentConfig.enabled
9051
+ });
9052
+ const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
9053
+ const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
9054
+ const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
9055
+ sourceOnEvent?.(event);
9056
+ if (includeLlmStreamEvents) {
9057
+ emitTelemetry({ type: "agent.run.stream", event });
9058
+ }
9059
+ } : void 0;
8088
9060
  try {
8089
- return await runToolLoop({
8090
- ...toolLoopRequest,
9061
+ const result = await runToolLoop({
9062
+ ...toolLoopRequestWithSteering,
8091
9063
  ...instructions ? { instructions } : {},
9064
+ ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
8092
9065
  tools: mergedTools
8093
9066
  });
9067
+ emitTelemetry({
9068
+ type: "agent.run.completed",
9069
+ success: true,
9070
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9071
+ stepCount: result.steps.length,
9072
+ toolCallCount: countToolCalls(result),
9073
+ totalCostUsd: result.totalCostUsd,
9074
+ usage: summarizeResultUsage(result)
9075
+ });
9076
+ return result;
9077
+ } catch (error) {
9078
+ emitTelemetry({
9079
+ type: "agent.run.completed",
9080
+ success: false,
9081
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9082
+ error: toErrorMessage2(error)
9083
+ });
9084
+ throw error;
8094
9085
  } finally {
8095
9086
  await subagentController?.closeAll();
8096
9087
  }
@@ -8136,6 +9127,10 @@ function createSubagentController(params) {
8136
9127
  config: params.resolvedSubagentConfig,
8137
9128
  parentDepth: params.depth,
8138
9129
  parentModel: params.resolvedSubagentConfig.model ?? params.model,
9130
+ forkContextMessages: normalizeForkContextMessages(params.toolLoopRequest.input),
9131
+ onBackgroundMessage: (message) => {
9132
+ params.steering?.append({ role: "user", content: message });
9133
+ },
8139
9134
  buildChildInstructions: (spawnInstructions, childDepth) => buildChildInstructions(spawnInstructions, params.resolvedSubagentConfig, childDepth),
8140
9135
  runSubagent: async (subagentRequest) => {
8141
9136
  const childCustomTools = params.resolvedSubagentConfig.inheritTools ? params.customTools : {};
@@ -8153,7 +9148,11 @@ function createSubagentController(params) {
8153
9148
  openAiReasoningEffort: params.toolLoopRequest.openAiReasoningEffort,
8154
9149
  signal: subagentRequest.signal
8155
9150
  },
8156
- { depth: params.depth + 1 }
9151
+ {
9152
+ depth: params.depth + 1,
9153
+ parentRunId: params.runId,
9154
+ telemetry: params.telemetry
9155
+ }
8157
9156
  );
8158
9157
  }
8159
9158
  });
@@ -8200,10 +9199,142 @@ function buildChildInstructions(spawnInstructions, config, childDepth) {
8200
9199
  }
8201
9200
  return blocks.length > 0 ? blocks.join("\n\n") : void 0;
8202
9201
  }
9202
+ function normalizeForkContextMessages(input) {
9203
+ if (typeof input === "string") {
9204
+ return [{ role: "user", content: input }];
9205
+ }
9206
+ return input.map((message) => ({
9207
+ role: message.role,
9208
+ content: Array.isArray(message.content) ? [...message.content] : message.content
9209
+ }));
9210
+ }
8203
9211
  function trimToUndefined2(value) {
8204
9212
  const trimmed = value?.trim();
8205
9213
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
8206
9214
  }
9215
+ function randomRunId() {
9216
+ return randomBytes3(8).toString("hex");
9217
+ }
9218
+ function toIsoNow() {
9219
+ return (/* @__PURE__ */ new Date()).toISOString();
9220
+ }
9221
+ function toErrorMessage2(error) {
9222
+ if (error instanceof Error && error.message) {
9223
+ return error.message;
9224
+ }
9225
+ if (typeof error === "string") {
9226
+ return error;
9227
+ }
9228
+ return "Unknown error";
9229
+ }
9230
+ function countToolCalls(result) {
9231
+ let count = 0;
9232
+ for (const step of result.steps) {
9233
+ count += step.toolCalls.length;
9234
+ }
9235
+ return count;
9236
+ }
9237
+ function sumUsageValue(current, next) {
9238
+ if (typeof next !== "number" || !Number.isFinite(next)) {
9239
+ return current;
9240
+ }
9241
+ const normalizedNext = Math.max(0, next);
9242
+ if (typeof current !== "number" || !Number.isFinite(current)) {
9243
+ return normalizedNext;
9244
+ }
9245
+ return Math.max(0, current) + normalizedNext;
9246
+ }
9247
+ function summarizeResultUsage(result) {
9248
+ let summary;
9249
+ for (const step of result.steps) {
9250
+ const usage = step.usage;
9251
+ if (!usage) {
9252
+ continue;
9253
+ }
9254
+ summary = {
9255
+ promptTokens: sumUsageValue(summary?.promptTokens, usage.promptTokens),
9256
+ cachedTokens: sumUsageValue(summary?.cachedTokens, usage.cachedTokens),
9257
+ responseTokens: sumUsageValue(summary?.responseTokens, usage.responseTokens),
9258
+ responseImageTokens: sumUsageValue(summary?.responseImageTokens, usage.responseImageTokens),
9259
+ thinkingTokens: sumUsageValue(summary?.thinkingTokens, usage.thinkingTokens),
9260
+ totalTokens: sumUsageValue(summary?.totalTokens, usage.totalTokens),
9261
+ toolUsePromptTokens: sumUsageValue(summary?.toolUsePromptTokens, usage.toolUsePromptTokens)
9262
+ };
9263
+ }
9264
+ return summary;
9265
+ }
9266
+ function isPromiseLike(value) {
9267
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
9268
+ }
9269
+ function isAgentTelemetrySink(value) {
9270
+ return typeof value === "object" && value !== null && typeof value.emit === "function";
9271
+ }
9272
+ function resolveTelemetrySelection(telemetry) {
9273
+ if (!telemetry) {
9274
+ return void 0;
9275
+ }
9276
+ if (isAgentTelemetrySink(telemetry)) {
9277
+ return { sink: telemetry };
9278
+ }
9279
+ if (isAgentTelemetrySink(telemetry.sink)) {
9280
+ return telemetry;
9281
+ }
9282
+ throw new Error("Invalid runAgentLoop telemetry config: expected a sink with emit(event).");
9283
+ }
9284
+ function createAgentTelemetrySession(telemetry) {
9285
+ const config = resolveTelemetrySelection(telemetry);
9286
+ if (!config) {
9287
+ return void 0;
9288
+ }
9289
+ const pending = /* @__PURE__ */ new Set();
9290
+ const trackPromise = (promise) => {
9291
+ pending.add(promise);
9292
+ promise.finally(() => {
9293
+ pending.delete(promise);
9294
+ });
9295
+ };
9296
+ const emit = (event) => {
9297
+ try {
9298
+ const output = config.sink.emit(event);
9299
+ if (isPromiseLike(output)) {
9300
+ const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
9301
+ trackPromise(task);
9302
+ }
9303
+ } catch {
9304
+ }
9305
+ };
9306
+ const flush = async () => {
9307
+ while (pending.size > 0) {
9308
+ await Promise.allSettled([...pending]);
9309
+ }
9310
+ if (typeof config.sink.flush === "function") {
9311
+ try {
9312
+ await config.sink.flush();
9313
+ } catch {
9314
+ }
9315
+ }
9316
+ };
9317
+ return {
9318
+ includeLlmStreamEvents: config.includeLlmStreamEvents === true,
9319
+ emit,
9320
+ flush
9321
+ };
9322
+ }
9323
+ function createAgentTelemetryEmitter(params) {
9324
+ return (event) => {
9325
+ if (!params.session) {
9326
+ return;
9327
+ }
9328
+ params.session.emit({
9329
+ ...event,
9330
+ timestamp: toIsoNow(),
9331
+ runId: params.runId,
9332
+ ...params.parentRunId ? { parentRunId: params.parentRunId } : {},
9333
+ depth: params.depth,
9334
+ model: params.model
9335
+ });
9336
+ };
9337
+ }
8207
9338
  export {
8208
9339
  CHATGPT_MODEL_IDS,
8209
9340
  CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
@@ -8226,6 +9357,7 @@ export {
8226
9357
  appendMarkdownSourcesSection,
8227
9358
  applyPatch,
8228
9359
  configureGemini,
9360
+ configureModelConcurrency,
8229
9361
  convertGooglePartsToLlmParts,
8230
9362
  createApplyPatchTool,
8231
9363
  createCodexApplyPatchTool,
@@ -8245,6 +9377,7 @@ export {
8245
9377
  createReadFilesTool,
8246
9378
  createReplaceTool,
8247
9379
  createRgSearchTool,
9380
+ createToolLoopSteeringChannel,
8248
9381
  createWriteFileTool,
8249
9382
  customTool,
8250
9383
  encodeChatGptAuthJson,
@@ -8270,13 +9403,16 @@ export {
8270
9403
  loadLocalEnv,
8271
9404
  parseJsonFromLlmText,
8272
9405
  refreshChatGptOauthToken,
9406
+ resetModelConcurrencyConfig,
8273
9407
  resolveFilesystemToolProfile,
8274
9408
  resolveFireworksModelId,
8275
9409
  runAgentLoop,
8276
9410
  runToolLoop,
8277
9411
  sanitisePartForLogging,
9412
+ streamAgentLoop,
8278
9413
  streamJson,
8279
9414
  streamText,
9415
+ streamToolLoop,
8280
9416
  stripCodexCitationMarkers,
8281
9417
  toGeminiJsonSchema,
8282
9418
  tool