mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:30:43 +00:00
fix(agents): route /btw through the provider's stream fn so Ollama URLs build correctly
This commit is contained in:
committed by
Peter Steinberger
parent
da8621df0d
commit
bc3c8eee2c
@@ -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.");
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user