Skip to content

Commit d907fb6

Browse files
committed
🤖 Force truncation:auto on OpenAI responses
Wrap the OpenAI provider fetch to inject on every Responses API call. This ensures automatic conversation truncation works immediately, regardless of @ai-sdk/openai support. _Generated with _
1 parent 6168efc commit d907fb6

File tree

1 file changed

+64
-1
lines changed

1 file changed

+64
-1
lines changed

src/services/aiService.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,73 @@ export class AIService extends EventEmitter {
219219
? (providerConfig.fetch as typeof fetch)
220220
: defaultFetchWithUnlimitedTimeout;
221221

222+
// Wrap fetch to force truncation: "auto" for OpenAI Responses API calls.
223+
// This is a temporary override until @ai-sdk/openai supports passing
224+
// truncation via providerOptions. Safe because it only targets the
225+
// OpenAI Responses endpoint and leaves other providers untouched.
226+
const fetchWithOpenAITruncation = Object.assign(
227+
async (
228+
input: Parameters<typeof fetch>[0],
229+
init?: Parameters<typeof fetch>[1]
230+
): Promise<Response> => {
231+
try {
232+
const urlString = (() => {
233+
if (typeof input === "string") {
234+
return input;
235+
}
236+
if (input instanceof URL) {
237+
return input.toString();
238+
}
239+
if (typeof input === "object" && input !== null && "url" in input) {
240+
const possibleUrl = (input as { url?: unknown }).url;
241+
if (typeof possibleUrl === "string") {
242+
return possibleUrl;
243+
}
244+
}
245+
return "";
246+
})();
247+
248+
const method = (init?.method ?? "GET").toUpperCase();
249+
const isOpenAIResponses = /\/v1\/responses(\?|$)/.test(urlString);
250+
251+
const body = init?.body;
252+
if (isOpenAIResponses && method === "POST" && typeof body === "string") {
253+
// Clone headers to avoid mutating caller-provided objects
254+
const headers = new Headers(init?.headers);
255+
// Remove content-length if present, since body will change
256+
headers.delete("content-length");
257+
258+
try {
259+
const json = JSON.parse(body) as Record<string, unknown>;
260+
// Only set if not already present
261+
if (json.truncation === undefined) {
262+
json.truncation = "auto";
263+
}
264+
const newBody = JSON.stringify(json);
265+
const newInit: RequestInit = { ...init, headers, body: newBody };
266+
return fetchToUse(input, newInit);
267+
} catch {
268+
// If body isn't JSON, fall through to normal fetch
269+
return fetchToUse(input, init);
270+
}
271+
}
272+
273+
// Default passthrough
274+
return fetchToUse(input, init);
275+
} catch {
276+
// On any unexpected error, fall back to original fetch
277+
return fetchToUse(input, init);
278+
}
279+
},
280+
"preconnect" in fetchToUse && typeof (fetchToUse as typeof fetch).preconnect === "function"
281+
? { preconnect: (fetchToUse as typeof fetch).preconnect.bind(fetchToUse) }
282+
: {}
283+
);
284+
222285
const provider = createOpenAI({
223286
...providerConfig,
224287
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
225-
fetch: fetchToUse as any,
288+
fetch: fetchWithOpenAITruncation as any,
226289
});
227290
// Use Responses API for persistence and built-in tools
228291
const baseModel = provider.responses(modelId);

0 commit comments

Comments
 (0)