@closedrouter/cli 1.1.6 → 1.1.7
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/README.md +10 -0
- package/dist/bridge/launch-proxy.d.ts +7 -0
- package/dist/bridge/launch-proxy.d.ts.map +1 -0
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/index.js +85 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -193,3 +193,13 @@ print(response.choices[0].message.content)
|
|
|
193
193
|
## License
|
|
194
194
|
|
|
195
195
|
MIT © ClosedRouter Contributors
|
|
196
|
+
|
|
197
|
+
### Harness telemetry
|
|
198
|
+
|
|
199
|
+
`cr start` attributes each model request to the launched client and a fresh launch UUID. A streaming loopback relay adds `X-ClosedRouter-Harness` and `X-ClosedRouter-Launch-ID` for all supported clients, including clients without custom-header settings. Keep the launcher running for the duration of the client session. Re-run `cr start` to create a new session.
|
|
200
|
+
|
|
201
|
+
Activity shows per-harness requests, tokens, cached tokens, reasoning tokens, latency, errors, and launch counts for the selected period. Search request logs by harness or launch UUID; open a request to inspect its attribution and routing attempts. The relay does not retain request or response bodies, keys, arguments, project paths, or hostnames. These client-supplied labels are diagnostic metadata, not an authorization boundary.
|
|
202
|
+
|
|
203
|
+
Other integrations may set the same headers directly. Known client user agents provide fallback attribution; unrecognized clients and older logs appear as `unknown`. Playground requests are tagged `playground`.
|
|
204
|
+
|
|
205
|
+
Request inspection also reports streaming mode, tools offered, tool-result messages supplied, and total message count. These counts describe the request, not which tools the client actually executed. Tool names, arguments, outputs, and message contents are not saved.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Streaming transport relay: inject attribution for clients with no custom-header setting. */
|
|
2
|
+
export declare function startLaunchProxy(gatewayUrl: string, apiKey: string, harness: string): Promise<{
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
launchId: `${string}-${string}-${string}-${string}-${string}`;
|
|
5
|
+
close: () => Promise<void>;
|
|
6
|
+
}>;
|
|
7
|
+
//# sourceMappingURL=launch-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch-proxy.d.ts","sourceRoot":"","sources":["../../src/bridge/launch-proxy.ts"],"names":[],"mappings":"AAMA,+FAA+F;AAC/F,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;GAqDzF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC;AACrF,KAAK,OAAO,GAAG,SAAS,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzF;AA8BD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAM,EAAO,EACvB,OAAO,SAAY,EACnB,cAAc,CAAC,EAAE,MAAM,EACvB,UAAU,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACxD,UAAU,CAiHZ;AAmBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAKzF;AAiED,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDnF"}
|
package/dist/index.js
CHANGED
|
@@ -8649,17 +8649,92 @@ import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync a
|
|
|
8649
8649
|
import { homedir as homedir9 } from "node:os";
|
|
8650
8650
|
import { dirname, join as join9 } from "node:path";
|
|
8651
8651
|
|
|
8652
|
-
// src/
|
|
8653
|
-
import {
|
|
8654
|
-
import { request as httpRequest } from "node:http";
|
|
8652
|
+
// src/bridge/launch-proxy.ts
|
|
8653
|
+
import { randomUUID } from "node:crypto";
|
|
8654
|
+
import { createServer as createServer2, request as httpRequest } from "node:http";
|
|
8655
8655
|
import { request as httpsRequest } from "node:https";
|
|
8656
|
+
var HOP_HEADERS = new Set(["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host"]);
|
|
8657
|
+
async function startLaunchProxy(gatewayUrl, apiKey, harness) {
|
|
8658
|
+
const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
|
|
8659
|
+
if (!["http:", "https:"].includes(upstream.protocol) || upstream.username || upstream.password) {
|
|
8660
|
+
throw new Error("ClosedRouter server URL must use HTTP or HTTPS without embedded credentials.");
|
|
8661
|
+
}
|
|
8662
|
+
const launchId = randomUUID();
|
|
8663
|
+
const transport = upstream.protocol === "https:" ? httpsRequest : httpRequest;
|
|
8664
|
+
const server = createServer2((incoming, outgoing) => {
|
|
8665
|
+
const authorized = incoming.headers.authorization === `Bearer ${apiKey}` || incoming.headers["x-api-key"] === apiKey || incoming.headers["x-goog-api-key"] === apiKey;
|
|
8666
|
+
if (!authorized) {
|
|
8667
|
+
outgoing.writeHead(401).end();
|
|
8668
|
+
return;
|
|
8669
|
+
}
|
|
8670
|
+
if (!incoming.url?.startsWith("/") || incoming.url.startsWith("//")) {
|
|
8671
|
+
outgoing.writeHead(400).end();
|
|
8672
|
+
return;
|
|
8673
|
+
}
|
|
8674
|
+
const target = new URL(upstream);
|
|
8675
|
+
const local = new URL(incoming.url, "http://localhost");
|
|
8676
|
+
target.pathname = upstream.pathname.replace(/\/$/, "") + local.pathname;
|
|
8677
|
+
target.search = local.search;
|
|
8678
|
+
const headers = { ...incoming.headers };
|
|
8679
|
+
const connectionHeaders = String(headers.connection || "").split(",").map((x) => x.trim().toLowerCase());
|
|
8680
|
+
for (const key of Object.keys(headers)) {
|
|
8681
|
+
if (HOP_HEADERS.has(key) || connectionHeaders.includes(key) || key.startsWith("x-closedrouter-harness") || key === "x-closedrouter-launch-id")
|
|
8682
|
+
delete headers[key];
|
|
8683
|
+
}
|
|
8684
|
+
headers["x-closedrouter-harness"] = harness;
|
|
8685
|
+
headers["x-closedrouter-launch-id"] = launchId;
|
|
8686
|
+
const request = transport(target, { method: incoming.method, headers }, (response) => {
|
|
8687
|
+
const responseHeaders = { ...response.headers };
|
|
8688
|
+
for (const key of HOP_HEADERS)
|
|
8689
|
+
delete responseHeaders[key];
|
|
8690
|
+
outgoing.writeHead(response.statusCode || 502, responseHeaders);
|
|
8691
|
+
response.on("error", () => outgoing.destroy());
|
|
8692
|
+
response.pipe(outgoing);
|
|
8693
|
+
});
|
|
8694
|
+
request.on("error", () => {
|
|
8695
|
+
if (!outgoing.headersSent)
|
|
8696
|
+
outgoing.writeHead(502).end(JSON.stringify({ error: { message: "ClosedRouter connection failed." } }));
|
|
8697
|
+
else
|
|
8698
|
+
outgoing.destroy();
|
|
8699
|
+
});
|
|
8700
|
+
incoming.on("aborted", () => request.destroy());
|
|
8701
|
+
outgoing.on("close", () => {
|
|
8702
|
+
if (!outgoing.writableFinished)
|
|
8703
|
+
request.destroy();
|
|
8704
|
+
});
|
|
8705
|
+
incoming.pipe(request);
|
|
8706
|
+
});
|
|
8707
|
+
await new Promise((resolve, reject) => {
|
|
8708
|
+
server.once("error", reject);
|
|
8709
|
+
server.listen(0, "127.0.0.1", () => {
|
|
8710
|
+
server.off("error", reject);
|
|
8711
|
+
resolve();
|
|
8712
|
+
});
|
|
8713
|
+
});
|
|
8714
|
+
const address = server.address();
|
|
8715
|
+
if (!address || typeof address === "string")
|
|
8716
|
+
throw new Error("Could not bind the launch relay.");
|
|
8717
|
+
return {
|
|
8718
|
+
baseUrl: `http://127.0.0.1:${address.port}`,
|
|
8719
|
+
launchId,
|
|
8720
|
+
close: () => new Promise((resolve, reject) => {
|
|
8721
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
8722
|
+
server.closeAllConnections();
|
|
8723
|
+
})
|
|
8724
|
+
};
|
|
8725
|
+
}
|
|
8726
|
+
|
|
8727
|
+
// src/commands/agy-proxy.ts
|
|
8728
|
+
import { createServer as createServer3 } from "node:http";
|
|
8729
|
+
import { request as httpRequest2 } from "node:http";
|
|
8730
|
+
import { request as httpsRequest2 } from "node:https";
|
|
8656
8731
|
async function startAgyModelProxy(gatewayUrl, apiKey, model) {
|
|
8657
8732
|
const upstream = new URL(gatewayUrl.replace(/\/+$/, "") + "/");
|
|
8658
8733
|
if (!["https:", "http:"].includes(upstream.protocol)) {
|
|
8659
8734
|
throw new Error("ClosedRouter server URL must use HTTP or HTTPS.");
|
|
8660
8735
|
}
|
|
8661
|
-
const requestUpstream = upstream.protocol === "https:" ?
|
|
8662
|
-
const server =
|
|
8736
|
+
const requestUpstream = upstream.protocol === "https:" ? httpsRequest2 : httpRequest2;
|
|
8737
|
+
const server = createServer3((incoming, outgoing) => {
|
|
8663
8738
|
if (incoming.headers["x-goog-api-key"] !== apiKey) {
|
|
8664
8739
|
outgoing.writeHead(401).end();
|
|
8665
8740
|
return;
|
|
@@ -8949,9 +9024,11 @@ async function startCommand(options = {}) {
|
|
|
8949
9024
|
throw new Error("Antigravity did not report any Gemini API models. Update `agy` and select Gemini API key mode in its settings.");
|
|
8950
9025
|
}
|
|
8951
9026
|
const recognizedModel = supportedAgyModels ? supportedAgyModels.has("gemini-3.8-flash") ? "gemini-3.8-flash" : [...supportedAgyModels][0] : undefined;
|
|
8952
|
-
const
|
|
9027
|
+
const relay = await startLaunchProxy(serverUrl, apiKey, tool);
|
|
9028
|
+
let agyProxy;
|
|
8953
9029
|
try {
|
|
8954
|
-
|
|
9030
|
+
agyProxy = tool === "agy" ? await startAgyModelProxy(relay.baseUrl, apiKey, model) : undefined;
|
|
9031
|
+
const plan = buildLaunchPlan(tool, relay.baseUrl, apiKey, model, options.toolArgs, homedir9(), options.executablePath, agyProxy && recognizedModel ? { baseUrl: agyProxy.baseUrl, recognizedModel } : undefined);
|
|
8955
9032
|
if (plan.configFile)
|
|
8956
9033
|
writeOwnedConfig(plan.configFile);
|
|
8957
9034
|
M2.info(`${import_picocolors12.default.green("Starting")} ${import_picocolors12.default.bold(TOOL_OPTIONS.find((option) => option.value === tool)?.label || tool)} with ${import_picocolors12.default.cyan(model)} through ${import_picocolors12.default.dim(serverUrl)}`);
|
|
@@ -8959,6 +9036,7 @@ async function startCommand(options = {}) {
|
|
|
8959
9036
|
process.exitCode = exitCode;
|
|
8960
9037
|
} finally {
|
|
8961
9038
|
await agyProxy?.close();
|
|
9039
|
+
await relay.close();
|
|
8962
9040
|
}
|
|
8963
9041
|
}
|
|
8964
9042
|
|
package/package.json
CHANGED