Skip to content

Commit 1484e4e

Browse files
author
hailory
committed
[AI] improve(openai): typeof guard, reasoningTagTextPartitioner for refusal routing, add packages/ai test
1 parent 85d0d3e commit 1484e4e

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

src/agents/openai-transport-stream.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3148,9 +3148,17 @@ async function processOpenAICompletionsStream(
31483148
appendRoutedContentDelta(contentDelta);
31493149
}
31503150
}
3151-
} else if ((choiceDelta as { refusal?: string }).refusal) {
3152-
// Safety or structured-output refusal surfaced as assistant visible text.
3153-
appendTextDelta((choiceDelta as { refusal?: string }).refusal!);
3151+
} else if (
3152+
!choiceDelta.content &&
3153+
typeof (choiceDelta as { refusal?: string }).refusal === "string"
3154+
) {
3155+
const refusalText = (choiceDelta as { refusal?: string }).refusal!;
3156+
const routedDeltas = hasMirroredReasoning
3157+
? reasoningTagTextPartitioner.push(refusalText)
3158+
: reasoningTagTextPartitioner.pushVisible(refusalText);
3159+
for (const routedDelta of routedDeltas) {
3160+
appendPartitionedVisibleDelta(routedDelta);
3161+
}
31543162
}
31553163
for (const reasoningDelta of reasoningDeltas) {
31563164
if (reasoningDelta.kind === "thinking" && !emitReasoning) {

src/llm/providers/openai-completions.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,28 @@ describe("OpenAI-compatible completions params", () => {
248248
expect(capturedPayload?.tools).toEqual([]);
249249
});
250250

251+
it("surfaces chat-completions refusal deltas as visible assistant text", async () => {
252+
mockChunksRef.chunks = [
253+
{
254+
id: "chatcmpl-test",
255+
choices: [
256+
{
257+
index: 0,
258+
delta: { role: "assistant", content: null, refusal: "I can't help with that." },
259+
finish_reason: "stop",
260+
},
261+
],
262+
},
263+
];
264+
265+
const result = await streamOpenAICompletions(model, context, {
266+
apiKey: "sk-test",
267+
}).result();
268+
269+
expect(result.content).toStrictEqual([{ type: "text", text: "I can't help with that." }]);
270+
expect(result.stopReason).toBe("stop");
271+
});
272+
251273
it("does not reread an unreadable tool inventory length", async () => {
252274
let capturedPayload: Record<string, unknown> | undefined;
253275
const tools = new Proxy([], {

src/llm/providers/openai-completions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,8 @@ export const streamOpenAICompletions: StreamFunction<
427427
choice.delta.content.length > 0
428428
) {
429429
appendPartitionedContent(choice.delta.content, Boolean(foundReasoningField));
430-
} else if ((choice.delta as { refusal?: string }).refusal) {
431-
appendPartitionedContent((choice.delta as { refusal?: string }).refusal!, false);
430+
} else if (typeof choice.delta.refusal === "string") {
431+
appendPartitionedContent(choice.delta.refusal, Boolean(foundReasoningField));
432432
}
433433

434434
if (choice?.delta?.tool_calls) {

0 commit comments

Comments
 (0)