diff --git a/README.md b/README.md index 489a7a7..6d952e1 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,18 @@ models: `command_code_working_directory` 决定 Command Code 能看到和操作的项目目录。`response_store_directory` 保存 `store: true` 的 Response;两个相对路径都以配置文件所在目录为基准。默认存储目录是隐藏目录 `.command-code-openai-bridge/responses`。`models..effort` 会传给 Command Code 的 `--effort`,Responses 请求中的 `reasoning.effort` 优先。可用 effort 由对应底层模型决定。服务强制只监听 `127.0.0.1`。客户端只能选择配置中的模型名,不能注入额外 CLI 参数。 +### Obsidian Copilot 流式输出 + +在 Copilot 中添加自定义模型时使用: + +- Provider:`3rd party (openai-format)` +- Base URL:`http://127.0.0.1:18000/v1` +- API Key:任意非空文本 +- CORS:关闭 +- Streaming:开启 + +Bridge 会直接响应浏览器 CORS 预检,包括 Copilot 和 OpenAI SDK 发送的自定义请求头及本机 Private Network Access。关闭 Copilot 的 CORS 绕过后,请求使用原生 `fetch`,可以读取 Bridge 返回的 SSE 流。 + ## 启动、停止与重启 ```bash diff --git a/config.yaml b/config.yaml index 94a59f2..fa515ee 100644 --- a/config.yaml +++ b/config.yaml @@ -12,7 +12,7 @@ dangerously_skip_permissions: true models: command-default: cli_model: deepseek/deepseek-v4-flash - effort: high + effort: max command-pro: cli_model: deepseek/deepseek-v4-pro effort: max diff --git a/src/server.ts b/src/server.ts index cbb26c1..e3ce95d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -33,10 +33,24 @@ export async function createServer(config: BridgeConfig): Promise }); const coordinator = new RequestCoordinator(); - app.addHook("onRequest", async (_request, reply) => { + app.addHook("onRequest", async (request, reply) => { + const requestedHeaders = request.headers["access-control-request-headers"]; + reply.raw.setHeader("Access-Control-Allow-Origin", "*"); - reply.raw.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - reply.raw.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); + reply.raw.setHeader( + "Access-Control-Allow-Headers", + requestedHeaders || "Content-Type, Authorization, dangerously-allow-browser", + ); + reply.raw.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"); + reply.raw.setHeader("Access-Control-Max-Age", "600"); + reply.raw.setHeader( + "Vary", + "Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network", + ); + + if (request.headers["access-control-request-private-network"] === "true") { + reply.raw.setHeader("Access-Control-Allow-Private-Network", "true"); + } }); app.options("/*", async (_request, reply) => reply.status(204).send());