mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(openrouter): forward safe music request policy (#104835)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
co-authored by
Peter Steinberger
parent
e868a1dbd1
commit
1c7ad75966
@@ -297,6 +297,45 @@ describe("openrouter music generation provider", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("applies configured OpenRouter request policy without allowing private networks", async () => {
|
||||
const requestPolicy = {
|
||||
allowPrivateNetwork: true,
|
||||
headers: { "X-OpenRouter-Trace": "trace-1" },
|
||||
proxy: { mode: "env-proxy" as const },
|
||||
};
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: sseResponse([
|
||||
`data: ${JSON.stringify({ choices: [{ delta: { audio: { data: Buffer.from("wav").toString("base64") } } }] })}\n`,
|
||||
"data: [DONE]\n",
|
||||
]),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
|
||||
await buildOpenRouterMusicGenerationProvider().generateMusic({
|
||||
provider: "openrouter",
|
||||
model: "google/lyria-3-pro-preview",
|
||||
prompt: "policy soundtrack",
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
openrouter: {
|
||||
request: requestPolicy,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(resolveProviderHttpRequestConfigMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "openrouter",
|
||||
capability: "audio",
|
||||
allowPrivateNetwork: false,
|
||||
request: requestPolicy,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("times out stalled OpenRouter audio streams after headers", async () => {
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: stalledSseResponse(
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// OpenRouter transport tests cover real provider-http request policy enforcement.
|
||||
import { createServer, type Server } from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const resolveApiKeyForProviderMock = vi.hoisted(() =>
|
||||
vi.fn(async () => ({
|
||||
apiKey: "test",
|
||||
source: "profile",
|
||||
mode: "api-key",
|
||||
})),
|
||||
);
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
|
||||
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
|
||||
}));
|
||||
|
||||
async function buildTransportProofProvider() {
|
||||
vi.resetModules();
|
||||
vi.doUnmock("openclaw/plugin-sdk/provider-http");
|
||||
vi.doMock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
|
||||
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
|
||||
}));
|
||||
const { buildOpenRouterMusicGenerationProvider } = await import("./music-generation-provider.js");
|
||||
return buildOpenRouterMusicGenerationProvider();
|
||||
}
|
||||
|
||||
const openServers: Server[] = [];
|
||||
|
||||
async function startPrivateMusicServer(): Promise<{
|
||||
baseUrl: string;
|
||||
requestCount: () => number;
|
||||
}> {
|
||||
let requests = 0;
|
||||
const server = createServer((_request, response) => {
|
||||
requests += 1;
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
});
|
||||
openServers.push(server);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.once("error", reject);
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
server.off("error", reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const address = server.address() as AddressInfo;
|
||||
return {
|
||||
baseUrl: `http://127.0.0.1:${address.port}/api/v1`,
|
||||
requestCount: () => requests,
|
||||
};
|
||||
}
|
||||
|
||||
async function closeOpenServers() {
|
||||
await Promise.all(
|
||||
openServers.splice(0).map(
|
||||
(server) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
describe("openrouter music generation provider transport", () => {
|
||||
afterEach(async () => {
|
||||
await closeOpenServers();
|
||||
});
|
||||
|
||||
it("denies private destinations even when the configured request policy opts in", async () => {
|
||||
const server = await startPrivateMusicServer();
|
||||
|
||||
const provider = await buildTransportProofProvider();
|
||||
await expect(
|
||||
provider.generateMusic({
|
||||
provider: "openrouter",
|
||||
model: "google/lyria-3-pro-preview",
|
||||
prompt: "transport denial proof",
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
openrouter: {
|
||||
baseUrl: server.baseUrl,
|
||||
request: {
|
||||
allowPrivateNetwork: true,
|
||||
headers: { "X-OpenRouter-Trace": "transport-proof" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
}),
|
||||
).rejects.toThrow(/private|internal|blocked/i);
|
||||
expect(server.requestCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
postJsonRequest,
|
||||
resolveProviderHttpRequestConfig,
|
||||
resolveProviderOperationTimeoutMs,
|
||||
sanitizeConfiguredModelProviderRequest,
|
||||
type ProviderOperationDeadline,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -393,6 +394,9 @@ export function buildOpenRouterMusicGenerationProvider(): MusicGenerationProvide
|
||||
"HTTP-Referer": "https://openclaw.ai",
|
||||
"X-OpenRouter-Title": "OpenClaw",
|
||||
},
|
||||
request: sanitizeConfiguredModelProviderRequest(
|
||||
req.cfg?.models?.providers?.openrouter?.request,
|
||||
),
|
||||
provider: "openrouter",
|
||||
capability: "audio",
|
||||
transport: "http",
|
||||
|
||||
Reference in New Issue
Block a user