import type { StreamFn } from "@earendil-works/pi-agent-core"; import type { Context } from "@earendil-works/pi-ai"; import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry"; import { buildCopilotIdeHeaders, COPILOT_INTEGRATION_ID } from "openclaw/plugin-sdk/provider-auth"; import { applyAnthropicEphemeralCacheControlMarkers, streamWithPayloadPatch, } from "openclaw/plugin-sdk/provider-stream-shared"; import { rewriteCopilotResponsePayloadConnectionBoundIds } from "./connection-bound-ids.js"; type StreamOptions = Parameters[2]; function containsCopilotContentType(value: unknown, type: string): boolean { if (Array.isArray(value)) { return value.some((item) => containsCopilotContentType(item, type)); } if (!value || typeof value !== "object") { return false; } const entry = value as { type?: unknown; content?: unknown }; return entry.type === type || containsCopilotContentType(entry.content, type); } function inferCopilotInitiator(messages: Context["messages"]): "agent" | "user" { const last = messages[messages.length - 1]; if (!last) { return "user"; } if (last.role === "user" && containsCopilotContentType(last.content, "tool_result")) { return "agent"; } return last.role === "user" ? "user" : "agent"; } export function hasCopilotVisionInput(messages: Context["messages"]): boolean { return messages.some((message) => { if (message.role === "user" && Array.isArray(message.content)) { return message.content.some((item) => containsCopilotContentType(item, "image")); } if (message.role === "toolResult" && Array.isArray(message.content)) { return message.content.some((item) => containsCopilotContentType(item, "image")); } return false; }); } export function buildCopilotDynamicHeaders(params: { messages: Context["messages"]; hasImages: boolean; }): Record { return { ...buildCopilotIdeHeaders(), "Copilot-Integration-Id": COPILOT_INTEGRATION_ID, "Openai-Organization": "github-copilot", "x-initiator": inferCopilotInitiator(params.messages), ...(params.hasImages ? { "Copilot-Vision-Request": "true" } : {}), }; } function patchOnPayloadResult(result: unknown): unknown { if (result && typeof result === "object" && "then" in result) { return Promise.resolve(result).then((next) => { rewriteCopilotResponsePayloadConnectionBoundIds(next); return next; }); } rewriteCopilotResponsePayloadConnectionBoundIds(result); return result; } function buildCopilotRequestHeaders( context: Parameters[1], headers: Record | undefined, ): Record { return { ...buildCopilotDynamicHeaders({ messages: context.messages, hasImages: hasCopilotVisionInput(context.messages), }), ...headers, }; } export function wrapCopilotAnthropicStream( baseStreamFn: StreamFn | undefined, ): StreamFn | undefined { if (!baseStreamFn) { return undefined; } const underlying = baseStreamFn; return (model, context, options) => { if (model.provider !== "github-copilot" || model.api !== "anthropic-messages") { return underlying(model, context, options); } return streamWithPayloadPatch( underlying, model, context, { ...options, headers: buildCopilotRequestHeaders(context, options?.headers), }, applyAnthropicEphemeralCacheControlMarkers, ); }; } export function wrapCopilotOpenAIResponsesStream( baseStreamFn: StreamFn | undefined, ): StreamFn | undefined { if (!baseStreamFn) { return undefined; } const underlying = baseStreamFn; return (model, context, options) => { if (model.provider !== "github-copilot" || model.api !== "openai-responses") { return underlying(model, context, options); } const originalOnPayload = options?.onPayload; const wrappedOptions: StreamOptions = { ...options, headers: buildCopilotRequestHeaders(context, options?.headers), onPayload: (payload, payloadModel) => { rewriteCopilotResponsePayloadConnectionBoundIds(payload); return patchOnPayloadResult(originalOnPayload?.(payload, payloadModel)); }, }; return underlying(model, context, wrappedOptions); }; } export function wrapCopilotOpenAICompletionsStream( baseStreamFn: StreamFn | undefined, ): StreamFn | undefined { if (!baseStreamFn) { return undefined; } const underlying = baseStreamFn; return (model, context, options) => { if (model.provider !== "github-copilot" || model.api !== "openai-completions") { return underlying(model, context, options); } return underlying(model, context, { ...options, headers: buildCopilotRequestHeaders(context, options?.headers), }); }; } export function wrapCopilotProviderStream(ctx: ProviderWrapStreamFnContext): StreamFn | undefined { return wrapCopilotOpenAICompletionsStream( wrapCopilotOpenAIResponsesStream(wrapCopilotAnthropicStream(ctx.streamFn)), ); }