diff --git a/core/index.d.ts b/core/index.d.ts
index b5b409ef929..a11f3730e57 100644
--- a/core/index.d.ts
+++ b/core/index.d.ts
@@ -397,6 +397,8 @@ export interface AssistantChatMessage {
content: MessageContent;
toolCalls?: ToolCallDelta[];
usage?: Usage;
+ opcRequestId?: string;
+ [key: string]: unknown;
}
export interface SystemChatMessage {
diff --git a/core/llm/index.ts b/core/llm/index.ts
index 54ea22243f9..b4fe3ff46e6 100644
--- a/core/llm/index.ts
+++ b/core/llm/index.ts
@@ -890,10 +890,18 @@ export abstract class BaseLLM implements ILLM {
options: LLMFullCompletionOptions = {},
) {
let completion = "";
+ let opcRequestId: string | undefined = undefined;
for await (const message of this.streamChat(messages, signal, options)) {
completion += renderChatMessage(message);
+ if (message.role === "assistant") {
+ opcRequestId = (message as import("../index").AssistantChatMessage).opcRequestId ?? opcRequestId;
+ }
}
- return { role: "assistant" as const, content: completion };
+ return {
+ role: "assistant" as const,
+ content: completion,
+ ...(opcRequestId ? { opcRequestId } : {}),
+ };
}
compileChatMessages(
diff --git a/core/llm/llms/oca/Oca.ts b/core/llm/llms/oca/Oca.ts
index d6a4307e96e..f28bddac0f2 100644
--- a/core/llm/llms/oca/Oca.ts
+++ b/core/llm/llms/oca/Oca.ts
@@ -16,7 +16,7 @@ import {
toChatBody,
} from "../../openaiTypeConverters.js";
import { OcaTokenManager } from "./util/ocaTokenManager.js";
-import { createOcaHeaders, generateOpcRequestId } from "./util/utils.js";
+import { createOcaHeaders } from "./util/utils.js";
interface ModelInfoMap {
models: { [key: string]: ModelInfo };
@@ -30,7 +30,6 @@ const parsePrice = (price: any) => {
};
class Oca extends BaseLLM {
private modelMap: ModelInfoMap;
-
constructor(options: LLMOptions) {
super({
...options,
@@ -48,8 +47,8 @@ class Oca extends BaseLLM {
static providerName = "oca";
protected useOpenAIAdapterFor: (LlmApiRequestType | "*")[] = [
- "list",
- "streamChat",
+ "list"
+ // "streamChat" removed so we use OCA's native streaming with custom fields
];
protected _convertModelName(model: string): string {
@@ -175,19 +174,43 @@ class Oca extends BaseLLM {
signal,
});
+ let responseOpcRequestId: string | undefined = undefined;
+ if (typeof response.headers?.get === "function") {
+ const raw = response.headers.get("opc-request-id");
+ console.log(
+ "[OPC DEBUG] raw response header opc-request-id:",
+ raw
+ );
+ responseOpcRequestId = raw !== null ? raw : undefined;
+ console.log(
+ "[OPC DEBUG] RESPONSE HEADER: opc-request-id (final value):",
+ responseOpcRequestId
+ );
+ }
+
// Handle non-streaming response
if (body.stream === false) {
if (response.status === 499) {
return; // Aborted by user
}
const data = await response.json();
- yield data.choices[0].message;
+ if (data.choices && data.choices[0] && data.choices[0].message) {
+ const msg = data.choices[0].message;
+ if (msg.role === "assistant") {
+ (msg as any).opcRequestId = responseOpcRequestId;
+ console.log("[OPC DEBUG] attached opcRequestId to yielded msg:", responseOpcRequestId);
+ }
+ console.log("[OPC DEBUG] yielding msg:", JSON.stringify(msg, null, 2));
+ yield msg;
+ }
return;
}
for await (const value of streamSse(response)) {
- const chunk = fromChatCompletionChunk(value);
+ const chunk = fromChatCompletionChunk(value, responseOpcRequestId);
if (chunk) {
+ console.log("[OPC DEBUG] yielding streamed chunk:", JSON.stringify(chunk, null, 2));
+ console.log("[OCA _streamChat] yielding chunk", JSON.stringify(chunk, null, 2));
yield chunk;
}
}
diff --git a/core/llm/openaiTypeConverters.ts b/core/llm/openaiTypeConverters.ts
index a24af02f7f0..92afed6521d 100644
--- a/core/llm/openaiTypeConverters.ts
+++ b/core/llm/openaiTypeConverters.ts
@@ -170,13 +170,15 @@ export function fromChatResponse(response: ChatCompletion): ChatMessage {
export function fromChatCompletionChunk(
chunk: ChatCompletionChunk,
+ opcRequestId?: string
): ChatMessage | undefined {
+ console.log("openaiTypeConverters : opcRequestId ", opcRequestId)
const delta = chunk.choices?.[0]?.delta;
-
if (delta?.content) {
return {
role: "assistant",
content: delta.content,
+ opcRequestId: opcRequestId
};
} else if (delta?.tool_calls) {
return {
@@ -190,6 +192,7 @@ export function fromChatCompletionChunk(
arguments: tool_call.function?.arguments,
},
})),
+ opcRequestId: opcRequestId
};
}
diff --git a/core/llm/streamChat.ts b/core/llm/streamChat.ts
index 2692195e226..264c644e139 100644
--- a/core/llm/streamChat.ts
+++ b/core/llm/streamChat.ts
@@ -105,10 +105,14 @@ export async function* llmStreamChat(
break;
}
if (next.value) {
- yield {
- role: "assistant",
- content: next.value,
- };
+ if (typeof next.value === "object" && next.value !== null) {
+ yield next.value;
+ } else {
+ yield {
+ role: "assistant",
+ content: typeof next.value === "string" ? next.value : String(next.value),
+ };
+ }
}
next = await gen.next();
}
@@ -137,7 +141,7 @@ export async function* llmStreamChat(
next = await gen.next();
}
if (config.experimental?.readResponseTTS && "completion" in next.value) {
- void TTS.read(next.value?.completion);
+ void TTS.read(typeof next.value?.completion === "string" ? next.value?.completion : String(next.value?.completion));
}
void Telemetry.capture(
diff --git a/extensions/vscode/src/webviewProtocol.ts b/extensions/vscode/src/webviewProtocol.ts
index 324219a516c..59ef09aa1ec 100644
--- a/extensions/vscode/src/webviewProtocol.ts
+++ b/extensions/vscode/src/webviewProtocol.ts
@@ -70,6 +70,8 @@ export class VsCodeWebviewProtocol
) {
let next = await response.next();
while (!next.done) {
+ console.log("webview_content", next)
+ console.log("[webviewProtocol] yielding chunk from extension", JSON.stringify(next.value, null, 2));
respond({
done: false,
content: next.value,
@@ -77,6 +79,7 @@ export class VsCodeWebviewProtocol
});
next = await response.next();
}
+ console.log("webview_content", next)
respond({
done: true,
content: next.value,
diff --git a/gui/src/components/StepContainer/ResponseActions.tsx b/gui/src/components/StepContainer/ResponseActions.tsx
index 0684327925c..1a3348d88f5 100644
--- a/gui/src/components/StepContainer/ResponseActions.tsx
+++ b/gui/src/components/StepContainer/ResponseActions.tsx
@@ -121,6 +121,27 @@ export default function ResponseActions({
checkIconClassName="h-3.5 w-3.5 text-success"
/>
+ {/* Show Copy button for Oracle opcRequestId if present */}
+ {item.message.role === "assistant" &&
+ !!(item.message as import("core").AssistantChatMessage).opcRequestId && (
+