auto-model-router 0.4.7 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.omp-plugin/marketplace.json +2 -2
- package/README.md +45 -14
- package/opencode-plugin/auto-model-router.ts +151 -0
- package/opencode-plugin/opencode-plugin.d.ts +39 -0
- package/package.json +1 -1
- package/src/server/http.ts +22 -7
- package/src/util/sse.ts +1 -1
- package/src/wire/openai/responses.ts +365 -0
- package/src/wire/openai/sink.ts +19 -2
- package/src/wire/types.ts +1 -1
- package/test/fixtures/harness/aider.json +47 -0
- package/test/fixtures/harness/codex-responses.json +507 -0
- package/test/fixtures/harness/opencode.json +338 -0
- package/test/harness-requests.test.ts +55 -24
- package/test/wire-responses.test.ts +174 -0
- package/test/wire-sink.test.ts +5 -0
- package/tools/capture-proxy.ts +79 -0
- package/tsconfig.all.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Request-capture proxy for harness fixtures.
|
|
4
|
+
*
|
|
5
|
+
* bun tools/capture-proxy.ts --listen 8799 --upstream http://127.0.0.1:8788 --out test/fixtures/harness --name codex
|
|
6
|
+
*
|
|
7
|
+
* Point a harness at http://127.0.0.1:8799/v1, run one turn, and every
|
|
8
|
+
* POST /v1/chat/completions body it sent is written to
|
|
9
|
+
* `<out>/<name>-<n>.json` with the request headers that matter (harness,
|
|
10
|
+
* session, subagent, content-type, user-agent) beside it. Everything is
|
|
11
|
+
* forwarded to the real router unchanged, streaming included, so the turn
|
|
12
|
+
* completes normally. Authorization headers are never written.
|
|
13
|
+
*
|
|
14
|
+
* The saved bodies are what test/harness-requests.test.ts parses: a harness
|
|
15
|
+
* release that changes its request shape then shows up as a failing test
|
|
16
|
+
* rather than a user report. Re-run only to refresh a harness's fixture.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { mkdirSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
|
|
22
|
+
const argv = process.argv.slice(2);
|
|
23
|
+
const flag = (name: string, fallback: string): string => {
|
|
24
|
+
const i = argv.indexOf(name);
|
|
25
|
+
return i >= 0 && argv[i + 1] !== undefined ? argv[i + 1]! : fallback;
|
|
26
|
+
};
|
|
27
|
+
const listen = Number.parseInt(flag("--listen", "8799"), 10);
|
|
28
|
+
const upstream = flag("--upstream", "http://127.0.0.1:8788").replace(/\/$/, "");
|
|
29
|
+
const out = flag("--out", "test/fixtures/harness");
|
|
30
|
+
const name = flag("--name", "harness");
|
|
31
|
+
mkdirSync(out, { recursive: true });
|
|
32
|
+
|
|
33
|
+
const KEEP_HEADERS = ["content-type", "user-agent", "x-omp-harness", "x-omp-session", "x-omp-subagent", "x-title", "http-referer"];
|
|
34
|
+
let n = 0;
|
|
35
|
+
|
|
36
|
+
/** Large text is not what the fixture guards; cap message text so files stay small. */
|
|
37
|
+
function trim(body: unknown): unknown {
|
|
38
|
+
if (typeof body === "string") return body.length > 400 ? `${body.slice(0, 400)}…[${body.length} chars]` : body;
|
|
39
|
+
if (Array.isArray(body)) return body.map(trim);
|
|
40
|
+
if (body !== null && typeof body === "object") return Object.fromEntries(Object.entries(body as Record<string, unknown>).map(([k, v]) => [k, trim(v)]));
|
|
41
|
+
return body;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
Bun.serve({
|
|
45
|
+
port: listen,
|
|
46
|
+
hostname: "127.0.0.1",
|
|
47
|
+
idleTimeout: 255,
|
|
48
|
+
async fetch(req) {
|
|
49
|
+
const url = new URL(req.url);
|
|
50
|
+
const raw = req.method === "POST" ? await req.text() : "";
|
|
51
|
+
if (req.method === "POST" && (url.pathname.endsWith("/chat/completions") || url.pathname.endsWith("/responses") || url.pathname.endsWith("/messages"))) {
|
|
52
|
+
n += 1;
|
|
53
|
+
let body: unknown = raw;
|
|
54
|
+
try {
|
|
55
|
+
body = JSON.parse(raw);
|
|
56
|
+
} catch {
|
|
57
|
+
// Not JSON: keep the raw text.
|
|
58
|
+
}
|
|
59
|
+
const headers: Record<string, string> = {};
|
|
60
|
+
for (const h of KEEP_HEADERS) {
|
|
61
|
+
const v = req.headers.get(h);
|
|
62
|
+
if (v !== null) headers[h] = v;
|
|
63
|
+
}
|
|
64
|
+
const file = join(out, `${name}-${n}.json`);
|
|
65
|
+
await Bun.write(file, JSON.stringify({ harness: name, capturedAtMs: Date.now(), headers, body: trim(body) }, null, 1));
|
|
66
|
+
console.log(`captured ${file} (${raw.length} bytes)`);
|
|
67
|
+
}
|
|
68
|
+
const fwd = new Headers(req.headers);
|
|
69
|
+
fwd.delete("host");
|
|
70
|
+
fwd.delete("content-length");
|
|
71
|
+
const res = await fetch(upstream + url.pathname + url.search, {
|
|
72
|
+
method: req.method,
|
|
73
|
+
headers: fwd,
|
|
74
|
+
...(req.method === "POST" ? { body: raw } : {}),
|
|
75
|
+
});
|
|
76
|
+
return new Response(res.body, { status: res.status, headers: res.headers });
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
console.log(`capture proxy on http://127.0.0.1:${listen} → ${upstream}; writing ${out}/${name}-<n>.json`);
|
package/tsconfig.all.json
CHANGED