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