@ljoukov/llm 4.0.0 → 4.0.2
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 +38 -0
- package/dist/index.cjs +1969 -1046
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +1969 -1046
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/llm.ts
|
|
2
|
-
import { Buffer as
|
|
3
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
3
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
4
4
|
import { randomBytes } from "crypto";
|
|
5
5
|
import {
|
|
6
6
|
FinishReason,
|
|
@@ -2643,8 +2643,375 @@ function stripChatGptPrefix(model) {
|
|
|
2643
2643
|
return model.slice("chatgpt-".length);
|
|
2644
2644
|
}
|
|
2645
2645
|
|
|
2646
|
+
// src/agentLogging.ts
|
|
2647
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2648
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
2649
|
+
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
2650
|
+
import path3 from "path";
|
|
2651
|
+
function toIsoNow() {
|
|
2652
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2653
|
+
}
|
|
2654
|
+
function toErrorMessage(error) {
|
|
2655
|
+
if (error instanceof Error && error.message) {
|
|
2656
|
+
return error.message;
|
|
2657
|
+
}
|
|
2658
|
+
if (typeof error === "string") {
|
|
2659
|
+
return error;
|
|
2660
|
+
}
|
|
2661
|
+
return String(error);
|
|
2662
|
+
}
|
|
2663
|
+
function isPromiseLike(value) {
|
|
2664
|
+
return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
|
|
2665
|
+
}
|
|
2666
|
+
function normalisePathSegment(value) {
|
|
2667
|
+
const cleaned = value.trim().replace(/[^a-z0-9._-]+/giu, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
2668
|
+
return cleaned.length > 0 ? cleaned : "segment";
|
|
2669
|
+
}
|
|
2670
|
+
function ensureTrailingNewline(value) {
|
|
2671
|
+
return value.endsWith("\n") ? value : `${value}
|
|
2672
|
+
`;
|
|
2673
|
+
}
|
|
2674
|
+
function redactDataUrlPayload(value) {
|
|
2675
|
+
if (!value.toLowerCase().startsWith("data:")) {
|
|
2676
|
+
return value;
|
|
2677
|
+
}
|
|
2678
|
+
const commaIndex = value.indexOf(",");
|
|
2679
|
+
if (commaIndex < 0) {
|
|
2680
|
+
return value;
|
|
2681
|
+
}
|
|
2682
|
+
return `${value.slice(0, commaIndex + 1)}...`;
|
|
2683
|
+
}
|
|
2684
|
+
function sanitiseLogValue(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
2685
|
+
if (typeof value === "string") {
|
|
2686
|
+
return redactDataUrlPayload(value);
|
|
2687
|
+
}
|
|
2688
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
|
|
2689
|
+
return value;
|
|
2690
|
+
}
|
|
2691
|
+
if (Array.isArray(value)) {
|
|
2692
|
+
return value.map((entry) => sanitiseLogValue(entry, seen));
|
|
2693
|
+
}
|
|
2694
|
+
if (typeof value !== "object") {
|
|
2695
|
+
return String(value);
|
|
2696
|
+
}
|
|
2697
|
+
if (seen.has(value)) {
|
|
2698
|
+
return "[circular]";
|
|
2699
|
+
}
|
|
2700
|
+
seen.add(value);
|
|
2701
|
+
const record = value;
|
|
2702
|
+
const output = {};
|
|
2703
|
+
const hasInlineMime = typeof record.mimeType === "string" && record.mimeType.trim().length > 0 || typeof record.mime_type === "string" && record.mime_type.trim().length > 0;
|
|
2704
|
+
for (const [key, entryValue] of Object.entries(record)) {
|
|
2705
|
+
if (key === "image_url") {
|
|
2706
|
+
if (typeof entryValue === "string") {
|
|
2707
|
+
output[key] = redactDataUrlPayload(entryValue);
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
if (entryValue && typeof entryValue === "object") {
|
|
2711
|
+
const nested = entryValue;
|
|
2712
|
+
if (typeof nested.url === "string") {
|
|
2713
|
+
output[key] = {
|
|
2714
|
+
...nested,
|
|
2715
|
+
url: redactDataUrlPayload(nested.url)
|
|
2716
|
+
};
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
if (key === "data" && hasInlineMime && typeof entryValue === "string") {
|
|
2722
|
+
output[key] = `[omitted:${Buffer3.byteLength(entryValue, "utf8")}b]`;
|
|
2723
|
+
continue;
|
|
2724
|
+
}
|
|
2725
|
+
output[key] = sanitiseLogValue(entryValue, seen);
|
|
2726
|
+
}
|
|
2727
|
+
return output;
|
|
2728
|
+
}
|
|
2729
|
+
function serialiseForSnippet(value) {
|
|
2730
|
+
if (typeof value === "string") {
|
|
2731
|
+
return value;
|
|
2732
|
+
}
|
|
2733
|
+
try {
|
|
2734
|
+
return JSON.stringify(sanitiseLogValue(value));
|
|
2735
|
+
} catch {
|
|
2736
|
+
return String(value);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
function formatToolLogSnippet(value) {
|
|
2740
|
+
const compact = serialiseForSnippet(value).replace(/\s+/gu, " ").trim();
|
|
2741
|
+
if (compact.length === 0) {
|
|
2742
|
+
return "<empty>";
|
|
2743
|
+
}
|
|
2744
|
+
const max = 600;
|
|
2745
|
+
if (compact.length <= max) {
|
|
2746
|
+
return compact;
|
|
2747
|
+
}
|
|
2748
|
+
return `${compact.slice(0, max)}...`;
|
|
2749
|
+
}
|
|
2750
|
+
function formatUsd(value) {
|
|
2751
|
+
const amount = typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
2752
|
+
return amount.toFixed(6);
|
|
2753
|
+
}
|
|
2754
|
+
function appendToolCallStreamLog(options) {
|
|
2755
|
+
const event = options.event;
|
|
2756
|
+
if (event.type !== "tool_call") {
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
const callIdSegment = typeof event.callId === "string" && event.callId.trim().length > 0 ? ` callId=${event.callId}` : "";
|
|
2760
|
+
const prefix = [
|
|
2761
|
+
`tool_call_${event.phase}:`,
|
|
2762
|
+
`turn=${event.turn.toString()}`,
|
|
2763
|
+
`index=${event.toolIndex.toString()}`,
|
|
2764
|
+
`tool=${event.toolName}${callIdSegment}`
|
|
2765
|
+
].join(" ");
|
|
2766
|
+
if (event.phase === "started") {
|
|
2767
|
+
options.append(prefix);
|
|
2768
|
+
options.append(`tool_call_input: ${formatToolLogSnippet(sanitiseLogValue(event.input))}`);
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const durationSegment = typeof event.durationMs === "number" && Number.isFinite(event.durationMs) ? ` durationMs=${Math.max(0, Math.round(event.durationMs)).toString()}` : "";
|
|
2772
|
+
options.append(`${prefix} status=${event.error ? "error" : "ok"}${durationSegment}`);
|
|
2773
|
+
options.append(`tool_call_output: ${formatToolLogSnippet(sanitiseLogValue(event.output))}`);
|
|
2774
|
+
if (typeof event.error === "string" && event.error.trim().length > 0) {
|
|
2775
|
+
options.append(`tool_call_error: ${event.error.trim()}`);
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
function appendAgentStreamEventLog(options) {
|
|
2779
|
+
const event = options.event;
|
|
2780
|
+
switch (event.type) {
|
|
2781
|
+
case "delta": {
|
|
2782
|
+
const channelPrefix = event.channel === "thought" ? "thought_delta" : "response_delta";
|
|
2783
|
+
options.append(`${channelPrefix}: ${event.text}`);
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
case "model": {
|
|
2787
|
+
options.append(`model: ${event.modelVersion}`);
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
case "usage": {
|
|
2791
|
+
options.append(
|
|
2792
|
+
[
|
|
2793
|
+
"usage:",
|
|
2794
|
+
`modelVersion=${event.modelVersion}`,
|
|
2795
|
+
`costUsd=${formatUsd(event.costUsd)}`,
|
|
2796
|
+
`tokens=${formatToolLogSnippet(sanitiseLogValue(event.usage))}`
|
|
2797
|
+
].join(" ")
|
|
2798
|
+
);
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
case "blocked": {
|
|
2802
|
+
options.append("blocked");
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
case "tool_call": {
|
|
2806
|
+
appendToolCallStreamLog({
|
|
2807
|
+
event,
|
|
2808
|
+
append: options.append
|
|
2809
|
+
});
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
var AgentLoggingSessionImpl = class {
|
|
2815
|
+
workspaceDir;
|
|
2816
|
+
logsRootDir;
|
|
2817
|
+
mirrorToConsole;
|
|
2818
|
+
sink;
|
|
2819
|
+
agentLogPath;
|
|
2820
|
+
ensureReady;
|
|
2821
|
+
pending = /* @__PURE__ */ new Set();
|
|
2822
|
+
lineChain = Promise.resolve();
|
|
2823
|
+
callCounter = 0;
|
|
2824
|
+
constructor(config) {
|
|
2825
|
+
this.workspaceDir = path3.resolve(config.workspaceDir ?? process.cwd());
|
|
2826
|
+
this.logsRootDir = path3.join(path3.dirname(this.workspaceDir), "logs");
|
|
2827
|
+
this.mirrorToConsole = config.mirrorToConsole !== false;
|
|
2828
|
+
this.sink = config.sink;
|
|
2829
|
+
this.agentLogPath = path3.join(this.workspaceDir, "agent.log");
|
|
2830
|
+
this.ensureReady = this.prepare();
|
|
2831
|
+
this.track(this.ensureReady);
|
|
2832
|
+
}
|
|
2833
|
+
async prepare() {
|
|
2834
|
+
await mkdir(this.workspaceDir, { recursive: true });
|
|
2835
|
+
await mkdir(this.logsRootDir, { recursive: true });
|
|
2836
|
+
}
|
|
2837
|
+
track(task) {
|
|
2838
|
+
this.pending.add(task);
|
|
2839
|
+
task.finally(() => {
|
|
2840
|
+
this.pending.delete(task);
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
enqueueLineWrite(line) {
|
|
2844
|
+
const next = this.lineChain.then(async () => {
|
|
2845
|
+
await this.ensureReady;
|
|
2846
|
+
await appendFile(this.agentLogPath, `${line}
|
|
2847
|
+
`, "utf8");
|
|
2848
|
+
const sinkResult = this.sink?.append(line);
|
|
2849
|
+
if (isPromiseLike(sinkResult)) {
|
|
2850
|
+
await sinkResult;
|
|
2851
|
+
}
|
|
2852
|
+
});
|
|
2853
|
+
const tracked = next.catch(() => void 0);
|
|
2854
|
+
this.lineChain = tracked;
|
|
2855
|
+
this.track(tracked);
|
|
2856
|
+
}
|
|
2857
|
+
logLine(line) {
|
|
2858
|
+
const timestamped = `${toIsoNow()} ${line}`;
|
|
2859
|
+
if (this.mirrorToConsole) {
|
|
2860
|
+
console.log(timestamped);
|
|
2861
|
+
}
|
|
2862
|
+
this.enqueueLineWrite(timestamped);
|
|
2863
|
+
}
|
|
2864
|
+
startLlmCall(input) {
|
|
2865
|
+
const callNumber = this.callCounter + 1;
|
|
2866
|
+
this.callCounter = callNumber;
|
|
2867
|
+
const timestampSegment = toIsoNow().replace(/[:]/g, "-");
|
|
2868
|
+
const modelSegment = normalisePathSegment(input.modelId);
|
|
2869
|
+
const baseDir = path3.join(
|
|
2870
|
+
this.logsRootDir,
|
|
2871
|
+
`${timestampSegment}-${callNumber.toString().padStart(4, "0")}`,
|
|
2872
|
+
modelSegment
|
|
2873
|
+
);
|
|
2874
|
+
const responsePath = path3.join(baseDir, "response.txt");
|
|
2875
|
+
const thoughtsPath = path3.join(baseDir, "thoughts.txt");
|
|
2876
|
+
const responseMetadataPath = path3.join(baseDir, "response.metadata.json");
|
|
2877
|
+
let chain = this.ensureReady.then(async () => {
|
|
2878
|
+
await mkdir(baseDir, { recursive: true });
|
|
2879
|
+
const requestText = input.requestText.trim().length > 0 ? input.requestText : "<empty request>";
|
|
2880
|
+
await writeFile(
|
|
2881
|
+
path3.join(baseDir, "request.txt"),
|
|
2882
|
+
ensureTrailingNewline(requestText),
|
|
2883
|
+
"utf8"
|
|
2884
|
+
);
|
|
2885
|
+
const requestMetadata = {
|
|
2886
|
+
capturedAt: toIsoNow(),
|
|
2887
|
+
provider: input.provider,
|
|
2888
|
+
modelId: input.modelId,
|
|
2889
|
+
...input.requestMetadata ? { request: sanitiseLogValue(input.requestMetadata) } : {}
|
|
2890
|
+
};
|
|
2891
|
+
await writeFile(
|
|
2892
|
+
path3.join(baseDir, "request.metadata.json"),
|
|
2893
|
+
`${JSON.stringify(requestMetadata, null, 2)}
|
|
2894
|
+
`,
|
|
2895
|
+
"utf8"
|
|
2896
|
+
);
|
|
2897
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
2898
|
+
for (const attachment of input.attachments ?? []) {
|
|
2899
|
+
let filename = normalisePathSegment(attachment.filename);
|
|
2900
|
+
if (!filename.includes(".")) {
|
|
2901
|
+
filename = `${filename}.bin`;
|
|
2902
|
+
}
|
|
2903
|
+
const ext = path3.extname(filename);
|
|
2904
|
+
const base = ext.length > 0 ? filename.slice(0, -ext.length) : filename;
|
|
2905
|
+
let candidate = filename;
|
|
2906
|
+
let duplicateIndex = 2;
|
|
2907
|
+
while (usedNames.has(candidate)) {
|
|
2908
|
+
candidate = `${base}-${duplicateIndex.toString()}${ext}`;
|
|
2909
|
+
duplicateIndex += 1;
|
|
2910
|
+
}
|
|
2911
|
+
usedNames.add(candidate);
|
|
2912
|
+
await writeFile(path3.join(baseDir, candidate), attachment.bytes);
|
|
2913
|
+
}
|
|
2914
|
+
}).catch(() => void 0);
|
|
2915
|
+
this.track(chain);
|
|
2916
|
+
let closed = false;
|
|
2917
|
+
const enqueue = (operation) => {
|
|
2918
|
+
const next = chain.then(operation);
|
|
2919
|
+
const tracked = next.catch(() => void 0);
|
|
2920
|
+
chain = tracked;
|
|
2921
|
+
this.track(tracked);
|
|
2922
|
+
};
|
|
2923
|
+
return {
|
|
2924
|
+
appendThoughtDelta: (text) => {
|
|
2925
|
+
if (closed || text.length === 0) {
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
enqueue(async () => {
|
|
2929
|
+
await appendFile(thoughtsPath, text, "utf8");
|
|
2930
|
+
});
|
|
2931
|
+
},
|
|
2932
|
+
appendResponseDelta: (text) => {
|
|
2933
|
+
if (closed || text.length === 0) {
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
enqueue(async () => {
|
|
2937
|
+
await appendFile(responsePath, text, "utf8");
|
|
2938
|
+
});
|
|
2939
|
+
},
|
|
2940
|
+
complete: (metadata) => {
|
|
2941
|
+
if (closed) {
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
closed = true;
|
|
2945
|
+
enqueue(async () => {
|
|
2946
|
+
const payload = {
|
|
2947
|
+
capturedAt: toIsoNow(),
|
|
2948
|
+
status: "completed"
|
|
2949
|
+
};
|
|
2950
|
+
if (metadata) {
|
|
2951
|
+
const sanitised = sanitiseLogValue(metadata);
|
|
2952
|
+
if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
|
|
2953
|
+
Object.assign(payload, sanitised);
|
|
2954
|
+
} else if (sanitised !== void 0) {
|
|
2955
|
+
payload.metadata = sanitised;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
await writeFile(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
|
|
2959
|
+
`, "utf8");
|
|
2960
|
+
});
|
|
2961
|
+
},
|
|
2962
|
+
fail: (error, metadata) => {
|
|
2963
|
+
if (closed) {
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
closed = true;
|
|
2967
|
+
enqueue(async () => {
|
|
2968
|
+
const payload = {
|
|
2969
|
+
capturedAt: toIsoNow(),
|
|
2970
|
+
status: "failed",
|
|
2971
|
+
error: toErrorMessage(error)
|
|
2972
|
+
};
|
|
2973
|
+
if (metadata) {
|
|
2974
|
+
const sanitised = sanitiseLogValue(metadata);
|
|
2975
|
+
if (sanitised && typeof sanitised === "object" && !Array.isArray(sanitised)) {
|
|
2976
|
+
Object.assign(payload, sanitised);
|
|
2977
|
+
} else if (sanitised !== void 0) {
|
|
2978
|
+
payload.metadata = sanitised;
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
await writeFile(responseMetadataPath, `${JSON.stringify(payload, null, 2)}
|
|
2982
|
+
`, "utf8");
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
async flush() {
|
|
2988
|
+
while (this.pending.size > 0) {
|
|
2989
|
+
await Promise.allSettled([...this.pending]);
|
|
2990
|
+
}
|
|
2991
|
+
if (typeof this.sink?.flush === "function") {
|
|
2992
|
+
try {
|
|
2993
|
+
await this.sink.flush();
|
|
2994
|
+
} catch {
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
};
|
|
2999
|
+
var loggingSessionStorage = new AsyncLocalStorage();
|
|
3000
|
+
function createAgentLoggingSession(config) {
|
|
3001
|
+
return new AgentLoggingSessionImpl(config);
|
|
3002
|
+
}
|
|
3003
|
+
function runWithAgentLoggingSession(session, fn) {
|
|
3004
|
+
if (!session) {
|
|
3005
|
+
return fn();
|
|
3006
|
+
}
|
|
3007
|
+
return loggingSessionStorage.run(session, fn);
|
|
3008
|
+
}
|
|
3009
|
+
function getCurrentAgentLoggingSession() {
|
|
3010
|
+
return loggingSessionStorage.getStore();
|
|
3011
|
+
}
|
|
3012
|
+
|
|
2646
3013
|
// src/llm.ts
|
|
2647
|
-
var toolCallContextStorage = new
|
|
3014
|
+
var toolCallContextStorage = new AsyncLocalStorage2();
|
|
2648
3015
|
function getCurrentToolCallContext() {
|
|
2649
3016
|
return toolCallContextStorage.getStore() ?? null;
|
|
2650
3017
|
}
|
|
@@ -2936,9 +3303,9 @@ function sanitisePartForLogging(part) {
|
|
|
2936
3303
|
case "inlineData": {
|
|
2937
3304
|
let omittedBytes;
|
|
2938
3305
|
try {
|
|
2939
|
-
omittedBytes =
|
|
3306
|
+
omittedBytes = Buffer4.from(part.data, "base64").byteLength;
|
|
2940
3307
|
} catch {
|
|
2941
|
-
omittedBytes =
|
|
3308
|
+
omittedBytes = Buffer4.byteLength(part.data, "utf8");
|
|
2942
3309
|
}
|
|
2943
3310
|
return {
|
|
2944
3311
|
type: "inlineData",
|
|
@@ -4001,8 +4368,8 @@ function parseOpenAiToolArguments(raw) {
|
|
|
4001
4368
|
function formatZodIssues(issues) {
|
|
4002
4369
|
const messages = [];
|
|
4003
4370
|
for (const issue of issues) {
|
|
4004
|
-
const
|
|
4005
|
-
messages.push(`${
|
|
4371
|
+
const path8 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
|
|
4372
|
+
messages.push(`${path8}: ${issue.message}`);
|
|
4006
4373
|
}
|
|
4007
4374
|
return messages.join("; ");
|
|
4008
4375
|
}
|
|
@@ -4388,9 +4755,9 @@ function resolveGeminiThinkingConfig(modelId, thinkingLevel) {
|
|
|
4388
4755
|
}
|
|
4389
4756
|
function decodeInlineDataBuffer(base64) {
|
|
4390
4757
|
try {
|
|
4391
|
-
return
|
|
4758
|
+
return Buffer4.from(base64, "base64");
|
|
4392
4759
|
} catch {
|
|
4393
|
-
return
|
|
4760
|
+
return Buffer4.from(base64, "base64url");
|
|
4394
4761
|
}
|
|
4395
4762
|
}
|
|
4396
4763
|
function extractImages(content) {
|
|
@@ -4407,6 +4774,195 @@ function extractImages(content) {
|
|
|
4407
4774
|
}
|
|
4408
4775
|
return images;
|
|
4409
4776
|
}
|
|
4777
|
+
function resolveAttachmentExtension(mimeType) {
|
|
4778
|
+
const normalized = (mimeType ?? "").trim().toLowerCase();
|
|
4779
|
+
switch (normalized) {
|
|
4780
|
+
case "image/jpeg":
|
|
4781
|
+
return "jpg";
|
|
4782
|
+
case "image/png":
|
|
4783
|
+
return "png";
|
|
4784
|
+
case "image/webp":
|
|
4785
|
+
return "webp";
|
|
4786
|
+
case "image/gif":
|
|
4787
|
+
return "gif";
|
|
4788
|
+
case "image/heic":
|
|
4789
|
+
return "heic";
|
|
4790
|
+
case "image/heif":
|
|
4791
|
+
return "heif";
|
|
4792
|
+
case "application/pdf":
|
|
4793
|
+
return "pdf";
|
|
4794
|
+
case "application/json":
|
|
4795
|
+
return "json";
|
|
4796
|
+
case "text/plain":
|
|
4797
|
+
return "txt";
|
|
4798
|
+
case "text/markdown":
|
|
4799
|
+
return "md";
|
|
4800
|
+
default: {
|
|
4801
|
+
const slashIndex = normalized.indexOf("/");
|
|
4802
|
+
if (slashIndex >= 0) {
|
|
4803
|
+
const subtype = normalized.slice(slashIndex + 1).split("+")[0] ?? "";
|
|
4804
|
+
const cleaned = subtype.replace(/[^a-z0-9]+/giu, "");
|
|
4805
|
+
if (cleaned.length > 0) {
|
|
4806
|
+
return cleaned;
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
return "bin";
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
function decodeDataUrlAttachment(value, basename) {
|
|
4814
|
+
const trimmed = value.trim();
|
|
4815
|
+
if (!trimmed.toLowerCase().startsWith("data:")) {
|
|
4816
|
+
return null;
|
|
4817
|
+
}
|
|
4818
|
+
const commaIndex = trimmed.indexOf(",");
|
|
4819
|
+
if (commaIndex < 0) {
|
|
4820
|
+
return null;
|
|
4821
|
+
}
|
|
4822
|
+
const header = trimmed.slice(5, commaIndex);
|
|
4823
|
+
const payload = trimmed.slice(commaIndex + 1);
|
|
4824
|
+
const isBase64 = /;base64(?:;|$)/iu.test(header);
|
|
4825
|
+
const mimeType = (header.split(";")[0] ?? "application/octet-stream").trim().toLowerCase();
|
|
4826
|
+
try {
|
|
4827
|
+
const bytes = isBase64 ? Buffer4.from(payload, "base64") : Buffer4.from(decodeURIComponent(payload), "utf8");
|
|
4828
|
+
return {
|
|
4829
|
+
filename: `${basename}.${resolveAttachmentExtension(mimeType)}`,
|
|
4830
|
+
bytes
|
|
4831
|
+
};
|
|
4832
|
+
} catch {
|
|
4833
|
+
return null;
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
function collectPayloadAttachments(value, options) {
|
|
4837
|
+
if (typeof value === "string") {
|
|
4838
|
+
const attachment = decodeDataUrlAttachment(
|
|
4839
|
+
value,
|
|
4840
|
+
`${options.prefix}-${options.counter.toString()}`
|
|
4841
|
+
);
|
|
4842
|
+
if (attachment) {
|
|
4843
|
+
options.attachments.push(attachment);
|
|
4844
|
+
options.counter += 1;
|
|
4845
|
+
}
|
|
4846
|
+
return;
|
|
4847
|
+
}
|
|
4848
|
+
if (!value || typeof value !== "object") {
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
if (options.seen.has(value)) {
|
|
4852
|
+
return;
|
|
4853
|
+
}
|
|
4854
|
+
options.seen.add(value);
|
|
4855
|
+
if (Array.isArray(value)) {
|
|
4856
|
+
for (const entry of value) {
|
|
4857
|
+
collectPayloadAttachments(entry, options);
|
|
4858
|
+
}
|
|
4859
|
+
return;
|
|
4860
|
+
}
|
|
4861
|
+
const record = value;
|
|
4862
|
+
const mimeType = typeof record.mimeType === "string" ? record.mimeType : void 0;
|
|
4863
|
+
if (typeof record.data === "string" && mimeType) {
|
|
4864
|
+
try {
|
|
4865
|
+
options.attachments.push({
|
|
4866
|
+
filename: `${options.prefix}-${options.counter.toString()}.${resolveAttachmentExtension(mimeType)}`,
|
|
4867
|
+
bytes: decodeInlineDataBuffer(record.data)
|
|
4868
|
+
});
|
|
4869
|
+
options.counter += 1;
|
|
4870
|
+
} catch {
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
for (const entry of Object.values(record)) {
|
|
4874
|
+
collectPayloadAttachments(entry, options);
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
function serialiseRequestPayloadForLogging(value) {
|
|
4878
|
+
try {
|
|
4879
|
+
return `${JSON.stringify(sanitiseLogValue(value), null, 2)}
|
|
4880
|
+
`;
|
|
4881
|
+
} catch {
|
|
4882
|
+
return `${String(value)}
|
|
4883
|
+
`;
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
function startLlmCallLoggerFromContents(options) {
|
|
4887
|
+
const session = getCurrentAgentLoggingSession();
|
|
4888
|
+
if (!session) {
|
|
4889
|
+
return void 0;
|
|
4890
|
+
}
|
|
4891
|
+
const attachments = [];
|
|
4892
|
+
const sections = [];
|
|
4893
|
+
for (const [messageIndex, message] of options.contents.entries()) {
|
|
4894
|
+
sections.push(`### message_${(messageIndex + 1).toString()} role=${message.role}`);
|
|
4895
|
+
for (const [partIndex, part] of message.parts.entries()) {
|
|
4896
|
+
if (part.type === "text") {
|
|
4897
|
+
const channel = part.thought === true ? "thought" : "response";
|
|
4898
|
+
sections.push(`[text:${channel}]`);
|
|
4899
|
+
sections.push(part.text);
|
|
4900
|
+
continue;
|
|
4901
|
+
}
|
|
4902
|
+
const filename = `message-${(messageIndex + 1).toString()}-part-${(partIndex + 1).toString()}.${resolveAttachmentExtension(part.mimeType)}`;
|
|
4903
|
+
attachments.push({
|
|
4904
|
+
filename,
|
|
4905
|
+
bytes: decodeInlineDataBuffer(part.data)
|
|
4906
|
+
});
|
|
4907
|
+
sections.push(
|
|
4908
|
+
`[inlineData] file=${filename} mime=${part.mimeType ?? "application/octet-stream"} bytes=${attachments[attachments.length - 1]?.bytes.byteLength ?? 0}`
|
|
4909
|
+
);
|
|
4910
|
+
}
|
|
4911
|
+
sections.push("");
|
|
4912
|
+
}
|
|
4913
|
+
return session.startLlmCall({
|
|
4914
|
+
provider: options.provider,
|
|
4915
|
+
modelId: options.request.model,
|
|
4916
|
+
requestText: sections.join("\n").trim(),
|
|
4917
|
+
requestMetadata: {
|
|
4918
|
+
model: options.request.model,
|
|
4919
|
+
input: options.contents.map((content) => ({
|
|
4920
|
+
role: content.role,
|
|
4921
|
+
parts: content.parts.map((part) => sanitisePartForLogging(part))
|
|
4922
|
+
})),
|
|
4923
|
+
...options.request.instructions ? {
|
|
4924
|
+
instructions: options.request.instructions
|
|
4925
|
+
} : {},
|
|
4926
|
+
...options.request.tools ? { tools: options.request.tools } : {},
|
|
4927
|
+
...options.request.responseMimeType ? {
|
|
4928
|
+
responseMimeType: options.request.responseMimeType
|
|
4929
|
+
} : {},
|
|
4930
|
+
...options.request.responseJsonSchema ? {
|
|
4931
|
+
responseJsonSchema: sanitiseLogValue(options.request.responseJsonSchema)
|
|
4932
|
+
} : {},
|
|
4933
|
+
...options.request.responseModalities ? { responseModalities: options.request.responseModalities } : {},
|
|
4934
|
+
...options.request.imageAspectRatio ? { imageAspectRatio: options.request.imageAspectRatio } : {},
|
|
4935
|
+
...options.request.imageSize ? { imageSize: options.request.imageSize } : {},
|
|
4936
|
+
...options.request.thinkingLevel ? { thinkingLevel: options.request.thinkingLevel } : {},
|
|
4937
|
+
...options.request.openAiTextFormat ? { openAiTextFormat: sanitiseLogValue(options.request.openAiTextFormat) } : {},
|
|
4938
|
+
...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
|
|
4939
|
+
},
|
|
4940
|
+
attachments
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4943
|
+
function startLlmCallLoggerFromPayload(options) {
|
|
4944
|
+
const session = getCurrentAgentLoggingSession();
|
|
4945
|
+
if (!session) {
|
|
4946
|
+
return void 0;
|
|
4947
|
+
}
|
|
4948
|
+
const attachments = [];
|
|
4949
|
+
collectPayloadAttachments(options.requestPayload, {
|
|
4950
|
+
prefix: `step-${options.step.toString()}`,
|
|
4951
|
+
attachments,
|
|
4952
|
+
seen: /* @__PURE__ */ new WeakSet(),
|
|
4953
|
+
counter: 1
|
|
4954
|
+
});
|
|
4955
|
+
return session.startLlmCall({
|
|
4956
|
+
provider: options.provider,
|
|
4957
|
+
modelId: options.modelId,
|
|
4958
|
+
requestText: serialiseRequestPayloadForLogging(options.requestPayload),
|
|
4959
|
+
requestMetadata: {
|
|
4960
|
+
step: options.step,
|
|
4961
|
+
...getCurrentToolCallContext() ? { toolContext: getCurrentToolCallContext() } : {}
|
|
4962
|
+
},
|
|
4963
|
+
attachments
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4410
4966
|
async function runTextCall(params) {
|
|
4411
4967
|
const { request, queue, abortController } = params;
|
|
4412
4968
|
const providerInfo = resolveProvider(request.model);
|
|
@@ -4416,6 +4972,11 @@ async function runTextCall(params) {
|
|
|
4416
4972
|
if (contents.length === 0) {
|
|
4417
4973
|
throw new Error("LLM call received an empty prompt.");
|
|
4418
4974
|
}
|
|
4975
|
+
const callLogger = startLlmCallLoggerFromContents({
|
|
4976
|
+
provider,
|
|
4977
|
+
request,
|
|
4978
|
+
contents
|
|
4979
|
+
});
|
|
4419
4980
|
let modelVersion = request.model;
|
|
4420
4981
|
let blocked = false;
|
|
4421
4982
|
let grounding;
|
|
@@ -4423,12 +4984,17 @@ async function runTextCall(params) {
|
|
|
4423
4984
|
let responseRole;
|
|
4424
4985
|
let latestUsage;
|
|
4425
4986
|
let responseImages = 0;
|
|
4426
|
-
const pushDelta = (channel,
|
|
4427
|
-
if (!
|
|
4987
|
+
const pushDelta = (channel, text) => {
|
|
4988
|
+
if (!text) {
|
|
4428
4989
|
return;
|
|
4429
4990
|
}
|
|
4430
|
-
responseParts.push({ type: "text", text
|
|
4431
|
-
|
|
4991
|
+
responseParts.push({ type: "text", text, ...channel === "thought" ? { thought: true } : {} });
|
|
4992
|
+
if (channel === "thought") {
|
|
4993
|
+
callLogger?.appendThoughtDelta(text);
|
|
4994
|
+
} else {
|
|
4995
|
+
callLogger?.appendResponseDelta(text);
|
|
4996
|
+
}
|
|
4997
|
+
queue.push({ type: "delta", channel, text });
|
|
4432
4998
|
};
|
|
4433
4999
|
const pushInline = (data, mimeType) => {
|
|
4434
5000
|
if (!data) {
|
|
@@ -4455,263 +5021,294 @@ async function runTextCall(params) {
|
|
|
4455
5021
|
return abortController.signal;
|
|
4456
5022
|
};
|
|
4457
5023
|
const signal = resolveAbortSignal();
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
5024
|
+
try {
|
|
5025
|
+
if (provider === "openai") {
|
|
5026
|
+
const openAiInput = toOpenAiInput(contents);
|
|
5027
|
+
const openAiTools = toOpenAiTools(request.tools);
|
|
5028
|
+
const reasoningEffort = resolveOpenAiReasoningEffort(modelForProvider, request.thinkingLevel);
|
|
5029
|
+
const openAiTextConfig = {
|
|
5030
|
+
format: request.openAiTextFormat ?? { type: "text" },
|
|
5031
|
+
verbosity: resolveOpenAiVerbosity(modelForProvider)
|
|
5032
|
+
};
|
|
5033
|
+
const reasoning = {
|
|
5034
|
+
effort: toOpenAiReasoningEffort(reasoningEffort),
|
|
5035
|
+
summary: "detailed"
|
|
5036
|
+
};
|
|
5037
|
+
await runOpenAiCall(async (client) => {
|
|
5038
|
+
const stream = client.responses.stream(
|
|
5039
|
+
{
|
|
5040
|
+
model: modelForProvider,
|
|
5041
|
+
input: openAiInput,
|
|
5042
|
+
reasoning,
|
|
5043
|
+
text: openAiTextConfig,
|
|
5044
|
+
...openAiTools ? { tools: openAiTools } : {},
|
|
5045
|
+
include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
|
|
5046
|
+
},
|
|
5047
|
+
{ signal }
|
|
5048
|
+
);
|
|
5049
|
+
for await (const event of stream) {
|
|
5050
|
+
switch (event.type) {
|
|
5051
|
+
case "response.output_text.delta": {
|
|
5052
|
+
const delta = event.delta ?? "";
|
|
5053
|
+
pushDelta("response", typeof delta === "string" ? delta : "");
|
|
5054
|
+
break;
|
|
5055
|
+
}
|
|
5056
|
+
case "response.reasoning_summary_text.delta": {
|
|
5057
|
+
const delta = event.delta ?? "";
|
|
5058
|
+
pushDelta("thought", typeof delta === "string" ? delta : "");
|
|
5059
|
+
break;
|
|
5060
|
+
}
|
|
5061
|
+
case "response.refusal.delta": {
|
|
5062
|
+
blocked = true;
|
|
5063
|
+
queue.push({ type: "blocked" });
|
|
5064
|
+
break;
|
|
5065
|
+
}
|
|
5066
|
+
default:
|
|
5067
|
+
break;
|
|
4488
5068
|
}
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
5069
|
+
}
|
|
5070
|
+
const finalResponse = await stream.finalResponse();
|
|
5071
|
+
modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
|
|
5072
|
+
queue.push({ type: "model", modelVersion });
|
|
5073
|
+
if (finalResponse.error) {
|
|
5074
|
+
const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
|
|
5075
|
+
throw new Error(message);
|
|
5076
|
+
}
|
|
5077
|
+
if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
|
|
5078
|
+
const detail = finalResponse.incomplete_details?.reason;
|
|
5079
|
+
throw new Error(
|
|
5080
|
+
`OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
|
|
5081
|
+
);
|
|
5082
|
+
}
|
|
5083
|
+
latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
|
|
5084
|
+
if (responseParts.length === 0) {
|
|
5085
|
+
const fallback = extractOpenAiResponseParts(finalResponse);
|
|
5086
|
+
blocked = blocked || fallback.blocked;
|
|
5087
|
+
for (const part of fallback.parts) {
|
|
5088
|
+
if (part.type === "text") {
|
|
5089
|
+
pushDelta(part.thought === true ? "thought" : "response", part.text);
|
|
5090
|
+
} else {
|
|
5091
|
+
pushInline(part.data, part.mimeType);
|
|
5092
|
+
}
|
|
4493
5093
|
}
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
5094
|
+
}
|
|
5095
|
+
}, modelForProvider);
|
|
5096
|
+
} else if (provider === "chatgpt") {
|
|
5097
|
+
const chatGptInput = toChatGptInput(contents);
|
|
5098
|
+
const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
|
|
5099
|
+
const openAiTools = toOpenAiTools(request.tools);
|
|
5100
|
+
const requestPayload = {
|
|
5101
|
+
model: modelForProvider,
|
|
5102
|
+
store: false,
|
|
5103
|
+
stream: true,
|
|
5104
|
+
instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
|
|
5105
|
+
input: chatGptInput.input,
|
|
5106
|
+
include: ["reasoning.encrypted_content"],
|
|
5107
|
+
reasoning: {
|
|
5108
|
+
effort: toOpenAiReasoningEffort(reasoningEffort),
|
|
5109
|
+
summary: "detailed"
|
|
5110
|
+
},
|
|
5111
|
+
text: {
|
|
5112
|
+
format: request.openAiTextFormat ?? { type: "text" },
|
|
5113
|
+
verbosity: resolveOpenAiVerbosity(request.model)
|
|
5114
|
+
},
|
|
5115
|
+
...openAiTools ? { tools: openAiTools } : {}
|
|
5116
|
+
};
|
|
5117
|
+
let sawResponseDelta = false;
|
|
5118
|
+
let sawThoughtDelta = false;
|
|
5119
|
+
const result = await collectChatGptCodexResponseWithRetry({
|
|
5120
|
+
request: requestPayload,
|
|
5121
|
+
signal,
|
|
5122
|
+
onDelta: (delta) => {
|
|
5123
|
+
if (delta.thoughtDelta) {
|
|
5124
|
+
sawThoughtDelta = true;
|
|
5125
|
+
pushDelta("thought", delta.thoughtDelta);
|
|
5126
|
+
}
|
|
5127
|
+
if (delta.textDelta) {
|
|
5128
|
+
sawResponseDelta = true;
|
|
5129
|
+
pushDelta("response", delta.textDelta);
|
|
4498
5130
|
}
|
|
4499
|
-
default:
|
|
4500
|
-
break;
|
|
4501
5131
|
}
|
|
5132
|
+
});
|
|
5133
|
+
blocked = blocked || result.blocked;
|
|
5134
|
+
if (blocked) {
|
|
5135
|
+
queue.push({ type: "blocked" });
|
|
4502
5136
|
}
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
if (finalResponse.error) {
|
|
4507
|
-
const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
|
|
4508
|
-
throw new Error(message);
|
|
5137
|
+
if (result.model) {
|
|
5138
|
+
modelVersion = `chatgpt-${result.model}`;
|
|
5139
|
+
queue.push({ type: "model", modelVersion });
|
|
4509
5140
|
}
|
|
4510
|
-
|
|
4511
|
-
|
|
5141
|
+
latestUsage = extractChatGptUsageTokens(result.usage);
|
|
5142
|
+
const fallbackText = typeof result.text === "string" ? result.text : "";
|
|
5143
|
+
const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
|
|
5144
|
+
if (!sawThoughtDelta && fallbackThoughts.length > 0) {
|
|
5145
|
+
pushDelta("thought", fallbackThoughts);
|
|
5146
|
+
}
|
|
5147
|
+
if (!sawResponseDelta && fallbackText.length > 0) {
|
|
5148
|
+
pushDelta("response", fallbackText);
|
|
5149
|
+
}
|
|
5150
|
+
} else if (provider === "fireworks") {
|
|
5151
|
+
if (request.tools && request.tools.length > 0) {
|
|
4512
5152
|
throw new Error(
|
|
4513
|
-
|
|
5153
|
+
"Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
|
|
4514
5154
|
);
|
|
4515
5155
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
5156
|
+
const fireworksMessages = toFireworksMessages(contents, {
|
|
5157
|
+
responseMimeType: request.responseMimeType,
|
|
5158
|
+
responseJsonSchema: request.responseJsonSchema
|
|
5159
|
+
});
|
|
5160
|
+
await runFireworksCall(async (client) => {
|
|
5161
|
+
const responseFormat = request.responseJsonSchema ? {
|
|
5162
|
+
type: "json_schema",
|
|
5163
|
+
json_schema: {
|
|
5164
|
+
name: "llm-response",
|
|
5165
|
+
schema: request.responseJsonSchema
|
|
4525
5166
|
}
|
|
4526
|
-
}
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
include: ["reasoning.encrypted_content"],
|
|
4540
|
-
reasoning: { effort: toOpenAiReasoningEffort(reasoningEffort), summary: "detailed" },
|
|
4541
|
-
text: {
|
|
4542
|
-
format: request.openAiTextFormat ?? { type: "text" },
|
|
4543
|
-
verbosity: resolveOpenAiVerbosity(request.model)
|
|
4544
|
-
},
|
|
4545
|
-
...openAiTools ? { tools: openAiTools } : {}
|
|
4546
|
-
};
|
|
4547
|
-
let sawResponseDelta = false;
|
|
4548
|
-
let sawThoughtDelta = false;
|
|
4549
|
-
const result = await collectChatGptCodexResponseWithRetry({
|
|
4550
|
-
request: requestPayload,
|
|
4551
|
-
signal,
|
|
4552
|
-
onDelta: (delta) => {
|
|
4553
|
-
if (delta.thoughtDelta) {
|
|
4554
|
-
sawThoughtDelta = true;
|
|
4555
|
-
pushDelta("thought", delta.thoughtDelta);
|
|
4556
|
-
}
|
|
4557
|
-
if (delta.textDelta) {
|
|
4558
|
-
sawResponseDelta = true;
|
|
4559
|
-
pushDelta("response", delta.textDelta);
|
|
4560
|
-
}
|
|
4561
|
-
}
|
|
4562
|
-
});
|
|
4563
|
-
blocked = blocked || result.blocked;
|
|
4564
|
-
if (blocked) {
|
|
4565
|
-
queue.push({ type: "blocked" });
|
|
4566
|
-
}
|
|
4567
|
-
if (result.model) {
|
|
4568
|
-
modelVersion = `chatgpt-${result.model}`;
|
|
4569
|
-
queue.push({ type: "model", modelVersion });
|
|
4570
|
-
}
|
|
4571
|
-
latestUsage = extractChatGptUsageTokens(result.usage);
|
|
4572
|
-
const fallbackText = typeof result.text === "string" ? result.text : "";
|
|
4573
|
-
const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
|
|
4574
|
-
if (!sawThoughtDelta && fallbackThoughts.length > 0) {
|
|
4575
|
-
pushDelta("thought", fallbackThoughts);
|
|
4576
|
-
}
|
|
4577
|
-
if (!sawResponseDelta && fallbackText.length > 0) {
|
|
4578
|
-
pushDelta("response", fallbackText);
|
|
4579
|
-
}
|
|
4580
|
-
} else if (provider === "fireworks") {
|
|
4581
|
-
if (request.tools && request.tools.length > 0) {
|
|
4582
|
-
throw new Error(
|
|
4583
|
-
"Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
|
|
4584
|
-
);
|
|
4585
|
-
}
|
|
4586
|
-
const fireworksMessages = toFireworksMessages(contents, {
|
|
4587
|
-
responseMimeType: request.responseMimeType,
|
|
4588
|
-
responseJsonSchema: request.responseJsonSchema
|
|
4589
|
-
});
|
|
4590
|
-
await runFireworksCall(async (client) => {
|
|
4591
|
-
const responseFormat = request.responseJsonSchema ? {
|
|
4592
|
-
type: "json_schema",
|
|
4593
|
-
json_schema: {
|
|
4594
|
-
name: "llm-response",
|
|
4595
|
-
schema: request.responseJsonSchema
|
|
4596
|
-
}
|
|
4597
|
-
} : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
|
|
4598
|
-
const response = await client.chat.completions.create(
|
|
4599
|
-
{
|
|
4600
|
-
model: modelForProvider,
|
|
4601
|
-
messages: fireworksMessages,
|
|
4602
|
-
...responseFormat ? { response_format: responseFormat } : {}
|
|
4603
|
-
},
|
|
4604
|
-
{ signal }
|
|
4605
|
-
);
|
|
4606
|
-
modelVersion = typeof response.model === "string" ? response.model : request.model;
|
|
4607
|
-
queue.push({ type: "model", modelVersion });
|
|
4608
|
-
const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
|
|
4609
|
-
if (choice?.finish_reason === "content_filter") {
|
|
4610
|
-
blocked = true;
|
|
4611
|
-
queue.push({ type: "blocked" });
|
|
4612
|
-
}
|
|
4613
|
-
const textOutput = extractFireworksMessageText(
|
|
4614
|
-
choice?.message
|
|
4615
|
-
);
|
|
4616
|
-
if (textOutput.length > 0) {
|
|
4617
|
-
pushDelta("response", textOutput);
|
|
4618
|
-
}
|
|
4619
|
-
latestUsage = extractFireworksUsageTokens(response.usage);
|
|
4620
|
-
}, modelForProvider);
|
|
4621
|
-
} else {
|
|
4622
|
-
const geminiContents = contents.map(convertLlmContentToGeminiContent);
|
|
4623
|
-
const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
|
|
4624
|
-
const config = {
|
|
4625
|
-
maxOutputTokens: 32e3,
|
|
4626
|
-
...thinkingConfig ? { thinkingConfig } : {},
|
|
4627
|
-
...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
|
|
4628
|
-
...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
|
|
4629
|
-
...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
|
|
4630
|
-
...request.imageAspectRatio || request.imageSize ? {
|
|
4631
|
-
imageConfig: {
|
|
4632
|
-
...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
|
|
4633
|
-
...request.imageSize ? { imageSize: request.imageSize } : {}
|
|
4634
|
-
}
|
|
4635
|
-
} : {}
|
|
4636
|
-
};
|
|
4637
|
-
const geminiTools = toGeminiTools(request.tools);
|
|
4638
|
-
if (geminiTools) {
|
|
4639
|
-
config.tools = geminiTools;
|
|
4640
|
-
}
|
|
4641
|
-
await runGeminiCall(async (client) => {
|
|
4642
|
-
const stream = await client.models.generateContentStream({
|
|
4643
|
-
model: modelForProvider,
|
|
4644
|
-
contents: geminiContents,
|
|
4645
|
-
config
|
|
4646
|
-
});
|
|
4647
|
-
let latestGrounding;
|
|
4648
|
-
for await (const chunk of stream) {
|
|
4649
|
-
if (chunk.modelVersion) {
|
|
4650
|
-
modelVersion = chunk.modelVersion;
|
|
4651
|
-
queue.push({ type: "model", modelVersion });
|
|
4652
|
-
}
|
|
4653
|
-
if (chunk.promptFeedback?.blockReason) {
|
|
5167
|
+
} : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
|
|
5168
|
+
const response = await client.chat.completions.create(
|
|
5169
|
+
{
|
|
5170
|
+
model: modelForProvider,
|
|
5171
|
+
messages: fireworksMessages,
|
|
5172
|
+
...responseFormat ? { response_format: responseFormat } : {}
|
|
5173
|
+
},
|
|
5174
|
+
{ signal }
|
|
5175
|
+
);
|
|
5176
|
+
modelVersion = typeof response.model === "string" ? response.model : request.model;
|
|
5177
|
+
queue.push({ type: "model", modelVersion });
|
|
5178
|
+
const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
|
|
5179
|
+
if (choice?.finish_reason === "content_filter") {
|
|
4654
5180
|
blocked = true;
|
|
4655
5181
|
queue.push({ type: "blocked" });
|
|
4656
5182
|
}
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
const primary = candidates[0];
|
|
4663
|
-
if (primary && isModerationFinish(primary.finishReason)) {
|
|
4664
|
-
blocked = true;
|
|
4665
|
-
queue.push({ type: "blocked" });
|
|
5183
|
+
const textOutput = extractFireworksMessageText(
|
|
5184
|
+
choice?.message
|
|
5185
|
+
);
|
|
5186
|
+
if (textOutput.length > 0) {
|
|
5187
|
+
pushDelta("response", textOutput);
|
|
4666
5188
|
}
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
5189
|
+
latestUsage = extractFireworksUsageTokens(response.usage);
|
|
5190
|
+
}, modelForProvider);
|
|
5191
|
+
} else {
|
|
5192
|
+
const geminiContents = contents.map(convertLlmContentToGeminiContent);
|
|
5193
|
+
const thinkingConfig = resolveGeminiThinkingConfig(modelForProvider, request.thinkingLevel);
|
|
5194
|
+
const config = {
|
|
5195
|
+
maxOutputTokens: 32e3,
|
|
5196
|
+
...thinkingConfig ? { thinkingConfig } : {},
|
|
5197
|
+
...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
|
|
5198
|
+
...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
|
|
5199
|
+
...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
|
|
5200
|
+
...request.imageAspectRatio || request.imageSize ? {
|
|
5201
|
+
imageConfig: {
|
|
5202
|
+
...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
|
|
5203
|
+
...request.imageSize ? { imageSize: request.imageSize } : {}
|
|
4671
5204
|
}
|
|
4672
|
-
|
|
4673
|
-
|
|
5205
|
+
} : {}
|
|
5206
|
+
};
|
|
5207
|
+
const geminiTools = toGeminiTools(request.tools);
|
|
5208
|
+
if (geminiTools) {
|
|
5209
|
+
config.tools = geminiTools;
|
|
5210
|
+
}
|
|
5211
|
+
await runGeminiCall(async (client) => {
|
|
5212
|
+
const stream = await client.models.generateContentStream({
|
|
5213
|
+
model: modelForProvider,
|
|
5214
|
+
contents: geminiContents,
|
|
5215
|
+
config
|
|
5216
|
+
});
|
|
5217
|
+
let latestGrounding;
|
|
5218
|
+
for await (const chunk of stream) {
|
|
5219
|
+
if (chunk.modelVersion) {
|
|
5220
|
+
modelVersion = chunk.modelVersion;
|
|
5221
|
+
queue.push({ type: "model", modelVersion });
|
|
4674
5222
|
}
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
5223
|
+
if (chunk.promptFeedback?.blockReason) {
|
|
5224
|
+
blocked = true;
|
|
5225
|
+
queue.push({ type: "blocked" });
|
|
4678
5226
|
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
5227
|
+
latestUsage = mergeTokenUpdates(
|
|
5228
|
+
latestUsage,
|
|
5229
|
+
extractGeminiUsageTokens(chunk.usageMetadata)
|
|
5230
|
+
);
|
|
5231
|
+
const candidates = chunk.candidates;
|
|
5232
|
+
if (!candidates || candidates.length === 0) {
|
|
5233
|
+
continue;
|
|
5234
|
+
}
|
|
5235
|
+
const primary = candidates[0];
|
|
5236
|
+
if (primary && isModerationFinish(primary.finishReason)) {
|
|
5237
|
+
blocked = true;
|
|
5238
|
+
queue.push({ type: "blocked" });
|
|
5239
|
+
}
|
|
5240
|
+
for (const candidate of candidates) {
|
|
5241
|
+
const candidateContent = candidate.content;
|
|
5242
|
+
if (!candidateContent) {
|
|
5243
|
+
continue;
|
|
5244
|
+
}
|
|
5245
|
+
if (candidate.groundingMetadata) {
|
|
5246
|
+
latestGrounding = candidate.groundingMetadata;
|
|
5247
|
+
}
|
|
5248
|
+
const content2 = convertGeminiContentToLlmContent(candidateContent);
|
|
5249
|
+
if (!responseRole) {
|
|
5250
|
+
responseRole = content2.role;
|
|
5251
|
+
}
|
|
5252
|
+
for (const part of content2.parts) {
|
|
5253
|
+
if (part.type === "text") {
|
|
5254
|
+
pushDelta(part.thought === true ? "thought" : "response", part.text);
|
|
5255
|
+
} else {
|
|
5256
|
+
pushInline(part.data, part.mimeType);
|
|
5257
|
+
}
|
|
4684
5258
|
}
|
|
4685
5259
|
}
|
|
4686
5260
|
}
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
5261
|
+
grounding = latestGrounding;
|
|
5262
|
+
}, modelForProvider);
|
|
5263
|
+
}
|
|
5264
|
+
const mergedParts = mergeConsecutiveTextParts(responseParts);
|
|
5265
|
+
const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
|
|
5266
|
+
const { text, thoughts } = extractTextByChannel(content);
|
|
5267
|
+
const costUsd = estimateCallCostUsd({
|
|
5268
|
+
modelId: modelVersion,
|
|
5269
|
+
tokens: latestUsage,
|
|
5270
|
+
responseImages,
|
|
5271
|
+
imageSize: request.imageSize
|
|
5272
|
+
});
|
|
5273
|
+
if (latestUsage) {
|
|
5274
|
+
queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
|
|
5275
|
+
}
|
|
5276
|
+
callLogger?.complete({
|
|
5277
|
+
provider,
|
|
5278
|
+
model: request.model,
|
|
5279
|
+
modelVersion,
|
|
5280
|
+
blocked,
|
|
5281
|
+
costUsd,
|
|
5282
|
+
usage: latestUsage,
|
|
5283
|
+
grounding: grounding ? sanitiseLogValue(grounding) : void 0,
|
|
5284
|
+
responseChars: text.length,
|
|
5285
|
+
thoughtChars: thoughts.length,
|
|
5286
|
+
responseImages
|
|
5287
|
+
});
|
|
5288
|
+
return {
|
|
5289
|
+
provider,
|
|
5290
|
+
model: request.model,
|
|
5291
|
+
modelVersion,
|
|
5292
|
+
content,
|
|
5293
|
+
text,
|
|
5294
|
+
thoughts,
|
|
5295
|
+
blocked,
|
|
5296
|
+
usage: latestUsage,
|
|
5297
|
+
costUsd,
|
|
5298
|
+
grounding
|
|
5299
|
+
};
|
|
5300
|
+
} catch (error) {
|
|
5301
|
+
callLogger?.fail(error, {
|
|
5302
|
+
provider,
|
|
5303
|
+
model: request.model,
|
|
5304
|
+
modelVersion,
|
|
5305
|
+
blocked,
|
|
5306
|
+
usage: latestUsage,
|
|
5307
|
+
partialResponseParts: responseParts.length,
|
|
5308
|
+
responseImages
|
|
5309
|
+
});
|
|
5310
|
+
throw error;
|
|
4702
5311
|
}
|
|
4703
|
-
return {
|
|
4704
|
-
provider,
|
|
4705
|
-
model: request.model,
|
|
4706
|
-
modelVersion,
|
|
4707
|
-
content,
|
|
4708
|
-
text,
|
|
4709
|
-
thoughts,
|
|
4710
|
-
blocked,
|
|
4711
|
-
usage: latestUsage,
|
|
4712
|
-
costUsd,
|
|
4713
|
-
grounding
|
|
4714
|
-
};
|
|
4715
5312
|
}
|
|
4716
5313
|
function streamText(request) {
|
|
4717
5314
|
const queue = createAsyncQueue();
|
|
@@ -5174,6 +5771,23 @@ async function runToolLoop(request) {
|
|
|
5174
5771
|
let modelVersion = request.model;
|
|
5175
5772
|
let usageTokens;
|
|
5176
5773
|
let thoughtDeltaEmitted = false;
|
|
5774
|
+
let blocked = false;
|
|
5775
|
+
const stepRequestPayload = {
|
|
5776
|
+
model: providerInfo.model,
|
|
5777
|
+
input,
|
|
5778
|
+
...previousResponseId ? { previous_response_id: previousResponseId } : {},
|
|
5779
|
+
...openAiTools.length > 0 ? { tools: openAiTools } : {},
|
|
5780
|
+
...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
|
|
5781
|
+
reasoning,
|
|
5782
|
+
text: textConfig,
|
|
5783
|
+
include: ["reasoning.encrypted_content"]
|
|
5784
|
+
};
|
|
5785
|
+
const stepCallLogger = startLlmCallLoggerFromPayload({
|
|
5786
|
+
provider: "openai",
|
|
5787
|
+
modelId: request.model,
|
|
5788
|
+
requestPayload: stepRequestPayload,
|
|
5789
|
+
step: turn
|
|
5790
|
+
});
|
|
5177
5791
|
const emitEvent = (ev) => {
|
|
5178
5792
|
onEvent?.(ev);
|
|
5179
5793
|
};
|
|
@@ -5182,226 +5796,276 @@ async function runToolLoop(request) {
|
|
|
5182
5796
|
firstModelEventAtMs = Date.now();
|
|
5183
5797
|
}
|
|
5184
5798
|
};
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5799
|
+
try {
|
|
5800
|
+
const finalResponse = await runOpenAiCall(
|
|
5801
|
+
async (client) => {
|
|
5802
|
+
const stream = client.responses.stream(
|
|
5803
|
+
{
|
|
5804
|
+
model: providerInfo.model,
|
|
5805
|
+
input,
|
|
5806
|
+
...previousResponseId ? { previous_response_id: previousResponseId } : {},
|
|
5807
|
+
...openAiTools.length > 0 ? { tools: openAiTools } : {},
|
|
5808
|
+
...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
|
|
5809
|
+
reasoning,
|
|
5810
|
+
text: textConfig,
|
|
5811
|
+
include: ["reasoning.encrypted_content"]
|
|
5812
|
+
},
|
|
5813
|
+
{ signal: abortController.signal }
|
|
5814
|
+
);
|
|
5815
|
+
for await (const event of stream) {
|
|
5816
|
+
markFirstModelEvent();
|
|
5817
|
+
switch (event.type) {
|
|
5818
|
+
case "response.output_text.delta": {
|
|
5819
|
+
const text = typeof event.delta === "string" ? event.delta : "";
|
|
5820
|
+
if (text.length > 0) {
|
|
5821
|
+
stepCallLogger?.appendResponseDelta(text);
|
|
5822
|
+
}
|
|
5823
|
+
emitEvent({
|
|
5824
|
+
type: "delta",
|
|
5825
|
+
channel: "response",
|
|
5826
|
+
text
|
|
5827
|
+
});
|
|
5828
|
+
break;
|
|
5829
|
+
}
|
|
5830
|
+
case "response.reasoning_summary_text.delta": {
|
|
5831
|
+
thoughtDeltaEmitted = true;
|
|
5832
|
+
const text = typeof event.delta === "string" ? event.delta : "";
|
|
5833
|
+
if (text.length > 0) {
|
|
5834
|
+
stepCallLogger?.appendThoughtDelta(text);
|
|
5835
|
+
}
|
|
5836
|
+
emitEvent({
|
|
5837
|
+
type: "delta",
|
|
5838
|
+
channel: "thought",
|
|
5839
|
+
text
|
|
5840
|
+
});
|
|
5841
|
+
break;
|
|
5842
|
+
}
|
|
5843
|
+
case "response.refusal.delta":
|
|
5844
|
+
blocked = true;
|
|
5845
|
+
emitEvent({ type: "blocked" });
|
|
5846
|
+
break;
|
|
5847
|
+
default:
|
|
5848
|
+
break;
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
return await stream.finalResponse();
|
|
5852
|
+
},
|
|
5853
|
+
providerInfo.model,
|
|
5854
|
+
{
|
|
5855
|
+
onSettled: (metrics) => {
|
|
5856
|
+
schedulerMetrics = metrics;
|
|
5223
5857
|
}
|
|
5224
5858
|
}
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
{
|
|
5229
|
-
|
|
5230
|
-
|
|
5859
|
+
);
|
|
5860
|
+
modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
|
|
5861
|
+
emitEvent({ type: "model", modelVersion });
|
|
5862
|
+
if (finalResponse.error) {
|
|
5863
|
+
const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
|
|
5864
|
+
throw new Error(message);
|
|
5865
|
+
}
|
|
5866
|
+
usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
|
|
5867
|
+
const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
|
|
5868
|
+
const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
|
|
5869
|
+
if (!thoughtDeltaEmitted && reasoningSummary.length > 0) {
|
|
5870
|
+
stepCallLogger?.appendThoughtDelta(reasoningSummary);
|
|
5871
|
+
emitEvent({ type: "delta", channel: "thought", text: reasoningSummary });
|
|
5872
|
+
}
|
|
5873
|
+
const modelCompletedAtMs = Date.now();
|
|
5874
|
+
const stepCostUsd = estimateCallCostUsd({
|
|
5875
|
+
modelId: modelVersion,
|
|
5876
|
+
tokens: usageTokens,
|
|
5877
|
+
responseImages: 0
|
|
5878
|
+
});
|
|
5879
|
+
totalCostUsd += stepCostUsd;
|
|
5880
|
+
if (usageTokens) {
|
|
5881
|
+
emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
|
|
5882
|
+
}
|
|
5883
|
+
const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
|
|
5884
|
+
const stepToolCalls = [];
|
|
5885
|
+
if (responseToolCalls.length === 0) {
|
|
5886
|
+
const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
|
|
5887
|
+
const steeringItems2 = steeringInput2.length > 0 ? toOpenAiInput(steeringInput2) : [];
|
|
5888
|
+
finalText = responseText;
|
|
5889
|
+
finalThoughts = reasoningSummary;
|
|
5890
|
+
const stepCompletedAtMs2 = Date.now();
|
|
5891
|
+
const timing2 = buildStepTiming({
|
|
5892
|
+
stepStartedAtMs,
|
|
5893
|
+
stepCompletedAtMs: stepCompletedAtMs2,
|
|
5894
|
+
modelCompletedAtMs,
|
|
5895
|
+
firstModelEventAtMs,
|
|
5896
|
+
schedulerMetrics,
|
|
5897
|
+
toolExecutionMs: 0,
|
|
5898
|
+
waitToolMs: 0
|
|
5899
|
+
});
|
|
5900
|
+
steps.push({
|
|
5901
|
+
step: steps.length + 1,
|
|
5902
|
+
modelVersion,
|
|
5903
|
+
text: responseText || void 0,
|
|
5904
|
+
thoughts: reasoningSummary || void 0,
|
|
5905
|
+
toolCalls: [],
|
|
5906
|
+
usage: usageTokens,
|
|
5907
|
+
costUsd: stepCostUsd,
|
|
5908
|
+
timing: timing2
|
|
5909
|
+
});
|
|
5910
|
+
stepCallLogger?.complete({
|
|
5911
|
+
provider: "openai",
|
|
5912
|
+
model: request.model,
|
|
5913
|
+
modelVersion,
|
|
5914
|
+
step: turn,
|
|
5915
|
+
usage: usageTokens,
|
|
5916
|
+
costUsd: stepCostUsd,
|
|
5917
|
+
blocked,
|
|
5918
|
+
responseChars: responseText.length,
|
|
5919
|
+
thoughtChars: reasoningSummary.length,
|
|
5920
|
+
toolCalls: 0,
|
|
5921
|
+
finalStep: steeringItems2.length === 0
|
|
5922
|
+
});
|
|
5923
|
+
if (steeringItems2.length === 0) {
|
|
5924
|
+
return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
|
|
5231
5925
|
}
|
|
5926
|
+
previousResponseId = finalResponse.id;
|
|
5927
|
+
input = steeringItems2;
|
|
5928
|
+
continue;
|
|
5232
5929
|
}
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
const
|
|
5930
|
+
const callInputs = responseToolCalls.map((call, index) => {
|
|
5931
|
+
const toolIndex = index + 1;
|
|
5932
|
+
const toolId = buildToolLogId(turn, toolIndex);
|
|
5933
|
+
const toolName = call.name;
|
|
5934
|
+
if (call.kind === "custom") {
|
|
5935
|
+
return {
|
|
5936
|
+
call,
|
|
5937
|
+
toolName,
|
|
5938
|
+
value: call.input,
|
|
5939
|
+
parseError: void 0,
|
|
5940
|
+
toolId,
|
|
5941
|
+
turn,
|
|
5942
|
+
toolIndex
|
|
5943
|
+
};
|
|
5944
|
+
}
|
|
5945
|
+
const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
|
|
5946
|
+
return { call, toolName, value, parseError, toolId, turn, toolIndex };
|
|
5947
|
+
});
|
|
5948
|
+
for (const entry of callInputs) {
|
|
5949
|
+
emitEvent({
|
|
5950
|
+
type: "tool_call",
|
|
5951
|
+
phase: "started",
|
|
5952
|
+
turn: entry.turn,
|
|
5953
|
+
toolIndex: entry.toolIndex,
|
|
5954
|
+
toolName: entry.toolName,
|
|
5955
|
+
toolId: entry.toolId,
|
|
5956
|
+
callKind: entry.call.kind,
|
|
5957
|
+
callId: entry.call.call_id,
|
|
5958
|
+
input: entry.value
|
|
5959
|
+
});
|
|
5960
|
+
}
|
|
5961
|
+
const callResults = await Promise.all(
|
|
5962
|
+
callInputs.map(async (entry) => {
|
|
5963
|
+
return await toolCallContextStorage.run(
|
|
5964
|
+
{
|
|
5965
|
+
toolName: entry.toolName,
|
|
5966
|
+
toolId: entry.toolId,
|
|
5967
|
+
turn: entry.turn,
|
|
5968
|
+
toolIndex: entry.toolIndex
|
|
5969
|
+
},
|
|
5970
|
+
async () => {
|
|
5971
|
+
const { result, outputPayload } = await executeToolCall({
|
|
5972
|
+
callKind: entry.call.kind,
|
|
5973
|
+
toolName: entry.toolName,
|
|
5974
|
+
tool: request.tools[entry.toolName],
|
|
5975
|
+
rawInput: entry.value,
|
|
5976
|
+
parseError: entry.parseError
|
|
5977
|
+
});
|
|
5978
|
+
return { entry, result, outputPayload };
|
|
5979
|
+
}
|
|
5980
|
+
);
|
|
5981
|
+
})
|
|
5982
|
+
);
|
|
5983
|
+
const toolOutputs = [];
|
|
5984
|
+
let toolExecutionMs = 0;
|
|
5985
|
+
let waitToolMs = 0;
|
|
5986
|
+
for (const { entry, result, outputPayload } of callResults) {
|
|
5987
|
+
stepToolCalls.push({ ...result, callId: entry.call.call_id });
|
|
5988
|
+
const callDurationMs = toToolResultDuration(result);
|
|
5989
|
+
toolExecutionMs += callDurationMs;
|
|
5990
|
+
if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
|
|
5991
|
+
waitToolMs += callDurationMs;
|
|
5992
|
+
}
|
|
5993
|
+
emitEvent({
|
|
5994
|
+
type: "tool_call",
|
|
5995
|
+
phase: "completed",
|
|
5996
|
+
turn: entry.turn,
|
|
5997
|
+
toolIndex: entry.toolIndex,
|
|
5998
|
+
toolName: entry.toolName,
|
|
5999
|
+
toolId: entry.toolId,
|
|
6000
|
+
callKind: entry.call.kind,
|
|
6001
|
+
callId: entry.call.call_id,
|
|
6002
|
+
input: entry.value,
|
|
6003
|
+
output: result.output,
|
|
6004
|
+
error: result.error,
|
|
6005
|
+
durationMs: result.durationMs
|
|
6006
|
+
});
|
|
6007
|
+
if (entry.call.kind === "custom") {
|
|
6008
|
+
toolOutputs.push({
|
|
6009
|
+
type: "custom_tool_call_output",
|
|
6010
|
+
call_id: entry.call.call_id,
|
|
6011
|
+
output: toOpenAiToolOutput(outputPayload)
|
|
6012
|
+
});
|
|
6013
|
+
} else {
|
|
6014
|
+
toolOutputs.push({
|
|
6015
|
+
type: "function_call_output",
|
|
6016
|
+
call_id: entry.call.call_id,
|
|
6017
|
+
output: toOpenAiToolOutput(outputPayload)
|
|
6018
|
+
});
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
6021
|
+
const stepCompletedAtMs = Date.now();
|
|
6022
|
+
const timing = buildStepTiming({
|
|
5265
6023
|
stepStartedAtMs,
|
|
5266
|
-
stepCompletedAtMs
|
|
6024
|
+
stepCompletedAtMs,
|
|
5267
6025
|
modelCompletedAtMs,
|
|
5268
6026
|
firstModelEventAtMs,
|
|
5269
6027
|
schedulerMetrics,
|
|
5270
|
-
toolExecutionMs
|
|
5271
|
-
waitToolMs
|
|
6028
|
+
toolExecutionMs,
|
|
6029
|
+
waitToolMs
|
|
5272
6030
|
});
|
|
5273
6031
|
steps.push({
|
|
5274
6032
|
step: steps.length + 1,
|
|
5275
6033
|
modelVersion,
|
|
5276
6034
|
text: responseText || void 0,
|
|
5277
6035
|
thoughts: reasoningSummary || void 0,
|
|
5278
|
-
toolCalls:
|
|
6036
|
+
toolCalls: stepToolCalls,
|
|
5279
6037
|
usage: usageTokens,
|
|
5280
6038
|
costUsd: stepCostUsd,
|
|
5281
|
-
timing
|
|
6039
|
+
timing
|
|
5282
6040
|
});
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
toolName,
|
|
5298
|
-
value: call.input,
|
|
5299
|
-
parseError: void 0,
|
|
5300
|
-
toolId,
|
|
5301
|
-
turn,
|
|
5302
|
-
toolIndex
|
|
5303
|
-
};
|
|
5304
|
-
}
|
|
5305
|
-
const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
|
|
5306
|
-
return { call, toolName, value, parseError, toolId, turn, toolIndex };
|
|
5307
|
-
});
|
|
5308
|
-
for (const entry of callInputs) {
|
|
5309
|
-
emitEvent({
|
|
5310
|
-
type: "tool_call",
|
|
5311
|
-
phase: "started",
|
|
5312
|
-
turn: entry.turn,
|
|
5313
|
-
toolIndex: entry.toolIndex,
|
|
5314
|
-
toolName: entry.toolName,
|
|
5315
|
-
toolId: entry.toolId,
|
|
5316
|
-
callKind: entry.call.kind,
|
|
5317
|
-
callId: entry.call.call_id,
|
|
5318
|
-
input: entry.value
|
|
6041
|
+
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
6042
|
+
const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
|
|
6043
|
+
stepCallLogger?.complete({
|
|
6044
|
+
provider: "openai",
|
|
6045
|
+
model: request.model,
|
|
6046
|
+
modelVersion,
|
|
6047
|
+
step: turn,
|
|
6048
|
+
usage: usageTokens,
|
|
6049
|
+
costUsd: stepCostUsd,
|
|
6050
|
+
blocked,
|
|
6051
|
+
responseChars: responseText.length,
|
|
6052
|
+
thoughtChars: reasoningSummary.length,
|
|
6053
|
+
toolCalls: stepToolCalls.length,
|
|
6054
|
+
finalStep: false
|
|
5319
6055
|
});
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
async () => {
|
|
5331
|
-
const { result, outputPayload } = await executeToolCall({
|
|
5332
|
-
callKind: entry.call.kind,
|
|
5333
|
-
toolName: entry.toolName,
|
|
5334
|
-
tool: request.tools[entry.toolName],
|
|
5335
|
-
rawInput: entry.value,
|
|
5336
|
-
parseError: entry.parseError
|
|
5337
|
-
});
|
|
5338
|
-
return { entry, result, outputPayload };
|
|
5339
|
-
}
|
|
5340
|
-
);
|
|
5341
|
-
})
|
|
5342
|
-
);
|
|
5343
|
-
const toolOutputs = [];
|
|
5344
|
-
let toolExecutionMs = 0;
|
|
5345
|
-
let waitToolMs = 0;
|
|
5346
|
-
for (const { entry, result, outputPayload } of callResults) {
|
|
5347
|
-
stepToolCalls.push({ ...result, callId: entry.call.call_id });
|
|
5348
|
-
const callDurationMs = toToolResultDuration(result);
|
|
5349
|
-
toolExecutionMs += callDurationMs;
|
|
5350
|
-
if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
|
|
5351
|
-
waitToolMs += callDurationMs;
|
|
5352
|
-
}
|
|
5353
|
-
emitEvent({
|
|
5354
|
-
type: "tool_call",
|
|
5355
|
-
phase: "completed",
|
|
5356
|
-
turn: entry.turn,
|
|
5357
|
-
toolIndex: entry.toolIndex,
|
|
5358
|
-
toolName: entry.toolName,
|
|
5359
|
-
toolId: entry.toolId,
|
|
5360
|
-
callKind: entry.call.kind,
|
|
5361
|
-
callId: entry.call.call_id,
|
|
5362
|
-
input: entry.value,
|
|
5363
|
-
output: result.output,
|
|
5364
|
-
error: result.error,
|
|
5365
|
-
durationMs: result.durationMs
|
|
6056
|
+
previousResponseId = finalResponse.id;
|
|
6057
|
+
input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
|
|
6058
|
+
} catch (error) {
|
|
6059
|
+
stepCallLogger?.fail(error, {
|
|
6060
|
+
provider: "openai",
|
|
6061
|
+
model: request.model,
|
|
6062
|
+
modelVersion,
|
|
6063
|
+
step: turn,
|
|
6064
|
+
usage: usageTokens,
|
|
6065
|
+
blocked
|
|
5366
6066
|
});
|
|
5367
|
-
|
|
5368
|
-
toolOutputs.push({
|
|
5369
|
-
type: "custom_tool_call_output",
|
|
5370
|
-
call_id: entry.call.call_id,
|
|
5371
|
-
output: toOpenAiToolOutput(outputPayload)
|
|
5372
|
-
});
|
|
5373
|
-
} else {
|
|
5374
|
-
toolOutputs.push({
|
|
5375
|
-
type: "function_call_output",
|
|
5376
|
-
call_id: entry.call.call_id,
|
|
5377
|
-
output: toOpenAiToolOutput(outputPayload)
|
|
5378
|
-
});
|
|
5379
|
-
}
|
|
6067
|
+
throw error;
|
|
5380
6068
|
}
|
|
5381
|
-
const stepCompletedAtMs = Date.now();
|
|
5382
|
-
const timing = buildStepTiming({
|
|
5383
|
-
stepStartedAtMs,
|
|
5384
|
-
stepCompletedAtMs,
|
|
5385
|
-
modelCompletedAtMs,
|
|
5386
|
-
firstModelEventAtMs,
|
|
5387
|
-
schedulerMetrics,
|
|
5388
|
-
toolExecutionMs,
|
|
5389
|
-
waitToolMs
|
|
5390
|
-
});
|
|
5391
|
-
steps.push({
|
|
5392
|
-
step: steps.length + 1,
|
|
5393
|
-
modelVersion,
|
|
5394
|
-
text: responseText || void 0,
|
|
5395
|
-
thoughts: reasoningSummary || void 0,
|
|
5396
|
-
toolCalls: stepToolCalls,
|
|
5397
|
-
usage: usageTokens,
|
|
5398
|
-
costUsd: stepCostUsd,
|
|
5399
|
-
timing
|
|
5400
|
-
});
|
|
5401
|
-
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
5402
|
-
const steeringItems = steeringInput.length > 0 ? toOpenAiInput(steeringInput) : [];
|
|
5403
|
-
previousResponseId = finalResponse.id;
|
|
5404
|
-
input = steeringItems.length > 0 ? toolOutputs.concat(steeringItems) : toolOutputs;
|
|
5405
6069
|
}
|
|
5406
6070
|
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
5407
6071
|
}
|
|
@@ -5419,242 +6083,655 @@ async function runToolLoop(request) {
|
|
|
5419
6083
|
const stepStartedAtMs = Date.now();
|
|
5420
6084
|
let firstModelEventAtMs;
|
|
5421
6085
|
let thoughtDeltaEmitted = false;
|
|
6086
|
+
let sawResponseDelta = false;
|
|
6087
|
+
let modelVersion = request.model;
|
|
6088
|
+
let usageTokens;
|
|
6089
|
+
let responseText = "";
|
|
6090
|
+
let reasoningSummaryText = "";
|
|
5422
6091
|
const markFirstModelEvent = () => {
|
|
5423
6092
|
if (firstModelEventAtMs === void 0) {
|
|
5424
6093
|
firstModelEventAtMs = Date.now();
|
|
5425
6094
|
}
|
|
5426
6095
|
};
|
|
5427
|
-
const
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
effort: toOpenAiReasoningEffort(reasoningEffort),
|
|
5442
|
-
summary: "detailed"
|
|
5443
|
-
},
|
|
5444
|
-
text: { verbosity: resolveOpenAiVerbosity(request.model) }
|
|
6096
|
+
const stepRequestPayload = {
|
|
6097
|
+
model: providerInfo.model,
|
|
6098
|
+
store: false,
|
|
6099
|
+
stream: true,
|
|
6100
|
+
instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
|
|
6101
|
+
input,
|
|
6102
|
+
prompt_cache_key: promptCacheKey,
|
|
6103
|
+
include: ["reasoning.encrypted_content"],
|
|
6104
|
+
tools: openAiTools,
|
|
6105
|
+
tool_choice: "auto",
|
|
6106
|
+
parallel_tool_calls: true,
|
|
6107
|
+
reasoning: {
|
|
6108
|
+
effort: toOpenAiReasoningEffort(reasoningEffort),
|
|
6109
|
+
summary: "detailed"
|
|
5445
6110
|
},
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
6111
|
+
text: { verbosity: resolveOpenAiVerbosity(request.model) }
|
|
6112
|
+
};
|
|
6113
|
+
const stepCallLogger = startLlmCallLoggerFromPayload({
|
|
6114
|
+
provider: "chatgpt",
|
|
6115
|
+
modelId: request.model,
|
|
6116
|
+
requestPayload: stepRequestPayload,
|
|
6117
|
+
step: turn
|
|
6118
|
+
});
|
|
6119
|
+
try {
|
|
6120
|
+
const response = await collectChatGptCodexResponseWithRetry({
|
|
6121
|
+
sessionId: conversationId,
|
|
6122
|
+
request: stepRequestPayload,
|
|
6123
|
+
signal: request.signal,
|
|
6124
|
+
onDelta: (delta) => {
|
|
6125
|
+
if (delta.thoughtDelta) {
|
|
6126
|
+
markFirstModelEvent();
|
|
6127
|
+
thoughtDeltaEmitted = true;
|
|
6128
|
+
stepCallLogger?.appendThoughtDelta(delta.thoughtDelta);
|
|
6129
|
+
request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
|
|
6130
|
+
}
|
|
6131
|
+
if (delta.textDelta) {
|
|
6132
|
+
markFirstModelEvent();
|
|
6133
|
+
sawResponseDelta = true;
|
|
6134
|
+
stepCallLogger?.appendResponseDelta(delta.textDelta);
|
|
6135
|
+
request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
|
|
6136
|
+
}
|
|
5452
6137
|
}
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
6138
|
+
});
|
|
6139
|
+
const modelCompletedAtMs = Date.now();
|
|
6140
|
+
modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
|
|
6141
|
+
usageTokens = extractChatGptUsageTokens(response.usage);
|
|
6142
|
+
const stepCostUsd = estimateCallCostUsd({
|
|
6143
|
+
modelId: modelVersion,
|
|
6144
|
+
tokens: usageTokens,
|
|
6145
|
+
responseImages: 0
|
|
6146
|
+
});
|
|
6147
|
+
totalCostUsd += stepCostUsd;
|
|
6148
|
+
responseText = (response.text ?? "").trim();
|
|
6149
|
+
reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
|
|
6150
|
+
if (!thoughtDeltaEmitted && reasoningSummaryText.length > 0) {
|
|
6151
|
+
stepCallLogger?.appendThoughtDelta(reasoningSummaryText);
|
|
6152
|
+
request.onEvent?.({ type: "delta", channel: "thought", text: reasoningSummaryText });
|
|
6153
|
+
}
|
|
6154
|
+
if (!sawResponseDelta && responseText.length > 0) {
|
|
6155
|
+
stepCallLogger?.appendResponseDelta(responseText);
|
|
6156
|
+
}
|
|
6157
|
+
const responseToolCalls = response.toolCalls ?? [];
|
|
6158
|
+
if (responseToolCalls.length === 0) {
|
|
6159
|
+
const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
|
|
6160
|
+
const steeringItems2 = steeringInput2.length > 0 ? toChatGptInput(steeringInput2).input : [];
|
|
6161
|
+
finalText = responseText;
|
|
6162
|
+
finalThoughts = reasoningSummaryText;
|
|
6163
|
+
const stepCompletedAtMs2 = Date.now();
|
|
6164
|
+
const timing2 = buildStepTiming({
|
|
6165
|
+
stepStartedAtMs,
|
|
6166
|
+
stepCompletedAtMs: stepCompletedAtMs2,
|
|
6167
|
+
modelCompletedAtMs,
|
|
6168
|
+
firstModelEventAtMs,
|
|
6169
|
+
toolExecutionMs: 0,
|
|
6170
|
+
waitToolMs: 0
|
|
6171
|
+
});
|
|
6172
|
+
steps.push({
|
|
6173
|
+
step: steps.length + 1,
|
|
6174
|
+
modelVersion,
|
|
6175
|
+
text: responseText || void 0,
|
|
6176
|
+
thoughts: reasoningSummaryText || void 0,
|
|
6177
|
+
toolCalls: [],
|
|
6178
|
+
usage: usageTokens,
|
|
6179
|
+
costUsd: stepCostUsd,
|
|
6180
|
+
timing: timing2
|
|
6181
|
+
});
|
|
6182
|
+
stepCallLogger?.complete({
|
|
6183
|
+
provider: "chatgpt",
|
|
6184
|
+
model: request.model,
|
|
6185
|
+
modelVersion,
|
|
6186
|
+
step: turn,
|
|
6187
|
+
usage: usageTokens,
|
|
6188
|
+
costUsd: stepCostUsd,
|
|
6189
|
+
responseChars: responseText.length,
|
|
6190
|
+
thoughtChars: reasoningSummaryText.length,
|
|
6191
|
+
toolCalls: 0,
|
|
6192
|
+
finalStep: steeringItems2.length === 0
|
|
6193
|
+
});
|
|
6194
|
+
if (steeringItems2.length === 0) {
|
|
6195
|
+
return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
|
|
5456
6196
|
}
|
|
6197
|
+
const assistantItem = toChatGptAssistantMessage(responseText);
|
|
6198
|
+
input = assistantItem ? input.concat(assistantItem, steeringItems2) : input.concat(steeringItems2);
|
|
6199
|
+
continue;
|
|
5457
6200
|
}
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
6201
|
+
const toolCalls = [];
|
|
6202
|
+
const toolOutputs = [];
|
|
6203
|
+
const callInputs = responseToolCalls.map((call, index) => {
|
|
6204
|
+
const toolIndex = index + 1;
|
|
6205
|
+
const toolId = buildToolLogId(turn, toolIndex);
|
|
6206
|
+
const toolName = call.name;
|
|
6207
|
+
const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
|
|
6208
|
+
const ids = normalizeChatGptToolIds({
|
|
6209
|
+
callKind: call.kind,
|
|
6210
|
+
callId: call.callId,
|
|
6211
|
+
itemId: call.id
|
|
6212
|
+
});
|
|
6213
|
+
return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
|
|
6214
|
+
});
|
|
6215
|
+
for (const entry of callInputs) {
|
|
6216
|
+
request.onEvent?.({
|
|
6217
|
+
type: "tool_call",
|
|
6218
|
+
phase: "started",
|
|
6219
|
+
turn: entry.turn,
|
|
6220
|
+
toolIndex: entry.toolIndex,
|
|
6221
|
+
toolName: entry.toolName,
|
|
6222
|
+
toolId: entry.toolId,
|
|
6223
|
+
callKind: entry.call.kind,
|
|
6224
|
+
callId: entry.ids.callId,
|
|
6225
|
+
input: entry.value
|
|
6226
|
+
});
|
|
6227
|
+
}
|
|
6228
|
+
const callResults = await Promise.all(
|
|
6229
|
+
callInputs.map(async (entry) => {
|
|
6230
|
+
return await toolCallContextStorage.run(
|
|
6231
|
+
{
|
|
6232
|
+
toolName: entry.toolName,
|
|
6233
|
+
toolId: entry.toolId,
|
|
6234
|
+
turn: entry.turn,
|
|
6235
|
+
toolIndex: entry.toolIndex
|
|
6236
|
+
},
|
|
6237
|
+
async () => {
|
|
6238
|
+
const { result, outputPayload } = await executeToolCall({
|
|
6239
|
+
callKind: entry.call.kind,
|
|
6240
|
+
toolName: entry.toolName,
|
|
6241
|
+
tool: request.tools[entry.toolName],
|
|
6242
|
+
rawInput: entry.value,
|
|
6243
|
+
parseError: entry.parseError
|
|
6244
|
+
});
|
|
6245
|
+
return { entry, result, outputPayload };
|
|
6246
|
+
}
|
|
6247
|
+
);
|
|
6248
|
+
})
|
|
6249
|
+
);
|
|
6250
|
+
let toolExecutionMs = 0;
|
|
6251
|
+
let waitToolMs = 0;
|
|
6252
|
+
for (const { entry, result, outputPayload } of callResults) {
|
|
6253
|
+
toolCalls.push({ ...result, callId: entry.ids.callId });
|
|
6254
|
+
const callDurationMs = toToolResultDuration(result);
|
|
6255
|
+
toolExecutionMs += callDurationMs;
|
|
6256
|
+
if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
|
|
6257
|
+
waitToolMs += callDurationMs;
|
|
6258
|
+
}
|
|
6259
|
+
request.onEvent?.({
|
|
6260
|
+
type: "tool_call",
|
|
6261
|
+
phase: "completed",
|
|
6262
|
+
turn: entry.turn,
|
|
6263
|
+
toolIndex: entry.toolIndex,
|
|
6264
|
+
toolName: entry.toolName,
|
|
6265
|
+
toolId: entry.toolId,
|
|
6266
|
+
callKind: entry.call.kind,
|
|
6267
|
+
callId: entry.ids.callId,
|
|
6268
|
+
input: entry.value,
|
|
6269
|
+
output: result.output,
|
|
6270
|
+
error: result.error,
|
|
6271
|
+
durationMs: result.durationMs
|
|
6272
|
+
});
|
|
6273
|
+
if (entry.call.kind === "custom") {
|
|
6274
|
+
toolOutputs.push({
|
|
6275
|
+
type: "custom_tool_call",
|
|
6276
|
+
id: entry.ids.itemId,
|
|
6277
|
+
call_id: entry.ids.callId,
|
|
6278
|
+
name: entry.toolName,
|
|
6279
|
+
input: entry.call.input,
|
|
6280
|
+
status: "completed"
|
|
6281
|
+
});
|
|
6282
|
+
toolOutputs.push({
|
|
6283
|
+
type: "custom_tool_call_output",
|
|
6284
|
+
call_id: entry.ids.callId,
|
|
6285
|
+
output: toOpenAiToolOutput(outputPayload)
|
|
6286
|
+
});
|
|
6287
|
+
} else {
|
|
6288
|
+
toolOutputs.push({
|
|
6289
|
+
type: "function_call",
|
|
6290
|
+
id: entry.ids.itemId,
|
|
6291
|
+
call_id: entry.ids.callId,
|
|
6292
|
+
name: entry.toolName,
|
|
6293
|
+
arguments: entry.call.arguments,
|
|
6294
|
+
status: "completed"
|
|
6295
|
+
});
|
|
6296
|
+
toolOutputs.push({
|
|
6297
|
+
type: "function_call_output",
|
|
6298
|
+
call_id: entry.ids.callId,
|
|
6299
|
+
output: toOpenAiToolOutput(outputPayload)
|
|
6300
|
+
});
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
const stepCompletedAtMs = Date.now();
|
|
6304
|
+
const timing = buildStepTiming({
|
|
5481
6305
|
stepStartedAtMs,
|
|
5482
|
-
stepCompletedAtMs
|
|
6306
|
+
stepCompletedAtMs,
|
|
5483
6307
|
modelCompletedAtMs,
|
|
5484
6308
|
firstModelEventAtMs,
|
|
5485
|
-
toolExecutionMs
|
|
5486
|
-
waitToolMs
|
|
6309
|
+
toolExecutionMs,
|
|
6310
|
+
waitToolMs
|
|
5487
6311
|
});
|
|
5488
6312
|
steps.push({
|
|
5489
6313
|
step: steps.length + 1,
|
|
5490
6314
|
modelVersion,
|
|
5491
6315
|
text: responseText || void 0,
|
|
5492
6316
|
thoughts: reasoningSummaryText || void 0,
|
|
5493
|
-
toolCalls
|
|
6317
|
+
toolCalls,
|
|
5494
6318
|
usage: usageTokens,
|
|
5495
6319
|
costUsd: stepCostUsd,
|
|
5496
|
-
timing
|
|
6320
|
+
timing
|
|
5497
6321
|
});
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
|
|
5512
|
-
const ids = normalizeChatGptToolIds({
|
|
5513
|
-
callKind: call.kind,
|
|
5514
|
-
callId: call.callId,
|
|
5515
|
-
itemId: call.id
|
|
6322
|
+
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
6323
|
+
const steeringItems = steeringInput.length > 0 ? toChatGptInput(steeringInput).input : [];
|
|
6324
|
+
stepCallLogger?.complete({
|
|
6325
|
+
provider: "chatgpt",
|
|
6326
|
+
model: request.model,
|
|
6327
|
+
modelVersion,
|
|
6328
|
+
step: turn,
|
|
6329
|
+
usage: usageTokens,
|
|
6330
|
+
costUsd: stepCostUsd,
|
|
6331
|
+
responseChars: responseText.length,
|
|
6332
|
+
thoughtChars: reasoningSummaryText.length,
|
|
6333
|
+
toolCalls: toolCalls.length,
|
|
6334
|
+
finalStep: false
|
|
5516
6335
|
});
|
|
5517
|
-
|
|
5518
|
-
})
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
toolName: entry.toolName,
|
|
5526
|
-
toolId: entry.toolId,
|
|
5527
|
-
callKind: entry.call.kind,
|
|
5528
|
-
callId: entry.ids.callId,
|
|
5529
|
-
input: entry.value
|
|
6336
|
+
input = steeringItems.length > 0 ? input.concat(toolOutputs, steeringItems) : input.concat(toolOutputs);
|
|
6337
|
+
} catch (error) {
|
|
6338
|
+
stepCallLogger?.fail(error, {
|
|
6339
|
+
provider: "chatgpt",
|
|
6340
|
+
model: request.model,
|
|
6341
|
+
modelVersion,
|
|
6342
|
+
step: turn,
|
|
6343
|
+
usage: usageTokens
|
|
5530
6344
|
});
|
|
6345
|
+
throw error;
|
|
5531
6346
|
}
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
toolIndex: entry.toolIndex
|
|
5540
|
-
},
|
|
5541
|
-
async () => {
|
|
5542
|
-
const { result, outputPayload } = await executeToolCall({
|
|
5543
|
-
callKind: entry.call.kind,
|
|
5544
|
-
toolName: entry.toolName,
|
|
5545
|
-
tool: request.tools[entry.toolName],
|
|
5546
|
-
rawInput: entry.value,
|
|
5547
|
-
parseError: entry.parseError
|
|
5548
|
-
});
|
|
5549
|
-
return { entry, result, outputPayload };
|
|
5550
|
-
}
|
|
5551
|
-
);
|
|
5552
|
-
})
|
|
6347
|
+
}
|
|
6348
|
+
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
6349
|
+
}
|
|
6350
|
+
if (providerInfo.provider === "fireworks") {
|
|
6351
|
+
if (request.modelTools && request.modelTools.length > 0) {
|
|
6352
|
+
throw new Error(
|
|
6353
|
+
"Fireworks provider does not support provider-native modelTools in runToolLoop."
|
|
5553
6354
|
);
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
6355
|
+
}
|
|
6356
|
+
const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
|
|
6357
|
+
const messages = toFireworksMessages(contents);
|
|
6358
|
+
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
6359
|
+
const turn = stepIndex + 1;
|
|
6360
|
+
const stepStartedAtMs = Date.now();
|
|
6361
|
+
let schedulerMetrics;
|
|
6362
|
+
let modelVersion = request.model;
|
|
6363
|
+
let usageTokens;
|
|
6364
|
+
let responseText = "";
|
|
6365
|
+
let blocked = false;
|
|
6366
|
+
const stepRequestPayload = {
|
|
6367
|
+
model: providerInfo.model,
|
|
6368
|
+
messages,
|
|
6369
|
+
tools: fireworksTools,
|
|
6370
|
+
tool_choice: "auto",
|
|
6371
|
+
parallel_tool_calls: true
|
|
6372
|
+
};
|
|
6373
|
+
const stepCallLogger = startLlmCallLoggerFromPayload({
|
|
6374
|
+
provider: "fireworks",
|
|
6375
|
+
modelId: request.model,
|
|
6376
|
+
requestPayload: stepRequestPayload,
|
|
6377
|
+
step: turn
|
|
6378
|
+
});
|
|
6379
|
+
try {
|
|
6380
|
+
const response = await runFireworksCall(
|
|
6381
|
+
async (client) => {
|
|
6382
|
+
return await client.chat.completions.create(
|
|
6383
|
+
{
|
|
6384
|
+
model: providerInfo.model,
|
|
6385
|
+
messages,
|
|
6386
|
+
tools: fireworksTools,
|
|
6387
|
+
tool_choice: "auto",
|
|
6388
|
+
parallel_tool_calls: true
|
|
6389
|
+
},
|
|
6390
|
+
{ signal: request.signal }
|
|
6391
|
+
);
|
|
6392
|
+
},
|
|
6393
|
+
providerInfo.model,
|
|
6394
|
+
{
|
|
6395
|
+
onSettled: (metrics) => {
|
|
6396
|
+
schedulerMetrics = metrics;
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
);
|
|
6400
|
+
const modelCompletedAtMs = Date.now();
|
|
6401
|
+
modelVersion = typeof response.model === "string" ? response.model : request.model;
|
|
6402
|
+
request.onEvent?.({ type: "model", modelVersion });
|
|
6403
|
+
const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
|
|
6404
|
+
if (choice?.finish_reason === "content_filter") {
|
|
6405
|
+
blocked = true;
|
|
6406
|
+
request.onEvent?.({ type: "blocked" });
|
|
5562
6407
|
}
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
error: result.error,
|
|
5575
|
-
durationMs: result.durationMs
|
|
6408
|
+
const message = choice?.message;
|
|
6409
|
+
responseText = extractFireworksMessageText(message).trim();
|
|
6410
|
+
if (responseText.length > 0) {
|
|
6411
|
+
stepCallLogger?.appendResponseDelta(responseText);
|
|
6412
|
+
request.onEvent?.({ type: "delta", channel: "response", text: responseText });
|
|
6413
|
+
}
|
|
6414
|
+
usageTokens = extractFireworksUsageTokens(response.usage);
|
|
6415
|
+
const stepCostUsd = estimateCallCostUsd({
|
|
6416
|
+
modelId: modelVersion,
|
|
6417
|
+
tokens: usageTokens,
|
|
6418
|
+
responseImages: 0
|
|
5576
6419
|
});
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
status: "completed"
|
|
6420
|
+
totalCostUsd += stepCostUsd;
|
|
6421
|
+
if (usageTokens) {
|
|
6422
|
+
request.onEvent?.({
|
|
6423
|
+
type: "usage",
|
|
6424
|
+
usage: usageTokens,
|
|
6425
|
+
costUsd: stepCostUsd,
|
|
6426
|
+
modelVersion
|
|
5585
6427
|
});
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
6428
|
+
}
|
|
6429
|
+
const responseToolCalls = extractFireworksToolCalls(message);
|
|
6430
|
+
if (responseToolCalls.length === 0) {
|
|
6431
|
+
const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
|
|
6432
|
+
const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
|
|
6433
|
+
finalText = responseText;
|
|
6434
|
+
finalThoughts = "";
|
|
6435
|
+
const stepCompletedAtMs2 = Date.now();
|
|
6436
|
+
const timing2 = buildStepTiming({
|
|
6437
|
+
stepStartedAtMs,
|
|
6438
|
+
stepCompletedAtMs: stepCompletedAtMs2,
|
|
6439
|
+
modelCompletedAtMs,
|
|
6440
|
+
schedulerMetrics,
|
|
6441
|
+
toolExecutionMs: 0,
|
|
6442
|
+
waitToolMs: 0
|
|
5590
6443
|
});
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
6444
|
+
steps.push({
|
|
6445
|
+
step: steps.length + 1,
|
|
6446
|
+
modelVersion,
|
|
6447
|
+
text: responseText || void 0,
|
|
6448
|
+
thoughts: void 0,
|
|
6449
|
+
toolCalls: [],
|
|
6450
|
+
usage: usageTokens,
|
|
6451
|
+
costUsd: stepCostUsd,
|
|
6452
|
+
timing: timing2
|
|
5599
6453
|
});
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
6454
|
+
stepCallLogger?.complete({
|
|
6455
|
+
provider: "fireworks",
|
|
6456
|
+
model: request.model,
|
|
6457
|
+
modelVersion,
|
|
6458
|
+
step: turn,
|
|
6459
|
+
usage: usageTokens,
|
|
6460
|
+
costUsd: stepCostUsd,
|
|
6461
|
+
blocked,
|
|
6462
|
+
responseChars: responseText.length,
|
|
6463
|
+
thoughtChars: 0,
|
|
6464
|
+
toolCalls: 0,
|
|
6465
|
+
finalStep: steeringMessages.length === 0
|
|
5604
6466
|
});
|
|
6467
|
+
if (steeringMessages.length === 0) {
|
|
6468
|
+
return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
|
|
6469
|
+
}
|
|
6470
|
+
if (responseText.length > 0) {
|
|
6471
|
+
messages.push({ role: "assistant", content: responseText });
|
|
6472
|
+
}
|
|
6473
|
+
messages.push(...steeringMessages);
|
|
6474
|
+
continue;
|
|
5605
6475
|
}
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
6476
|
+
const stepToolCalls = [];
|
|
6477
|
+
const callInputs = responseToolCalls.map((call, index) => {
|
|
6478
|
+
const toolIndex = index + 1;
|
|
6479
|
+
const toolId = buildToolLogId(turn, toolIndex);
|
|
6480
|
+
const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
|
|
6481
|
+
return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
|
|
6482
|
+
});
|
|
6483
|
+
for (const entry of callInputs) {
|
|
6484
|
+
request.onEvent?.({
|
|
6485
|
+
type: "tool_call",
|
|
6486
|
+
phase: "started",
|
|
6487
|
+
turn: entry.turn,
|
|
6488
|
+
toolIndex: entry.toolIndex,
|
|
6489
|
+
toolName: entry.toolName,
|
|
6490
|
+
toolId: entry.toolId,
|
|
6491
|
+
callKind: "function",
|
|
6492
|
+
callId: entry.call.id,
|
|
6493
|
+
input: entry.value
|
|
6494
|
+
});
|
|
6495
|
+
}
|
|
6496
|
+
const callResults = await Promise.all(
|
|
6497
|
+
callInputs.map(async (entry) => {
|
|
6498
|
+
return await toolCallContextStorage.run(
|
|
6499
|
+
{
|
|
6500
|
+
toolName: entry.toolName,
|
|
6501
|
+
toolId: entry.toolId,
|
|
6502
|
+
turn: entry.turn,
|
|
6503
|
+
toolIndex: entry.toolIndex
|
|
6504
|
+
},
|
|
6505
|
+
async () => {
|
|
6506
|
+
const { result, outputPayload } = await executeToolCall({
|
|
6507
|
+
callKind: "function",
|
|
6508
|
+
toolName: entry.toolName,
|
|
6509
|
+
tool: request.tools[entry.toolName],
|
|
6510
|
+
rawInput: entry.value,
|
|
6511
|
+
parseError: entry.parseError
|
|
6512
|
+
});
|
|
6513
|
+
return { entry, result, outputPayload };
|
|
6514
|
+
}
|
|
6515
|
+
);
|
|
6516
|
+
})
|
|
6517
|
+
);
|
|
6518
|
+
const assistantToolCalls = [];
|
|
6519
|
+
const toolMessages = [];
|
|
6520
|
+
let toolExecutionMs = 0;
|
|
6521
|
+
let waitToolMs = 0;
|
|
6522
|
+
for (const { entry, result, outputPayload } of callResults) {
|
|
6523
|
+
stepToolCalls.push({ ...result, callId: entry.call.id });
|
|
6524
|
+
const callDurationMs = toToolResultDuration(result);
|
|
6525
|
+
toolExecutionMs += callDurationMs;
|
|
6526
|
+
if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
|
|
6527
|
+
waitToolMs += callDurationMs;
|
|
6528
|
+
}
|
|
6529
|
+
request.onEvent?.({
|
|
6530
|
+
type: "tool_call",
|
|
6531
|
+
phase: "completed",
|
|
6532
|
+
turn: entry.turn,
|
|
6533
|
+
toolIndex: entry.toolIndex,
|
|
6534
|
+
toolName: entry.toolName,
|
|
6535
|
+
toolId: entry.toolId,
|
|
6536
|
+
callKind: "function",
|
|
6537
|
+
callId: entry.call.id,
|
|
6538
|
+
input: entry.value,
|
|
6539
|
+
output: result.output,
|
|
6540
|
+
error: result.error,
|
|
6541
|
+
durationMs: result.durationMs
|
|
6542
|
+
});
|
|
6543
|
+
assistantToolCalls.push({
|
|
6544
|
+
id: entry.call.id,
|
|
6545
|
+
type: "function",
|
|
6546
|
+
function: {
|
|
6547
|
+
name: entry.toolName,
|
|
6548
|
+
arguments: entry.call.arguments
|
|
6549
|
+
}
|
|
6550
|
+
});
|
|
6551
|
+
toolMessages.push({
|
|
6552
|
+
role: "tool",
|
|
6553
|
+
tool_call_id: entry.call.id,
|
|
6554
|
+
content: mergeToolOutput(outputPayload)
|
|
6555
|
+
});
|
|
6556
|
+
}
|
|
6557
|
+
const stepCompletedAtMs = Date.now();
|
|
6558
|
+
const timing = buildStepTiming({
|
|
6559
|
+
stepStartedAtMs,
|
|
6560
|
+
stepCompletedAtMs,
|
|
6561
|
+
modelCompletedAtMs,
|
|
6562
|
+
schedulerMetrics,
|
|
6563
|
+
toolExecutionMs,
|
|
6564
|
+
waitToolMs
|
|
6565
|
+
});
|
|
6566
|
+
steps.push({
|
|
6567
|
+
step: steps.length + 1,
|
|
6568
|
+
modelVersion,
|
|
6569
|
+
text: responseText || void 0,
|
|
6570
|
+
thoughts: void 0,
|
|
6571
|
+
toolCalls: stepToolCalls,
|
|
6572
|
+
usage: usageTokens,
|
|
6573
|
+
costUsd: stepCostUsd,
|
|
6574
|
+
timing
|
|
6575
|
+
});
|
|
6576
|
+
stepCallLogger?.complete({
|
|
6577
|
+
provider: "fireworks",
|
|
6578
|
+
model: request.model,
|
|
6579
|
+
modelVersion,
|
|
6580
|
+
step: turn,
|
|
6581
|
+
usage: usageTokens,
|
|
6582
|
+
costUsd: stepCostUsd,
|
|
6583
|
+
blocked,
|
|
6584
|
+
responseChars: responseText.length,
|
|
6585
|
+
thoughtChars: 0,
|
|
6586
|
+
toolCalls: stepToolCalls.length,
|
|
6587
|
+
finalStep: false
|
|
6588
|
+
});
|
|
6589
|
+
messages.push({
|
|
6590
|
+
role: "assistant",
|
|
6591
|
+
...responseText.length > 0 ? { content: responseText } : {},
|
|
6592
|
+
tool_calls: assistantToolCalls
|
|
6593
|
+
});
|
|
6594
|
+
messages.push(...toolMessages);
|
|
6595
|
+
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
6596
|
+
if (steeringInput.length > 0) {
|
|
6597
|
+
messages.push(...toFireworksMessages(steeringInput));
|
|
6598
|
+
}
|
|
6599
|
+
} catch (error) {
|
|
6600
|
+
stepCallLogger?.fail(error, {
|
|
6601
|
+
provider: "fireworks",
|
|
6602
|
+
model: request.model,
|
|
6603
|
+
modelVersion,
|
|
6604
|
+
step: turn,
|
|
6605
|
+
usage: usageTokens,
|
|
6606
|
+
blocked
|
|
6607
|
+
});
|
|
6608
|
+
throw error;
|
|
6609
|
+
}
|
|
5629
6610
|
}
|
|
5630
6611
|
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
5631
6612
|
}
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
const
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
6613
|
+
const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
|
|
6614
|
+
const geminiNativeTools = toGeminiTools(request.modelTools);
|
|
6615
|
+
const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
|
|
6616
|
+
const geminiContents = contents.map(convertLlmContentToGeminiContent);
|
|
6617
|
+
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
6618
|
+
const turn = stepIndex + 1;
|
|
6619
|
+
const stepStartedAtMs = Date.now();
|
|
6620
|
+
let firstModelEventAtMs;
|
|
6621
|
+
let schedulerMetrics;
|
|
6622
|
+
let modelVersion = request.model;
|
|
6623
|
+
let usageTokens;
|
|
6624
|
+
let responseText = "";
|
|
6625
|
+
let thoughtsText = "";
|
|
6626
|
+
const markFirstModelEvent = () => {
|
|
6627
|
+
if (firstModelEventAtMs === void 0) {
|
|
6628
|
+
firstModelEventAtMs = Date.now();
|
|
6629
|
+
}
|
|
6630
|
+
};
|
|
6631
|
+
const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
|
|
6632
|
+
const config = {
|
|
6633
|
+
maxOutputTokens: 32e3,
|
|
6634
|
+
tools: geminiTools,
|
|
6635
|
+
toolConfig: {
|
|
6636
|
+
functionCallingConfig: {
|
|
6637
|
+
mode: FunctionCallingConfigMode.VALIDATED
|
|
6638
|
+
}
|
|
6639
|
+
},
|
|
6640
|
+
...thinkingConfig ? { thinkingConfig } : {}
|
|
6641
|
+
};
|
|
6642
|
+
const onEvent = request.onEvent;
|
|
6643
|
+
const stepRequestPayload = {
|
|
6644
|
+
model: request.model,
|
|
6645
|
+
contents: geminiContents,
|
|
6646
|
+
config
|
|
6647
|
+
};
|
|
6648
|
+
const stepCallLogger = startLlmCallLoggerFromPayload({
|
|
6649
|
+
provider: "gemini",
|
|
6650
|
+
modelId: request.model,
|
|
6651
|
+
requestPayload: stepRequestPayload,
|
|
6652
|
+
step: turn
|
|
6653
|
+
});
|
|
6654
|
+
try {
|
|
6655
|
+
const response = await runGeminiCall(
|
|
5645
6656
|
async (client) => {
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
);
|
|
6657
|
+
const stream = await client.models.generateContentStream({
|
|
6658
|
+
model: request.model,
|
|
6659
|
+
contents: geminiContents,
|
|
6660
|
+
config
|
|
6661
|
+
});
|
|
6662
|
+
let responseText2 = "";
|
|
6663
|
+
let thoughtsText2 = "";
|
|
6664
|
+
const modelParts = [];
|
|
6665
|
+
const functionCalls = [];
|
|
6666
|
+
const seenFunctionCallIds = /* @__PURE__ */ new Set();
|
|
6667
|
+
const seenFunctionCallKeys = /* @__PURE__ */ new Set();
|
|
6668
|
+
let latestUsageMetadata;
|
|
6669
|
+
let resolvedModelVersion;
|
|
6670
|
+
for await (const chunk of stream) {
|
|
6671
|
+
markFirstModelEvent();
|
|
6672
|
+
if (chunk.modelVersion) {
|
|
6673
|
+
resolvedModelVersion = chunk.modelVersion;
|
|
6674
|
+
onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
|
|
6675
|
+
}
|
|
6676
|
+
if (chunk.usageMetadata) {
|
|
6677
|
+
latestUsageMetadata = chunk.usageMetadata;
|
|
6678
|
+
}
|
|
6679
|
+
const candidates = chunk.candidates;
|
|
6680
|
+
if (!candidates || candidates.length === 0) {
|
|
6681
|
+
continue;
|
|
6682
|
+
}
|
|
6683
|
+
const primary = candidates[0];
|
|
6684
|
+
const parts = primary?.content?.parts;
|
|
6685
|
+
if (!parts || parts.length === 0) {
|
|
6686
|
+
continue;
|
|
6687
|
+
}
|
|
6688
|
+
for (const part of parts) {
|
|
6689
|
+
modelParts.push(part);
|
|
6690
|
+
const call = part.functionCall;
|
|
6691
|
+
if (call) {
|
|
6692
|
+
const id = typeof call.id === "string" ? call.id : "";
|
|
6693
|
+
const shouldAdd = (() => {
|
|
6694
|
+
if (id.length > 0) {
|
|
6695
|
+
if (seenFunctionCallIds.has(id)) {
|
|
6696
|
+
return false;
|
|
6697
|
+
}
|
|
6698
|
+
seenFunctionCallIds.add(id);
|
|
6699
|
+
return true;
|
|
6700
|
+
}
|
|
6701
|
+
const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
|
|
6702
|
+
if (seenFunctionCallKeys.has(key)) {
|
|
6703
|
+
return false;
|
|
6704
|
+
}
|
|
6705
|
+
seenFunctionCallKeys.add(key);
|
|
6706
|
+
return true;
|
|
6707
|
+
})();
|
|
6708
|
+
if (shouldAdd) {
|
|
6709
|
+
functionCalls.push(call);
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
if (typeof part.text === "string" && part.text.length > 0) {
|
|
6713
|
+
if (part.thought) {
|
|
6714
|
+
thoughtsText2 += part.text;
|
|
6715
|
+
stepCallLogger?.appendThoughtDelta(part.text);
|
|
6716
|
+
onEvent?.({ type: "delta", channel: "thought", text: part.text });
|
|
6717
|
+
} else {
|
|
6718
|
+
responseText2 += part.text;
|
|
6719
|
+
stepCallLogger?.appendResponseDelta(part.text);
|
|
6720
|
+
onEvent?.({ type: "delta", channel: "response", text: part.text });
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
return {
|
|
6726
|
+
responseText: responseText2,
|
|
6727
|
+
thoughtsText: thoughtsText2,
|
|
6728
|
+
functionCalls,
|
|
6729
|
+
modelParts,
|
|
6730
|
+
usageMetadata: latestUsageMetadata,
|
|
6731
|
+
modelVersion: resolvedModelVersion ?? request.model
|
|
6732
|
+
};
|
|
5656
6733
|
},
|
|
5657
|
-
|
|
6734
|
+
request.model,
|
|
5658
6735
|
{
|
|
5659
6736
|
onSettled: (metrics) => {
|
|
5660
6737
|
schedulerMetrics = metrics;
|
|
@@ -5662,43 +6739,26 @@ async function runToolLoop(request) {
|
|
|
5662
6739
|
}
|
|
5663
6740
|
);
|
|
5664
6741
|
const modelCompletedAtMs = Date.now();
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
request.onEvent?.({ type: "blocked" });
|
|
5670
|
-
}
|
|
5671
|
-
const message = choice?.message;
|
|
5672
|
-
const responseText = extractFireworksMessageText(message).trim();
|
|
5673
|
-
if (responseText.length > 0) {
|
|
5674
|
-
request.onEvent?.({ type: "delta", channel: "response", text: responseText });
|
|
5675
|
-
}
|
|
5676
|
-
const usageTokens = extractFireworksUsageTokens(response.usage);
|
|
6742
|
+
usageTokens = extractGeminiUsageTokens(response.usageMetadata);
|
|
6743
|
+
modelVersion = response.modelVersion ?? request.model;
|
|
6744
|
+
responseText = response.responseText.trim();
|
|
6745
|
+
thoughtsText = response.thoughtsText.trim();
|
|
5677
6746
|
const stepCostUsd = estimateCallCostUsd({
|
|
5678
6747
|
modelId: modelVersion,
|
|
5679
6748
|
tokens: usageTokens,
|
|
5680
6749
|
responseImages: 0
|
|
5681
6750
|
});
|
|
5682
6751
|
totalCostUsd += stepCostUsd;
|
|
5683
|
-
if (
|
|
5684
|
-
request.onEvent?.({
|
|
5685
|
-
type: "usage",
|
|
5686
|
-
usage: usageTokens,
|
|
5687
|
-
costUsd: stepCostUsd,
|
|
5688
|
-
modelVersion
|
|
5689
|
-
});
|
|
5690
|
-
}
|
|
5691
|
-
const responseToolCalls = extractFireworksToolCalls(message);
|
|
5692
|
-
if (responseToolCalls.length === 0) {
|
|
6752
|
+
if (response.functionCalls.length === 0) {
|
|
5693
6753
|
const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
|
|
5694
|
-
const steeringMessages = steeringInput2.length > 0 ? toFireworksMessages(steeringInput2) : [];
|
|
5695
6754
|
finalText = responseText;
|
|
5696
|
-
finalThoughts =
|
|
6755
|
+
finalThoughts = thoughtsText;
|
|
5697
6756
|
const stepCompletedAtMs2 = Date.now();
|
|
5698
6757
|
const timing2 = buildStepTiming({
|
|
5699
6758
|
stepStartedAtMs,
|
|
5700
6759
|
stepCompletedAtMs: stepCompletedAtMs2,
|
|
5701
6760
|
modelCompletedAtMs,
|
|
6761
|
+
firstModelEventAtMs,
|
|
5702
6762
|
schedulerMetrics,
|
|
5703
6763
|
toolExecutionMs: 0,
|
|
5704
6764
|
waitToolMs: 0
|
|
@@ -5706,31 +6766,65 @@ async function runToolLoop(request) {
|
|
|
5706
6766
|
steps.push({
|
|
5707
6767
|
step: steps.length + 1,
|
|
5708
6768
|
modelVersion,
|
|
5709
|
-
text:
|
|
5710
|
-
thoughts: void 0,
|
|
6769
|
+
text: finalText || void 0,
|
|
6770
|
+
thoughts: finalThoughts || void 0,
|
|
5711
6771
|
toolCalls: [],
|
|
5712
6772
|
usage: usageTokens,
|
|
5713
6773
|
costUsd: stepCostUsd,
|
|
5714
6774
|
timing: timing2
|
|
5715
6775
|
});
|
|
5716
|
-
|
|
6776
|
+
stepCallLogger?.complete({
|
|
6777
|
+
provider: "gemini",
|
|
6778
|
+
model: request.model,
|
|
6779
|
+
modelVersion,
|
|
6780
|
+
step: turn,
|
|
6781
|
+
usage: usageTokens,
|
|
6782
|
+
costUsd: stepCostUsd,
|
|
6783
|
+
responseChars: responseText.length,
|
|
6784
|
+
thoughtChars: thoughtsText.length,
|
|
6785
|
+
toolCalls: 0,
|
|
6786
|
+
finalStep: steeringInput2.length === 0
|
|
6787
|
+
});
|
|
6788
|
+
if (steeringInput2.length === 0) {
|
|
5717
6789
|
return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
|
|
5718
6790
|
}
|
|
5719
|
-
|
|
5720
|
-
|
|
6791
|
+
const modelPartsForHistory2 = response.modelParts.filter(
|
|
6792
|
+
(part) => !(typeof part.text === "string" && part.thought === true)
|
|
6793
|
+
);
|
|
6794
|
+
if (modelPartsForHistory2.length > 0) {
|
|
6795
|
+
geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
|
|
6796
|
+
} else if (response.responseText.length > 0) {
|
|
6797
|
+
geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
|
|
5721
6798
|
}
|
|
5722
|
-
|
|
6799
|
+
geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
|
|
5723
6800
|
continue;
|
|
5724
6801
|
}
|
|
5725
|
-
const
|
|
5726
|
-
const
|
|
6802
|
+
const toolCalls = [];
|
|
6803
|
+
const modelPartsForHistory = response.modelParts.filter(
|
|
6804
|
+
(part) => !(typeof part.text === "string" && part.thought === true)
|
|
6805
|
+
);
|
|
6806
|
+
if (modelPartsForHistory.length > 0) {
|
|
6807
|
+
geminiContents.push({ role: "model", parts: modelPartsForHistory });
|
|
6808
|
+
} else {
|
|
6809
|
+
const parts = [];
|
|
6810
|
+
if (response.responseText) {
|
|
6811
|
+
parts.push({ text: response.responseText });
|
|
6812
|
+
}
|
|
6813
|
+
for (const call of response.functionCalls) {
|
|
6814
|
+
parts.push({ functionCall: call });
|
|
6815
|
+
}
|
|
6816
|
+
geminiContents.push({ role: "model", parts });
|
|
6817
|
+
}
|
|
6818
|
+
const responseParts = [];
|
|
6819
|
+
const callInputs = response.functionCalls.map((call, index) => {
|
|
5727
6820
|
const toolIndex = index + 1;
|
|
5728
6821
|
const toolId = buildToolLogId(turn, toolIndex);
|
|
5729
|
-
const
|
|
5730
|
-
|
|
6822
|
+
const toolName = call.name ?? "unknown";
|
|
6823
|
+
const rawInput = call.args ?? {};
|
|
6824
|
+
return { call, toolName, rawInput, toolId, turn, toolIndex };
|
|
5731
6825
|
});
|
|
5732
6826
|
for (const entry of callInputs) {
|
|
5733
|
-
|
|
6827
|
+
onEvent?.({
|
|
5734
6828
|
type: "tool_call",
|
|
5735
6829
|
phase: "started",
|
|
5736
6830
|
turn: entry.turn,
|
|
@@ -5739,7 +6833,7 @@ async function runToolLoop(request) {
|
|
|
5739
6833
|
toolId: entry.toolId,
|
|
5740
6834
|
callKind: "function",
|
|
5741
6835
|
callId: entry.call.id,
|
|
5742
|
-
input: entry.
|
|
6836
|
+
input: entry.rawInput
|
|
5743
6837
|
});
|
|
5744
6838
|
}
|
|
5745
6839
|
const callResults = await Promise.all(
|
|
@@ -5756,26 +6850,23 @@ async function runToolLoop(request) {
|
|
|
5756
6850
|
callKind: "function",
|
|
5757
6851
|
toolName: entry.toolName,
|
|
5758
6852
|
tool: request.tools[entry.toolName],
|
|
5759
|
-
rawInput: entry.
|
|
5760
|
-
parseError: entry.parseError
|
|
6853
|
+
rawInput: entry.rawInput
|
|
5761
6854
|
});
|
|
5762
6855
|
return { entry, result, outputPayload };
|
|
5763
6856
|
}
|
|
5764
6857
|
);
|
|
5765
6858
|
})
|
|
5766
6859
|
);
|
|
5767
|
-
const assistantToolCalls = [];
|
|
5768
|
-
const toolMessages = [];
|
|
5769
6860
|
let toolExecutionMs = 0;
|
|
5770
6861
|
let waitToolMs = 0;
|
|
5771
6862
|
for (const { entry, result, outputPayload } of callResults) {
|
|
5772
|
-
|
|
6863
|
+
toolCalls.push({ ...result, callId: entry.call.id });
|
|
5773
6864
|
const callDurationMs = toToolResultDuration(result);
|
|
5774
6865
|
toolExecutionMs += callDurationMs;
|
|
5775
6866
|
if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
|
|
5776
6867
|
waitToolMs += callDurationMs;
|
|
5777
6868
|
}
|
|
5778
|
-
|
|
6869
|
+
onEvent?.({
|
|
5779
6870
|
type: "tool_call",
|
|
5780
6871
|
phase: "completed",
|
|
5781
6872
|
turn: entry.turn,
|
|
@@ -5784,30 +6875,26 @@ async function runToolLoop(request) {
|
|
|
5784
6875
|
toolId: entry.toolId,
|
|
5785
6876
|
callKind: "function",
|
|
5786
6877
|
callId: entry.call.id,
|
|
5787
|
-
input: entry.
|
|
6878
|
+
input: entry.rawInput,
|
|
5788
6879
|
output: result.output,
|
|
5789
6880
|
error: result.error,
|
|
5790
6881
|
durationMs: result.durationMs
|
|
5791
6882
|
});
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
function: {
|
|
6883
|
+
const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
|
|
6884
|
+
responseParts.push({
|
|
6885
|
+
functionResponse: {
|
|
5796
6886
|
name: entry.toolName,
|
|
5797
|
-
|
|
6887
|
+
response: responsePayload,
|
|
6888
|
+
...entry.call.id ? { id: entry.call.id } : {}
|
|
5798
6889
|
}
|
|
5799
6890
|
});
|
|
5800
|
-
toolMessages.push({
|
|
5801
|
-
role: "tool",
|
|
5802
|
-
tool_call_id: entry.call.id,
|
|
5803
|
-
content: mergeToolOutput(outputPayload)
|
|
5804
|
-
});
|
|
5805
6891
|
}
|
|
5806
6892
|
const stepCompletedAtMs = Date.now();
|
|
5807
6893
|
const timing = buildStepTiming({
|
|
5808
6894
|
stepStartedAtMs,
|
|
5809
6895
|
stepCompletedAtMs,
|
|
5810
6896
|
modelCompletedAtMs,
|
|
6897
|
+
firstModelEventAtMs,
|
|
5811
6898
|
schedulerMetrics,
|
|
5812
6899
|
toolExecutionMs,
|
|
5813
6900
|
waitToolMs
|
|
@@ -5816,296 +6903,40 @@ async function runToolLoop(request) {
|
|
|
5816
6903
|
step: steps.length + 1,
|
|
5817
6904
|
modelVersion,
|
|
5818
6905
|
text: responseText || void 0,
|
|
5819
|
-
thoughts: void 0,
|
|
5820
|
-
toolCalls
|
|
6906
|
+
thoughts: thoughtsText || void 0,
|
|
6907
|
+
toolCalls,
|
|
5821
6908
|
usage: usageTokens,
|
|
5822
6909
|
costUsd: stepCostUsd,
|
|
5823
6910
|
timing
|
|
5824
6911
|
});
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
6912
|
+
stepCallLogger?.complete({
|
|
6913
|
+
provider: "gemini",
|
|
6914
|
+
model: request.model,
|
|
6915
|
+
modelVersion,
|
|
6916
|
+
step: turn,
|
|
6917
|
+
usage: usageTokens,
|
|
6918
|
+
costUsd: stepCostUsd,
|
|
6919
|
+
responseChars: responseText.length,
|
|
6920
|
+
thoughtChars: thoughtsText.length,
|
|
6921
|
+
toolCalls: toolCalls.length,
|
|
6922
|
+
finalStep: false
|
|
5829
6923
|
});
|
|
5830
|
-
|
|
6924
|
+
geminiContents.push({ role: "user", parts: responseParts });
|
|
5831
6925
|
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
5832
6926
|
if (steeringInput.length > 0) {
|
|
5833
|
-
|
|
5834
|
-
}
|
|
5835
|
-
}
|
|
5836
|
-
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
5837
|
-
}
|
|
5838
|
-
const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
|
|
5839
|
-
const geminiNativeTools = toGeminiTools(request.modelTools);
|
|
5840
|
-
const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
|
|
5841
|
-
const geminiContents = contents.map(convertLlmContentToGeminiContent);
|
|
5842
|
-
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
5843
|
-
const stepStartedAtMs = Date.now();
|
|
5844
|
-
let firstModelEventAtMs;
|
|
5845
|
-
let schedulerMetrics;
|
|
5846
|
-
const markFirstModelEvent = () => {
|
|
5847
|
-
if (firstModelEventAtMs === void 0) {
|
|
5848
|
-
firstModelEventAtMs = Date.now();
|
|
5849
|
-
}
|
|
5850
|
-
};
|
|
5851
|
-
const thinkingConfig = resolveGeminiThinkingConfig(request.model, request.thinkingLevel);
|
|
5852
|
-
const config = {
|
|
5853
|
-
maxOutputTokens: 32e3,
|
|
5854
|
-
tools: geminiTools,
|
|
5855
|
-
toolConfig: {
|
|
5856
|
-
functionCallingConfig: {
|
|
5857
|
-
mode: FunctionCallingConfigMode.VALIDATED
|
|
5858
|
-
}
|
|
5859
|
-
},
|
|
5860
|
-
...thinkingConfig ? { thinkingConfig } : {}
|
|
5861
|
-
};
|
|
5862
|
-
const onEvent = request.onEvent;
|
|
5863
|
-
const response = await runGeminiCall(
|
|
5864
|
-
async (client) => {
|
|
5865
|
-
const stream = await client.models.generateContentStream({
|
|
5866
|
-
model: request.model,
|
|
5867
|
-
contents: geminiContents,
|
|
5868
|
-
config
|
|
5869
|
-
});
|
|
5870
|
-
let responseText = "";
|
|
5871
|
-
let thoughtsText = "";
|
|
5872
|
-
const modelParts = [];
|
|
5873
|
-
const functionCalls = [];
|
|
5874
|
-
const seenFunctionCallIds = /* @__PURE__ */ new Set();
|
|
5875
|
-
const seenFunctionCallKeys = /* @__PURE__ */ new Set();
|
|
5876
|
-
let latestUsageMetadata;
|
|
5877
|
-
let resolvedModelVersion;
|
|
5878
|
-
for await (const chunk of stream) {
|
|
5879
|
-
markFirstModelEvent();
|
|
5880
|
-
if (chunk.modelVersion) {
|
|
5881
|
-
resolvedModelVersion = chunk.modelVersion;
|
|
5882
|
-
onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
|
|
5883
|
-
}
|
|
5884
|
-
if (chunk.usageMetadata) {
|
|
5885
|
-
latestUsageMetadata = chunk.usageMetadata;
|
|
5886
|
-
}
|
|
5887
|
-
const candidates = chunk.candidates;
|
|
5888
|
-
if (!candidates || candidates.length === 0) {
|
|
5889
|
-
continue;
|
|
5890
|
-
}
|
|
5891
|
-
const primary = candidates[0];
|
|
5892
|
-
const parts = primary?.content?.parts;
|
|
5893
|
-
if (!parts || parts.length === 0) {
|
|
5894
|
-
continue;
|
|
5895
|
-
}
|
|
5896
|
-
for (const part of parts) {
|
|
5897
|
-
modelParts.push(part);
|
|
5898
|
-
const call = part.functionCall;
|
|
5899
|
-
if (call) {
|
|
5900
|
-
const id = typeof call.id === "string" ? call.id : "";
|
|
5901
|
-
const shouldAdd = (() => {
|
|
5902
|
-
if (id.length > 0) {
|
|
5903
|
-
if (seenFunctionCallIds.has(id)) {
|
|
5904
|
-
return false;
|
|
5905
|
-
}
|
|
5906
|
-
seenFunctionCallIds.add(id);
|
|
5907
|
-
return true;
|
|
5908
|
-
}
|
|
5909
|
-
const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
|
|
5910
|
-
if (seenFunctionCallKeys.has(key)) {
|
|
5911
|
-
return false;
|
|
5912
|
-
}
|
|
5913
|
-
seenFunctionCallKeys.add(key);
|
|
5914
|
-
return true;
|
|
5915
|
-
})();
|
|
5916
|
-
if (shouldAdd) {
|
|
5917
|
-
functionCalls.push(call);
|
|
5918
|
-
}
|
|
5919
|
-
}
|
|
5920
|
-
if (typeof part.text === "string" && part.text.length > 0) {
|
|
5921
|
-
if (part.thought) {
|
|
5922
|
-
thoughtsText += part.text;
|
|
5923
|
-
onEvent?.({ type: "delta", channel: "thought", text: part.text });
|
|
5924
|
-
} else {
|
|
5925
|
-
responseText += part.text;
|
|
5926
|
-
onEvent?.({ type: "delta", channel: "response", text: part.text });
|
|
5927
|
-
}
|
|
5928
|
-
}
|
|
5929
|
-
}
|
|
5930
|
-
}
|
|
5931
|
-
return {
|
|
5932
|
-
responseText,
|
|
5933
|
-
thoughtsText,
|
|
5934
|
-
functionCalls,
|
|
5935
|
-
modelParts,
|
|
5936
|
-
usageMetadata: latestUsageMetadata,
|
|
5937
|
-
modelVersion: resolvedModelVersion ?? request.model
|
|
5938
|
-
};
|
|
5939
|
-
},
|
|
5940
|
-
request.model,
|
|
5941
|
-
{
|
|
5942
|
-
onSettled: (metrics) => {
|
|
5943
|
-
schedulerMetrics = metrics;
|
|
5944
|
-
}
|
|
6927
|
+
geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
|
|
5945
6928
|
}
|
|
5946
|
-
)
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
const stepCostUsd = estimateCallCostUsd({
|
|
5951
|
-
modelId: modelVersion,
|
|
5952
|
-
tokens: usageTokens,
|
|
5953
|
-
responseImages: 0
|
|
5954
|
-
});
|
|
5955
|
-
totalCostUsd += stepCostUsd;
|
|
5956
|
-
if (response.functionCalls.length === 0) {
|
|
5957
|
-
const steeringInput2 = steeringInternal?.drainPendingContents() ?? [];
|
|
5958
|
-
finalText = response.responseText.trim();
|
|
5959
|
-
finalThoughts = response.thoughtsText.trim();
|
|
5960
|
-
const stepCompletedAtMs2 = Date.now();
|
|
5961
|
-
const timing2 = buildStepTiming({
|
|
5962
|
-
stepStartedAtMs,
|
|
5963
|
-
stepCompletedAtMs: stepCompletedAtMs2,
|
|
5964
|
-
modelCompletedAtMs,
|
|
5965
|
-
firstModelEventAtMs,
|
|
5966
|
-
schedulerMetrics,
|
|
5967
|
-
toolExecutionMs: 0,
|
|
5968
|
-
waitToolMs: 0
|
|
5969
|
-
});
|
|
5970
|
-
steps.push({
|
|
5971
|
-
step: steps.length + 1,
|
|
6929
|
+
} catch (error) {
|
|
6930
|
+
stepCallLogger?.fail(error, {
|
|
6931
|
+
provider: "gemini",
|
|
6932
|
+
model: request.model,
|
|
5972
6933
|
modelVersion,
|
|
5973
|
-
|
|
5974
|
-
thoughts: finalThoughts || void 0,
|
|
5975
|
-
toolCalls: [],
|
|
6934
|
+
step: turn,
|
|
5976
6935
|
usage: usageTokens,
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
});
|
|
5980
|
-
if (steeringInput2.length === 0) {
|
|
5981
|
-
return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
|
|
5982
|
-
}
|
|
5983
|
-
const modelPartsForHistory2 = response.modelParts.filter(
|
|
5984
|
-
(part) => !(typeof part.text === "string" && part.thought === true)
|
|
5985
|
-
);
|
|
5986
|
-
if (modelPartsForHistory2.length > 0) {
|
|
5987
|
-
geminiContents.push({ role: "model", parts: modelPartsForHistory2 });
|
|
5988
|
-
} else if (response.responseText.length > 0) {
|
|
5989
|
-
geminiContents.push({ role: "model", parts: [{ text: response.responseText }] });
|
|
5990
|
-
}
|
|
5991
|
-
geminiContents.push(...steeringInput2.map(convertLlmContentToGeminiContent));
|
|
5992
|
-
continue;
|
|
5993
|
-
}
|
|
5994
|
-
const toolCalls = [];
|
|
5995
|
-
const modelPartsForHistory = response.modelParts.filter(
|
|
5996
|
-
(part) => !(typeof part.text === "string" && part.thought === true)
|
|
5997
|
-
);
|
|
5998
|
-
if (modelPartsForHistory.length > 0) {
|
|
5999
|
-
geminiContents.push({ role: "model", parts: modelPartsForHistory });
|
|
6000
|
-
} else {
|
|
6001
|
-
const parts = [];
|
|
6002
|
-
if (response.responseText) {
|
|
6003
|
-
parts.push({ text: response.responseText });
|
|
6004
|
-
}
|
|
6005
|
-
for (const call of response.functionCalls) {
|
|
6006
|
-
parts.push({ functionCall: call });
|
|
6007
|
-
}
|
|
6008
|
-
geminiContents.push({ role: "model", parts });
|
|
6009
|
-
}
|
|
6010
|
-
const responseParts = [];
|
|
6011
|
-
const callInputs = response.functionCalls.map((call, index) => {
|
|
6012
|
-
const turn = stepIndex + 1;
|
|
6013
|
-
const toolIndex = index + 1;
|
|
6014
|
-
const toolId = buildToolLogId(turn, toolIndex);
|
|
6015
|
-
const toolName = call.name ?? "unknown";
|
|
6016
|
-
const rawInput = call.args ?? {};
|
|
6017
|
-
return { call, toolName, rawInput, toolId, turn, toolIndex };
|
|
6018
|
-
});
|
|
6019
|
-
for (const entry of callInputs) {
|
|
6020
|
-
onEvent?.({
|
|
6021
|
-
type: "tool_call",
|
|
6022
|
-
phase: "started",
|
|
6023
|
-
turn: entry.turn,
|
|
6024
|
-
toolIndex: entry.toolIndex,
|
|
6025
|
-
toolName: entry.toolName,
|
|
6026
|
-
toolId: entry.toolId,
|
|
6027
|
-
callKind: "function",
|
|
6028
|
-
callId: entry.call.id,
|
|
6029
|
-
input: entry.rawInput
|
|
6030
|
-
});
|
|
6031
|
-
}
|
|
6032
|
-
const callResults = await Promise.all(
|
|
6033
|
-
callInputs.map(async (entry) => {
|
|
6034
|
-
return await toolCallContextStorage.run(
|
|
6035
|
-
{
|
|
6036
|
-
toolName: entry.toolName,
|
|
6037
|
-
toolId: entry.toolId,
|
|
6038
|
-
turn: entry.turn,
|
|
6039
|
-
toolIndex: entry.toolIndex
|
|
6040
|
-
},
|
|
6041
|
-
async () => {
|
|
6042
|
-
const { result, outputPayload } = await executeToolCall({
|
|
6043
|
-
callKind: "function",
|
|
6044
|
-
toolName: entry.toolName,
|
|
6045
|
-
tool: request.tools[entry.toolName],
|
|
6046
|
-
rawInput: entry.rawInput
|
|
6047
|
-
});
|
|
6048
|
-
return { entry, result, outputPayload };
|
|
6049
|
-
}
|
|
6050
|
-
);
|
|
6051
|
-
})
|
|
6052
|
-
);
|
|
6053
|
-
let toolExecutionMs = 0;
|
|
6054
|
-
let waitToolMs = 0;
|
|
6055
|
-
for (const { entry, result, outputPayload } of callResults) {
|
|
6056
|
-
toolCalls.push({ ...result, callId: entry.call.id });
|
|
6057
|
-
const callDurationMs = toToolResultDuration(result);
|
|
6058
|
-
toolExecutionMs += callDurationMs;
|
|
6059
|
-
if (entry.toolName.toLowerCase() === SUBAGENT_WAIT_TOOL_NAME) {
|
|
6060
|
-
waitToolMs += callDurationMs;
|
|
6061
|
-
}
|
|
6062
|
-
onEvent?.({
|
|
6063
|
-
type: "tool_call",
|
|
6064
|
-
phase: "completed",
|
|
6065
|
-
turn: entry.turn,
|
|
6066
|
-
toolIndex: entry.toolIndex,
|
|
6067
|
-
toolName: entry.toolName,
|
|
6068
|
-
toolId: entry.toolId,
|
|
6069
|
-
callKind: "function",
|
|
6070
|
-
callId: entry.call.id,
|
|
6071
|
-
input: entry.rawInput,
|
|
6072
|
-
output: result.output,
|
|
6073
|
-
error: result.error,
|
|
6074
|
-
durationMs: result.durationMs
|
|
6075
|
-
});
|
|
6076
|
-
const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
|
|
6077
|
-
responseParts.push({
|
|
6078
|
-
functionResponse: {
|
|
6079
|
-
name: entry.toolName,
|
|
6080
|
-
response: responsePayload,
|
|
6081
|
-
...entry.call.id ? { id: entry.call.id } : {}
|
|
6082
|
-
}
|
|
6936
|
+
responseChars: responseText.length,
|
|
6937
|
+
thoughtChars: thoughtsText.length
|
|
6083
6938
|
});
|
|
6084
|
-
|
|
6085
|
-
const stepCompletedAtMs = Date.now();
|
|
6086
|
-
const timing = buildStepTiming({
|
|
6087
|
-
stepStartedAtMs,
|
|
6088
|
-
stepCompletedAtMs,
|
|
6089
|
-
modelCompletedAtMs,
|
|
6090
|
-
firstModelEventAtMs,
|
|
6091
|
-
schedulerMetrics,
|
|
6092
|
-
toolExecutionMs,
|
|
6093
|
-
waitToolMs
|
|
6094
|
-
});
|
|
6095
|
-
steps.push({
|
|
6096
|
-
step: steps.length + 1,
|
|
6097
|
-
modelVersion,
|
|
6098
|
-
text: response.responseText.trim() || void 0,
|
|
6099
|
-
thoughts: response.thoughtsText.trim() || void 0,
|
|
6100
|
-
toolCalls,
|
|
6101
|
-
usage: usageTokens,
|
|
6102
|
-
costUsd: stepCostUsd,
|
|
6103
|
-
timing
|
|
6104
|
-
});
|
|
6105
|
-
geminiContents.push({ role: "user", parts: responseParts });
|
|
6106
|
-
const steeringInput = steeringInternal?.drainPendingContents() ?? [];
|
|
6107
|
-
if (steeringInput.length > 0) {
|
|
6108
|
-
geminiContents.push(...steeringInput.map(convertLlmContentToGeminiContent));
|
|
6939
|
+
throw error;
|
|
6109
6940
|
}
|
|
6110
6941
|
}
|
|
6111
6942
|
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
@@ -6419,6 +7250,7 @@ ${lines}`;
|
|
|
6419
7250
|
|
|
6420
7251
|
// src/agent.ts
|
|
6421
7252
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
7253
|
+
import path7 from "path";
|
|
6422
7254
|
|
|
6423
7255
|
// src/agent/subagents.ts
|
|
6424
7256
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
@@ -6893,26 +7725,26 @@ function resolveInputItemsText(items) {
|
|
|
6893
7725
|
}
|
|
6894
7726
|
const itemType = typeof item.type === "string" ? item.type.trim() : "";
|
|
6895
7727
|
const name = typeof item.name === "string" ? item.name.trim() : "";
|
|
6896
|
-
const
|
|
7728
|
+
const path8 = typeof item.path === "string" ? item.path.trim() : "";
|
|
6897
7729
|
const imageUrl = typeof item.image_url === "string" ? item.image_url.trim() : "";
|
|
6898
7730
|
if (itemType === "image") {
|
|
6899
7731
|
lines.push("[image]");
|
|
6900
7732
|
continue;
|
|
6901
7733
|
}
|
|
6902
|
-
if (itemType === "local_image" &&
|
|
6903
|
-
lines.push(`[local_image:${
|
|
7734
|
+
if (itemType === "local_image" && path8) {
|
|
7735
|
+
lines.push(`[local_image:${path8}]`);
|
|
6904
7736
|
continue;
|
|
6905
7737
|
}
|
|
6906
|
-
if (itemType === "skill" && name &&
|
|
6907
|
-
lines.push(`[skill:$${name}](${
|
|
7738
|
+
if (itemType === "skill" && name && path8) {
|
|
7739
|
+
lines.push(`[skill:$${name}](${path8})`);
|
|
6908
7740
|
continue;
|
|
6909
7741
|
}
|
|
6910
|
-
if (itemType === "mention" && name &&
|
|
6911
|
-
lines.push(`[mention:$${name}](${
|
|
7742
|
+
if (itemType === "mention" && name && path8) {
|
|
7743
|
+
lines.push(`[mention:$${name}](${path8})`);
|
|
6912
7744
|
continue;
|
|
6913
7745
|
}
|
|
6914
|
-
if (
|
|
6915
|
-
lines.push(`[${itemType || "input"}:${
|
|
7746
|
+
if (path8 || imageUrl) {
|
|
7747
|
+
lines.push(`[${itemType || "input"}:${path8 || imageUrl}]`);
|
|
6916
7748
|
continue;
|
|
6917
7749
|
}
|
|
6918
7750
|
if (name) {
|
|
@@ -7037,7 +7869,7 @@ function startRun(agent, options) {
|
|
|
7037
7869
|
setLifecycle(agent, "idle", "input_queued", `Subagent ${agent.id} run interrupted.`);
|
|
7038
7870
|
return;
|
|
7039
7871
|
}
|
|
7040
|
-
const message =
|
|
7872
|
+
const message = toErrorMessage2(error);
|
|
7041
7873
|
agent.lastError = message;
|
|
7042
7874
|
setLifecycle(agent, "failed", "run_failed", `Subagent ${agent.id} failed: ${message}`);
|
|
7043
7875
|
emitBackgroundNotification(agent, options);
|
|
@@ -7217,7 +8049,7 @@ function trimToUndefined(value) {
|
|
|
7217
8049
|
const trimmed = value?.trim();
|
|
7218
8050
|
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
7219
8051
|
}
|
|
7220
|
-
function
|
|
8052
|
+
function toErrorMessage2(error) {
|
|
7221
8053
|
if (error instanceof Error) {
|
|
7222
8054
|
return error.message;
|
|
7223
8055
|
}
|
|
@@ -7230,27 +8062,27 @@ function sleep2(ms) {
|
|
|
7230
8062
|
}
|
|
7231
8063
|
|
|
7232
8064
|
// src/tools/filesystemTools.ts
|
|
7233
|
-
import
|
|
7234
|
-
import { Buffer as
|
|
8065
|
+
import path6 from "path";
|
|
8066
|
+
import { Buffer as Buffer5 } from "buffer";
|
|
7235
8067
|
import { z as z6 } from "zod";
|
|
7236
8068
|
|
|
7237
8069
|
// src/tools/applyPatch.ts
|
|
7238
|
-
import
|
|
8070
|
+
import path5 from "path";
|
|
7239
8071
|
import { z as z5 } from "zod";
|
|
7240
8072
|
|
|
7241
8073
|
// src/tools/filesystem.ts
|
|
7242
8074
|
import { promises as fs3 } from "fs";
|
|
7243
|
-
import
|
|
8075
|
+
import path4 from "path";
|
|
7244
8076
|
var InMemoryAgentFilesystem = class {
|
|
7245
8077
|
#files = /* @__PURE__ */ new Map();
|
|
7246
8078
|
#dirs = /* @__PURE__ */ new Map();
|
|
7247
8079
|
#clock = 0;
|
|
7248
8080
|
constructor(initialFiles = {}) {
|
|
7249
|
-
const root =
|
|
8081
|
+
const root = path4.resolve("/");
|
|
7250
8082
|
this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
|
|
7251
8083
|
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
7252
|
-
const absolutePath =
|
|
7253
|
-
this.#ensureDirSync(
|
|
8084
|
+
const absolutePath = path4.resolve(filePath);
|
|
8085
|
+
this.#ensureDirSync(path4.dirname(absolutePath));
|
|
7254
8086
|
this.#files.set(absolutePath, {
|
|
7255
8087
|
content,
|
|
7256
8088
|
mtimeMs: this.#nextMtime()
|
|
@@ -7258,7 +8090,7 @@ var InMemoryAgentFilesystem = class {
|
|
|
7258
8090
|
}
|
|
7259
8091
|
}
|
|
7260
8092
|
async readTextFile(filePath) {
|
|
7261
|
-
const absolutePath =
|
|
8093
|
+
const absolutePath = path4.resolve(filePath);
|
|
7262
8094
|
const file = this.#files.get(absolutePath);
|
|
7263
8095
|
if (!file) {
|
|
7264
8096
|
throw createNoSuchFileError("open", absolutePath);
|
|
@@ -7270,24 +8102,24 @@ var InMemoryAgentFilesystem = class {
|
|
|
7270
8102
|
return Buffer.from(content, "utf8");
|
|
7271
8103
|
}
|
|
7272
8104
|
async writeTextFile(filePath, content) {
|
|
7273
|
-
const absolutePath =
|
|
7274
|
-
const parentPath =
|
|
8105
|
+
const absolutePath = path4.resolve(filePath);
|
|
8106
|
+
const parentPath = path4.dirname(absolutePath);
|
|
7275
8107
|
if (!this.#dirs.has(parentPath)) {
|
|
7276
8108
|
throw createNoSuchFileError("open", parentPath);
|
|
7277
8109
|
}
|
|
7278
8110
|
this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
|
|
7279
8111
|
}
|
|
7280
8112
|
async deleteFile(filePath) {
|
|
7281
|
-
const absolutePath =
|
|
8113
|
+
const absolutePath = path4.resolve(filePath);
|
|
7282
8114
|
if (!this.#files.delete(absolutePath)) {
|
|
7283
8115
|
throw createNoSuchFileError("unlink", absolutePath);
|
|
7284
8116
|
}
|
|
7285
8117
|
}
|
|
7286
8118
|
async ensureDir(directoryPath) {
|
|
7287
|
-
this.#ensureDirSync(
|
|
8119
|
+
this.#ensureDirSync(path4.resolve(directoryPath));
|
|
7288
8120
|
}
|
|
7289
8121
|
async readDir(directoryPath) {
|
|
7290
|
-
const absolutePath =
|
|
8122
|
+
const absolutePath = path4.resolve(directoryPath);
|
|
7291
8123
|
const directory = this.#dirs.get(absolutePath);
|
|
7292
8124
|
if (!directory) {
|
|
7293
8125
|
throw createNoSuchFileError("scandir", absolutePath);
|
|
@@ -7298,10 +8130,10 @@ var InMemoryAgentFilesystem = class {
|
|
|
7298
8130
|
if (dirPath === absolutePath) {
|
|
7299
8131
|
continue;
|
|
7300
8132
|
}
|
|
7301
|
-
if (
|
|
8133
|
+
if (path4.dirname(dirPath) !== absolutePath) {
|
|
7302
8134
|
continue;
|
|
7303
8135
|
}
|
|
7304
|
-
const name =
|
|
8136
|
+
const name = path4.basename(dirPath);
|
|
7305
8137
|
if (seenNames.has(name)) {
|
|
7306
8138
|
continue;
|
|
7307
8139
|
}
|
|
@@ -7314,10 +8146,10 @@ var InMemoryAgentFilesystem = class {
|
|
|
7314
8146
|
});
|
|
7315
8147
|
}
|
|
7316
8148
|
for (const [filePath, fileRecord] of this.#files.entries()) {
|
|
7317
|
-
if (
|
|
8149
|
+
if (path4.dirname(filePath) !== absolutePath) {
|
|
7318
8150
|
continue;
|
|
7319
8151
|
}
|
|
7320
|
-
const name =
|
|
8152
|
+
const name = path4.basename(filePath);
|
|
7321
8153
|
if (seenNames.has(name)) {
|
|
7322
8154
|
continue;
|
|
7323
8155
|
}
|
|
@@ -7333,7 +8165,7 @@ var InMemoryAgentFilesystem = class {
|
|
|
7333
8165
|
return entries;
|
|
7334
8166
|
}
|
|
7335
8167
|
async stat(entryPath) {
|
|
7336
|
-
const absolutePath =
|
|
8168
|
+
const absolutePath = path4.resolve(entryPath);
|
|
7337
8169
|
const file = this.#files.get(absolutePath);
|
|
7338
8170
|
if (file) {
|
|
7339
8171
|
return { kind: "file", mtimeMs: file.mtimeMs };
|
|
@@ -7349,7 +8181,7 @@ var InMemoryAgentFilesystem = class {
|
|
|
7349
8181
|
return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
|
|
7350
8182
|
}
|
|
7351
8183
|
#ensureDirSync(directoryPath) {
|
|
7352
|
-
const absolutePath =
|
|
8184
|
+
const absolutePath = path4.resolve(directoryPath);
|
|
7353
8185
|
const parts = [];
|
|
7354
8186
|
let cursor = absolutePath;
|
|
7355
8187
|
for (; ; ) {
|
|
@@ -7357,7 +8189,7 @@ var InMemoryAgentFilesystem = class {
|
|
|
7357
8189
|
break;
|
|
7358
8190
|
}
|
|
7359
8191
|
parts.push(cursor);
|
|
7360
|
-
const parent =
|
|
8192
|
+
const parent = path4.dirname(cursor);
|
|
7361
8193
|
if (parent === cursor) {
|
|
7362
8194
|
break;
|
|
7363
8195
|
}
|
|
@@ -7391,7 +8223,7 @@ function createNodeAgentFilesystem() {
|
|
|
7391
8223
|
const entries = await fs3.readdir(directoryPath, { withFileTypes: true });
|
|
7392
8224
|
const result = [];
|
|
7393
8225
|
for (const entry of entries) {
|
|
7394
|
-
const entryPath =
|
|
8226
|
+
const entryPath = path4.resolve(directoryPath, entry.name);
|
|
7395
8227
|
const stats = await fs3.lstat(entryPath);
|
|
7396
8228
|
result.push({
|
|
7397
8229
|
name: entry.name,
|
|
@@ -7555,7 +8387,7 @@ function createApplyPatchTool(options = {}) {
|
|
|
7555
8387
|
});
|
|
7556
8388
|
}
|
|
7557
8389
|
async function applyPatch(request) {
|
|
7558
|
-
const cwd =
|
|
8390
|
+
const cwd = path5.resolve(request.cwd ?? process.cwd());
|
|
7559
8391
|
const adapter = request.fs ?? createNodeAgentFilesystem();
|
|
7560
8392
|
const allowOutsideCwd = request.allowOutsideCwd === true;
|
|
7561
8393
|
const patchBytes = Buffer.byteLength(request.patch, "utf8");
|
|
@@ -7577,7 +8409,7 @@ async function applyPatch(request) {
|
|
|
7577
8409
|
kind: "add",
|
|
7578
8410
|
path: absolutePath2
|
|
7579
8411
|
});
|
|
7580
|
-
await adapter.ensureDir(
|
|
8412
|
+
await adapter.ensureDir(path5.dirname(absolutePath2));
|
|
7581
8413
|
await adapter.writeTextFile(absolutePath2, operation.content);
|
|
7582
8414
|
added.push(toDisplayPath(absolutePath2, cwd));
|
|
7583
8415
|
continue;
|
|
@@ -7611,7 +8443,7 @@ async function applyPatch(request) {
|
|
|
7611
8443
|
fromPath: absolutePath,
|
|
7612
8444
|
toPath: destinationPath
|
|
7613
8445
|
});
|
|
7614
|
-
await adapter.ensureDir(
|
|
8446
|
+
await adapter.ensureDir(path5.dirname(destinationPath));
|
|
7615
8447
|
await adapter.writeTextFile(destinationPath, next);
|
|
7616
8448
|
await adapter.deleteFile(absolutePath);
|
|
7617
8449
|
modified.push(toDisplayPath(destinationPath, cwd));
|
|
@@ -7642,22 +8474,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
|
|
|
7642
8474
|
if (trimmed.length === 0) {
|
|
7643
8475
|
throw new Error("apply_patch failed: empty file path");
|
|
7644
8476
|
}
|
|
7645
|
-
const absolutePath =
|
|
8477
|
+
const absolutePath = path5.isAbsolute(trimmed) ? path5.resolve(trimmed) : path5.resolve(cwd, trimmed);
|
|
7646
8478
|
if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
|
|
7647
8479
|
throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
|
|
7648
8480
|
}
|
|
7649
8481
|
return absolutePath;
|
|
7650
8482
|
}
|
|
7651
8483
|
function isPathInsideCwd(candidatePath, cwd) {
|
|
7652
|
-
const relative =
|
|
7653
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
8484
|
+
const relative = path5.relative(cwd, candidatePath);
|
|
8485
|
+
return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
|
|
7654
8486
|
}
|
|
7655
8487
|
function toDisplayPath(absolutePath, cwd) {
|
|
7656
|
-
const relative =
|
|
8488
|
+
const relative = path5.relative(cwd, absolutePath);
|
|
7657
8489
|
if (relative === "") {
|
|
7658
8490
|
return ".";
|
|
7659
8491
|
}
|
|
7660
|
-
if (!relative.startsWith("..") && !
|
|
8492
|
+
if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
|
|
7661
8493
|
return relative;
|
|
7662
8494
|
}
|
|
7663
8495
|
return absolutePath;
|
|
@@ -8424,7 +9256,7 @@ async function readBinaryFile(filesystem, filePath) {
|
|
|
8424
9256
|
return await filesystem.readBinaryFile(filePath);
|
|
8425
9257
|
}
|
|
8426
9258
|
const text = await filesystem.readTextFile(filePath);
|
|
8427
|
-
return
|
|
9259
|
+
return Buffer5.from(text, "utf8");
|
|
8428
9260
|
}
|
|
8429
9261
|
function detectImageMimeType(buffer, filePath) {
|
|
8430
9262
|
if (buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) {
|
|
@@ -8442,7 +9274,7 @@ function detectImageMimeType(buffer, filePath) {
|
|
|
8442
9274
|
if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
8443
9275
|
return "image/webp";
|
|
8444
9276
|
}
|
|
8445
|
-
const fromExtension = IMAGE_MIME_BY_EXTENSION[
|
|
9277
|
+
const fromExtension = IMAGE_MIME_BY_EXTENSION[path6.extname(filePath).toLowerCase()];
|
|
8446
9278
|
if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
|
|
8447
9279
|
return fromExtension;
|
|
8448
9280
|
}
|
|
@@ -8452,13 +9284,13 @@ function isPdfFile(buffer, filePath) {
|
|
|
8452
9284
|
if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
|
|
8453
9285
|
return true;
|
|
8454
9286
|
}
|
|
8455
|
-
return
|
|
9287
|
+
return path6.extname(filePath).toLowerCase() === ".pdf";
|
|
8456
9288
|
}
|
|
8457
9289
|
function isValidUtf8(buffer) {
|
|
8458
9290
|
if (buffer.length === 0) {
|
|
8459
9291
|
return true;
|
|
8460
9292
|
}
|
|
8461
|
-
return
|
|
9293
|
+
return Buffer5.from(buffer.toString("utf8"), "utf8").equals(buffer);
|
|
8462
9294
|
}
|
|
8463
9295
|
async function readFileGemini(input, options) {
|
|
8464
9296
|
const runtime = resolveRuntime(options);
|
|
@@ -8490,7 +9322,7 @@ async function writeFileGemini(input, options) {
|
|
|
8490
9322
|
action: "write",
|
|
8491
9323
|
path: filePath
|
|
8492
9324
|
});
|
|
8493
|
-
await runtime.filesystem.ensureDir(
|
|
9325
|
+
await runtime.filesystem.ensureDir(path6.dirname(filePath));
|
|
8494
9326
|
await runtime.filesystem.writeTextFile(filePath, input.content);
|
|
8495
9327
|
return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
|
|
8496
9328
|
}
|
|
@@ -8511,7 +9343,7 @@ async function replaceFileContentGemini(input, options) {
|
|
|
8511
9343
|
originalContent = await runtime.filesystem.readTextFile(filePath);
|
|
8512
9344
|
} catch (error) {
|
|
8513
9345
|
if (isNoEntError(error) && oldValue.length === 0) {
|
|
8514
|
-
await runtime.filesystem.ensureDir(
|
|
9346
|
+
await runtime.filesystem.ensureDir(path6.dirname(filePath));
|
|
8515
9347
|
await runtime.filesystem.writeTextFile(filePath, newValue);
|
|
8516
9348
|
return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
|
|
8517
9349
|
}
|
|
@@ -8689,7 +9521,7 @@ async function globFilesGemini(input, options) {
|
|
|
8689
9521
|
});
|
|
8690
9522
|
const matched = [];
|
|
8691
9523
|
for (const filePath of files) {
|
|
8692
|
-
const relativePath = normalizeSlashes(
|
|
9524
|
+
const relativePath = normalizeSlashes(path6.relative(dirPath, filePath));
|
|
8693
9525
|
if (!matcher(relativePath)) {
|
|
8694
9526
|
continue;
|
|
8695
9527
|
}
|
|
@@ -8707,7 +9539,7 @@ async function globFilesGemini(input, options) {
|
|
|
8707
9539
|
}
|
|
8708
9540
|
function resolveRuntime(options) {
|
|
8709
9541
|
return {
|
|
8710
|
-
cwd:
|
|
9542
|
+
cwd: path6.resolve(options.cwd ?? process.cwd()),
|
|
8711
9543
|
filesystem: options.fs ?? createNodeAgentFilesystem(),
|
|
8712
9544
|
allowOutsideCwd: options.allowOutsideCwd === true,
|
|
8713
9545
|
checkAccess: options.checkAccess,
|
|
@@ -8738,13 +9570,13 @@ function mapApplyPatchAction(action) {
|
|
|
8738
9570
|
return "move";
|
|
8739
9571
|
}
|
|
8740
9572
|
function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
|
|
8741
|
-
const absolutePath =
|
|
9573
|
+
const absolutePath = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(cwd, inputPath);
|
|
8742
9574
|
if (allowOutsideCwd || isPathInsideCwd2(absolutePath, cwd)) {
|
|
8743
9575
|
return absolutePath;
|
|
8744
9576
|
}
|
|
8745
|
-
if (
|
|
9577
|
+
if (path6.isAbsolute(inputPath)) {
|
|
8746
9578
|
const sandboxRelativePath = inputPath.replace(/^[/\\]+/, "");
|
|
8747
|
-
const sandboxRootedPath =
|
|
9579
|
+
const sandboxRootedPath = path6.resolve(cwd, sandboxRelativePath);
|
|
8748
9580
|
if (isPathInsideCwd2(sandboxRootedPath, cwd)) {
|
|
8749
9581
|
return sandboxRootedPath;
|
|
8750
9582
|
}
|
|
@@ -8752,25 +9584,25 @@ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
|
|
|
8752
9584
|
throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
|
|
8753
9585
|
}
|
|
8754
9586
|
function isPathInsideCwd2(candidatePath, cwd) {
|
|
8755
|
-
const relative =
|
|
8756
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
9587
|
+
const relative = path6.relative(cwd, candidatePath);
|
|
9588
|
+
return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
|
|
8757
9589
|
}
|
|
8758
9590
|
function toDisplayPath2(absolutePath, cwd) {
|
|
8759
|
-
const relative =
|
|
9591
|
+
const relative = path6.relative(cwd, absolutePath);
|
|
8760
9592
|
if (relative === "") {
|
|
8761
9593
|
return ".";
|
|
8762
9594
|
}
|
|
8763
|
-
if (!relative.startsWith("..") && !
|
|
9595
|
+
if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
|
|
8764
9596
|
return relative;
|
|
8765
9597
|
}
|
|
8766
9598
|
return absolutePath;
|
|
8767
9599
|
}
|
|
8768
9600
|
function toSandboxDisplayPath(absolutePath, cwd) {
|
|
8769
|
-
const relative =
|
|
9601
|
+
const relative = path6.relative(cwd, absolutePath);
|
|
8770
9602
|
if (relative === "") {
|
|
8771
9603
|
return "/";
|
|
8772
9604
|
}
|
|
8773
|
-
if (!relative.startsWith("..") && !
|
|
9605
|
+
if (!relative.startsWith("..") && !path6.isAbsolute(relative)) {
|
|
8774
9606
|
return `/${normalizeSlashes(relative)}`;
|
|
8775
9607
|
}
|
|
8776
9608
|
return normalizeSlashes(absolutePath);
|
|
@@ -8886,7 +9718,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
|
|
|
8886
9718
|
}));
|
|
8887
9719
|
return (candidatePath) => {
|
|
8888
9720
|
const normalizedPath = normalizeSlashes(candidatePath);
|
|
8889
|
-
const basename =
|
|
9721
|
+
const basename = path6.posix.basename(normalizedPath);
|
|
8890
9722
|
return compiled.some(
|
|
8891
9723
|
(entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
|
|
8892
9724
|
);
|
|
@@ -9002,10 +9834,18 @@ function isNoEntError(error) {
|
|
|
9002
9834
|
// src/agent.ts
|
|
9003
9835
|
async function runAgentLoop(request) {
|
|
9004
9836
|
const telemetry = createAgentTelemetrySession(request.telemetry);
|
|
9837
|
+
const logging = createRootAgentLoggingSession(request);
|
|
9005
9838
|
try {
|
|
9006
|
-
return await
|
|
9839
|
+
return await runWithAgentLoggingSession(logging, async () => {
|
|
9840
|
+
return await runAgentLoopInternal(request, {
|
|
9841
|
+
depth: 0,
|
|
9842
|
+
telemetry,
|
|
9843
|
+
logging
|
|
9844
|
+
});
|
|
9845
|
+
});
|
|
9007
9846
|
} finally {
|
|
9008
9847
|
await telemetry?.flush();
|
|
9848
|
+
await logging?.flush();
|
|
9009
9849
|
}
|
|
9010
9850
|
}
|
|
9011
9851
|
function mergeAbortSignals2(first, second) {
|
|
@@ -9076,9 +9916,11 @@ async function runAgentLoopInternal(request, context) {
|
|
|
9076
9916
|
subagent_tool,
|
|
9077
9917
|
subagents,
|
|
9078
9918
|
telemetry,
|
|
9919
|
+
logging: _logging,
|
|
9079
9920
|
...toolLoopRequest
|
|
9080
9921
|
} = request;
|
|
9081
9922
|
const telemetrySession = context.telemetry ?? createAgentTelemetrySession(telemetry);
|
|
9923
|
+
const loggingSession = context.logging;
|
|
9082
9924
|
const runId = randomRunId();
|
|
9083
9925
|
const startedAtMs = Date.now();
|
|
9084
9926
|
const steeringChannel = toolLoopRequest.steering ?? createToolLoopSteeringChannel();
|
|
@@ -9092,6 +9934,7 @@ async function runAgentLoopInternal(request, context) {
|
|
|
9092
9934
|
model: request.model,
|
|
9093
9935
|
depth: context.depth,
|
|
9094
9936
|
telemetry: telemetrySession,
|
|
9937
|
+
logging: loggingSession,
|
|
9095
9938
|
customTools: customTools ?? {},
|
|
9096
9939
|
filesystemSelection,
|
|
9097
9940
|
subagentSelection,
|
|
@@ -9128,6 +9971,16 @@ async function runAgentLoopInternal(request, context) {
|
|
|
9128
9971
|
filesystemToolsEnabled: Object.keys(filesystemTools).length > 0,
|
|
9129
9972
|
subagentToolsEnabled: resolvedSubagentConfig.enabled
|
|
9130
9973
|
});
|
|
9974
|
+
loggingSession?.logLine(
|
|
9975
|
+
[
|
|
9976
|
+
`[agent:${runId}] run_started`,
|
|
9977
|
+
`depth=${context.depth.toString()}`,
|
|
9978
|
+
`model=${request.model}`,
|
|
9979
|
+
`tools=${Object.keys(mergedTools).length.toString()}`,
|
|
9980
|
+
`filesystemTools=${Object.keys(filesystemTools).length > 0 ? "true" : "false"}`,
|
|
9981
|
+
`subagentTools=${resolvedSubagentConfig.enabled ? "true" : "false"}`
|
|
9982
|
+
].join(" ")
|
|
9983
|
+
);
|
|
9131
9984
|
const sourceOnEvent = toolLoopRequestWithSteering.onEvent;
|
|
9132
9985
|
const includeLlmStreamEvents = telemetrySession?.includeLlmStreamEvents === true;
|
|
9133
9986
|
const wrappedOnEvent = sourceOnEvent || includeLlmStreamEvents ? (event) => {
|
|
@@ -9135,6 +9988,14 @@ async function runAgentLoopInternal(request, context) {
|
|
|
9135
9988
|
if (includeLlmStreamEvents) {
|
|
9136
9989
|
emitTelemetry({ type: "agent.run.stream", event });
|
|
9137
9990
|
}
|
|
9991
|
+
if (loggingSession) {
|
|
9992
|
+
appendAgentStreamEventLog({
|
|
9993
|
+
event,
|
|
9994
|
+
append: (line) => {
|
|
9995
|
+
loggingSession.logLine(`[agent:${runId}] ${line}`);
|
|
9996
|
+
}
|
|
9997
|
+
});
|
|
9998
|
+
}
|
|
9138
9999
|
} : void 0;
|
|
9139
10000
|
try {
|
|
9140
10001
|
const result = await runToolLoop({
|
|
@@ -9152,14 +10013,43 @@ async function runAgentLoopInternal(request, context) {
|
|
|
9152
10013
|
totalCostUsd: result.totalCostUsd,
|
|
9153
10014
|
usage: summarizeResultUsage(result)
|
|
9154
10015
|
});
|
|
10016
|
+
loggingSession?.logLine(
|
|
10017
|
+
[
|
|
10018
|
+
`[agent:${runId}] run_completed`,
|
|
10019
|
+
`status=ok`,
|
|
10020
|
+
`durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
|
|
10021
|
+
`steps=${result.steps.length.toString()}`,
|
|
10022
|
+
`toolCalls=${countToolCalls(result).toString()}`,
|
|
10023
|
+
`totalCostUsd=${(result.totalCostUsd ?? 0).toFixed(6)}`
|
|
10024
|
+
].join(" ")
|
|
10025
|
+
);
|
|
10026
|
+
for (const step of result.steps) {
|
|
10027
|
+
loggingSession?.logLine(
|
|
10028
|
+
[
|
|
10029
|
+
`[agent:${runId}] step_completed`,
|
|
10030
|
+
`step=${step.step.toString()}`,
|
|
10031
|
+
`modelVersion=${step.modelVersion}`,
|
|
10032
|
+
`toolCalls=${step.toolCalls.length.toString()}`,
|
|
10033
|
+
`costUsd=${(step.costUsd ?? 0).toFixed(6)}`
|
|
10034
|
+
].join(" ")
|
|
10035
|
+
);
|
|
10036
|
+
}
|
|
9155
10037
|
return result;
|
|
9156
10038
|
} catch (error) {
|
|
9157
10039
|
emitTelemetry({
|
|
9158
10040
|
type: "agent.run.completed",
|
|
9159
10041
|
success: false,
|
|
9160
10042
|
durationMs: Math.max(0, Date.now() - startedAtMs),
|
|
9161
|
-
error:
|
|
10043
|
+
error: toErrorMessage3(error)
|
|
9162
10044
|
});
|
|
10045
|
+
loggingSession?.logLine(
|
|
10046
|
+
[
|
|
10047
|
+
`[agent:${runId}] run_completed`,
|
|
10048
|
+
`status=error`,
|
|
10049
|
+
`durationMs=${Math.max(0, Date.now() - startedAtMs).toString()}`,
|
|
10050
|
+
`error=${toErrorMessage3(error)}`
|
|
10051
|
+
].join(" ")
|
|
10052
|
+
);
|
|
9163
10053
|
throw error;
|
|
9164
10054
|
} finally {
|
|
9165
10055
|
await subagentController?.closeAll();
|
|
@@ -9230,7 +10120,8 @@ function createSubagentController(params) {
|
|
|
9230
10120
|
{
|
|
9231
10121
|
depth: params.depth + 1,
|
|
9232
10122
|
parentRunId: params.runId,
|
|
9233
|
-
telemetry: params.telemetry
|
|
10123
|
+
telemetry: params.telemetry,
|
|
10124
|
+
logging: params.logging
|
|
9234
10125
|
}
|
|
9235
10126
|
);
|
|
9236
10127
|
}
|
|
@@ -9294,10 +10185,10 @@ function trimToUndefined2(value) {
|
|
|
9294
10185
|
function randomRunId() {
|
|
9295
10186
|
return randomBytes3(8).toString("hex");
|
|
9296
10187
|
}
|
|
9297
|
-
function
|
|
10188
|
+
function toIsoNow2() {
|
|
9298
10189
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9299
10190
|
}
|
|
9300
|
-
function
|
|
10191
|
+
function toErrorMessage3(error) {
|
|
9301
10192
|
if (error instanceof Error && error.message) {
|
|
9302
10193
|
return error.message;
|
|
9303
10194
|
}
|
|
@@ -9342,9 +10233,41 @@ function summarizeResultUsage(result) {
|
|
|
9342
10233
|
}
|
|
9343
10234
|
return summary;
|
|
9344
10235
|
}
|
|
9345
|
-
function
|
|
10236
|
+
function isPromiseLike2(value) {
|
|
9346
10237
|
return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
|
|
9347
10238
|
}
|
|
10239
|
+
function resolveAgentLoggingSelection(value) {
|
|
10240
|
+
if (value === false) {
|
|
10241
|
+
return void 0;
|
|
10242
|
+
}
|
|
10243
|
+
if (value === void 0 || value === true) {
|
|
10244
|
+
return {
|
|
10245
|
+
mirrorToConsole: true
|
|
10246
|
+
};
|
|
10247
|
+
}
|
|
10248
|
+
return value;
|
|
10249
|
+
}
|
|
10250
|
+
function resolveWorkspaceDirForLogging(request) {
|
|
10251
|
+
const explicitSelection = request.filesystemTool ?? request.filesystem_tool;
|
|
10252
|
+
if (explicitSelection && typeof explicitSelection === "object" && !Array.isArray(explicitSelection)) {
|
|
10253
|
+
const cwd = explicitSelection.options?.cwd;
|
|
10254
|
+
if (typeof cwd === "string" && cwd.trim().length > 0) {
|
|
10255
|
+
return path7.resolve(cwd);
|
|
10256
|
+
}
|
|
10257
|
+
}
|
|
10258
|
+
return process.cwd();
|
|
10259
|
+
}
|
|
10260
|
+
function createRootAgentLoggingSession(request) {
|
|
10261
|
+
const selected = resolveAgentLoggingSelection(request.logging);
|
|
10262
|
+
if (!selected) {
|
|
10263
|
+
return void 0;
|
|
10264
|
+
}
|
|
10265
|
+
return createAgentLoggingSession({
|
|
10266
|
+
...selected,
|
|
10267
|
+
workspaceDir: typeof selected.workspaceDir === "string" && selected.workspaceDir.trim().length > 0 ? path7.resolve(selected.workspaceDir) : resolveWorkspaceDirForLogging(request),
|
|
10268
|
+
mirrorToConsole: selected.mirrorToConsole !== false
|
|
10269
|
+
});
|
|
10270
|
+
}
|
|
9348
10271
|
function isAgentTelemetrySink(value) {
|
|
9349
10272
|
return typeof value === "object" && value !== null && typeof value.emit === "function";
|
|
9350
10273
|
}
|
|
@@ -9375,7 +10298,7 @@ function createAgentTelemetrySession(telemetry) {
|
|
|
9375
10298
|
const emit = (event) => {
|
|
9376
10299
|
try {
|
|
9377
10300
|
const output = config.sink.emit(event);
|
|
9378
|
-
if (
|
|
10301
|
+
if (isPromiseLike2(output)) {
|
|
9379
10302
|
const task = Promise.resolve(output).then(() => void 0).catch(() => void 0);
|
|
9380
10303
|
trackPromise(task);
|
|
9381
10304
|
}
|
|
@@ -9406,7 +10329,7 @@ function createAgentTelemetryEmitter(params) {
|
|
|
9406
10329
|
}
|
|
9407
10330
|
params.session.emit({
|
|
9408
10331
|
...event,
|
|
9409
|
-
timestamp:
|
|
10332
|
+
timestamp: toIsoNow2(),
|
|
9410
10333
|
runId: params.runId,
|
|
9411
10334
|
...params.parentRunId ? { parentRunId: params.parentRunId } : {},
|
|
9412
10335
|
depth: params.depth,
|