@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.cjs CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  appendMarkdownSourcesSection: () => appendMarkdownSourcesSection,
52
52
  applyPatch: () => applyPatch,
53
53
  configureGemini: () => configureGemini,
54
+ configureModelConcurrency: () => configureModelConcurrency,
54
55
  convertGooglePartsToLlmParts: () => convertGooglePartsToLlmParts,
55
56
  createApplyPatchTool: () => createApplyPatchTool,
56
57
  createCodexApplyPatchTool: () => createCodexApplyPatchTool,
@@ -70,6 +71,7 @@ __export(index_exports, {
70
71
  createReadFilesTool: () => createReadFilesTool,
71
72
  createReplaceTool: () => createReplaceTool,
72
73
  createRgSearchTool: () => createRgSearchTool,
74
+ createToolLoopSteeringChannel: () => createToolLoopSteeringChannel,
73
75
  createWriteFileTool: () => createWriteFileTool,
74
76
  customTool: () => customTool,
75
77
  encodeChatGptAuthJson: () => encodeChatGptAuthJson,
@@ -95,13 +97,16 @@ __export(index_exports, {
95
97
  loadLocalEnv: () => loadLocalEnv,
96
98
  parseJsonFromLlmText: () => parseJsonFromLlmText,
97
99
  refreshChatGptOauthToken: () => refreshChatGptOauthToken,
100
+ resetModelConcurrencyConfig: () => resetModelConcurrencyConfig,
98
101
  resolveFilesystemToolProfile: () => resolveFilesystemToolProfile,
99
102
  resolveFireworksModelId: () => resolveFireworksModelId,
100
103
  runAgentLoop: () => runAgentLoop,
101
104
  runToolLoop: () => runToolLoop,
102
105
  sanitisePartForLogging: () => sanitisePartForLogging,
106
+ streamAgentLoop: () => streamAgentLoop,
103
107
  streamJson: () => streamJson,
104
108
  streamText: () => streamText,
109
+ streamToolLoop: () => streamToolLoop,
105
110
  stripCodexCitationMarkers: () => stripCodexCitationMarkers,
106
111
  toGeminiJsonSchema: () => toGeminiJsonSchema,
107
112
  tool: () => tool
@@ -1693,23 +1698,16 @@ function parseEventBlock(raw) {
1693
1698
  var MIN_MODEL_CONCURRENCY_CAP = 1;
1694
1699
  var MAX_MODEL_CONCURRENCY_CAP = 64;
1695
1700
  var DEFAULT_MODEL_CONCURRENCY_CAP = 3;
1696
- function parsePositiveInteger(raw) {
1697
- if (raw === void 0) {
1698
- return void 0;
1699
- }
1700
- const normalized = raw.trim();
1701
- if (!normalized) {
1702
- return void 0;
1703
- }
1704
- if (!/^-?\d+$/u.test(normalized)) {
1705
- return void 0;
1706
- }
1707
- const parsed = Number.parseInt(normalized, 10);
1708
- if (!Number.isFinite(parsed)) {
1709
- return void 0;
1710
- }
1711
- return parsed;
1712
- }
1701
+ var DEFAULT_OPENAI_MODEL_CONCURRENCY_CAP = 12;
1702
+ var DEFAULT_GOOGLE_MODEL_CONCURRENCY_CAP = 4;
1703
+ var DEFAULT_GOOGLE_PREVIEW_MODEL_CONCURRENCY_CAP = 2;
1704
+ var DEFAULT_FIREWORKS_MODEL_CONCURRENCY_CAP = 6;
1705
+ var MODEL_CONCURRENCY_PROVIDERS = [
1706
+ "openai",
1707
+ "google",
1708
+ "fireworks"
1709
+ ];
1710
+ var configuredModelConcurrency = normalizeModelConcurrencyConfig({});
1713
1711
  function clampModelConcurrencyCap(value) {
1714
1712
  if (!Number.isFinite(value)) {
1715
1713
  return DEFAULT_MODEL_CONCURRENCY_CAP;
@@ -1723,30 +1721,94 @@ function clampModelConcurrencyCap(value) {
1723
1721
  }
1724
1722
  return rounded;
1725
1723
  }
1726
- function normalizeModelIdForEnv(modelId) {
1727
- return modelId.trim().replace(/[^A-Za-z0-9]+/gu, "_").replace(/^_+|_+$/gu, "").toUpperCase();
1724
+ function normalizeModelIdForConfig(modelId) {
1725
+ return modelId.trim().toLowerCase();
1728
1726
  }
1729
- function resolveModelConcurrencyCap(options) {
1730
- const env = options.env ?? process.env;
1731
- const providerPrefix = options.providerEnvPrefix;
1732
- const defaultCap = clampModelConcurrencyCap(options.defaultCap ?? DEFAULT_MODEL_CONCURRENCY_CAP);
1733
- const normalizedModelId = options.modelId ? normalizeModelIdForEnv(options.modelId) : "";
1734
- const candidateKeys = [
1735
- ...normalizedModelId ? [
1736
- `${providerPrefix}_MAX_PARALLEL_REQUESTS_MODEL_${normalizedModelId}`,
1737
- `LLM_MAX_PARALLEL_REQUESTS_MODEL_${normalizedModelId}`
1738
- ] : [],
1739
- `${providerPrefix}_MAX_PARALLEL_REQUESTS_PER_MODEL`,
1740
- "LLM_MAX_PARALLEL_REQUESTS_PER_MODEL"
1741
- ];
1742
- for (const key of candidateKeys) {
1743
- const parsed = parsePositiveInteger(env[key]);
1744
- if (parsed === void 0) {
1727
+ function normalizeCap(value) {
1728
+ if (value === void 0 || !Number.isFinite(value)) {
1729
+ return void 0;
1730
+ }
1731
+ return clampModelConcurrencyCap(value);
1732
+ }
1733
+ function normalizeModelCapMap(caps) {
1734
+ const normalized = /* @__PURE__ */ new Map();
1735
+ if (!caps) {
1736
+ return normalized;
1737
+ }
1738
+ for (const [modelId, cap] of Object.entries(caps)) {
1739
+ const modelKey = normalizeModelIdForConfig(modelId);
1740
+ if (!modelKey) {
1745
1741
  continue;
1746
1742
  }
1747
- return clampModelConcurrencyCap(parsed);
1743
+ const normalizedCap = normalizeCap(cap);
1744
+ if (normalizedCap === void 0) {
1745
+ continue;
1746
+ }
1747
+ normalized.set(modelKey, normalizedCap);
1748
+ }
1749
+ return normalized;
1750
+ }
1751
+ function normalizeModelConcurrencyConfig(config) {
1752
+ const providerCaps = {};
1753
+ const providerModelCaps = {
1754
+ openai: /* @__PURE__ */ new Map(),
1755
+ google: /* @__PURE__ */ new Map(),
1756
+ fireworks: /* @__PURE__ */ new Map()
1757
+ };
1758
+ for (const provider of MODEL_CONCURRENCY_PROVIDERS) {
1759
+ const providerCap = normalizeCap(config.providerCaps?.[provider]);
1760
+ if (providerCap !== void 0) {
1761
+ providerCaps[provider] = providerCap;
1762
+ }
1763
+ providerModelCaps[provider] = new Map(
1764
+ normalizeModelCapMap(config.providerModelCaps?.[provider])
1765
+ );
1766
+ }
1767
+ return {
1768
+ globalCap: normalizeCap(config.globalCap),
1769
+ providerCaps,
1770
+ modelCaps: normalizeModelCapMap(config.modelCaps),
1771
+ providerModelCaps
1772
+ };
1773
+ }
1774
+ function resolveDefaultProviderCap(provider, modelId) {
1775
+ if (provider === "openai") {
1776
+ return DEFAULT_OPENAI_MODEL_CONCURRENCY_CAP;
1777
+ }
1778
+ if (provider === "google") {
1779
+ return modelId?.includes("preview") ? DEFAULT_GOOGLE_PREVIEW_MODEL_CONCURRENCY_CAP : DEFAULT_GOOGLE_MODEL_CONCURRENCY_CAP;
1780
+ }
1781
+ return DEFAULT_FIREWORKS_MODEL_CONCURRENCY_CAP;
1782
+ }
1783
+ function configureModelConcurrency(config = {}) {
1784
+ configuredModelConcurrency = normalizeModelConcurrencyConfig(config);
1785
+ }
1786
+ function resetModelConcurrencyConfig() {
1787
+ configuredModelConcurrency = normalizeModelConcurrencyConfig({});
1788
+ }
1789
+ function resolveModelConcurrencyCap(options) {
1790
+ const modelId = options.modelId ? normalizeModelIdForConfig(options.modelId) : void 0;
1791
+ const config = options.config ? normalizeModelConcurrencyConfig(options.config) : configuredModelConcurrency;
1792
+ const providerModelCap = modelId ? config.providerModelCaps[options.provider].get(modelId) : void 0;
1793
+ if (providerModelCap !== void 0) {
1794
+ return providerModelCap;
1795
+ }
1796
+ const modelCap = modelId ? config.modelCaps.get(modelId) : void 0;
1797
+ if (modelCap !== void 0) {
1798
+ return modelCap;
1748
1799
  }
1749
- return defaultCap;
1800
+ const providerCap = config.providerCaps[options.provider];
1801
+ if (providerCap !== void 0) {
1802
+ return providerCap;
1803
+ }
1804
+ if (config.globalCap !== void 0) {
1805
+ return config.globalCap;
1806
+ }
1807
+ const defaultCap = normalizeCap(options.defaultCap);
1808
+ if (defaultCap !== void 0) {
1809
+ return defaultCap;
1810
+ }
1811
+ return resolveDefaultProviderCap(options.provider, modelId);
1750
1812
  }
1751
1813
 
1752
1814
  // src/utils/scheduler.ts
@@ -1855,12 +1917,20 @@ function createCallScheduler(options = {}) {
1855
1917
  release?.();
1856
1918
  }
1857
1919
  }
1858
- async function attemptWithRetries(fn, attempt) {
1920
+ async function attemptWithRetries(fn, attempt, state) {
1859
1921
  try {
1922
+ const spacingStartedAtMs = Date.now();
1860
1923
  await applyStartSpacing();
1924
+ const callStartedAtMs = Date.now();
1925
+ state.schedulerDelayMs += Math.max(0, callStartedAtMs - spacingStartedAtMs);
1926
+ if (state.startedAtMs === void 0) {
1927
+ state.startedAtMs = callStartedAtMs;
1928
+ }
1929
+ state.attempts = Math.max(state.attempts, attempt);
1861
1930
  return await fn();
1862
1931
  } catch (error) {
1863
1932
  if (isOverloadError2(error)) {
1933
+ state.overloadCount += 1;
1864
1934
  consecutiveSuccesses = 0;
1865
1935
  currentParallelLimit = Math.max(1, Math.ceil(currentParallelLimit / 2));
1866
1936
  }
@@ -1877,9 +1947,10 @@ function createCallScheduler(options = {}) {
1877
1947
  }
1878
1948
  const normalizedDelay = Math.max(0, delay);
1879
1949
  if (normalizedDelay > 0) {
1950
+ state.retryDelayMs += normalizedDelay;
1880
1951
  await sleep(normalizedDelay);
1881
1952
  }
1882
- return attemptWithRetries(fn, attempt + 1);
1953
+ return attemptWithRetries(fn, attempt + 1, state);
1883
1954
  }
1884
1955
  }
1885
1956
  function drainQueue() {
@@ -1892,11 +1963,22 @@ function createCallScheduler(options = {}) {
1892
1963
  void task();
1893
1964
  }
1894
1965
  }
1895
- function run(fn) {
1966
+ function run(fn, runOptions = {}) {
1896
1967
  return new Promise((resolve, reject) => {
1968
+ const enqueuedAtMs = Date.now();
1897
1969
  const job = async () => {
1970
+ const dequeuedAtMs = Date.now();
1971
+ const state = {
1972
+ enqueuedAtMs,
1973
+ dequeuedAtMs,
1974
+ schedulerDelayMs: 0,
1975
+ retryDelayMs: 0,
1976
+ attempts: 0,
1977
+ overloadCount: 0
1978
+ };
1898
1979
  try {
1899
- const result = await attemptWithRetries(fn, 1);
1980
+ const result = await attemptWithRetries(fn, 1, state);
1981
+ state.completedAtMs = Date.now();
1900
1982
  consecutiveSuccesses += 1;
1901
1983
  if (currentParallelLimit < maxParallelRequests && consecutiveSuccesses >= increaseAfterConsecutiveSuccesses) {
1902
1984
  currentParallelLimit += 1;
@@ -1904,8 +1986,26 @@ function createCallScheduler(options = {}) {
1904
1986
  }
1905
1987
  resolve(result);
1906
1988
  } catch (error) {
1989
+ state.completedAtMs = Date.now();
1907
1990
  reject(toError(error));
1908
1991
  } finally {
1992
+ const startedAtMs = state.startedAtMs ?? state.dequeuedAtMs;
1993
+ const completedAtMs = state.completedAtMs ?? Date.now();
1994
+ const metrics = {
1995
+ enqueuedAtMs: state.enqueuedAtMs,
1996
+ dequeuedAtMs: state.dequeuedAtMs,
1997
+ startedAtMs,
1998
+ completedAtMs,
1999
+ queueWaitMs: Math.max(0, state.dequeuedAtMs - state.enqueuedAtMs),
2000
+ schedulerDelayMs: Math.max(0, state.schedulerDelayMs),
2001
+ retryDelayMs: Math.max(0, state.retryDelayMs),
2002
+ attempts: Math.max(1, state.attempts),
2003
+ overloadCount: Math.max(0, state.overloadCount)
2004
+ };
2005
+ try {
2006
+ runOptions.onSettled?.(metrics);
2007
+ } catch {
2008
+ }
1909
2009
  activeCount -= 1;
1910
2010
  queueMicrotask(drainQueue);
1911
2011
  }
@@ -2002,7 +2102,7 @@ function getSchedulerForModel(modelId) {
2002
2102
  }
2003
2103
  const created = createCallScheduler({
2004
2104
  maxParallelRequests: resolveModelConcurrencyCap({
2005
- providerEnvPrefix: "FIREWORKS",
2105
+ provider: "fireworks",
2006
2106
  modelId: normalizedModelId
2007
2107
  }),
2008
2108
  minIntervalBetweenStartMs: 200,
@@ -2011,8 +2111,8 @@ function getSchedulerForModel(modelId) {
2011
2111
  schedulerByModel.set(schedulerKey, created);
2012
2112
  return created;
2013
2113
  }
2014
- async function runFireworksCall(fn, modelId) {
2015
- return getSchedulerForModel(modelId).run(async () => fn(getFireworksClient()));
2114
+ async function runFireworksCall(fn, modelId, runOptions) {
2115
+ return getSchedulerForModel(modelId).run(async () => fn(getFireworksClient()), runOptions);
2016
2116
  }
2017
2117
 
2018
2118
  // src/fireworks/models.ts
@@ -2378,7 +2478,7 @@ function getSchedulerForModel2(modelId) {
2378
2478
  }
2379
2479
  const created = createCallScheduler({
2380
2480
  maxParallelRequests: resolveModelConcurrencyCap({
2381
- providerEnvPrefix: "GOOGLE",
2481
+ provider: "google",
2382
2482
  modelId: normalizedModelId
2383
2483
  }),
2384
2484
  minIntervalBetweenStartMs: 200,
@@ -2398,8 +2498,8 @@ function getSchedulerForModel2(modelId) {
2398
2498
  schedulerByModel2.set(schedulerKey, created);
2399
2499
  return created;
2400
2500
  }
2401
- async function runGeminiCall(fn, modelId) {
2402
- return getSchedulerForModel2(modelId).run(async () => fn(await getGeminiClient()));
2501
+ async function runGeminiCall(fn, modelId, runOptions) {
2502
+ return getSchedulerForModel2(modelId).run(async () => fn(await getGeminiClient()), runOptions);
2403
2503
  }
2404
2504
 
2405
2505
  // src/openai/client.ts
@@ -2571,7 +2671,7 @@ function getSchedulerForModel3(modelId) {
2571
2671
  }
2572
2672
  const created = createCallScheduler({
2573
2673
  maxParallelRequests: resolveModelConcurrencyCap({
2574
- providerEnvPrefix: "OPENAI",
2674
+ provider: "openai",
2575
2675
  modelId: normalizedModelId
2576
2676
  }),
2577
2677
  minIntervalBetweenStartMs: 200,
@@ -2580,8 +2680,8 @@ function getSchedulerForModel3(modelId) {
2580
2680
  schedulerByModel3.set(schedulerKey, created);
2581
2681
  return created;
2582
2682
  }
2583
- async function runOpenAiCall(fn, modelId) {
2584
- return getSchedulerForModel3(modelId).run(async () => fn(getOpenAiClient()));
2683
+ async function runOpenAiCall(fn, modelId, runOptions) {
2684
+ return getSchedulerForModel3(modelId).run(async () => fn(getOpenAiClient()), runOptions);
2585
2685
  }
2586
2686
 
2587
2687
  // src/openai/models.ts
@@ -3035,9 +3135,9 @@ function isRetryableChatGptTransportError(error) {
3035
3135
  return false;
3036
3136
  }
3037
3137
  const message = error.message.toLowerCase();
3038
- return message === "terminated" || message.includes("socket hang up") || message.includes("fetch failed") || message.includes("network");
3138
+ return message === "terminated" || message.includes("socket hang up") || message.includes("fetch failed") || message.includes("network") || message.includes("responses websocket");
3039
3139
  }
3040
- async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 2) {
3140
+ async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 3) {
3041
3141
  let attempt = 1;
3042
3142
  while (true) {
3043
3143
  try {
@@ -3942,77 +4042,153 @@ function buildToolErrorOutput(message, issues) {
3942
4042
  }
3943
4043
  return output;
3944
4044
  }
4045
+ var SUBAGENT_WAIT_TOOL_NAME = "wait";
4046
+ function toIsoTimestamp(ms) {
4047
+ return new Date(ms).toISOString();
4048
+ }
4049
+ function toToolResultDuration(result) {
4050
+ return typeof result.durationMs === "number" && Number.isFinite(result.durationMs) ? Math.max(0, result.durationMs) : 0;
4051
+ }
4052
+ function schedulerMetricsOrDefault(metrics) {
4053
+ if (!metrics) {
4054
+ return {
4055
+ queueWaitMs: 0,
4056
+ schedulerDelayMs: 0,
4057
+ providerRetryDelayMs: 0,
4058
+ providerAttempts: 1
4059
+ };
4060
+ }
4061
+ return {
4062
+ queueWaitMs: Math.max(0, metrics.queueWaitMs),
4063
+ schedulerDelayMs: Math.max(0, metrics.schedulerDelayMs),
4064
+ providerRetryDelayMs: Math.max(0, metrics.retryDelayMs),
4065
+ providerAttempts: Math.max(1, metrics.attempts),
4066
+ modelCallStartedAtMs: metrics.startedAtMs
4067
+ };
4068
+ }
4069
+ function buildStepTiming(params) {
4070
+ const scheduler = schedulerMetricsOrDefault(params.schedulerMetrics);
4071
+ const modelCallStartedAtMs = scheduler.modelCallStartedAtMs ?? params.stepStartedAtMs;
4072
+ const firstModelEventAtMs = params.firstModelEventAtMs;
4073
+ const effectiveFirstEventAtMs = firstModelEventAtMs !== void 0 ? Math.max(modelCallStartedAtMs, firstModelEventAtMs) : params.modelCompletedAtMs;
4074
+ const connectionSetupMs = Math.max(0, effectiveFirstEventAtMs - modelCallStartedAtMs);
4075
+ const activeGenerationMs = Math.max(0, params.modelCompletedAtMs - effectiveFirstEventAtMs);
4076
+ return {
4077
+ startedAt: toIsoTimestamp(params.stepStartedAtMs),
4078
+ completedAt: toIsoTimestamp(params.stepCompletedAtMs),
4079
+ totalMs: Math.max(0, params.stepCompletedAtMs - params.stepStartedAtMs),
4080
+ queueWaitMs: scheduler.queueWaitMs,
4081
+ connectionSetupMs,
4082
+ activeGenerationMs,
4083
+ toolExecutionMs: Math.max(0, params.toolExecutionMs),
4084
+ waitToolMs: Math.max(0, params.waitToolMs),
4085
+ schedulerDelayMs: scheduler.schedulerDelayMs,
4086
+ providerRetryDelayMs: scheduler.providerRetryDelayMs,
4087
+ providerAttempts: scheduler.providerAttempts
4088
+ };
4089
+ }
4090
+ function extractSpawnStartupMetrics(outputPayload) {
4091
+ if (!outputPayload || typeof outputPayload !== "object") {
4092
+ return void 0;
4093
+ }
4094
+ const outputRecord = outputPayload;
4095
+ const notification = typeof outputRecord.notification === "string" ? outputRecord.notification : "";
4096
+ if (notification !== "spawned") {
4097
+ return void 0;
4098
+ }
4099
+ const agent = outputRecord.agent;
4100
+ if (!agent || typeof agent !== "object") {
4101
+ return void 0;
4102
+ }
4103
+ const agentRecord = agent;
4104
+ const startupLatencyMs = agentRecord.spawn_startup_latency_ms;
4105
+ if (typeof startupLatencyMs !== "number" || !Number.isFinite(startupLatencyMs)) {
4106
+ return void 0;
4107
+ }
4108
+ return {
4109
+ spawnStartupLatencyMs: Math.max(0, startupLatencyMs)
4110
+ };
4111
+ }
3945
4112
  async function executeToolCall(params) {
3946
4113
  const { callKind, toolName, tool: tool2, rawInput, parseError } = params;
3947
- if (!tool2) {
3948
- const message = `Unknown tool: ${toolName}`;
4114
+ const startedAtMs = Date.now();
4115
+ const finalize = (base, outputPayload, metrics) => {
4116
+ const completedAtMs = Date.now();
3949
4117
  return {
3950
- result: { toolName, input: rawInput, output: { error: message }, error: message },
3951
- outputPayload: buildToolErrorOutput(message)
4118
+ result: {
4119
+ ...base,
4120
+ startedAt: toIsoTimestamp(startedAtMs),
4121
+ completedAt: toIsoTimestamp(completedAtMs),
4122
+ durationMs: Math.max(0, completedAtMs - startedAtMs),
4123
+ ...metrics ? { metrics } : {}
4124
+ },
4125
+ outputPayload
3952
4126
  };
4127
+ };
4128
+ if (!tool2) {
4129
+ const message = `Unknown tool: ${toolName}`;
4130
+ const outputPayload = buildToolErrorOutput(message);
4131
+ return finalize(
4132
+ { toolName, input: rawInput, output: outputPayload, error: message },
4133
+ outputPayload
4134
+ );
3953
4135
  }
3954
4136
  if (callKind === "custom") {
3955
4137
  if (!isCustomTool(tool2)) {
3956
4138
  const message = `Tool ${toolName} was called as custom_tool_call but is declared as function.`;
3957
4139
  const outputPayload = buildToolErrorOutput(message);
3958
- return {
3959
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4140
+ return finalize(
4141
+ { toolName, input: rawInput, output: outputPayload, error: message },
3960
4142
  outputPayload
3961
- };
4143
+ );
3962
4144
  }
3963
4145
  const input = typeof rawInput === "string" ? rawInput : String(rawInput ?? "");
3964
4146
  try {
3965
4147
  const output = await tool2.execute(input);
3966
- return {
3967
- result: { toolName, input, output },
3968
- outputPayload: output
3969
- };
4148
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4149
+ return finalize({ toolName, input, output }, output, metrics);
3970
4150
  } catch (error) {
3971
4151
  const message = error instanceof Error ? error.message : String(error);
3972
4152
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
3973
- return {
3974
- result: { toolName, input, output: outputPayload, error: message },
3975
- outputPayload
3976
- };
4153
+ return finalize({ toolName, input, output: outputPayload, error: message }, outputPayload);
3977
4154
  }
3978
4155
  }
3979
4156
  if (isCustomTool(tool2)) {
3980
4157
  const message = `Tool ${toolName} was called as function_call but is declared as custom.`;
3981
4158
  const outputPayload = buildToolErrorOutput(message);
3982
- return {
3983
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4159
+ return finalize(
4160
+ { toolName, input: rawInput, output: outputPayload, error: message },
3984
4161
  outputPayload
3985
- };
4162
+ );
3986
4163
  }
3987
4164
  if (parseError) {
3988
4165
  const message = `Invalid JSON for tool ${toolName}: ${parseError}`;
3989
- return {
3990
- result: { toolName, input: rawInput, output: { error: message }, error: message },
3991
- outputPayload: buildToolErrorOutput(message)
3992
- };
4166
+ const outputPayload = buildToolErrorOutput(message);
4167
+ return finalize(
4168
+ { toolName, input: rawInput, output: outputPayload, error: message },
4169
+ outputPayload
4170
+ );
3993
4171
  }
3994
4172
  const parsed = tool2.inputSchema.safeParse(rawInput);
3995
4173
  if (!parsed.success) {
3996
4174
  const message = `Invalid tool arguments for ${toolName}: ${formatZodIssues(parsed.error.issues)}`;
3997
4175
  const outputPayload = buildToolErrorOutput(message, parsed.error.issues);
3998
- return {
3999
- result: { toolName, input: rawInput, output: outputPayload, error: message },
4176
+ return finalize(
4177
+ { toolName, input: rawInput, output: outputPayload, error: message },
4000
4178
  outputPayload
4001
- };
4179
+ );
4002
4180
  }
4003
4181
  try {
4004
4182
  const output = await tool2.execute(parsed.data);
4005
- return {
4006
- result: { toolName, input: parsed.data, output },
4007
- outputPayload: output
4008
- };
4183
+ const metrics = toolName === "spawn_agent" ? extractSpawnStartupMetrics(output) : void 0;
4184
+ return finalize({ toolName, input: parsed.data, output }, output, metrics);
4009
4185
  } catch (error) {
4010
4186
  const message = error instanceof Error ? error.message : String(error);
4011
4187
  const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
4012
- return {
4013
- result: { toolName, input: parsed.data, output: outputPayload, error: message },
4188
+ return finalize(
4189
+ { toolName, input: parsed.data, output: outputPayload, error: message },
4014
4190
  outputPayload
4015
- };
4191
+ );
4016
4192
  }
4017
4193
  }
4018
4194
  function buildToolLogId(turn, toolIndex) {
@@ -4735,6 +4911,102 @@ var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
4735
4911
  function resolveToolLoopContents(input) {
4736
4912
  return resolveTextContents(input);
4737
4913
  }
4914
+ var toolLoopSteeringInternals = /* @__PURE__ */ new WeakMap();
4915
+ function createToolLoopSteeringChannel() {
4916
+ const pending = [];
4917
+ let closed = false;
4918
+ const channel = {
4919
+ append: (input) => {
4920
+ if (closed) {
4921
+ return { accepted: false, queuedCount: pending.length };
4922
+ }
4923
+ const normalized = normalizeToolLoopSteeringInput(input);
4924
+ if (normalized.length === 0) {
4925
+ return { accepted: false, queuedCount: pending.length };
4926
+ }
4927
+ pending.push(...normalized);
4928
+ return { accepted: true, queuedCount: pending.length };
4929
+ },
4930
+ steer: (input) => channel.append(input),
4931
+ pendingCount: () => pending.length,
4932
+ close: () => {
4933
+ if (closed) {
4934
+ return;
4935
+ }
4936
+ closed = true;
4937
+ pending.length = 0;
4938
+ }
4939
+ };
4940
+ const internalState = {
4941
+ drainPendingContents: () => {
4942
+ if (pending.length === 0) {
4943
+ return [];
4944
+ }
4945
+ return pending.splice(0, pending.length);
4946
+ },
4947
+ close: channel.close
4948
+ };
4949
+ toolLoopSteeringInternals.set(channel, internalState);
4950
+ return channel;
4951
+ }
4952
+ function resolveToolLoopSteeringInternal(steering) {
4953
+ if (!steering) {
4954
+ return void 0;
4955
+ }
4956
+ const internal = toolLoopSteeringInternals.get(steering);
4957
+ if (!internal) {
4958
+ throw new Error(
4959
+ "Invalid tool loop steering channel. Use createToolLoopSteeringChannel() to construct one."
4960
+ );
4961
+ }
4962
+ return internal;
4963
+ }
4964
+ function normalizeToolLoopSteeringInput(input) {
4965
+ const messages = typeof input === "string" ? [{ role: "user", content: input }] : Array.isArray(input) ? input : [input];
4966
+ const normalized = [];
4967
+ for (const message of messages) {
4968
+ const role = message.role ?? "user";
4969
+ if (role !== "user") {
4970
+ throw new Error("Tool loop steering only accepts role='user' messages.");
4971
+ }
4972
+ if (typeof message.content === "string") {
4973
+ if (message.content.length === 0) {
4974
+ continue;
4975
+ }
4976
+ normalized.push({
4977
+ role: "user",
4978
+ parts: [{ type: "text", text: message.content }]
4979
+ });
4980
+ continue;
4981
+ }
4982
+ if (!Array.isArray(message.content) || message.content.length === 0) {
4983
+ continue;
4984
+ }
4985
+ const parts = [];
4986
+ for (const part of message.content) {
4987
+ if (part.type === "text") {
4988
+ parts.push({ type: "text", text: part.text });
4989
+ } else {
4990
+ parts.push({ type: "inlineData", data: part.data, mimeType: part.mimeType });
4991
+ }
4992
+ }
4993
+ if (parts.length > 0) {
4994
+ normalized.push({ role: "user", parts });
4995
+ }
4996
+ }
4997
+ return normalized;
4998
+ }
4999
+ function toChatGptAssistantMessage(text) {
5000
+ if (!text) {
5001
+ return void 0;
5002
+ }
5003
+ return {
5004
+ type: "message",
5005
+ role: "assistant",
5006
+ status: "completed",
5007
+ content: [{ type: "output_text", text }]
5008
+ };
5009
+ }
4738
5010
  function isCustomTool(toolDef) {
4739
5011
  return toolDef.type === "custom";
4740
5012
  }
@@ -4853,411 +5125,908 @@ async function runToolLoop(request) {
4853
5125
  }
4854
5126
  const maxSteps = Math.max(1, Math.floor(request.maxSteps ?? DEFAULT_TOOL_LOOP_MAX_STEPS));
4855
5127
  const providerInfo = resolveProvider(request.model);
5128
+ const steeringInternal = resolveToolLoopSteeringInternal(request.steering);
4856
5129
  const steps = [];
4857
5130
  let totalCostUsd = 0;
4858
5131
  let finalText = "";
4859
5132
  let finalThoughts = "";
4860
- if (providerInfo.provider === "openai") {
4861
- const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
4862
- const openAiNativeTools = toOpenAiTools(request.modelTools);
4863
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
4864
- const reasoningEffort = resolveOpenAiReasoningEffort(
4865
- providerInfo.model,
4866
- request.openAiReasoningEffort
4867
- );
4868
- const textConfig = {
4869
- format: { type: "text" },
4870
- verbosity: resolveOpenAiVerbosity(providerInfo.model)
4871
- };
4872
- const reasoning = {
4873
- effort: toOpenAiReasoningEffort(reasoningEffort),
4874
- summary: "detailed"
4875
- };
4876
- let previousResponseId;
4877
- let input = toOpenAiInput(contents);
4878
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
4879
- const turn = stepIndex + 1;
4880
- const abortController = new AbortController();
4881
- if (request.signal) {
4882
- if (request.signal.aborted) {
4883
- abortController.abort(request.signal.reason);
4884
- } else {
4885
- request.signal.addEventListener(
4886
- "abort",
4887
- () => abortController.abort(request.signal?.reason),
4888
- { once: true }
4889
- );
4890
- }
4891
- }
4892
- const onEvent = request.onEvent;
4893
- let modelVersion = request.model;
4894
- let usageTokens;
4895
- const emitEvent = (ev) => {
4896
- onEvent?.(ev);
5133
+ try {
5134
+ if (providerInfo.provider === "openai") {
5135
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
5136
+ const openAiNativeTools = toOpenAiTools(request.modelTools);
5137
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
5138
+ const reasoningEffort = resolveOpenAiReasoningEffort(
5139
+ providerInfo.model,
5140
+ request.openAiReasoningEffort
5141
+ );
5142
+ const textConfig = {
5143
+ format: { type: "text" },
5144
+ verbosity: resolveOpenAiVerbosity(providerInfo.model)
4897
5145
  };
4898
- const finalResponse = await runOpenAiCall(async (client) => {
4899
- const stream = client.responses.stream(
4900
- {
4901
- model: providerInfo.model,
4902
- input,
4903
- ...previousResponseId ? { previous_response_id: previousResponseId } : {},
4904
- ...openAiTools.length > 0 ? { tools: openAiTools } : {},
4905
- ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
4906
- reasoning,
4907
- text: textConfig,
4908
- include: ["reasoning.encrypted_content"]
5146
+ const reasoning = {
5147
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5148
+ summary: "detailed"
5149
+ };
5150
+ let previousResponseId;
5151
+ let input = toOpenAiInput(contents);
5152
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5153
+ const turn = stepIndex + 1;
5154
+ const stepStartedAtMs = Date.now();
5155
+ let firstModelEventAtMs;
5156
+ let schedulerMetrics;
5157
+ const abortController = new AbortController();
5158
+ if (request.signal) {
5159
+ if (request.signal.aborted) {
5160
+ abortController.abort(request.signal.reason);
5161
+ } else {
5162
+ request.signal.addEventListener(
5163
+ "abort",
5164
+ () => abortController.abort(request.signal?.reason),
5165
+ { once: true }
5166
+ );
5167
+ }
5168
+ }
5169
+ const onEvent = request.onEvent;
5170
+ let modelVersion = request.model;
5171
+ let usageTokens;
5172
+ let thoughtDeltaEmitted = false;
5173
+ const emitEvent = (ev) => {
5174
+ onEvent?.(ev);
5175
+ };
5176
+ const markFirstModelEvent = () => {
5177
+ if (firstModelEventAtMs === void 0) {
5178
+ firstModelEventAtMs = Date.now();
5179
+ }
5180
+ };
5181
+ const finalResponse = await runOpenAiCall(
5182
+ async (client) => {
5183
+ const stream = client.responses.stream(
5184
+ {
5185
+ model: providerInfo.model,
5186
+ input,
5187
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
5188
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
5189
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
5190
+ reasoning,
5191
+ text: textConfig,
5192
+ include: ["reasoning.encrypted_content"]
5193
+ },
5194
+ { signal: abortController.signal }
5195
+ );
5196
+ for await (const event of stream) {
5197
+ markFirstModelEvent();
5198
+ switch (event.type) {
5199
+ case "response.output_text.delta":
5200
+ emitEvent({
5201
+ type: "delta",
5202
+ channel: "response",
5203
+ text: typeof event.delta === "string" ? event.delta : ""
5204
+ });
5205
+ break;
5206
+ case "response.reasoning_summary_text.delta":
5207
+ thoughtDeltaEmitted = true;
5208
+ emitEvent({
5209
+ type: "delta",
5210
+ channel: "thought",
5211
+ text: typeof event.delta === "string" ? event.delta : ""
5212
+ });
5213
+ break;
5214
+ case "response.refusal.delta":
5215
+ emitEvent({ type: "blocked" });
5216
+ break;
5217
+ default:
5218
+ break;
5219
+ }
5220
+ }
5221
+ return await stream.finalResponse();
4909
5222
  },
4910
- { signal: abortController.signal }
5223
+ providerInfo.model,
5224
+ {
5225
+ onSettled: (metrics) => {
5226
+ schedulerMetrics = metrics;
5227
+ }
5228
+ }
4911
5229
  );
4912
- for await (const event of stream) {
4913
- switch (event.type) {
4914
- case "response.output_text.delta":
4915
- emitEvent({
4916
- type: "delta",
4917
- channel: "response",
4918
- text: typeof event.delta === "string" ? event.delta : ""
4919
- });
4920
- break;
4921
- case "response.reasoning_summary_text.delta":
4922
- emitEvent({
4923
- type: "delta",
4924
- channel: "thought",
4925
- text: typeof event.delta === "string" ? event.delta : ""
4926
- });
4927
- break;
4928
- case "response.refusal.delta":
4929
- emitEvent({ type: "blocked" });
4930
- break;
4931
- default:
4932
- break;
5230
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
5231
+ emitEvent({ type: "model", modelVersion });
5232
+ if (finalResponse.error) {
5233
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
5234
+ throw new Error(message);
5235
+ }
5236
+ usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
5237
+ const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
5238
+ const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
5239
+ if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
5240
+ emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
5241
+ }
5242
+ const modelCompletedAtMs = Date.now();
5243
+ const stepCostUsd = estimateCallCostUsd({
5244
+ modelId: modelVersion,
5245
+ tokens: usageTokens,
5246
+ responseImages: 0
5247
+ });
5248
+ totalCostUsd += stepCostUsd;
5249
+ if (usageTokens) {
5250
+ emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
5251
+ }
5252
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
5253
+ const stepToolCalls = [];
5254
+ if (responseToolCalls.length === 0) {
5255
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5256
+ const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
5257
+ finalText = responseText;
5258
+ finalThoughts = reasoningSummary;
5259
+ const stepCompletedAtMs2 = Date.now();
5260
+ const timing2 = buildStepTiming({
5261
+ stepStartedAtMs,
5262
+ stepCompletedAtMs: stepCompletedAtMs2,
5263
+ modelCompletedAtMs,
5264
+ firstModelEventAtMs,
5265
+ schedulerMetrics,
5266
+ toolExecutionMs: 0,
5267
+ waitToolMs: 0
5268
+ });
5269
+ steps.push({
5270
+ step: steps.length + 1,
5271
+ modelVersion,
5272
+ text: responseText || void 0,
5273
+ thoughts: reasoningSummary || void 0,
5274
+ toolCalls: [],
5275
+ usage: usageTokens,
5276
+ costUsd: stepCostUsd,
5277
+ timing: timing2
5278
+ });
5279
+ if (steeringItems2.length === 0) {
5280
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4933
5281
  }
5282
+ previousResponseId = finalResponse.id;
5283
+ input = steeringItems2;
5284
+ continue;
4934
5285
  }
4935
- return await stream.finalResponse();
4936
- }, providerInfo.model);
4937
- modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
4938
- emitEvent({ type: "model", modelVersion });
4939
- if (finalResponse.error) {
4940
- const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
4941
- throw new Error(message);
4942
- }
4943
- usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
4944
- const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
4945
- const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
4946
- const stepCostUsd = estimateCallCostUsd({
4947
- modelId: modelVersion,
4948
- tokens: usageTokens,
4949
- responseImages: 0
4950
- });
4951
- totalCostUsd += stepCostUsd;
4952
- if (usageTokens) {
4953
- emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
4954
- }
4955
- const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
4956
- const stepToolCalls = [];
4957
- if (responseToolCalls.length === 0) {
4958
- finalText = responseText;
4959
- finalThoughts = reasoningSummary;
5286
+ const callInputs = responseToolCalls.map((call, index) => {
5287
+ const toolIndex = index + 1;
5288
+ const toolId = buildToolLogId(turn, toolIndex);
5289
+ const toolName = call.name;
5290
+ if (call.kind === "custom") {
5291
+ return {
5292
+ call,
5293
+ toolName,
5294
+ value: call.input,
5295
+ parseError: void 0,
5296
+ toolId,
5297
+ turn,
5298
+ toolIndex
5299
+ };
5300
+ }
5301
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5302
+ return { call, toolName, value, parseError, toolId, turn, toolIndex };
5303
+ });
5304
+ for (const entry of callInputs) {
5305
+ emitEvent({
5306
+ type: "tool_call",
5307
+ phase: "started",
5308
+ turn: entry.turn,
5309
+ toolIndex: entry.toolIndex,
5310
+ toolName: entry.toolName,
5311
+ toolId: entry.toolId,
5312
+ callKind: entry.call.kind,
5313
+ callId: entry.call.call_id,
5314
+ input: entry.value
5315
+ });
5316
+ }
5317
+ const callResults = await Promise.all(
5318
+ callInputs.map(async (entry) => {
5319
+ return await toolCallContextStorage.run(
5320
+ {
5321
+ toolName: entry.toolName,
5322
+ toolId: entry.toolId,
5323
+ turn: entry.turn,
5324
+ toolIndex: entry.toolIndex
5325
+ },
5326
+ async () => {
5327
+ const { result, outputPayload } = await executeToolCall({
5328
+ callKind: entry.call.kind,
5329
+ toolName: entry.toolName,
5330
+ tool: request.tools[entry.toolName],
5331
+ rawInput: entry.value,
5332
+ parseError: entry.parseError
5333
+ });
5334
+ return { entry, result, outputPayload };
5335
+ }
5336
+ );
5337
+ })
5338
+ );
5339
+ const toolOutputs = [];
5340
+ let toolExecutionMs = 0;
5341
+ let waitToolMs = 0;
5342
+ for (const { entry, result, outputPayload } of callResults) {
5343
+ stepToolCalls.push({ ...result, callId: entry.call.call_id });
5344
+ const callDurationMs = toToolResultDuration(result);
5345
+ toolExecutionMs += callDurationMs;
5346
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5347
+ waitToolMs += callDurationMs;
5348
+ }
5349
+ emitEvent({
5350
+ type: "tool_call",
5351
+ phase: "completed",
5352
+ turn: entry.turn,
5353
+ toolIndex: entry.toolIndex,
5354
+ toolName: entry.toolName,
5355
+ toolId: entry.toolId,
5356
+ callKind: entry.call.kind,
5357
+ callId: entry.call.call_id,
5358
+ input: entry.value,
5359
+ output: result.output,
5360
+ error: result.error,
5361
+ durationMs: result.durationMs
5362
+ });
5363
+ if (entry.call.kind === "custom") {
5364
+ toolOutputs.push({
5365
+ type: "custom_tool_call_output",
5366
+ call_id: entry.call.call_id,
5367
+ output: mergeToolOutput(outputPayload)
5368
+ });
5369
+ } else {
5370
+ toolOutputs.push({
5371
+ type: "function_call_output",
5372
+ call_id: entry.call.call_id,
5373
+ output: mergeToolOutput(outputPayload)
5374
+ });
5375
+ }
5376
+ }
5377
+ const stepCompletedAtMs = Date.now();
5378
+ const timing = buildStepTiming({
5379
+ stepStartedAtMs,
5380
+ stepCompletedAtMs,
5381
+ modelCompletedAtMs,
5382
+ firstModelEventAtMs,
5383
+ schedulerMetrics,
5384
+ toolExecutionMs,
5385
+ waitToolMs
5386
+ });
4960
5387
  steps.push({
4961
5388
  step: steps.length + 1,
4962
5389
  modelVersion,
4963
5390
  text: responseText || void 0,
4964
5391
  thoughts: reasoningSummary || void 0,
4965
- toolCalls: [],
5392
+ toolCalls: stepToolCalls,
4966
5393
  usage: usageTokens,
4967
- costUsd: stepCostUsd
5394
+ costUsd: stepCostUsd,
5395
+ timing
4968
5396
  });
4969
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4970
- }
4971
- const callInputs = responseToolCalls.map((call, index) => {
4972
- const toolIndex = index + 1;
4973
- const toolId = buildToolLogId(turn, toolIndex);
4974
- const toolName = call.name;
4975
- if (call.kind === "custom") {
4976
- return {
4977
- call,
4978
- toolName,
4979
- value: call.input,
4980
- parseError: void 0,
4981
- toolId,
4982
- turn,
4983
- toolIndex
4984
- };
4985
- }
4986
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
4987
- return { call, toolName, value, parseError, toolId, turn, toolIndex };
4988
- });
4989
- const callResults = await Promise.all(
4990
- callInputs.map(async (entry) => {
4991
- return await toolCallContextStorage.run(
4992
- {
4993
- toolName: entry.toolName,
4994
- toolId: entry.toolId,
4995
- turn: entry.turn,
4996
- toolIndex: entry.toolIndex
5397
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5398
+ const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
5399
+ previousResponseId = finalResponse.id;
5400
+ input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
5401
+ }
5402
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5403
+ }
5404
+ if (providerInfo.provider === "chatgpt") {
5405
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
5406
+ const openAiNativeTools = toOpenAiTools(request.modelTools);
5407
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
5408
+ const reasoningEffort = resolveOpenAiReasoningEffort(
5409
+ request.model,
5410
+ request.openAiReasoningEffort
5411
+ );
5412
+ const toolLoopInput = toChatGptInput(contents);
5413
+ const conversationId = `tool-loop-${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
5414
+ const promptCacheKey = conversationId;
5415
+ let input = [...toolLoopInput.input];
5416
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5417
+ const turn = stepIndex + 1;
5418
+ const stepStartedAtMs = Date.now();
5419
+ let firstModelEventAtMs;
5420
+ let thoughtDeltaEmitted = false;
5421
+ const markFirstModelEvent = () => {
5422
+ if (firstModelEventAtMs === void 0) {
5423
+ firstModelEventAtMs = Date.now();
5424
+ }
5425
+ };
5426
+ const response = await collectChatGptCodexResponseWithRetry({
5427
+ sessionId: conversationId,
5428
+ request: {
5429
+ model: providerInfo.model,
5430
+ store: false,
5431
+ stream: true,
5432
+ instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
5433
+ input,
5434
+ prompt_cache_key: promptCacheKey,
5435
+ include: ["reasoning.encrypted_content"],
5436
+ tools: openAiTools,
5437
+ tool_choice: "auto",
5438
+ parallel_tool_calls: true,
5439
+ reasoning: {
5440
+ effort: toOpenAiReasoningEffort(reasoningEffort),
5441
+ summary: "detailed"
4997
5442
  },
4998
- async () => {
4999
- const { result, outputPayload } = await executeToolCall({
5000
- callKind: entry.call.kind,
5001
- toolName: entry.toolName,
5002
- tool: request.tools[entry.toolName],
5003
- rawInput: entry.value,
5004
- parseError: entry.parseError
5005
- });
5006
- return { entry, result, outputPayload };
5443
+ text: { verbosity: resolveOpenAiVerbosity(request.model) }
5444
+ },
5445
+ signal: request.signal,
5446
+ onDelta: (delta) => {
5447
+ if (delta.thoughtDelta) {
5448
+ markFirstModelEvent();
5449
+ thoughtDeltaEmitted = true;
5450
+ request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
5007
5451
  }
5008
- );
5009
- })
5010
- );
5011
- const toolOutputs = [];
5012
- for (const { entry, result, outputPayload } of callResults) {
5013
- stepToolCalls.push({ ...result, callId: entry.call.call_id });
5014
- if (entry.call.kind === "custom") {
5015
- toolOutputs.push({
5016
- type: "custom_tool_call_output",
5017
- call_id: entry.call.call_id,
5018
- output: mergeToolOutput(outputPayload)
5019
- });
5020
- } else {
5021
- toolOutputs.push({
5022
- type: "function_call_output",
5023
- call_id: entry.call.call_id,
5024
- output: mergeToolOutput(outputPayload)
5452
+ if (delta.textDelta) {
5453
+ markFirstModelEvent();
5454
+ request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
5455
+ }
5456
+ }
5457
+ });
5458
+ const modelCompletedAtMs = Date.now();
5459
+ const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
5460
+ const usageTokens = extractChatGptUsageTokens(response.usage);
5461
+ const stepCostUsd = estimateCallCostUsd({
5462
+ modelId: modelVersion,
5463
+ tokens: usageTokens,
5464
+ responseImages: 0
5465
+ });
5466
+ totalCostUsd += stepCostUsd;
5467
+ const responseText = (response.text ?? "").trim();
5468
+ const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
5469
+ if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
5470
+ request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
5471
+ }
5472
+ const responseToolCalls = response.toolCalls ?? [];
5473
+ if (responseToolCalls.length === 0) {
5474
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5475
+ const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
5476
+ finalText = responseText;
5477
+ finalThoughts = reasoningSummaryText;
5478
+ const stepCompletedAtMs2 = Date.now();
5479
+ const timing2 = buildStepTiming({
5480
+ stepStartedAtMs,
5481
+ stepCompletedAtMs: stepCompletedAtMs2,
5482
+ modelCompletedAtMs,
5483
+ firstModelEventAtMs,
5484
+ toolExecutionMs: 0,
5485
+ waitToolMs: 0
5025
5486
  });
5487
+ steps.push({
5488
+ step: steps.length + 1,
5489
+ modelVersion,
5490
+ text: responseText || void 0,
5491
+ thoughts: reasoningSummaryText || void 0,
5492
+ toolCalls: [],
5493
+ usage: usageTokens,
5494
+ costUsd: stepCostUsd,
5495
+ timing: timing2
5496
+ });
5497
+ if (steeringItems2.length === 0) {
5498
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5499
+ }
5500
+ const assistantItem = toChatGptAssistantMessage(responseText);
5501
+ input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
5502
+ continue;
5026
5503
  }
5027
- }
5028
- steps.push({
5029
- step: steps.length + 1,
5030
- modelVersion,
5031
- text: responseText || void 0,
5032
- thoughts: reasoningSummary || void 0,
5033
- toolCalls: stepToolCalls,
5034
- usage: usageTokens,
5035
- costUsd: stepCostUsd
5036
- });
5037
- previousResponseId = finalResponse.id;
5038
- input = toolOutputs;
5039
- }
5040
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5041
- }
5042
- if (providerInfo.provider === "chatgpt") {
5043
- const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
5044
- const openAiNativeTools = toOpenAiTools(request.modelTools);
5045
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
5046
- const reasoningEffort = resolveOpenAiReasoningEffort(
5047
- request.model,
5048
- request.openAiReasoningEffort
5049
- );
5050
- const toolLoopInput = toChatGptInput(contents);
5051
- const conversationId = `tool-loop-${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
5052
- const promptCacheKey = conversationId;
5053
- let input = [...toolLoopInput.input];
5054
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5055
- const turn = stepIndex + 1;
5056
- const response = await collectChatGptCodexResponseWithRetry({
5057
- sessionId: conversationId,
5058
- request: {
5059
- model: providerInfo.model,
5060
- store: false,
5061
- stream: true,
5062
- instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
5063
- input,
5064
- prompt_cache_key: promptCacheKey,
5065
- include: ["reasoning.encrypted_content"],
5066
- tools: openAiTools,
5067
- tool_choice: "auto",
5068
- parallel_tool_calls: true,
5069
- reasoning: {
5070
- effort: toOpenAiReasoningEffort(reasoningEffort),
5071
- summary: "detailed"
5072
- },
5073
- text: { verbosity: resolveOpenAiVerbosity(request.model) }
5074
- },
5075
- signal: request.signal,
5076
- onDelta: (delta) => {
5077
- if (delta.thoughtDelta) {
5078
- request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
5504
+ const toolCalls = [];
5505
+ const toolOutputs = [];
5506
+ const callInputs = responseToolCalls.map((call, index) => {
5507
+ const toolIndex = index + 1;
5508
+ const toolId = buildToolLogId(turn, toolIndex);
5509
+ const toolName = call.name;
5510
+ const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
5511
+ const ids = normalizeChatGptToolIds({
5512
+ callKind: call.kind,
5513
+ callId: call.callId,
5514
+ itemId: call.id
5515
+ });
5516
+ return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
5517
+ });
5518
+ for (const entry of callInputs) {
5519
+ request.onEvent?.({
5520
+ type: "tool_call",
5521
+ phase: "started",
5522
+ turn: entry.turn,
5523
+ toolIndex: entry.toolIndex,
5524
+ toolName: entry.toolName,
5525
+ toolId: entry.toolId,
5526
+ callKind: entry.call.kind,
5527
+ callId: entry.ids.callId,
5528
+ input: entry.value
5529
+ });
5530
+ }
5531
+ const callResults = await Promise.all(
5532
+ callInputs.map(async (entry) => {
5533
+ return await toolCallContextStorage.run(
5534
+ {
5535
+ toolName: entry.toolName,
5536
+ toolId: entry.toolId,
5537
+ turn: entry.turn,
5538
+ toolIndex: entry.toolIndex
5539
+ },
5540
+ async () => {
5541
+ const { result, outputPayload } = await executeToolCall({
5542
+ callKind: entry.call.kind,
5543
+ toolName: entry.toolName,
5544
+ tool: request.tools[entry.toolName],
5545
+ rawInput: entry.value,
5546
+ parseError: entry.parseError
5547
+ });
5548
+ return { entry, result, outputPayload };
5549
+ }
5550
+ );
5551
+ })
5552
+ );
5553
+ let toolExecutionMs = 0;
5554
+ let waitToolMs = 0;
5555
+ for (const { entry, result, outputPayload } of callResults) {
5556
+ toolCalls.push({ ...result, callId: entry.ids.callId });
5557
+ const callDurationMs = toToolResultDuration(result);
5558
+ toolExecutionMs += callDurationMs;
5559
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5560
+ waitToolMs += callDurationMs;
5079
5561
  }
5080
- if (delta.textDelta) {
5081
- request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
5562
+ request.onEvent?.({
5563
+ type: "tool_call",
5564
+ phase: "completed",
5565
+ turn: entry.turn,
5566
+ toolIndex: entry.toolIndex,
5567
+ toolName: entry.toolName,
5568
+ toolId: entry.toolId,
5569
+ callKind: entry.call.kind,
5570
+ callId: entry.ids.callId,
5571
+ input: entry.value,
5572
+ output: result.output,
5573
+ error: result.error,
5574
+ durationMs: result.durationMs
5575
+ });
5576
+ if (entry.call.kind === "custom") {
5577
+ toolOutputs.push({
5578
+ type: "custom_tool_call",
5579
+ id: entry.ids.itemId,
5580
+ call_id: entry.ids.callId,
5581
+ name: entry.toolName,
5582
+ input: entry.call.input,
5583
+ status: "completed"
5584
+ });
5585
+ toolOutputs.push({
5586
+ type: "custom_tool_call_output",
5587
+ call_id: entry.ids.callId,
5588
+ output: mergeToolOutput(outputPayload)
5589
+ });
5590
+ } else {
5591
+ toolOutputs.push({
5592
+ type: "function_call",
5593
+ id: entry.ids.itemId,
5594
+ call_id: entry.ids.callId,
5595
+ name: entry.toolName,
5596
+ arguments: entry.call.arguments,
5597
+ status: "completed"
5598
+ });
5599
+ toolOutputs.push({
5600
+ type: "function_call_output",
5601
+ call_id: entry.ids.callId,
5602
+ output: mergeToolOutput(outputPayload)
5603
+ });
5082
5604
  }
5083
5605
  }
5084
- });
5085
- const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
5086
- const usageTokens = extractChatGptUsageTokens(response.usage);
5087
- const stepCostUsd = estimateCallCostUsd({
5088
- modelId: modelVersion,
5089
- tokens: usageTokens,
5090
- responseImages: 0
5091
- });
5092
- totalCostUsd += stepCostUsd;
5093
- const responseText = (response.text ?? "").trim();
5094
- const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
5095
- const responseToolCalls = response.toolCalls ?? [];
5096
- if (responseToolCalls.length === 0) {
5097
- finalText = responseText;
5098
- finalThoughts = reasoningSummaryText;
5606
+ const stepCompletedAtMs = Date.now();
5607
+ const timing = buildStepTiming({
5608
+ stepStartedAtMs,
5609
+ stepCompletedAtMs,
5610
+ modelCompletedAtMs,
5611
+ firstModelEventAtMs,
5612
+ toolExecutionMs,
5613
+ waitToolMs
5614
+ });
5099
5615
  steps.push({
5100
5616
  step: steps.length + 1,
5101
5617
  modelVersion,
5102
5618
  text: responseText || void 0,
5103
5619
  thoughts: reasoningSummaryText || void 0,
5104
- toolCalls: [],
5620
+ toolCalls,
5105
5621
  usage: usageTokens,
5106
- costUsd: stepCostUsd
5622
+ costUsd: stepCostUsd,
5623
+ timing
5107
5624
  });
5108
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5625
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5626
+ const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
5627
+ input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
5109
5628
  }
5110
- const toolCalls = [];
5111
- const toolOutputs = [];
5112
- const callInputs = responseToolCalls.map((call, index) => {
5113
- const toolIndex = index + 1;
5114
- const toolId = buildToolLogId(turn, toolIndex);
5115
- const toolName = call.name;
5116
- const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
5117
- const ids = normalizeChatGptToolIds({
5118
- callKind: call.kind,
5119
- callId: call.callId,
5120
- itemId: call.id
5121
- });
5122
- return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
5123
- });
5124
- const callResults = await Promise.all(
5125
- callInputs.map(async (entry) => {
5126
- return await toolCallContextStorage.run(
5127
- {
5128
- toolName: entry.toolName,
5129
- toolId: entry.toolId,
5130
- turn: entry.turn,
5131
- toolIndex: entry.toolIndex
5132
- },
5133
- async () => {
5134
- const { result, outputPayload } = await executeToolCall({
5135
- callKind: entry.call.kind,
5136
- toolName: entry.toolName,
5137
- tool: request.tools[entry.toolName],
5138
- rawInput: entry.value,
5139
- parseError: entry.parseError
5140
- });
5141
- return { entry, result, outputPayload };
5629
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5630
+ }
5631
+ if (providerInfo.provider === "fireworks") {
5632
+ if (request.modelTools && request.modelTools.length > 0) {
5633
+ throw new Error(
5634
+ "Fireworks provider does not support provider-native modelTools in runToolLoop."
5635
+ );
5636
+ }
5637
+ const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
5638
+ const messages = toFireworksMessages(contents);
5639
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5640
+ const turn = stepIndex + 1;
5641
+ const stepStartedAtMs = Date.now();
5642
+ let schedulerMetrics;
5643
+ const response = await runFireworksCall(
5644
+ async (client) => {
5645
+ return await client.chat.completions.create(
5646
+ {
5647
+ model: providerInfo.model,
5648
+ messages,
5649
+ tools: fireworksTools,
5650
+ tool_choice: "auto",
5651
+ parallel_tool_calls: true
5652
+ },
5653
+ { signal: request.signal }
5654
+ );
5655
+ },
5656
+ providerInfo.model,
5657
+ {
5658
+ onSettled: (metrics) => {
5659
+ schedulerMetrics = metrics;
5142
5660
  }
5143
- );
5144
- })
5145
- );
5146
- for (const { entry, result, outputPayload } of callResults) {
5147
- toolCalls.push({ ...result, callId: entry.ids.callId });
5148
- if (entry.call.kind === "custom") {
5149
- toolOutputs.push({
5150
- type: "custom_tool_call",
5151
- id: entry.ids.itemId,
5152
- call_id: entry.ids.callId,
5153
- name: entry.toolName,
5154
- input: entry.call.input,
5155
- status: "completed"
5661
+ }
5662
+ );
5663
+ const modelCompletedAtMs = Date.now();
5664
+ const modelVersion = typeof response.model === "string" ? response.model : request.model;
5665
+ request.onEvent?.({ type: "model", modelVersion });
5666
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5667
+ if (choice?.finish_reason === "content_filter") {
5668
+ request.onEvent?.({ type: "blocked" });
5669
+ }
5670
+ const message = choice?.message;
5671
+ const responseText = extractFireworksMessageText(message).trim();
5672
+ if (responseText.length > 0) {
5673
+ request.onEvent?.({ type: "delta", channel: "response", text: responseText });
5674
+ }
5675
+ const usageTokens = extractFireworksUsageTokens(response.usage);
5676
+ const stepCostUsd = estimateCallCostUsd({
5677
+ modelId: modelVersion,
5678
+ tokens: usageTokens,
5679
+ responseImages: 0
5680
+ });
5681
+ totalCostUsd += stepCostUsd;
5682
+ if (usageTokens) {
5683
+ request.onEvent?.({
5684
+ type: "usage",
5685
+ usage: usageTokens,
5686
+ costUsd: stepCostUsd,
5687
+ modelVersion
5688
+ });
5689
+ }
5690
+ const responseToolCalls = extractFireworksToolCalls(message);
5691
+ if (responseToolCalls.length === 0) {
5692
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5693
+ const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
5694
+ finalText = responseText;
5695
+ finalThoughts = "";
5696
+ const stepCompletedAtMs2 = Date.now();
5697
+ const timing2 = buildStepTiming({
5698
+ stepStartedAtMs,
5699
+ stepCompletedAtMs: stepCompletedAtMs2,
5700
+ modelCompletedAtMs,
5701
+ schedulerMetrics,
5702
+ toolExecutionMs: 0,
5703
+ waitToolMs: 0
5156
5704
  });
5157
- toolOutputs.push({
5158
- type: "custom_tool_call_output",
5159
- call_id: entry.ids.callId,
5160
- output: mergeToolOutput(outputPayload)
5705
+ steps.push({
5706
+ step: steps.length + 1,
5707
+ modelVersion,
5708
+ text: responseText || void 0,
5709
+ thoughts: void 0,
5710
+ toolCalls: [],
5711
+ usage: usageTokens,
5712
+ costUsd: stepCostUsd,
5713
+ timing: timing2
5161
5714
  });
5162
- } else {
5163
- toolOutputs.push({
5164
- type: "function_call",
5165
- id: entry.ids.itemId,
5166
- call_id: entry.ids.callId,
5167
- name: entry.toolName,
5168
- arguments: entry.call.arguments,
5169
- status: "completed"
5715
+ if (steeringMessages.length === 0) {
5716
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5717
+ }
5718
+ if (responseText.length > 0) {
5719
+ messages.push({ role: "assistant", content: responseText });
5720
+ }
5721
+ messages.push(...steeringMessages);
5722
+ continue;
5723
+ }
5724
+ const stepToolCalls = [];
5725
+ const callInputs = responseToolCalls.map((call, index) => {
5726
+ const toolIndex = index + 1;
5727
+ const toolId = buildToolLogId(turn, toolIndex);
5728
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5729
+ return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
5730
+ });
5731
+ for (const entry of callInputs) {
5732
+ request.onEvent?.({
5733
+ type: "tool_call",
5734
+ phase: "started",
5735
+ turn: entry.turn,
5736
+ toolIndex: entry.toolIndex,
5737
+ toolName: entry.toolName,
5738
+ toolId: entry.toolId,
5739
+ callKind: "function",
5740
+ callId: entry.call.id,
5741
+ input: entry.value
5170
5742
  });
5171
- toolOutputs.push({
5172
- type: "function_call_output",
5173
- call_id: entry.ids.callId,
5174
- output: mergeToolOutput(outputPayload)
5743
+ }
5744
+ const callResults = await Promise.all(
5745
+ callInputs.map(async (entry) => {
5746
+ return await toolCallContextStorage.run(
5747
+ {
5748
+ toolName: entry.toolName,
5749
+ toolId: entry.toolId,
5750
+ turn: entry.turn,
5751
+ toolIndex: entry.toolIndex
5752
+ },
5753
+ async () => {
5754
+ const { result, outputPayload } = await executeToolCall({
5755
+ callKind: "function",
5756
+ toolName: entry.toolName,
5757
+ tool: request.tools[entry.toolName],
5758
+ rawInput: entry.value,
5759
+ parseError: entry.parseError
5760
+ });
5761
+ return { entry, result, outputPayload };
5762
+ }
5763
+ );
5764
+ })
5765
+ );
5766
+ const assistantToolCalls = [];
5767
+ const toolMessages = [];
5768
+ let toolExecutionMs = 0;
5769
+ let waitToolMs = 0;
5770
+ for (const { entry, result, outputPayload } of callResults) {
5771
+ stepToolCalls.push({ ...result, callId: entry.call.id });
5772
+ const callDurationMs = toToolResultDuration(result);
5773
+ toolExecutionMs += callDurationMs;
5774
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
5775
+ waitToolMs += callDurationMs;
5776
+ }
5777
+ request.onEvent?.({
5778
+ type: "tool_call",
5779
+ phase: "completed",
5780
+ turn: entry.turn,
5781
+ toolIndex: entry.toolIndex,
5782
+ toolName: entry.toolName,
5783
+ toolId: entry.toolId,
5784
+ callKind: "function",
5785
+ callId: entry.call.id,
5786
+ input: entry.value,
5787
+ output: result.output,
5788
+ error: result.error,
5789
+ durationMs: result.durationMs
5790
+ });
5791
+ assistantToolCalls.push({
5792
+ id: entry.call.id,
5793
+ type: "function",
5794
+ function: {
5795
+ name: entry.toolName,
5796
+ arguments: entry.call.arguments
5797
+ }
5175
5798
  });
5799
+ toolMessages.push({
5800
+ role: "tool",
5801
+ tool_call_id: entry.call.id,
5802
+ content: mergeToolOutput(outputPayload)
5803
+ });
5804
+ }
5805
+ const stepCompletedAtMs = Date.now();
5806
+ const timing = buildStepTiming({
5807
+ stepStartedAtMs,
5808
+ stepCompletedAtMs,
5809
+ modelCompletedAtMs,
5810
+ schedulerMetrics,
5811
+ toolExecutionMs,
5812
+ waitToolMs
5813
+ });
5814
+ steps.push({
5815
+ step: steps.length + 1,
5816
+ modelVersion,
5817
+ text: responseText || void 0,
5818
+ thoughts: void 0,
5819
+ toolCalls: stepToolCalls,
5820
+ usage: usageTokens,
5821
+ costUsd: stepCostUsd,
5822
+ timing
5823
+ });
5824
+ messages.push({
5825
+ role: "assistant",
5826
+ ...responseText.length > 0 ? { content: responseText } : {},
5827
+ tool_calls: assistantToolCalls
5828
+ });
5829
+ messages.push(...toolMessages);
5830
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
5831
+ if (steeringInput.length > 0) {
5832
+ messages.push(...toFireworksMessages(steeringInput));
5176
5833
  }
5177
5834
  }
5178
- steps.push({
5179
- step: steps.length + 1,
5180
- modelVersion,
5181
- text: responseText || void 0,
5182
- thoughts: reasoningSummaryText || void 0,
5183
- toolCalls,
5184
- usage: usageTokens,
5185
- costUsd: stepCostUsd
5186
- });
5187
- input = input.concat(toolOutputs);
5835
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5188
5836
  }
5189
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
5190
- }
5191
- if (providerInfo.provider === "fireworks") {
5192
- if (request.modelTools && request.modelTools.length > 0) {
5193
- throw new Error(
5194
- "Fireworks provider does not support provider-native modelTools in runToolLoop."
5195
- );
5196
- }
5197
- const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
5198
- const messages = toFireworksMessages(contents);
5837
+ const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
5838
+ const geminiNativeTools = toGeminiTools(request.modelTools);
5839
+ const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
5840
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
5199
5841
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5200
- const turn = stepIndex + 1;
5201
- const response = await runFireworksCall(async (client) => {
5202
- return await client.chat.completions.create(
5203
- {
5204
- model: providerInfo.model,
5205
- messages,
5206
- tools: fireworksTools,
5207
- tool_choice: "auto",
5208
- parallel_tool_calls: true
5209
- },
5210
- { signal: request.signal }
5211
- );
5212
- }, providerInfo.model);
5213
- const modelVersion = typeof response.model === "string" ? response.model : request.model;
5214
- request.onEvent?.({ type: "model", modelVersion });
5215
- const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
5216
- if (choice?.finish_reason === "content_filter") {
5217
- request.onEvent?.({ type: "blocked" });
5218
- }
5219
- const message = choice?.message;
5220
- const responseText = extractFireworksMessageText(message).trim();
5221
- if (responseText.length > 0) {
5222
- request.onEvent?.({ type: "delta", channel: "response", text: responseText });
5223
- }
5224
- const usageTokens = extractFireworksUsageTokens(response.usage);
5842
+ const stepStartedAtMs = Date.now();
5843
+ let firstModelEventAtMs;
5844
+ let schedulerMetrics;
5845
+ const markFirstModelEvent = () => {
5846
+ if (firstModelEventAtMs === void 0) {
5847
+ firstModelEventAtMs = Date.now();
5848
+ }
5849
+ };
5850
+ const config = {
5851
+ maxOutputTokens: 32e3,
5852
+ tools: geminiTools,
5853
+ toolConfig: {
5854
+ functionCallingConfig: {
5855
+ mode: import_genai2.FunctionCallingConfigMode.VALIDATED
5856
+ }
5857
+ },
5858
+ thinkingConfig: resolveGeminiThinkingConfig(request.model)
5859
+ };
5860
+ const onEvent = request.onEvent;
5861
+ const response = await runGeminiCall(
5862
+ async (client) => {
5863
+ const stream = await client.models.generateContentStream({
5864
+ model: request.model,
5865
+ contents: geminiContents,
5866
+ config
5867
+ });
5868
+ let responseText = "";
5869
+ let thoughtsText = "";
5870
+ const modelParts = [];
5871
+ const functionCalls = [];
5872
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
5873
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5874
+ let latestUsageMetadata;
5875
+ let resolvedModelVersion;
5876
+ for await (const chunk of stream) {
5877
+ markFirstModelEvent();
5878
+ if (chunk.modelVersion) {
5879
+ resolvedModelVersion = chunk.modelVersion;
5880
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5881
+ }
5882
+ if (chunk.usageMetadata) {
5883
+ latestUsageMetadata = chunk.usageMetadata;
5884
+ }
5885
+ const candidates = chunk.candidates;
5886
+ if (!candidates || candidates.length === 0) {
5887
+ continue;
5888
+ }
5889
+ const primary = candidates[0];
5890
+ const parts = primary?.content?.parts;
5891
+ if (!parts || parts.length === 0) {
5892
+ continue;
5893
+ }
5894
+ for (const part of parts) {
5895
+ modelParts.push(part);
5896
+ const call = part.functionCall;
5897
+ if (call) {
5898
+ const id = typeof call.id === "string" ? call.id : "";
5899
+ const shouldAdd = (() => {
5900
+ if (id.length > 0) {
5901
+ if (seenFunctionCallIds.has(id)) {
5902
+ return false;
5903
+ }
5904
+ seenFunctionCallIds.add(id);
5905
+ return true;
5906
+ }
5907
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
5908
+ if (seenFunctionCallKeys.has(key)) {
5909
+ return false;
5910
+ }
5911
+ seenFunctionCallKeys.add(key);
5912
+ return true;
5913
+ })();
5914
+ if (shouldAdd) {
5915
+ functionCalls.push(call);
5916
+ }
5917
+ }
5918
+ if (typeof part.text === "string" && part.text.length > 0) {
5919
+ if (part.thought) {
5920
+ thoughtsText += part.text;
5921
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
5922
+ } else {
5923
+ responseText += part.text;
5924
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
5925
+ }
5926
+ }
5927
+ }
5928
+ }
5929
+ return {
5930
+ responseText,
5931
+ thoughtsText,
5932
+ functionCalls,
5933
+ modelParts,
5934
+ usageMetadata: latestUsageMetadata,
5935
+ modelVersion: resolvedModelVersion ?? request.model
5936
+ };
5937
+ },
5938
+ request.model,
5939
+ {
5940
+ onSettled: (metrics) => {
5941
+ schedulerMetrics = metrics;
5942
+ }
5943
+ }
5944
+ );
5945
+ const modelCompletedAtMs = Date.now();
5946
+ const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
5947
+ const modelVersion = response.modelVersion ?? request.model;
5225
5948
  const stepCostUsd = estimateCallCostUsd({
5226
5949
  modelId: modelVersion,
5227
5950
  tokens: usageTokens,
5228
5951
  responseImages: 0
5229
5952
  });
5230
5953
  totalCostUsd += stepCostUsd;
5231
- if (usageTokens) {
5232
- request.onEvent?.({
5233
- type: "usage",
5234
- usage: usageTokens,
5235
- costUsd: stepCostUsd,
5236
- modelVersion
5954
+ if (response.functionCalls.length === 0) {
5955
+ const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
5956
+ finalText = response.responseText.trim();
5957
+ finalThoughts = response.thoughtsText.trim();
5958
+ const stepCompletedAtMs2 = Date.now();
5959
+ const timing2 = buildStepTiming({
5960
+ stepStartedAtMs,
5961
+ stepCompletedAtMs: stepCompletedAtMs2,
5962
+ modelCompletedAtMs,
5963
+ firstModelEventAtMs,
5964
+ schedulerMetrics,
5965
+ toolExecutionMs: 0,
5966
+ waitToolMs: 0
5237
5967
  });
5238
- }
5239
- const responseToolCalls = extractFireworksToolCalls(message);
5240
- if (responseToolCalls.length === 0) {
5241
- finalText = responseText;
5242
- finalThoughts = "";
5243
5968
  steps.push({
5244
5969
  step: steps.length + 1,
5245
5970
  modelVersion,
5246
- text: responseText || void 0,
5247
- thoughts: void 0,
5971
+ text: finalText || void 0,
5972
+ thoughts: finalThoughts || void 0,
5248
5973
  toolCalls: [],
5249
5974
  usage: usageTokens,
5250
- costUsd: stepCostUsd
5975
+ costUsd: stepCostUsd,
5976
+ timing: timing2
5251
5977
  });
5252
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5978
+ if (steeringInput2.length === 0) {
5979
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
5980
+ }
5981
+ const modelPartsForHistory2 = response.modelParts.filter(
5982
+ (part) => !(typeof part.text === "string" && part.thought === true)
5983
+ );
5984
+ if (modelPartsForHistory2.length > 0) {
5985
+ geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
5986
+ } else if (response.responseText.length > 0) {
5987
+ geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
5988
+ }
5989
+ geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
5990
+ continue;
5253
5991
  }
5254
- const stepToolCalls = [];
5255
- const callInputs = responseToolCalls.map((call, index) => {
5992
+ const toolCalls = [];
5993
+ const modelPartsForHistory = response.modelParts.filter(
5994
+ (part) => !(typeof part.text === "string" && part.thought === true)
5995
+ );
5996
+ if (modelPartsForHistory.length > 0) {
5997
+ geminiContents.push({ role: "model", parts: modelPartsForHistory });
5998
+ } else {
5999
+ const parts = [];
6000
+ if (response.responseText) {
6001
+ parts.push({ text: response.responseText });
6002
+ }
6003
+ for (const call of response.functionCalls) {
6004
+ parts.push({ functionCall: call });
6005
+ }
6006
+ geminiContents.push({ role: "model", parts });
6007
+ }
6008
+ const responseParts = [];
6009
+ const callInputs = response.functionCalls.map((call, index) => {
6010
+ const turn = stepIndex + 1;
5256
6011
  const toolIndex = index + 1;
5257
6012
  const toolId = buildToolLogId(turn, toolIndex);
5258
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
5259
- return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
6013
+ const toolName = call.name ?? "unknown";
6014
+ const rawInput = call.args ?? {};
6015
+ return { call, toolName, rawInput, toolId, turn, toolIndex };
5260
6016
  });
6017
+ for (const entry of callInputs) {
6018
+ onEvent?.({
6019
+ type: "tool_call",
6020
+ phase: "started",
6021
+ turn: entry.turn,
6022
+ toolIndex: entry.toolIndex,
6023
+ toolName: entry.toolName,
6024
+ toolId: entry.toolId,
6025
+ callKind: "function",
6026
+ callId: entry.call.id,
6027
+ input: entry.rawInput
6028
+ });
6029
+ }
5261
6030
  const callResults = await Promise.all(
5262
6031
  callInputs.map(async (entry) => {
5263
6032
  return await toolCallContextStorage.run(
@@ -5272,232 +6041,134 @@ async function runToolLoop(request) {
5272
6041
  callKind: "function",
5273
6042
  toolName: entry.toolName,
5274
6043
  tool: request.tools[entry.toolName],
5275
- rawInput: entry.value,
5276
- parseError: entry.parseError
6044
+ rawInput: entry.rawInput
5277
6045
  });
5278
6046
  return { entry, result, outputPayload };
5279
6047
  }
5280
6048
  );
5281
6049
  })
5282
6050
  );
5283
- const assistantToolCalls = [];
5284
- const toolMessages = [];
6051
+ let toolExecutionMs = 0;
6052
+ let waitToolMs = 0;
5285
6053
  for (const { entry, result, outputPayload } of callResults) {
5286
- stepToolCalls.push({ ...result, callId: entry.call.id });
5287
- assistantToolCalls.push({
5288
- id: entry.call.id,
5289
- type: "function",
5290
- function: {
6054
+ toolCalls.push({ ...result, callId: entry.call.id });
6055
+ const callDurationMs = toToolResultDuration(result);
6056
+ toolExecutionMs += callDurationMs;
6057
+ if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
6058
+ waitToolMs += callDurationMs;
6059
+ }
6060
+ onEvent?.({
6061
+ type: "tool_call",
6062
+ phase: "completed",
6063
+ turn: entry.turn,
6064
+ toolIndex: entry.toolIndex,
6065
+ toolName: entry.toolName,
6066
+ toolId: entry.toolId,
6067
+ callKind: "function",
6068
+ callId: entry.call.id,
6069
+ input: entry.rawInput,
6070
+ output: result.output,
6071
+ error: result.error,
6072
+ durationMs: result.durationMs
6073
+ });
6074
+ const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
6075
+ responseParts.push({
6076
+ functionResponse: {
5291
6077
  name: entry.toolName,
5292
- arguments: entry.call.arguments
6078
+ response: responsePayload,
6079
+ ...entry.call.id ? { id: entry.call.id } : {}
5293
6080
  }
5294
6081
  });
5295
- toolMessages.push({
5296
- role: "tool",
5297
- tool_call_id: entry.call.id,
5298
- content: mergeToolOutput(outputPayload)
5299
- });
5300
6082
  }
6083
+ const stepCompletedAtMs = Date.now();
6084
+ const timing = buildStepTiming({
6085
+ stepStartedAtMs,
6086
+ stepCompletedAtMs,
6087
+ modelCompletedAtMs,
6088
+ firstModelEventAtMs,
6089
+ schedulerMetrics,
6090
+ toolExecutionMs,
6091
+ waitToolMs
6092
+ });
5301
6093
  steps.push({
5302
6094
  step: steps.length + 1,
5303
6095
  modelVersion,
5304
- text: responseText || void 0,
5305
- thoughts: void 0,
5306
- toolCalls: stepToolCalls,
6096
+ text: response.responseText.trim() || void 0,
6097
+ thoughts: response.thoughtsText.trim() || void 0,
6098
+ toolCalls,
5307
6099
  usage: usageTokens,
5308
- costUsd: stepCostUsd
5309
- });
5310
- messages.push({
5311
- role: "assistant",
5312
- ...responseText.length > 0 ? { content: responseText } : {},
5313
- tool_calls: assistantToolCalls
6100
+ costUsd: stepCostUsd,
6101
+ timing
5314
6102
  });
5315
- messages.push(...toolMessages);
6103
+ geminiContents.push({ role: "user", parts: responseParts });
6104
+ const steeringInput = steeringInternal?.drainPendingContents() ?? [];
6105
+ if (steeringInput.length > 0) {
6106
+ geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
6107
+ }
5316
6108
  }
5317
6109
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
6110
+ } finally {
6111
+ steeringInternal?.close();
5318
6112
  }
5319
- const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
5320
- const geminiNativeTools = toGeminiTools(request.modelTools);
5321
- const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
5322
- const geminiContents = contents.map(convertLlmContentToGeminiContent);
5323
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
5324
- const config = {
5325
- maxOutputTokens: 32e3,
5326
- tools: geminiTools,
5327
- toolConfig: {
5328
- functionCallingConfig: {
5329
- mode: import_genai2.FunctionCallingConfigMode.VALIDATED
5330
- }
5331
- },
5332
- thinkingConfig: resolveGeminiThinkingConfig(request.model)
5333
- };
5334
- const onEvent = request.onEvent;
5335
- const response = await runGeminiCall(async (client) => {
5336
- const stream = await client.models.generateContentStream({
5337
- model: request.model,
5338
- contents: geminiContents,
5339
- config
5340
- });
5341
- let responseText = "";
5342
- let thoughtsText = "";
5343
- const modelParts = [];
5344
- const functionCalls = [];
5345
- const seenFunctionCallIds = /* @__PURE__ */ new Set();
5346
- const seenFunctionCallKeys = /* @__PURE__ */ new Set();
5347
- let latestUsageMetadata;
5348
- let resolvedModelVersion;
5349
- for await (const chunk of stream) {
5350
- if (chunk.modelVersion) {
5351
- resolvedModelVersion = chunk.modelVersion;
5352
- onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
5353
- }
5354
- if (chunk.usageMetadata) {
5355
- latestUsageMetadata = chunk.usageMetadata;
5356
- }
5357
- const candidates = chunk.candidates;
5358
- if (!candidates || candidates.length === 0) {
5359
- continue;
5360
- }
5361
- const primary = candidates[0];
5362
- const parts = primary?.content?.parts;
5363
- if (!parts || parts.length === 0) {
5364
- continue;
5365
- }
5366
- for (const part of parts) {
5367
- modelParts.push(part);
5368
- const call = part.functionCall;
5369
- if (call) {
5370
- const id = typeof call.id === "string" ? call.id : "";
5371
- const shouldAdd = (() => {
5372
- if (id.length > 0) {
5373
- if (seenFunctionCallIds.has(id)) {
5374
- return false;
5375
- }
5376
- seenFunctionCallIds.add(id);
5377
- return true;
5378
- }
5379
- const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
5380
- if (seenFunctionCallKeys.has(key)) {
5381
- return false;
5382
- }
5383
- seenFunctionCallKeys.add(key);
5384
- return true;
5385
- })();
5386
- if (shouldAdd) {
5387
- functionCalls.push(call);
5388
- }
5389
- }
5390
- if (typeof part.text === "string" && part.text.length > 0) {
5391
- if (part.thought) {
5392
- thoughtsText += part.text;
5393
- onEvent?.({ type: "delta", channel: "thought", text: part.text });
5394
- } else {
5395
- responseText += part.text;
5396
- onEvent?.({ type: "delta", channel: "response", text: part.text });
5397
- }
5398
- }
5399
- }
5400
- }
5401
- return {
5402
- responseText,
5403
- thoughtsText,
5404
- functionCalls,
5405
- modelParts,
5406
- usageMetadata: latestUsageMetadata,
5407
- modelVersion: resolvedModelVersion ?? request.model
5408
- };
5409
- }, request.model);
5410
- const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
5411
- const modelVersion = response.modelVersion ?? request.model;
5412
- const stepCostUsd = estimateCallCostUsd({
5413
- modelId: modelVersion,
5414
- tokens: usageTokens,
5415
- responseImages: 0
5416
- });
5417
- totalCostUsd += stepCostUsd;
5418
- if (response.functionCalls.length === 0) {
5419
- finalText = response.responseText.trim();
5420
- finalThoughts = response.thoughtsText.trim();
5421
- steps.push({
5422
- step: steps.length + 1,
5423
- modelVersion,
5424
- text: finalText || void 0,
5425
- thoughts: finalThoughts || void 0,
5426
- toolCalls: [],
5427
- usage: usageTokens,
5428
- costUsd: stepCostUsd
5429
- });
5430
- return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
6113
+ }
6114
+ function mergeAbortSignals(first, second) {
6115
+ if (!first) {
6116
+ return second;
6117
+ }
6118
+ if (!second) {
6119
+ return first;
6120
+ }
6121
+ const controller = new AbortController();
6122
+ const abortFrom = (signal) => {
6123
+ if (!controller.signal.aborted) {
6124
+ controller.abort(signal.reason);
5431
6125
  }
5432
- const toolCalls = [];
5433
- const modelPartsForHistory = response.modelParts.filter(
5434
- (part) => !(typeof part.text === "string" && part.thought === true)
5435
- );
5436
- if (modelPartsForHistory.length > 0) {
5437
- geminiContents.push({ role: "model", parts: modelPartsForHistory });
5438
- } else {
5439
- const parts = [];
5440
- if (response.responseText) {
5441
- parts.push({ text: response.responseText });
5442
- }
5443
- for (const call of response.functionCalls) {
5444
- parts.push({ functionCall: call });
5445
- }
5446
- geminiContents.push({ role: "model", parts });
5447
- }
5448
- const responseParts = [];
5449
- const callInputs = response.functionCalls.map((call, index) => {
5450
- const turn = stepIndex + 1;
5451
- const toolIndex = index + 1;
5452
- const toolId = buildToolLogId(turn, toolIndex);
5453
- const toolName = call.name ?? "unknown";
5454
- const rawInput = call.args ?? {};
5455
- return { call, toolName, rawInput, toolId, turn, toolIndex };
5456
- });
5457
- const callResults = await Promise.all(
5458
- callInputs.map(async (entry) => {
5459
- return await toolCallContextStorage.run(
5460
- {
5461
- toolName: entry.toolName,
5462
- toolId: entry.toolId,
5463
- turn: entry.turn,
5464
- toolIndex: entry.toolIndex
5465
- },
5466
- async () => {
5467
- const { result, outputPayload } = await executeToolCall({
5468
- callKind: "function",
5469
- toolName: entry.toolName,
5470
- tool: request.tools[entry.toolName],
5471
- rawInput: entry.rawInput
5472
- });
5473
- return { entry, result, outputPayload };
5474
- }
5475
- );
5476
- })
5477
- );
5478
- for (const { entry, result, outputPayload } of callResults) {
5479
- toolCalls.push({ ...result, callId: entry.call.id });
5480
- const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
5481
- responseParts.push({
5482
- functionResponse: {
5483
- name: entry.toolName,
5484
- response: responsePayload,
5485
- ...entry.call.id ? { id: entry.call.id } : {}
6126
+ };
6127
+ if (first.aborted) {
6128
+ abortFrom(first);
6129
+ } else {
6130
+ first.addEventListener("abort", () => abortFrom(first), { once: true });
6131
+ }
6132
+ if (second.aborted) {
6133
+ abortFrom(second);
6134
+ } else {
6135
+ second.addEventListener("abort", () => abortFrom(second), { once: true });
6136
+ }
6137
+ return controller.signal;
6138
+ }
6139
+ function streamToolLoop(request) {
6140
+ const queue = createAsyncQueue();
6141
+ const abortController = new AbortController();
6142
+ const steering = request.steering ?? createToolLoopSteeringChannel();
6143
+ const signal = mergeAbortSignals(request.signal, abortController.signal);
6144
+ const sourceOnEvent = request.onEvent;
6145
+ const result = (async () => {
6146
+ try {
6147
+ const output = await runToolLoop({
6148
+ ...request,
6149
+ steering,
6150
+ ...signal ? { signal } : {},
6151
+ onEvent: (event) => {
6152
+ sourceOnEvent?.(event);
6153
+ queue.push(event);
5486
6154
  }
5487
6155
  });
6156
+ queue.close();
6157
+ return output;
6158
+ } catch (error) {
6159
+ const err = error instanceof Error ? error : new Error(String(error));
6160
+ queue.fail(err);
6161
+ throw err;
5488
6162
  }
5489
- steps.push({
5490
- step: steps.length + 1,
5491
- modelVersion,
5492
- text: response.responseText.trim() || void 0,
5493
- thoughts: response.thoughtsText.trim() || void 0,
5494
- toolCalls,
5495
- usage: usageTokens,
5496
- costUsd: stepCostUsd
5497
- });
5498
- geminiContents.push({ role: "user", parts: responseParts });
5499
- }
5500
- throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
6163
+ })();
6164
+ return {
6165
+ events: queue.iterable,
6166
+ result,
6167
+ append: steering.append,
6168
+ steer: steering.steer,
6169
+ pendingSteeringCount: steering.pendingCount,
6170
+ abort: () => abortController.abort()
6171
+ };
5501
6172
  }
5502
6173
  var IMAGE_GRADE_SCHEMA = import_zod3.z.enum(["pass", "fail"]);
5503
6174
  async function gradeGeneratedImage(params) {
@@ -5744,68 +6415,112 @@ function appendMarkdownSourcesSection(value, sources) {
5744
6415
  ${lines}`;
5745
6416
  }
5746
6417
 
6418
+ // src/agent.ts
6419
+ var import_node_crypto3 = require("crypto");
6420
+
5747
6421
  // src/agent/subagents.ts
5748
6422
  var import_node_crypto2 = require("crypto");
5749
6423
  var import_zod4 = require("zod");
5750
- var DEFAULT_SUBAGENT_MAX_AGENTS = 4;
5751
- var DEFAULT_SUBAGENT_MAX_DEPTH = 2;
5752
- var DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS = 1500;
5753
- var DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS = 9e4;
6424
+ var DEFAULT_SUBAGENT_MAX_AGENTS = 6;
6425
+ var DEFAULT_SUBAGENT_MAX_DEPTH = 1;
6426
+ var DEFAULT_SUBAGENT_MIN_WAIT_TIMEOUT_MS = 1e4;
6427
+ var DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS = 3e4;
6428
+ var DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS = 36e5;
5754
6429
  var MAX_SUBAGENT_MAX_AGENTS = 64;
5755
6430
  var MAX_SUBAGENT_MAX_DEPTH = 12;
5756
6431
  var MAX_SUBAGENT_MAX_STEPS = 64;
5757
- var MAX_SUBAGENT_WAIT_TIMEOUT_MS = 6e5;
6432
+ var MAX_SUBAGENT_WAIT_TIMEOUT_MS = 36e5;
5758
6433
  var SUBAGENT_CONTROL_TOOL_NAMES = ["send_input", "resume_agent", "wait", "close_agent"];
6434
+ var DEFAULT_AGENT_TYPE = "default";
6435
+ var BUILT_IN_AGENT_TYPES = ["default", "researcher", "worker", "reviewer"];
6436
+ var RESEARCHER_ROLE_DESCRIPTION = `Use \`researcher\` for focused discovery and fact-finding work.
6437
+ Researchers are fast and authoritative.
6438
+ They should be used for specific, well-scoped research questions.
6439
+ Rules:
6440
+ - Do not repeat searches they have already completed.
6441
+ - Trust researcher findings unless there is a clear contradiction.
6442
+ - Run researchers in parallel when useful.
6443
+ - Reuse existing researchers for related follow-up questions.`;
6444
+ var WORKER_ROLE_DESCRIPTION = `Use for execution and production work across domains.
6445
+ Typical tasks:
6446
+ - Build part of a deliverable
6447
+ - Implement requested changes
6448
+ - Produce concrete outputs (documents, plans, analyses, artifacts)
6449
+ Rules:
6450
+ - Explicitly assign **ownership** of the task (scope / responsibility).
6451
+ - Always tell workers they are **not alone in the workspace**, and they should ignore edits made by others without touching them unless asked.`;
6452
+ var REVIEWER_ROLE_DESCRIPTION = `Use \`reviewer\` to evaluate completed work and provide feedback.
6453
+ Reviewers focus on quality, correctness, risk, and clarity.
6454
+ Rules:
6455
+ - Review critically and prioritize issues by severity.
6456
+ - Call out gaps, assumptions, and edge cases explicitly.
6457
+ - Provide actionable, concrete feedback to improve the result.
6458
+ - Do not redo the entire task unless explicitly requested; evaluate first.`;
6459
+ var BUILT_IN_AGENT_TYPE_DESCRIPTIONS = {
6460
+ default: "Default agent.",
6461
+ researcher: RESEARCHER_ROLE_DESCRIPTION,
6462
+ worker: WORKER_ROLE_DESCRIPTION,
6463
+ reviewer: REVIEWER_ROLE_DESCRIPTION
6464
+ };
6465
+ var BUILT_IN_AGENT_TYPE_INSTRUCTIONS = {
6466
+ default: void 0,
6467
+ researcher: RESEARCHER_ROLE_DESCRIPTION,
6468
+ worker: WORKER_ROLE_DESCRIPTION,
6469
+ reviewer: REVIEWER_ROLE_DESCRIPTION
6470
+ };
6471
+ var SUBAGENT_NOTIFICATION_OPEN_TAG = "<subagent_notification>";
6472
+ var SUBAGENT_NOTIFICATION_CLOSE_TAG = "</subagent_notification>";
6473
+ var SPAWN_AGENT_TYPE_DESCRIPTION = buildSpawnAgentTypeDescription();
5759
6474
  var subagentInputItemSchema = import_zod4.z.object({
5760
- text: import_zod4.z.string().optional(),
5761
- image_url: import_zod4.z.string().optional(),
5762
- name: import_zod4.z.string().optional(),
5763
- path: import_zod4.z.string().optional(),
5764
- type: import_zod4.z.string().optional()
6475
+ text: import_zod4.z.string().nullish(),
6476
+ image_url: import_zod4.z.string().nullish(),
6477
+ name: import_zod4.z.string().nullish(),
6478
+ path: import_zod4.z.string().nullish(),
6479
+ type: import_zod4.z.string().nullish()
5765
6480
  }).passthrough();
5766
6481
  var spawnAgentInputSchema = import_zod4.z.object({
5767
- prompt: import_zod4.z.string().optional().describe("Initial prompt for the subagent."),
5768
- message: import_zod4.z.string().optional().describe("Codex-style alias for prompt."),
5769
- items: import_zod4.z.array(subagentInputItemSchema).optional().describe("Optional Codex-style input items."),
5770
- agent_type: import_zod4.z.string().optional().describe("Codex-style agent type hint."),
5771
- instructions: import_zod4.z.string().optional().describe("Optional extra instructions for this subagent instance."),
5772
- model: import_zod4.z.string().optional().describe("Optional model override. Must be one of this package's supported text model ids."),
5773
- max_steps: import_zod4.z.number().int().min(1).max(MAX_SUBAGENT_MAX_STEPS).optional().describe("Optional max step budget for each subagent run.")
5774
- }).refine((value) => Boolean(resolvePromptValue(value.prompt, value.message, value.items)), {
5775
- message: "Either prompt, message, or items must contain non-empty input."
6482
+ prompt: import_zod4.z.string().nullish().describe("Alias for message. Initial plain-text task for the new agent."),
6483
+ message: import_zod4.z.string().nullish().describe("Initial plain-text task for the new agent. Use either message or items."),
6484
+ items: import_zod4.z.array(subagentInputItemSchema).nullish().describe(
6485
+ "Structured input items. Use this to pass explicit mentions (for example app:// connector paths)."
6486
+ ),
6487
+ agent_type: import_zod4.z.string().nullish().describe(SPAWN_AGENT_TYPE_DESCRIPTION),
6488
+ fork_context: import_zod4.z.boolean().nullish().describe(
6489
+ "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."
6490
+ ),
6491
+ instructions: import_zod4.z.string().nullish().describe("Optional extra instructions for this subagent instance."),
6492
+ model: import_zod4.z.string().nullish().describe("Optional model override. Must be one of this package's supported text model ids."),
6493
+ max_steps: import_zod4.z.number().int().min(1).max(MAX_SUBAGENT_MAX_STEPS).nullish().describe("Optional max step budget for each subagent run.")
5776
6494
  });
5777
6495
  var sendInputSchema = import_zod4.z.object({
5778
- agent_id: import_zod4.z.string().optional().describe("Target subagent id."),
5779
- id: import_zod4.z.string().optional().describe("Codex-style alias for agent_id."),
5780
- input: import_zod4.z.string().optional().describe("New user input queued for the subagent."),
5781
- message: import_zod4.z.string().optional().describe("Codex-style alias for input."),
5782
- items: import_zod4.z.array(subagentInputItemSchema).optional().describe("Optional Codex-style input items."),
5783
- interrupt: import_zod4.z.boolean().optional().describe("If true and currently running, aborts active run before queuing input.")
6496
+ agent_id: import_zod4.z.string().nullish().describe("Target subagent id."),
6497
+ id: import_zod4.z.string().nullish().describe("Agent id to message (from spawn_agent)."),
6498
+ input: import_zod4.z.string().nullish().describe("New user input queued for the subagent."),
6499
+ message: import_zod4.z.string().nullish().describe("Legacy plain-text message to send to the agent. Use either message or items."),
6500
+ items: import_zod4.z.array(subagentInputItemSchema).nullish().describe(
6501
+ "Structured input items. Use this to pass explicit mentions (for example app:// connector paths)."
6502
+ ),
6503
+ interrupt: import_zod4.z.boolean().nullish().describe("If true and currently running, aborts active run before queuing input.")
5784
6504
  }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
5785
6505
  message: "agent_id (or id) is required."
5786
- }).refine((value) => Boolean(resolvePromptValue(value.input, value.message, value.items)), {
5787
- message: "input (or message/items) is required."
5788
6506
  });
5789
6507
  var resumeAgentSchema = import_zod4.z.object({
5790
- agent_id: import_zod4.z.string().optional().describe("Target subagent id."),
5791
- id: import_zod4.z.string().optional().describe("Codex-style alias for agent_id.")
6508
+ agent_id: import_zod4.z.string().nullish().describe("Target subagent id."),
6509
+ id: import_zod4.z.string().nullish().describe("Agent id to resume.")
5792
6510
  }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
5793
6511
  message: "agent_id (or id) is required."
5794
6512
  });
5795
6513
  var waitSchema = import_zod4.z.object({
5796
- agent_id: import_zod4.z.string().optional().describe("Target subagent id."),
5797
- id: import_zod4.z.string().optional().describe("Codex-style alias for agent_id."),
5798
- ids: import_zod4.z.array(import_zod4.z.string().min(1)).optional().describe("Codex-style list of agent ids."),
5799
- timeout_ms: import_zod4.z.number().int().min(1).optional().describe("Optional wait timeout in milliseconds.")
5800
- }).refine(
5801
- (value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)) || Array.isArray(value.ids) && value.ids.length > 0,
5802
- {
5803
- message: "agent_id/id or ids is required."
5804
- }
5805
- );
6514
+ agent_id: import_zod4.z.string().nullish().describe("Target subagent id."),
6515
+ id: import_zod4.z.string().nullish().describe("Codex-style alias for agent_id."),
6516
+ ids: import_zod4.z.array(import_zod4.z.string().min(1)).nullish().describe("Agent ids to wait on. Pass multiple ids to wait for whichever finishes first."),
6517
+ timeout_ms: import_zod4.z.number().int().nullish().describe(
6518
+ "Optional timeout in milliseconds. Defaults to 30000, min 10000, max 3600000. Prefer longer waits (minutes) to avoid busy polling."
6519
+ )
6520
+ });
5806
6521
  var closeSchema = import_zod4.z.object({
5807
- agent_id: import_zod4.z.string().optional().describe("Target subagent id."),
5808
- id: import_zod4.z.string().optional().describe("Codex-style alias for agent_id.")
6522
+ agent_id: import_zod4.z.string().nullish().describe("Target subagent id."),
6523
+ id: import_zod4.z.string().nullish().describe("Agent id to close (from spawn_agent).")
5809
6524
  }).refine((value) => Boolean(resolveAgentIdValue(value.agent_id, value.id)), {
5810
6525
  message: "agent_id (or id) is required."
5811
6526
  });
@@ -5813,6 +6528,7 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5813
6528
  const defaults = {
5814
6529
  maxAgents: DEFAULT_SUBAGENT_MAX_AGENTS,
5815
6530
  maxDepth: DEFAULT_SUBAGENT_MAX_DEPTH,
6531
+ minWaitTimeoutMs: DEFAULT_SUBAGENT_MIN_WAIT_TIMEOUT_MS,
5816
6532
  defaultWaitTimeoutMs: DEFAULT_SUBAGENT_WAIT_TIMEOUT_MS,
5817
6533
  maxWaitTimeoutMs: DEFAULT_SUBAGENT_MAX_WAIT_TIMEOUT_MS,
5818
6534
  promptPattern: "codex",
@@ -5833,10 +6549,16 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5833
6549
  MAX_SUBAGENT_MAX_AGENTS
5834
6550
  );
5835
6551
  const maxDepth = normalizeInteger(config.maxDepth, defaults.maxDepth, 1, MAX_SUBAGENT_MAX_DEPTH);
6552
+ const minWaitTimeoutMs = normalizeInteger(
6553
+ config.minWaitTimeoutMs,
6554
+ defaults.minWaitTimeoutMs,
6555
+ 1,
6556
+ MAX_SUBAGENT_WAIT_TIMEOUT_MS
6557
+ );
5836
6558
  const defaultWaitTimeoutMs = normalizeInteger(
5837
6559
  config.defaultWaitTimeoutMs,
5838
6560
  defaults.defaultWaitTimeoutMs,
5839
- 1,
6561
+ minWaitTimeoutMs,
5840
6562
  MAX_SUBAGENT_WAIT_TIMEOUT_MS
5841
6563
  );
5842
6564
  const maxWaitTimeoutMs = normalizeInteger(
@@ -5853,6 +6575,7 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5853
6575
  enabled,
5854
6576
  maxAgents,
5855
6577
  maxDepth,
6578
+ minWaitTimeoutMs,
5856
6579
  defaultWaitTimeoutMs,
5857
6580
  maxWaitTimeoutMs,
5858
6581
  promptPattern,
@@ -5866,9 +6589,11 @@ function resolveSubagentToolConfig(selection, currentDepth) {
5866
6589
  function buildCodexSubagentOrchestratorInstructions(params) {
5867
6590
  return [
5868
6591
  "Subagent orchestration tools are available: spawn_agent, send_input, resume_agent, wait, close_agent.",
6592
+ "Background updates may appear as <subagent_notification>{...}</subagent_notification>; treat them as status updates, not new user intent.",
6593
+ "Available spawn_agent agent_type values: default, researcher, worker, reviewer.",
5869
6594
  "Use this control pattern:",
5870
6595
  "1. spawn_agent with a focused prompt.",
5871
- "2. wait on that agent_id until it is no longer running.",
6596
+ "2. wait with ids=[agent_id] until the agent reaches a non-running state. Prefer long waits (minutes).",
5872
6597
  "3. For follow-up turns, send_input then resume_agent.",
5873
6598
  "4. close_agent when delegation is complete.",
5874
6599
  `Limits: max active subagents ${params.maxAgents}, max depth ${params.maxDepth}, current depth ${params.currentDepth}.`
@@ -5890,9 +6615,10 @@ function createSubagentToolController(options) {
5890
6615
  };
5891
6616
  }
5892
6617
  const agents = /* @__PURE__ */ new Map();
6618
+ const roleNicknameCounts = /* @__PURE__ */ new Map();
5893
6619
  const tools = {
5894
6620
  spawn_agent: tool({
5895
- description: "Spawns a subagent asynchronously. Returns immediately with agent status and id.",
6621
+ 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.",
5896
6622
  inputSchema: spawnAgentInputSchema,
5897
6623
  execute: async (input) => {
5898
6624
  if (countActiveAgents(agents) >= options.config.maxAgents) {
@@ -5915,24 +6641,36 @@ function createSubagentToolController(options) {
5915
6641
  }
5916
6642
  const id = `agent_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
5917
6643
  const now = Date.now();
5918
- const initialPrompt = resolvePromptValue(input.prompt, input.message, input.items);
5919
- if (!initialPrompt) {
5920
- throw new Error("spawn_agent requires prompt/message/items with non-empty text.");
5921
- }
6644
+ const { roleName, roleInstructions } = resolveAgentType(input.agent_type);
6645
+ const nickname = reserveAgentNickname(roleName, roleNicknameCounts);
6646
+ const perSpawnInstructions = joinInstructionBlocks(
6647
+ roleInstructions,
6648
+ trimToUndefined(input.instructions)
6649
+ );
6650
+ const initialPrompt = resolveCollabInputText({
6651
+ textCandidates: [{ value: input.prompt }, { value: input.message }],
6652
+ items: input.items,
6653
+ bothError: "Provide either prompt/message or items, but not both.",
6654
+ missingError: "Provide one of: prompt/message or items.",
6655
+ emptyTextError: "Empty message can't be sent to an agent.",
6656
+ emptyItemsError: "Items can't be empty."
6657
+ });
5922
6658
  const agent = {
5923
6659
  id,
5924
6660
  depth: childDepth,
5925
6661
  model,
6662
+ ...nickname ? { nickname } : {},
6663
+ agentRole: roleName,
5926
6664
  status: "idle",
5927
6665
  createdAtMs: now,
5928
6666
  updatedAtMs: now,
5929
6667
  pendingInputs: [initialPrompt],
5930
- history: [],
6668
+ history: input.fork_context && options.forkContextMessages ? [...options.forkContextMessages] : [],
5931
6669
  ...options.buildChildInstructions ? {
5932
6670
  instructions: trimToUndefined(
5933
- options.buildChildInstructions(input.instructions, childDepth)
6671
+ options.buildChildInstructions(perSpawnInstructions, childDepth)
5934
6672
  )
5935
- } : input.instructions ? { instructions: trimToUndefined(input.instructions) } : {},
6673
+ } : perSpawnInstructions ? { instructions: perSpawnInstructions } : {},
5936
6674
  ...input.max_steps ? { maxSteps: input.max_steps } : options.config.maxSteps ? { maxSteps: options.config.maxSteps } : {},
5937
6675
  turns: 0,
5938
6676
  notification: "spawned",
@@ -5942,41 +6680,50 @@ function createSubagentToolController(options) {
5942
6680
  };
5943
6681
  agents.set(id, agent);
5944
6682
  startRun(agent, options);
5945
- return buildToolResponse(agent, {
5946
- notification: "spawned",
5947
- message: `Spawned subagent ${id}.`
5948
- });
6683
+ return buildToolResponse(
6684
+ agent,
6685
+ {
6686
+ notification: "spawned",
6687
+ message: `Spawned subagent ${id}.`
6688
+ },
6689
+ { nickname: agent.nickname }
6690
+ );
5949
6691
  }
5950
6692
  }),
5951
6693
  send_input: tool({
5952
- description: "Queues new input for an existing subagent.",
6694
+ description: "Send a message to an existing agent. Use interrupt=true to redirect work immediately.",
5953
6695
  inputSchema: sendInputSchema,
5954
6696
  execute: async (input) => {
6697
+ const submissionId = randomSubmissionId();
5955
6698
  const agentId = resolveAgentIdValue(input.agent_id, input.id);
5956
6699
  if (!agentId) {
5957
6700
  throw new Error("send_input requires agent_id or id.");
5958
6701
  }
5959
6702
  const agent = requireAgent(agents, agentId);
5960
- const nextInput = resolvePromptValue(input.input, input.message, input.items);
5961
- if (!nextInput) {
5962
- throw new Error("send_input requires input/message/items with non-empty text.");
5963
- }
6703
+ const nextInput = resolveCollabInputText({
6704
+ textCandidates: [{ value: input.input }, { value: input.message }],
6705
+ items: input.items,
6706
+ bothError: "Provide either input/message or items, but not both.",
6707
+ missingError: "Provide one of: input/message or items.",
6708
+ emptyTextError: "Empty message can't be sent to an agent.",
6709
+ emptyItemsError: "Items can't be empty."
6710
+ });
5964
6711
  if (agent.status === "closed") {
5965
- throw new Error(`Subagent ${agent.id} is closed.`);
6712
+ throw new Error(`agent with id ${agent.id} is closed`);
5966
6713
  }
5967
6714
  if (input.interrupt && agent.abortController) {
5968
6715
  agent.abortController.abort("send_input_interrupt");
5969
6716
  agent.pendingInputs.unshift(nextInput);
5970
6717
  setNotification(agent, "input_queued", `Interrupted ${agent.id} and queued new input.`);
5971
- return buildToolResponse(agent);
6718
+ return buildToolResponse(agent, void 0, { submission_id: submissionId });
5972
6719
  }
5973
6720
  agent.pendingInputs.push(nextInput);
5974
6721
  setNotification(agent, "input_queued", `Queued input for ${agent.id}.`);
5975
- return buildToolResponse(agent);
6722
+ return buildToolResponse(agent, void 0, { submission_id: submissionId });
5976
6723
  }
5977
6724
  }),
5978
6725
  resume_agent: tool({
5979
- description: "Resumes a subagent run when queued input is available.",
6726
+ description: "Resume a previously closed agent by id so it can receive send_input and wait calls.",
5980
6727
  inputSchema: resumeAgentSchema,
5981
6728
  execute: async (input) => {
5982
6729
  const agentId = resolveAgentIdValue(input.agent_id, input.id);
@@ -5985,10 +6732,11 @@ function createSubagentToolController(options) {
5985
6732
  }
5986
6733
  const agent = requireAgent(agents, agentId);
5987
6734
  if (agent.status === "closed") {
5988
- setNotification(agent, "already_closed", `Subagent ${agent.id} is already closed.`);
6735
+ agent.status = "idle";
6736
+ setNotification(agent, "resumed", `Resumed subagent ${agent.id}.`);
5989
6737
  return buildToolResponse(agent, {
5990
- notification: "already_closed",
5991
- message: `Subagent ${agent.id} is already closed.`
6738
+ notification: "resumed",
6739
+ message: `Resumed subagent ${agent.id}.`
5992
6740
  });
5993
6741
  }
5994
6742
  const outcome = startRun(agent, options);
@@ -6007,41 +6755,42 @@ function createSubagentToolController(options) {
6007
6755
  }
6008
6756
  }),
6009
6757
  wait: tool({
6010
- description: "Waits for a running subagent to change state or until timeout. Returns current status.",
6758
+ 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.",
6011
6759
  inputSchema: waitSchema,
6012
6760
  execute: async (input) => {
6013
- const usesIdsArray = Array.isArray(input.ids) && input.ids.length > 0;
6014
6761
  const ids = resolveAgentIdList(input.agent_id, input.id, input.ids);
6015
6762
  if (ids.length === 0) {
6016
- throw new Error("wait requires agent_id/id or ids.");
6763
+ throw new Error("ids must be non-empty");
6764
+ }
6765
+ if (typeof input.timeout_ms === "number" && input.timeout_ms <= 0) {
6766
+ throw new Error("timeout_ms must be greater than zero");
6017
6767
  }
6018
6768
  const timeoutMs = normalizeInteger(
6019
6769
  input.timeout_ms,
6020
6770
  options.config.defaultWaitTimeoutMs,
6021
- 1,
6771
+ options.config.minWaitTimeoutMs,
6022
6772
  options.config.maxWaitTimeoutMs
6023
6773
  );
6024
- if (usesIdsArray) {
6025
- const status = await waitForAnyAgentStatus(agents, ids, timeoutMs);
6026
- return { status, timed_out: Object.keys(status).length === 0, timeout_ms: timeoutMs };
6027
- }
6028
- const agent = requireAgent(agents, ids[0]);
6029
- if (agent.status === "running") {
6030
- const completed = await waitUntilNotRunning(agent, timeoutMs);
6031
- if (!completed) {
6032
- setNotification(
6033
- agent,
6034
- "timeout",
6035
- `Timed out after ${timeoutMs}ms while waiting for ${agent.id}.`
6036
- );
6037
- return buildToolResponse(agent, void 0, { timed_out: true, timeout_ms: timeoutMs });
6038
- }
6774
+ const status = await waitForAnyAgentStatus(agents, ids, timeoutMs);
6775
+ const timedOut = Object.keys(status).length === 0;
6776
+ if (timedOut && ids.length === 1) {
6777
+ const agent = requireAgent(agents, ids[0]);
6778
+ setNotification(
6779
+ agent,
6780
+ "timeout",
6781
+ `Timed out after ${timeoutMs}ms while waiting for ${agent.id}.`
6782
+ );
6039
6783
  }
6040
- return buildToolResponse(agent, void 0, { timed_out: false, timeout_ms: timeoutMs });
6784
+ return {
6785
+ status,
6786
+ status_summary: summarizeAgentStatuses(status),
6787
+ timed_out: timedOut,
6788
+ timeout_ms: timeoutMs
6789
+ };
6041
6790
  }
6042
6791
  }),
6043
6792
  close_agent: tool({
6044
- description: "Closes a subagent and aborts its current run if it is still running.",
6793
+ description: "Close an agent when it is no longer needed and return its last known status.",
6045
6794
  inputSchema: closeSchema,
6046
6795
  execute: async (input) => {
6047
6796
  const agentId = resolveAgentIdValue(input.agent_id, input.id);
@@ -6053,7 +6802,7 @@ function createSubagentToolController(options) {
6053
6802
  setNotification(agent, "already_closed", `Subagent ${agent.id} is already closed.`);
6054
6803
  return buildToolResponse(agent, void 0, { cancelled: false });
6055
6804
  }
6056
- const cancelled = closeSubagent(agent, `Closed ${agent.id}.`);
6805
+ const cancelled = closeSubagent(agent, `Closed ${agent.id}.`, options);
6057
6806
  return buildToolResponse(
6058
6807
  agent,
6059
6808
  { notification: "closed", message: `Closed ${agent.id}.` },
@@ -6068,7 +6817,7 @@ function createSubagentToolController(options) {
6068
6817
  const running = [];
6069
6818
  for (const agent of agents.values()) {
6070
6819
  if (agent.status !== "closed") {
6071
- closeSubagent(agent, `Parent agent loop closed ${agent.id}.`);
6820
+ closeSubagent(agent, `Parent agent loop closed ${agent.id}.`, options);
6072
6821
  }
6073
6822
  if (agent.runningPromise) {
6074
6823
  running.push(agent.runningPromise);
@@ -6083,7 +6832,7 @@ function createSubagentToolController(options) {
6083
6832
  function requireAgent(agents, id) {
6084
6833
  const agent = agents.get(id);
6085
6834
  if (!agent) {
6086
- throw new Error(`Unknown subagent id: ${id}`);
6835
+ throw new Error(`agent with id ${id} not found`);
6087
6836
  }
6088
6837
  return agent;
6089
6838
  }
@@ -6102,17 +6851,33 @@ function resolveAgentIdList(agentId, idAlias, ids) {
6102
6851
  const single = resolveAgentIdValue(agentId, idAlias);
6103
6852
  return single ? [single] : [];
6104
6853
  }
6105
- function resolvePromptValue(prompt, message, items) {
6106
- const promptValue = prompt?.trim();
6107
- if (promptValue) {
6108
- return promptValue;
6854
+ function resolveCollabInputText(params) {
6855
+ const textCandidate = params.textCandidates.find(
6856
+ (candidate) => candidate.value !== void 0 && candidate.value !== null
6857
+ );
6858
+ const hasText = Boolean(textCandidate);
6859
+ const hasItems = params.items !== void 0 && params.items !== null;
6860
+ if (hasText && hasItems) {
6861
+ throw new Error(params.bothError);
6109
6862
  }
6110
- const messageValue = message?.trim();
6111
- if (messageValue) {
6112
- return messageValue;
6863
+ if (!hasText && !hasItems) {
6864
+ throw new Error(params.missingError);
6113
6865
  }
6114
- const itemText = resolveInputItemsText(items);
6115
- return itemText ?? "";
6866
+ if (hasText) {
6867
+ const value = textCandidate?.value?.trim();
6868
+ if (!value) {
6869
+ throw new Error(params.emptyTextError);
6870
+ }
6871
+ return value;
6872
+ }
6873
+ if (!params.items || params.items.length === 0) {
6874
+ throw new Error(params.emptyItemsError);
6875
+ }
6876
+ const itemText = resolveInputItemsText(params.items);
6877
+ if (!itemText) {
6878
+ throw new Error(params.emptyItemsError);
6879
+ }
6880
+ return itemText;
6116
6881
  }
6117
6882
  function resolveInputItemsText(items) {
6118
6883
  if (!items || items.length === 0) {
@@ -6128,9 +6893,28 @@ function resolveInputItemsText(items) {
6128
6893
  const name = typeof item.name === "string" ? item.name.trim() : "";
6129
6894
  const path6 = typeof item.path === "string" ? item.path.trim() : "";
6130
6895
  const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
6131
- const compact = [itemType, name, path6 || imageUrl].filter(Boolean).join(" ");
6132
- if (compact) {
6133
- lines.push(compact);
6896
+ if (itemType === "image") {
6897
+ lines.push("[image]");
6898
+ continue;
6899
+ }
6900
+ if (itemType === "local_image" && path6) {
6901
+ lines.push(`[local_image:${path6}]`);
6902
+ continue;
6903
+ }
6904
+ if (itemType === "skill" && name && path6) {
6905
+ lines.push(`[skill:$${name}](${path6})`);
6906
+ continue;
6907
+ }
6908
+ if (itemType === "mention" && name && path6) {
6909
+ lines.push(`[mention:$${name}](${path6})`);
6910
+ continue;
6911
+ }
6912
+ if (path6 || imageUrl) {
6913
+ lines.push(`[${itemType || "input"}:${path6 || imageUrl}]`);
6914
+ continue;
6915
+ }
6916
+ if (name) {
6917
+ lines.push(`[${itemType || "input"}:${name}]`);
6134
6918
  }
6135
6919
  }
6136
6920
  if (lines.length === 0) {
@@ -6205,7 +6989,12 @@ function startRun(agent, options) {
6205
6989
  }
6206
6990
  const input = [...agent.history, { role: "user", content: nextInput }];
6207
6991
  const abortController = new AbortController();
6992
+ const runStartedAtMs = Date.now();
6208
6993
  agent.abortController = abortController;
6994
+ if (agent.firstRunStartedAtMs === void 0) {
6995
+ agent.firstRunStartedAtMs = runStartedAtMs;
6996
+ }
6997
+ agent.lastRunStartedAtMs = runStartedAtMs;
6209
6998
  agent.lastError = void 0;
6210
6999
  setLifecycle(
6211
7000
  agent,
@@ -6237,6 +7026,7 @@ function startRun(agent, options) {
6237
7026
  "run_completed",
6238
7027
  `Subagent ${agent.id} completed run ${agent.turns}.`
6239
7028
  );
7029
+ emitBackgroundNotification(agent, options);
6240
7030
  } catch (error) {
6241
7031
  if (agent.status === "closed") {
6242
7032
  return;
@@ -6248,7 +7038,11 @@ function startRun(agent, options) {
6248
7038
  const message = toErrorMessage(error);
6249
7039
  agent.lastError = message;
6250
7040
  setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
7041
+ emitBackgroundNotification(agent, options);
6251
7042
  } finally {
7043
+ const runCompletedAtMs = Date.now();
7044
+ agent.lastRunCompletedAtMs = runCompletedAtMs;
7045
+ agent.lastRunDurationMs = Math.max(0, runCompletedAtMs - runStartedAtMs);
6252
7046
  agent.runningPromise = void 0;
6253
7047
  agent.abortController = void 0;
6254
7048
  }
@@ -6256,30 +7050,16 @@ function startRun(agent, options) {
6256
7050
  agent.runningPromise = runPromise;
6257
7051
  return "started";
6258
7052
  }
6259
- function closeSubagent(agent, message) {
7053
+ function closeSubagent(agent, message, options) {
6260
7054
  const cancelled = Boolean(agent.runningPromise);
6261
7055
  agent.pendingInputs = [];
6262
7056
  if (agent.abortController) {
6263
7057
  agent.abortController.abort("close_agent");
6264
7058
  }
6265
7059
  setLifecycle(agent, "closed", "closed", message);
7060
+ emitBackgroundNotification(agent, options);
6266
7061
  return cancelled;
6267
7062
  }
6268
- async function waitUntilNotRunning(agent, timeoutMs) {
6269
- const deadline = Date.now() + timeoutMs;
6270
- while (agent.status === "running") {
6271
- const remaining = deadline - Date.now();
6272
- if (remaining <= 0) {
6273
- return false;
6274
- }
6275
- const currentVersion = agent.version;
6276
- const changed = await waitForVersionChange(agent, currentVersion, remaining);
6277
- if (!changed) {
6278
- return false;
6279
- }
6280
- }
6281
- return true;
6282
- }
6283
7063
  async function waitForVersionChange(agent, version, timeoutMs) {
6284
7064
  if (agent.version !== version) {
6285
7065
  return true;
@@ -6317,6 +7097,8 @@ function buildToolResponse(agent, override, extra = {}) {
6317
7097
  function buildSnapshot(agent) {
6318
7098
  return {
6319
7099
  agent_id: agent.id,
7100
+ ...agent.nickname ? { nickname: agent.nickname } : {},
7101
+ agent_role: agent.agentRole,
6320
7102
  status: agent.status,
6321
7103
  depth: agent.depth,
6322
7104
  model: agent.model,
@@ -6324,6 +7106,13 @@ function buildSnapshot(agent) {
6324
7106
  turns: agent.turns,
6325
7107
  created_at: new Date(agent.createdAtMs).toISOString(),
6326
7108
  updated_at: new Date(agent.updatedAtMs).toISOString(),
7109
+ ...agent.firstRunStartedAtMs ? {
7110
+ first_run_started_at: new Date(agent.firstRunStartedAtMs).toISOString(),
7111
+ spawn_startup_latency_ms: Math.max(0, agent.firstRunStartedAtMs - agent.createdAtMs)
7112
+ } : {},
7113
+ ...agent.lastRunStartedAtMs ? { last_run_started_at: new Date(agent.lastRunStartedAtMs).toISOString() } : {},
7114
+ ...agent.lastRunCompletedAtMs ? { last_run_completed_at: new Date(agent.lastRunCompletedAtMs).toISOString() } : {},
7115
+ ...typeof agent.lastRunDurationMs === "number" ? { last_run_duration_ms: Math.max(0, agent.lastRunDurationMs) } : {},
6327
7116
  ...agent.lastError ? { last_error: agent.lastError } : {},
6328
7117
  ...agent.lastResult ? {
6329
7118
  last_result: {
@@ -6335,6 +7124,83 @@ function buildSnapshot(agent) {
6335
7124
  } : {}
6336
7125
  };
6337
7126
  }
7127
+ function emitBackgroundNotification(agent, options) {
7128
+ if (!options?.onBackgroundMessage) {
7129
+ return;
7130
+ }
7131
+ if (!isBackgroundNotification(agent.notification)) {
7132
+ return;
7133
+ }
7134
+ const payload = {
7135
+ agent_id: agent.id,
7136
+ status: buildSnapshot(agent)
7137
+ };
7138
+ const body = JSON.stringify(payload);
7139
+ try {
7140
+ options.onBackgroundMessage(
7141
+ `${SUBAGENT_NOTIFICATION_OPEN_TAG}${body}${SUBAGENT_NOTIFICATION_CLOSE_TAG}`
7142
+ );
7143
+ } catch {
7144
+ }
7145
+ }
7146
+ function isBackgroundNotification(notification) {
7147
+ return notification === "run_completed" || notification === "run_failed" || notification === "closed";
7148
+ }
7149
+ function summarizeAgentStatuses(status) {
7150
+ const summary = {};
7151
+ for (const [agentId, snapshot] of Object.entries(status)) {
7152
+ const value = snapshot.status;
7153
+ summary[agentId] = typeof value === "string" ? value : "unknown";
7154
+ }
7155
+ return summary;
7156
+ }
7157
+ function buildSpawnAgentTypeDescription() {
7158
+ const sections = BUILT_IN_AGENT_TYPES.map((name) => {
7159
+ const description = BUILT_IN_AGENT_TYPE_DESCRIPTIONS[name];
7160
+ return `${name}: {
7161
+ ${description}
7162
+ }`;
7163
+ });
7164
+ return [
7165
+ `Optional type name for the new agent. If omitted, \`${DEFAULT_AGENT_TYPE}\` is used.`,
7166
+ "Available roles:",
7167
+ ...sections
7168
+ ].join("\n");
7169
+ }
7170
+ function resolveAgentType(agentType) {
7171
+ const requestedRoleName = trimToUndefined(agentType) ?? DEFAULT_AGENT_TYPE;
7172
+ const roleName = requestedRoleName;
7173
+ const description = BUILT_IN_AGENT_TYPE_DESCRIPTIONS[roleName];
7174
+ if (!description) {
7175
+ throw new Error(`unknown agent_type '${requestedRoleName}'`);
7176
+ }
7177
+ return {
7178
+ roleName,
7179
+ roleInstructions: BUILT_IN_AGENT_TYPE_INSTRUCTIONS[roleName]
7180
+ };
7181
+ }
7182
+ function reserveAgentNickname(roleName, counts) {
7183
+ const prefixByRole = {
7184
+ default: "Agent",
7185
+ researcher: "Researcher",
7186
+ worker: "Worker",
7187
+ reviewer: "Reviewer"
7188
+ };
7189
+ const prefix = prefixByRole[roleName] ?? "Agent";
7190
+ const next = (counts.get(prefix) ?? 0) + 1;
7191
+ counts.set(prefix, next);
7192
+ return `${prefix}_${next}`;
7193
+ }
7194
+ function joinInstructionBlocks(...blocks) {
7195
+ const parts = blocks.map(trimToUndefined).filter((value) => Boolean(value));
7196
+ if (parts.length === 0) {
7197
+ return void 0;
7198
+ }
7199
+ return parts.join("\n\n");
7200
+ }
7201
+ function randomSubmissionId() {
7202
+ return `sub_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}`;
7203
+ }
6338
7204
  function normalizeInteger(value, fallback, min, max) {
6339
7205
  const parsed = Number.isFinite(value) ? Math.floor(value) : fallback;
6340
7206
  return Math.max(min, Math.min(max, parsed));
@@ -7073,29 +7939,33 @@ var DEFAULT_MAX_LINE_LENGTH = 500;
7073
7939
  var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
7074
7940
  var DEFAULT_TAB_WIDTH = 4;
7075
7941
  var codexReadFileInputSchema = import_zod6.z.object({
7076
- file_path: import_zod6.z.string().min(1).describe("Absolute path to the file"),
7077
- offset: import_zod6.z.number().int().min(1).optional().describe("The line number to start reading from. Must be 1 or greater."),
7078
- limit: import_zod6.z.number().int().min(1).optional().describe("The maximum number of lines to return."),
7079
- mode: import_zod6.z.enum(["slice", "indentation"]).optional().describe('Optional mode selector: "slice" (default) or "indentation".'),
7942
+ file_path: import_zod6.z.string().min(1).describe(
7943
+ "Path to the file (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7944
+ ),
7945
+ offset: import_zod6.z.number().int().min(1).nullish().describe("The line number to start reading from. Must be 1 or greater."),
7946
+ limit: import_zod6.z.number().int().min(1).nullish().describe("The maximum number of lines to return."),
7947
+ mode: import_zod6.z.enum(["slice", "indentation"]).nullish().describe('Optional mode selector: "slice" (default) or "indentation".'),
7080
7948
  indentation: import_zod6.z.object({
7081
- anchor_line: import_zod6.z.number().int().min(1).optional(),
7082
- max_levels: import_zod6.z.number().int().min(0).optional(),
7083
- include_siblings: import_zod6.z.boolean().optional(),
7084
- include_header: import_zod6.z.boolean().optional(),
7085
- max_lines: import_zod6.z.number().int().min(1).optional()
7086
- }).optional()
7949
+ anchor_line: import_zod6.z.number().int().min(1).nullish(),
7950
+ max_levels: import_zod6.z.number().int().min(0).nullish(),
7951
+ include_siblings: import_zod6.z.boolean().nullish(),
7952
+ include_header: import_zod6.z.boolean().nullish(),
7953
+ max_lines: import_zod6.z.number().int().min(1).nullish()
7954
+ }).nullish()
7087
7955
  });
7088
7956
  var codexListDirInputSchema = import_zod6.z.object({
7089
- dir_path: import_zod6.z.string().min(1).describe("Absolute path to the directory to list."),
7090
- offset: import_zod6.z.number().int().min(1).optional().describe("The entry number to start listing from. Must be 1 or greater."),
7091
- limit: import_zod6.z.number().int().min(1).optional().describe("The maximum number of entries to return."),
7092
- depth: import_zod6.z.number().int().min(1).optional().describe("The maximum directory depth to traverse. Must be 1 or greater.")
7957
+ dir_path: import_zod6.z.string().min(1).describe(
7958
+ "Path to the directory to list (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7959
+ ),
7960
+ offset: import_zod6.z.number().int().min(1).nullish().describe("The entry number to start listing from. Must be 1 or greater."),
7961
+ limit: import_zod6.z.number().int().min(1).nullish().describe("The maximum number of entries to return."),
7962
+ depth: import_zod6.z.number().int().min(1).nullish().describe("The maximum directory depth to traverse. Must be 1 or greater.")
7093
7963
  });
7094
7964
  var codexGrepFilesInputSchema = import_zod6.z.object({
7095
7965
  pattern: import_zod6.z.string().min(1).describe("Regular expression pattern to search for."),
7096
- include: import_zod6.z.string().optional().describe('Optional glob limiting searched files (for example "*.rs").'),
7097
- path: import_zod6.z.string().optional().describe("Directory or file path to search. Defaults to cwd."),
7098
- limit: import_zod6.z.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
7966
+ include: import_zod6.z.string().nullish().describe('Optional glob limiting searched files (for example "*.rs").'),
7967
+ path: import_zod6.z.string().nullish().describe("Directory or file path to search. Defaults to cwd."),
7968
+ limit: import_zod6.z.number().int().min(1).nullish().describe("Maximum number of file paths to return (defaults to 100).")
7099
7969
  });
7100
7970
  var applyPatchInputSchema = import_zod6.z.object({
7101
7971
  input: import_zod6.z.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
@@ -7330,9 +8200,6 @@ function createGlobTool(options = {}) {
7330
8200
  }
7331
8201
  async function readFileCodex(input, options) {
7332
8202
  const runtime = resolveRuntime(options);
7333
- if (!import_node_path5.default.isAbsolute(input.file_path)) {
7334
- throw new Error("file_path must be an absolute path");
7335
- }
7336
8203
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
7337
8204
  await runAccessHook2(runtime, {
7338
8205
  cwd: runtime.cwd,
@@ -7375,15 +8242,12 @@ async function readFileCodex(input, options) {
7375
8242
  maxLevels: indentation.max_levels ?? 0,
7376
8243
  includeSiblings: indentation.include_siblings ?? false,
7377
8244
  includeHeader: indentation.include_header ?? true,
7378
- maxLines: indentation.max_lines
8245
+ maxLines: indentation.max_lines ?? void 0
7379
8246
  });
7380
8247
  return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
7381
8248
  }
7382
8249
  async function listDirectoryCodex(input, options) {
7383
8250
  const runtime = resolveRuntime(options);
7384
- if (!import_node_path5.default.isAbsolute(input.dir_path)) {
7385
- throw new Error("dir_path must be an absolute path");
7386
- }
7387
8251
  const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
7388
8252
  await runAccessHook2(runtime, {
7389
8253
  cwd: runtime.cwd,
@@ -7411,7 +8275,7 @@ async function listDirectoryCodex(input, options) {
7411
8275
  const remaining = entries.length - startIndex;
7412
8276
  const cappedLimit = Math.min(limit, remaining);
7413
8277
  const selected = entries.slice(startIndex, startIndex + cappedLimit);
7414
- const output = [`Absolute path: ${dirPath}`];
8278
+ const output = [`Absolute path: ${toSandboxDisplayPath(dirPath, runtime.cwd)}`];
7415
8279
  for (const entry of selected) {
7416
8280
  output.push(formatListEntry(entry));
7417
8281
  }
@@ -7797,10 +8661,17 @@ function mapApplyPatchAction(action) {
7797
8661
  }
7798
8662
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
7799
8663
  const absolutePath = import_node_path5.default.isAbsolute(inputPath) ? import_node_path5.default.resolve(inputPath) : import_node_path5.default.resolve(cwd, inputPath);
7800
- if (!allowOutsideCwd && !isPathInsideCwd2(absolutePath, cwd)) {
7801
- throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
8664
+ if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
8665
+ return absolutePath;
7802
8666
  }
7803
- return absolutePath;
8667
+ if (import_node_path5.default.isAbsolute(inputPath)) {
8668
+ const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
8669
+ const sandboxRootedPath = import_node_path5.default.resolve(cwd, sandboxRelativePath);
8670
+ if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
8671
+ return sandboxRootedPath;
8672
+ }
8673
+ }
8674
+ throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
7804
8675
  }
7805
8676
  function isPathInsideCwd2(candidatePath, cwd) {
7806
8677
  const relative = import_node_path5.default.relative(cwd, candidatePath);
@@ -7816,6 +8687,16 @@ function toDisplayPath2(absolutePath, cwd) {
7816
8687
  }
7817
8688
  return absolutePath;
7818
8689
  }
8690
+ function toSandboxDisplayPath(absolutePath, cwd) {
8691
+ const relative = import_node_path5.default.relative(cwd, absolutePath);
8692
+ if (relative === "") {
8693
+ return "/";
8694
+ }
8695
+ if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
8696
+ return `/${normalizeSlashes(relative)}`;
8697
+ }
8698
+ return normalizeSlashes(absolutePath);
8699
+ }
7819
8700
  function splitLines(content) {
7820
8701
  const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7821
8702
  const lines = normalized.split("\n");
@@ -8153,7 +9034,71 @@ function isNoEntError(error) {
8153
9034
 
8154
9035
  // src/agent.ts
8155
9036
  async function runAgentLoop(request) {
8156
- return await runAgentLoopInternal(request, { depth: 0 });
9037
+ const telemetry = createAgentTelemetrySession(request.telemetry);
9038
+ try {
9039
+ return await runAgentLoopInternal(request, { depth: 0, telemetry });
9040
+ } finally {
9041
+ await telemetry?.flush();
9042
+ }
9043
+ }
9044
+ function mergeAbortSignals2(first, second) {
9045
+ if (!first) {
9046
+ return second;
9047
+ }
9048
+ if (!second) {
9049
+ return first;
9050
+ }
9051
+ const controller = new AbortController();
9052
+ const abortFrom = (signal) => {
9053
+ if (!controller.signal.aborted) {
9054
+ controller.abort(signal.reason);
9055
+ }
9056
+ };
9057
+ if (first.aborted) {
9058
+ abortFrom(first);
9059
+ } else {
9060
+ first.addEventListener("abort", () => abortFrom(first), { once: true });
9061
+ }
9062
+ if (second.aborted) {
9063
+ abortFrom(second);
9064
+ } else {
9065
+ second.addEventListener("abort", () => abortFrom(second), { once: true });
9066
+ }
9067
+ return controller.signal;
9068
+ }
9069
+ function streamAgentLoop(request) {
9070
+ const queue = createAsyncQueue();
9071
+ const abortController = new AbortController();
9072
+ const steering = request.steering ?? createToolLoopSteeringChannel();
9073
+ const signal = mergeAbortSignals2(request.signal, abortController.signal);
9074
+ const sourceOnEvent = request.onEvent;
9075
+ const result = (async () => {
9076
+ try {
9077
+ const output = await runAgentLoop({
9078
+ ...request,
9079
+ steering,
9080
+ ...signal ? { signal } : {},
9081
+ onEvent: (event) => {
9082
+ sourceOnEvent?.(event);
9083
+ queue.push(event);
9084
+ }
9085
+ });
9086
+ queue.close();
9087
+ return output;
9088
+ } catch (error) {
9089
+ const err = error instanceof Error ? error : new Error(String(error));
9090
+ queue.fail(err);
9091
+ throw err;
9092
+ }
9093
+ })();
9094
+ return {
9095
+ events: queue.iterable,
9096
+ result,
9097
+ append: steering.append,
9098
+ steer: steering.steer,
9099
+ pendingSteeringCount: steering.pendingCount,
9100
+ abort: () => abortController.abort()
9101
+ };
8157
9102
  }
8158
9103
  async function runAgentLoopInternal(request, context) {
8159
9104
  const {
@@ -8163,19 +9108,28 @@ async function runAgentLoopInternal(request, context) {
8163
9108
  subagentTool,
8164
9109
  subagent_tool,
8165
9110
  subagents,
9111
+ telemetry,
8166
9112
  ...toolLoopRequest
8167
9113
  } = request;
9114
+ const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
9115
+ const runId = randomRunId();
9116
+ const startedAtMs = Date.now();
9117
+ const steeringChannel = toolLoopRequest.steering ?? createToolLoopSteeringChannel();
9118
+ const toolLoopRequestWithSteering = toolLoopRequest.steering === steeringChannel ? toolLoopRequest : { ...toolLoopRequest, steering: steeringChannel };
8168
9119
  const filesystemSelection = filesystemTool ?? filesystem_tool;
8169
9120
  const subagentSelection = subagentTool ?? subagent_tool ?? subagents;
8170
9121
  const filesystemTools = resolveFilesystemTools(request.model, filesystemSelection);
8171
9122
  const resolvedSubagentConfig = resolveSubagentToolConfig(subagentSelection, context.depth);
8172
9123
  const subagentController = createSubagentController({
9124
+ runId,
8173
9125
  model: request.model,
8174
9126
  depth: context.depth,
9127
+ telemetry: telemetrySession,
8175
9128
  customTools: customTools ?? {},
8176
9129
  filesystemSelection,
8177
9130
  subagentSelection,
8178
- toolLoopRequest,
9131
+ toolLoopRequest: toolLoopRequestWithSteering,
9132
+ steering: steeringChannel,
8179
9133
  resolvedSubagentConfig
8180
9134
  });
8181
9135
  const mergedTools = mergeToolSets(
@@ -8188,16 +9142,58 @@ async function runAgentLoopInternal(request, context) {
8188
9142
  );
8189
9143
  }
8190
9144
  const instructions = buildLoopInstructions(
8191
- toolLoopRequest.instructions,
9145
+ toolLoopRequestWithSteering.instructions,
8192
9146
  resolvedSubagentConfig,
8193
9147
  context.depth
8194
9148
  );
9149
+ const emitTelemetry = createAgentTelemetryEmitter({
9150
+ session: telemetrySession,
9151
+ runId,
9152
+ parentRunId: context.parentRunId,
9153
+ depth: context.depth,
9154
+ model: request.model
9155
+ });
9156
+ emitTelemetry({
9157
+ type: "agent.run.started",
9158
+ inputMode: typeof request.input === "string" ? "string" : "messages",
9159
+ customToolCount: Object.keys(customTools ?? {}).length,
9160
+ mergedToolCount: Object.keys(mergedTools).length,
9161
+ filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
9162
+ subagentToolsEnabled: resolvedSubagentConfig.enabled
9163
+ });
9164
+ const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
9165
+ const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
9166
+ const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
9167
+ sourceOnEvent?.(event);
9168
+ if (includeLlmStreamEvents) {
9169
+ emitTelemetry({ type: "agent.run.stream", event });
9170
+ }
9171
+ } : void 0;
8195
9172
  try {
8196
- return await runToolLoop({
8197
- ...toolLoopRequest,
9173
+ const result = await runToolLoop({
9174
+ ...toolLoopRequestWithSteering,
8198
9175
  ...instructions ? { instructions } : {},
9176
+ ...wrappedOnEvent ? { onEvent: wrappedOnEvent } : {},
8199
9177
  tools: mergedTools
8200
9178
  });
9179
+ emitTelemetry({
9180
+ type: "agent.run.completed",
9181
+ success: true,
9182
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9183
+ stepCount: result.steps.length,
9184
+ toolCallCount: countToolCalls(result),
9185
+ totalCostUsd: result.totalCostUsd,
9186
+ usage: summarizeResultUsage(result)
9187
+ });
9188
+ return result;
9189
+ } catch (error) {
9190
+ emitTelemetry({
9191
+ type: "agent.run.completed",
9192
+ success: false,
9193
+ durationMs: Math.max(0, Date.now() - startedAtMs),
9194
+ error: toErrorMessage2(error)
9195
+ });
9196
+ throw error;
8201
9197
  } finally {
8202
9198
  await subagentController?.closeAll();
8203
9199
  }
@@ -8243,6 +9239,10 @@ function createSubagentController(params) {
8243
9239
  config: params.resolvedSubagentConfig,
8244
9240
  parentDepth: params.depth,
8245
9241
  parentModel: params.resolvedSubagentConfig.model ?? params.model,
9242
+ forkContextMessages: normalizeForkContextMessages(params.toolLoopRequest.input),
9243
+ onBackgroundMessage: (message) => {
9244
+ params.steering?.append({ role: "user", content: message });
9245
+ },
8246
9246
  buildChildInstructions: (spawnInstructions, childDepth) => buildChildInstructions(spawnInstructions, params.resolvedSubagentConfig, childDepth),
8247
9247
  runSubagent: async (subagentRequest) => {
8248
9248
  const childCustomTools = params.resolvedSubagentConfig.inheritTools ? params.customTools : {};
@@ -8260,7 +9260,11 @@ function createSubagentController(params) {
8260
9260
  openAiReasoningEffort: params.toolLoopRequest.openAiReasoningEffort,
8261
9261
  signal: subagentRequest.signal
8262
9262
  },
8263
- { depth: params.depth + 1 }
9263
+ {
9264
+ depth: params.depth + 1,
9265
+ parentRunId: params.runId,
9266
+ telemetry: params.telemetry
9267
+ }
8264
9268
  );
8265
9269
  }
8266
9270
  });
@@ -8307,10 +9311,142 @@ function buildChildInstructions(spawnInstructions, config, childDepth) {
8307
9311
  }
8308
9312
  return blocks.length > 0 ? blocks.join("\n\n") : void 0;
8309
9313
  }
9314
+ function normalizeForkContextMessages(input) {
9315
+ if (typeof input === "string") {
9316
+ return [{ role: "user", content: input }];
9317
+ }
9318
+ return input.map((message) => ({
9319
+ role: message.role,
9320
+ content: Array.isArray(message.content) ? [...message.content] : message.content
9321
+ }));
9322
+ }
8310
9323
  function trimToUndefined2(value) {
8311
9324
  const trimmed = value?.trim();
8312
9325
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
8313
9326
  }
9327
+ function randomRunId() {
9328
+ return (0, import_node_crypto3.randomBytes)(8).toString("hex");
9329
+ }
9330
+ function toIsoNow() {
9331
+ return (/* @__PURE__ */ new Date()).toISOString();
9332
+ }
9333
+ function toErrorMessage2(error) {
9334
+ if (error instanceof Error && error.message) {
9335
+ return error.message;
9336
+ }
9337
+ if (typeof error === "string") {
9338
+ return error;
9339
+ }
9340
+ return "Unknown error";
9341
+ }
9342
+ function countToolCalls(result) {
9343
+ let count = 0;
9344
+ for (const step of result.steps) {
9345
+ count += step.toolCalls.length;
9346
+ }
9347
+ return count;
9348
+ }
9349
+ function sumUsageValue(current, next) {
9350
+ if (typeof next !== "number" || !Number.isFinite(next)) {
9351
+ return current;
9352
+ }
9353
+ const normalizedNext = Math.max(0, next);
9354
+ if (typeof current !== "number" || !Number.isFinite(current)) {
9355
+ return normalizedNext;
9356
+ }
9357
+ return Math.max(0, current) + normalizedNext;
9358
+ }
9359
+ function summarizeResultUsage(result) {
9360
+ let summary;
9361
+ for (const step of result.steps) {
9362
+ const usage = step.usage;
9363
+ if (!usage) {
9364
+ continue;
9365
+ }
9366
+ summary = {
9367
+ promptTokens: sumUsageValue(summary?.promptTokens, usage.promptTokens),
9368
+ cachedTokens: sumUsageValue(summary?.cachedTokens, usage.cachedTokens),
9369
+ responseTokens: sumUsageValue(summary?.responseTokens, usage.responseTokens),
9370
+ responseImageTokens: sumUsageValue(summary?.responseImageTokens, usage.responseImageTokens),
9371
+ thinkingTokens: sumUsageValue(summary?.thinkingTokens, usage.thinkingTokens),
9372
+ totalTokens: sumUsageValue(summary?.totalTokens, usage.totalTokens),
9373
+ toolUsePromptTokens: sumUsageValue(summary?.toolUsePromptTokens, usage.toolUsePromptTokens)
9374
+ };
9375
+ }
9376
+ return summary;
9377
+ }
9378
+ function isPromiseLike(value) {
9379
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
9380
+ }
9381
+ function isAgentTelemetrySink(value) {
9382
+ return typeof value === "object" && value !== null && typeof value.emit === "function";
9383
+ }
9384
+ function resolveTelemetrySelection(telemetry) {
9385
+ if (!telemetry) {
9386
+ return void 0;
9387
+ }
9388
+ if (isAgentTelemetrySink(telemetry)) {
9389
+ return { sink: telemetry };
9390
+ }
9391
+ if (isAgentTelemetrySink(telemetry.sink)) {
9392
+ return telemetry;
9393
+ }
9394
+ throw new Error("Invalid runAgentLoop telemetry config: expected a sink with emit(event).");
9395
+ }
9396
+ function createAgentTelemetrySession(telemetry) {
9397
+ const config = resolveTelemetrySelection(telemetry);
9398
+ if (!config) {
9399
+ return void 0;
9400
+ }
9401
+ const pending = /* @__PURE__ */ new Set();
9402
+ const trackPromise = (promise) => {
9403
+ pending.add(promise);
9404
+ promise.finally(() => {
9405
+ pending.delete(promise);
9406
+ });
9407
+ };
9408
+ const emit = (event) => {
9409
+ try {
9410
+ const output = config.sink.emit(event);
9411
+ if (isPromiseLike(output)) {
9412
+ const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
9413
+ trackPromise(task);
9414
+ }
9415
+ } catch {
9416
+ }
9417
+ };
9418
+ const flush = async () => {
9419
+ while (pending.size > 0) {
9420
+ await Promise.allSettled([...pending]);
9421
+ }
9422
+ if (typeof config.sink.flush === "function") {
9423
+ try {
9424
+ await config.sink.flush();
9425
+ } catch {
9426
+ }
9427
+ }
9428
+ };
9429
+ return {
9430
+ includeLlmStreamEvents: config.includeLlmStreamEvents === true,
9431
+ emit,
9432
+ flush
9433
+ };
9434
+ }
9435
+ function createAgentTelemetryEmitter(params) {
9436
+ return (event) => {
9437
+ if (!params.session) {
9438
+ return;
9439
+ }
9440
+ params.session.emit({
9441
+ ...event,
9442
+ timestamp: toIsoNow(),
9443
+ runId: params.runId,
9444
+ ...params.parentRunId ? { parentRunId: params.parentRunId } : {},
9445
+ depth: params.depth,
9446
+ model: params.model
9447
+ });
9448
+ };
9449
+ }
8314
9450
  // Annotate the CommonJS export names for ESM import in node:
8315
9451
  0 && (module.exports = {
8316
9452
  CHATGPT_MODEL_IDS,
@@ -8334,6 +9470,7 @@ function trimToUndefined2(value) {
8334
9470
  appendMarkdownSourcesSection,
8335
9471
  applyPatch,
8336
9472
  configureGemini,
9473
+ configureModelConcurrency,
8337
9474
  convertGooglePartsToLlmParts,
8338
9475
  createApplyPatchTool,
8339
9476
  createCodexApplyPatchTool,
@@ -8353,6 +9490,7 @@ function trimToUndefined2(value) {
8353
9490
  createReadFilesTool,
8354
9491
  createReplaceTool,
8355
9492
  createRgSearchTool,
9493
+ createToolLoopSteeringChannel,
8356
9494
  createWriteFileTool,
8357
9495
  customTool,
8358
9496
  encodeChatGptAuthJson,
@@ -8378,13 +9516,16 @@ function trimToUndefined2(value) {
8378
9516
  loadLocalEnv,
8379
9517
  parseJsonFromLlmText,
8380
9518
  refreshChatGptOauthToken,
9519
+ resetModelConcurrencyConfig,
8381
9520
  resolveFilesystemToolProfile,
8382
9521
  resolveFireworksModelId,
8383
9522
  runAgentLoop,
8384
9523
  runToolLoop,
8385
9524
  sanitisePartForLogging,
9525
+ streamAgentLoop,
8386
9526
  streamJson,
8387
9527
  streamText,
9528
+ streamToolLoop,
8388
9529
  stripCodexCitationMarkers,
8389
9530
  toGeminiJsonSchema,
8390
9531
  tool