fix(agents): route /btw through the provider's stream fn so Ollama URLs build correctly

This commit is contained in:
Subash
2026-04-23 06:25:22 +05:30
committed by Peter Steinberger
parent da8621df0d
commit bc3c8eee2c
2 changed files with 63 additions and 1 deletions

View File

@@ -17,6 +17,7 @@ const getActiveEmbeddedRunSnapshotMock = vi.fn();
const resolveSessionAgentIdMock = vi.fn();
const resolveAgentWorkspaceDirMock = vi.fn();
const prepareProviderRuntimeAuthMock = vi.fn();
const registerProviderStreamForModelMock = vi.fn();
const diagDebugMock = vi.fn();
vi.mock("@mariozechner/pi-ai", async () => {
@@ -71,6 +72,11 @@ vi.mock("../plugins/provider-runtime.js", () => ({
prepareProviderRuntimeAuth: (...args: unknown[]) => prepareProviderRuntimeAuthMock(...args),
}));
vi.mock("./provider-stream.js", () => ({
registerProviderStreamForModel: (...args: unknown[]) =>
registerProviderStreamForModelMock(...args),
}));
vi.mock("./auth-profiles/session-override.js", () => ({
resolveSessionAuthProfileOverride: (...args: unknown[]) =>
resolveSessionAuthProfileOverrideMock(...args),
@@ -275,6 +281,7 @@ describe("runBtwSideQuestion", () => {
resolveSessionAgentIdMock.mockReset();
resolveAgentWorkspaceDirMock.mockReset();
prepareProviderRuntimeAuthMock.mockReset();
registerProviderStreamForModelMock.mockReset();
diagDebugMock.mockReset();
buildSessionContextMock.mockReturnValue({
@@ -293,6 +300,7 @@ describe("runBtwSideQuestion", () => {
resolveSessionAgentIdMock.mockReturnValue("main");
resolveAgentWorkspaceDirMock.mockReturnValue("/tmp/workspace");
prepareProviderRuntimeAuthMock.mockResolvedValue(undefined);
registerProviderStreamForModelMock.mockReturnValue(undefined);
});
it("streams blocks without persisting BTW data to disk", async () => {
@@ -432,6 +440,48 @@ describe("runBtwSideQuestion", () => {
);
});
it("uses the provider's stream fn when registered so provider URL construction runs (#68336)", async () => {
// Regression: before this fix, /btw called streamSimple directly and
// bypassed the provider's createStreamFn/wrapStreamFn hooks. That caused
// Ollama Cloud (api: "openai-completions", baseUrl: "https://ollama.com/")
// to hit the marketing site instead of /v1/chat/completions.
resolveModelWithRegistryMock.mockReturnValue({
provider: "ollama",
id: "glm-5.1",
api: "openai-completions",
baseUrl: "https://ollama.com/",
});
const providerStreamFn = vi
.fn()
.mockReturnValue(makeAsyncEvents([createDoneEvent("Ollama Cloud answer.")]));
registerProviderStreamForModelMock.mockReturnValue(providerStreamFn);
const result = await runSideQuestion({ provider: "ollama", model: "glm-5.1" });
expect(result).toEqual({ text: "Ollama Cloud answer." });
expect(registerProviderStreamForModelMock).toHaveBeenCalledWith(
expect.objectContaining({
model: expect.objectContaining({
provider: "ollama",
api: "openai-completions",
baseUrl: "https://ollama.com/",
}),
}),
);
expect(providerStreamFn).toHaveBeenCalledTimes(1);
expect(streamSimpleMock).not.toHaveBeenCalled();
});
it("falls back to streamSimple when no provider stream fn is registered", async () => {
registerProviderStreamForModelMock.mockReturnValue(undefined);
mockDoneAnswer("Fallback answer.");
const result = await runSideQuestion();
expect(result).toEqual({ text: "Fallback answer." });
expect(streamSimpleMock).toHaveBeenCalledTimes(1);
});
it("strips injected empty tools arrays from BTW payloads before sending", async () => {
mockDoneAnswer("Final answer.");

View File

@@ -33,6 +33,7 @@ import { resolveModelWithRegistry } from "./pi-embedded-runner/model.js";
import { getActiveEmbeddedRunSnapshot } from "./pi-embedded-runner/runs.js";
import { streamWithPayloadPatch } from "./pi-embedded-runner/stream-payload-utils.js";
import { discoverAuthStorage, discoverModels } from "./pi-model-discovery.js";
import { registerProviderStreamForModel } from "./provider-stream.js";
import { stripToolResultDetails } from "./session-transcript-repair.js";
import { sanitizeImageBlocks } from "./tool-images.js";
@@ -432,6 +433,17 @@ export async function runBtwSideQuestion(
}
}
// Use the provider's own stream fn so providers like Ollama (which build
// `/api/chat` or `/v1/chat/completions` paths based on api mode) construct
// URLs correctly. Without this, streamSimple hits the provider's baseUrl
// directly and 404s on endpoints like Ollama Cloud (#68336).
const providerStreamFn = registerProviderStreamForModel({
model: runtimeModel,
cfg: params.cfg,
agentDir: params.agentDir,
env: process.env,
});
const chunker =
params.opts?.onBlockReply && params.blockReplyChunking
? new EmbeddedBlockChunker(params.blockReplyChunking)
@@ -459,7 +471,7 @@ export async function runBtwSideQuestion(
};
const stream = await streamWithPayloadPatch(
streamSimple,
providerStreamFn ?? streamSimple,
runtimeModel,
{
systemPrompt: buildBtwSystemPrompt(),