omnirush 0.1.1
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/assets/collect-once.ts +126 -0
- package/assets/extensions/omnirush/auth.js +419 -0
- package/assets/extensions/omnirush/collector-lib.ts +873 -0
- package/assets/extensions/omnirush/collector.ts +100 -0
- package/assets/extensions/omnirush/commands.ts +34 -0
- package/assets/extensions/omnirush/index.ts +18 -0
- package/assets/extensions/omnirush/recorder.ts +38 -0
- package/assets/extensions/omnirush/refresh.ts +12 -0
- package/assets/extensions/omnirush/sota-lib.ts +62 -0
- package/assets/extensions/omnirush/sota.ts +221 -0
- package/assets/extensions/omnirush/usage.ts +21 -0
- package/assets/models.json +21 -0
- package/package.json +35 -0
- package/scripts/package.sh +22 -0
- package/scripts/postinstall.js +78 -0
- package/src/bin.js +456 -0
- package/src/lib.js +179 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Omnirush workspace collection during agent sessions (pi extension).
|
|
2
|
+
//
|
|
3
|
+
// Owns the lifecycle: start snapshot on session start (pi session id,
|
|
4
|
+
// workspace root = session cwd), trace events per turn/tool/provider
|
|
5
|
+
// response, debounced change snapshots from the collector's watcher, and
|
|
6
|
+
// trace + end snapshots on session shutdown (the shutdown handler is
|
|
7
|
+
// awaited by pi, bounded below so a slow upload can't wedge exit).
|
|
8
|
+
//
|
|
9
|
+
// Silent no-op when there are no device credentials — collection only
|
|
10
|
+
// runs for signed-in omnirush identities.
|
|
11
|
+
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
|
|
14
|
+
import { gatewayUrlForOrigin, omniDir, resolveOrigin } from "./auth";
|
|
15
|
+
import { WorkspaceCollector } from "./collector-lib";
|
|
16
|
+
import { sharedRefresher } from "./refresh";
|
|
17
|
+
import { recordGatewayUsage } from "./usage";
|
|
18
|
+
|
|
19
|
+
const SHUTDOWN_UPLOAD_BUDGET_MS = 15_000;
|
|
20
|
+
|
|
21
|
+
/** pi session ids are UUIDs; still, never let a foreign format stall us. */
|
|
22
|
+
function sanitizeSessionId(raw: string): string {
|
|
23
|
+
const id = String(raw || "").replace(/[^A-Za-z0-9._:-]/g, "-").slice(0, 128);
|
|
24
|
+
return id.length >= 8 ? id : "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function (pi: any) {
|
|
28
|
+
const origin = resolveOrigin(process.env);
|
|
29
|
+
const accessToken =
|
|
30
|
+
(process.env.OMNIRUSH_ACCESS_TOKEN || process.env.OMNIRUSH_TOKEN || "").trim();
|
|
31
|
+
const collector = new WorkspaceCollector({
|
|
32
|
+
gatewayUrl: process.env.OMNIRUSH_GATEWAY_URL || gatewayUrlForOrigin(origin),
|
|
33
|
+
accessToken,
|
|
34
|
+
stateDir: omniDir(),
|
|
35
|
+
refresh: accessToken
|
|
36
|
+
? async (tokenUsed: string) => {
|
|
37
|
+
const refresher = sharedRefresher();
|
|
38
|
+
const ok = await refresher.refresh(tokenUsed).catch(() => false);
|
|
39
|
+
return ok ? refresher.auth?.accessToken ?? null : null;
|
|
40
|
+
}
|
|
41
|
+
: undefined,
|
|
42
|
+
log: (level, message) => {
|
|
43
|
+
if (level === "warn") {
|
|
44
|
+
try {
|
|
45
|
+
process.stderr.write(`omnirush collector: ${message}\n`);
|
|
46
|
+
} catch {
|
|
47
|
+
/* stderr may be closed late in shutdown */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (!collector.enabled) return;
|
|
53
|
+
|
|
54
|
+
let sessionId = "";
|
|
55
|
+
|
|
56
|
+
pi.on("session_start", (event: any, ctx: any) => {
|
|
57
|
+
sessionId = sanitizeSessionId(ctx?.sessionManager?.getSessionId?.() ?? "");
|
|
58
|
+
const root = ctx?.cwd || process.cwd();
|
|
59
|
+
if (!sessionId) return;
|
|
60
|
+
collector.startSession(sessionId, path.basename(root) || "workspace", root);
|
|
61
|
+
collector.recordTrace(sessionId, "session.start", { reason: event?.reason });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
pi.on("turn_start", (event: any) => {
|
|
65
|
+
collector.recordTrace(sessionId, "turn.start", { turn_index: event?.turnIndex });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
pi.on("turn_end", (event: any) => {
|
|
69
|
+
collector.recordTrace(sessionId, "turn.end", { turn_index: event?.turnIndex });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
pi.on("tool_execution_start", (event: any) => {
|
|
73
|
+
collector.recordTrace(sessionId, "tool.start", { tool: event?.toolName });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
pi.on("agent_end", () => {
|
|
77
|
+
collector.recordTrace(sessionId, "agent.end");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
pi.on("after_provider_response", (event: any) => {
|
|
81
|
+
const usage = recordGatewayUsage(event?.headers);
|
|
82
|
+
collector.recordTrace(sessionId, "provider.response", {
|
|
83
|
+
status: event?.status,
|
|
84
|
+
...(usage ? { omnirush: usage } : {}),
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
pi.on("session_shutdown", async () => {
|
|
89
|
+
if (!sessionId) return;
|
|
90
|
+
collector.finishSession(sessionId);
|
|
91
|
+
await Promise.race([
|
|
92
|
+
collector.stop(),
|
|
93
|
+
new Promise((resolve) => {
|
|
94
|
+
const timer = setTimeout(resolve, SHUTDOWN_UPLOAD_BUDGET_MS);
|
|
95
|
+
timer.unref?.();
|
|
96
|
+
}),
|
|
97
|
+
]);
|
|
98
|
+
sessionId = "";
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Omnirush commands: /grant (latest gateway grant/usage headers) and
|
|
3
|
+
* /verify (prove the served model is genuine — full implementation lands
|
|
4
|
+
* with the gateway's model-attestation endpoint).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { gatewayUsage } from "./usage";
|
|
9
|
+
|
|
10
|
+
export default function (pi: ExtensionAPI) {
|
|
11
|
+
pi.registerCommand("grant", {
|
|
12
|
+
description: "Show today's Omnirush token grant and usage",
|
|
13
|
+
handler: async (ctx: any) => {
|
|
14
|
+
const usage = gatewayUsage();
|
|
15
|
+
ctx.ui?.notify?.(
|
|
16
|
+
usage
|
|
17
|
+
? `Omnirush gateway usage (latest x-omnirush-* headers): ${JSON.stringify(usage)}`
|
|
18
|
+
: `Omnirush: no gateway usage seen yet. Provider: ${process.env.PI_PROVIDER ?? "omnirush"}, ` +
|
|
19
|
+
`model: ${process.env.PI_MODEL ?? "gpt-6-astra"}.`,
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
pi.registerCommand("verify", {
|
|
25
|
+
description: "Verify the model serving your requests is genuine GPT-6 Astra",
|
|
26
|
+
handler: async (ctx: any) => {
|
|
27
|
+
// M2: POST /omnirush/verify → signed attestation compared against
|
|
28
|
+
// observed model behavior. v0 placeholder.
|
|
29
|
+
ctx.ui?.notify?.(
|
|
30
|
+
"verify: model attestation arrives with the gateway lane (M2).",
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Omnirush extension entry point.
|
|
2
|
+
//
|
|
3
|
+
// pi discovers either extensions/*.ts files or extensions/<dir>/index.ts —
|
|
4
|
+
// this index makes the whole omnirush extension directory load as one
|
|
5
|
+
// extension (without it, none of these files were ever registered).
|
|
6
|
+
|
|
7
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import collector from "./collector";
|
|
9
|
+
import commands from "./commands";
|
|
10
|
+
import recorder from "./recorder";
|
|
11
|
+
import sota from "./sota";
|
|
12
|
+
|
|
13
|
+
export default function (pi: ExtensionAPI) {
|
|
14
|
+
commands(pi as any);
|
|
15
|
+
recorder(pi as any);
|
|
16
|
+
sota(pi as any);
|
|
17
|
+
collector(pi as any);
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Omnirush recorder indicator.
|
|
3
|
+
*
|
|
4
|
+
* Displays a persistent "REC" marker in the TUI header while a session is
|
|
5
|
+
* active. Omnirush captures agent traces + workspace data for consenting
|
|
6
|
+
* users; this indicator keeps that visible at all times (trust moat).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
export default function (pi: ExtensionAPI) {
|
|
12
|
+
let recording = false;
|
|
13
|
+
|
|
14
|
+
const show = () => {
|
|
15
|
+
try {
|
|
16
|
+
// ctx-level UI may not be available in every host mode; fail silent.
|
|
17
|
+
(pi as any).ctx?.ui?.setHeader?.((theme: any) => {
|
|
18
|
+
const rec = theme?.fg ? theme.fg("error", "● REC") : "● REC";
|
|
19
|
+
const name = theme?.fg ? theme.fg("accent", " omnirush") : " omnirush";
|
|
20
|
+
return [`${rec}${name}`];
|
|
21
|
+
});
|
|
22
|
+
} catch {
|
|
23
|
+
/* non-TUI host */
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
pi.on("session_start", () => {
|
|
28
|
+
recording = true;
|
|
29
|
+
show();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
pi.on("agent_start", () => {
|
|
33
|
+
if (!recording) {
|
|
34
|
+
recording = true;
|
|
35
|
+
show();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Process-wide single-flight refresher shared by the sota guard and the
|
|
2
|
+
// collector (both run inside the pi process). Lazily created from the
|
|
3
|
+
// environment; refreshes rotate both tokens back to the state dir (0600).
|
|
4
|
+
|
|
5
|
+
import { createRefresher, omniDir, resolveOrigin } from "./auth";
|
|
6
|
+
|
|
7
|
+
let instance: ReturnType<typeof createRefresher> | null = null;
|
|
8
|
+
|
|
9
|
+
export function sharedRefresher() {
|
|
10
|
+
instance ??= createRefresher({ dir: omniDir(), origin: resolveOrigin(process.env) });
|
|
11
|
+
return instance;
|
|
12
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Pure helpers for the Omnirush SOTA guard (charter rule 1: SOTA models
|
|
2
|
+
// only). Loaded by the sota extension and by node:test via jiti — keep
|
|
3
|
+
// this module free of imports and side effects.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract the served model from an OpenAI Responses SSE `data:` payload.
|
|
7
|
+
* Returns undefined for anything that is not a response.created /
|
|
8
|
+
* response.completed frame carrying a non-empty model string.
|
|
9
|
+
*/
|
|
10
|
+
export function servedModelFromSseData(data: string): string | undefined {
|
|
11
|
+
if (!data || data === "[DONE]") return undefined;
|
|
12
|
+
let obj: any;
|
|
13
|
+
try {
|
|
14
|
+
obj = JSON.parse(data);
|
|
15
|
+
} catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (obj === null || typeof obj !== "object") return undefined;
|
|
19
|
+
if (obj.type !== "response.created" && obj.type !== "response.completed") {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const model = obj.response?.model;
|
|
23
|
+
return typeof model === "string" && model.length > 0 ? model : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Feed SSE text chunks in; every complete `data:` line is handed to
|
|
28
|
+
* `onData` (payload only, whitespace trimmed). Handles events split
|
|
29
|
+
* across chunk boundaries.
|
|
30
|
+
*/
|
|
31
|
+
export function createSseDataScanner(
|
|
32
|
+
onData: (data: string) => void,
|
|
33
|
+
): (chunk: string) => void {
|
|
34
|
+
let buffer = "";
|
|
35
|
+
return (chunk: string) => {
|
|
36
|
+
buffer += chunk;
|
|
37
|
+
let idx: number;
|
|
38
|
+
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
39
|
+
const line = buffer.slice(0, idx).replace(/\r$/, "");
|
|
40
|
+
buffer = buffer.slice(idx + 1);
|
|
41
|
+
if (line.startsWith("data:")) onData(line.slice(5).trim());
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The exact user-facing warning line for a served-model mismatch.
|
|
48
|
+
*/
|
|
49
|
+
export function formatSotaWarning(expected: string, served: string): string {
|
|
50
|
+
return (
|
|
51
|
+
`OMNIRUSH SOTA VIOLATION: requested ${expected} but received ${served} — ` +
|
|
52
|
+
"this turn violates the SOTA-only guarantee"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Charter rule 1 check: did the gateway serve something other than the
|
|
58
|
+
* requested model?
|
|
59
|
+
*/
|
|
60
|
+
export function isSotaViolation(expected: string, served: string): boolean {
|
|
61
|
+
return served !== expected;
|
|
62
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Omnirush SOTA guard (charter rule 1: SOTA models only).
|
|
2
|
+
//
|
|
3
|
+
// pi reports the served model for anthropic/openai-completions APIs, but
|
|
4
|
+
// the openai-responses stream never surfaces it (pi-ai only extracts
|
|
5
|
+
// response.id from response.created). So this extension registers a
|
|
6
|
+
// native omnirush provider whose stream delegates to the built-in
|
|
7
|
+
// openai-responses implementation with a tapped `fetch`. The tap:
|
|
8
|
+
// - reads response.created / response.completed frames as they stream
|
|
9
|
+
// through, compares the served model against the requested one, and
|
|
10
|
+
// warns loudly on stderr; --strict-sota makes that fatal (exit 3).
|
|
11
|
+
// - on a 401 from the gateway, runs the single-flight device-token
|
|
12
|
+
// refresh (rotate both tokens, persisted 0600) and retries the
|
|
13
|
+
// request exactly once — the GUI gateway-broker pattern.
|
|
14
|
+
//
|
|
15
|
+
// Registered during the extension factory, so the provider is queued and
|
|
16
|
+
// becomes the composition base at runner init: every omnirush request is
|
|
17
|
+
// guarded, including the very first.
|
|
18
|
+
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import { openAIResponsesApi } from "@earendil-works/pi-ai";
|
|
21
|
+
import { sharedRefresher } from "./refresh";
|
|
22
|
+
import {
|
|
23
|
+
createSseDataScanner,
|
|
24
|
+
formatSotaWarning,
|
|
25
|
+
servedModelFromSseData,
|
|
26
|
+
} from "./sota-lib";
|
|
27
|
+
|
|
28
|
+
const PROVIDER_ID = "omnirush";
|
|
29
|
+
const GATEWAY_DEFAULT = "https://omnirush.ai/omnirush/v1";
|
|
30
|
+
const STRICT_EXIT_CODE = 3;
|
|
31
|
+
|
|
32
|
+
// Must mirror the models.json payload bin.js installs (src/lib.js
|
|
33
|
+
// modelsConfig) so provider composition keeps serving the same catalog.
|
|
34
|
+
// The `api` field is load-bearing: provider composition routes streams to
|
|
35
|
+
// this base only when the model's api matches one of base.getModels().
|
|
36
|
+
const CATALOG_MODEL = {
|
|
37
|
+
id: "gpt-6-astra",
|
|
38
|
+
name: "GPT-6 Astra",
|
|
39
|
+
api: "openai-responses",
|
|
40
|
+
reasoning: true,
|
|
41
|
+
input: ["text", "image"],
|
|
42
|
+
cost: { input: 10, output: 50, cacheRead: 2.5, cacheWrite: 12.5 },
|
|
43
|
+
contextWindow: 400000,
|
|
44
|
+
compat: { supportsMaxOutputTokens: false },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function warnStderr(line: string): void {
|
|
48
|
+
try {
|
|
49
|
+
fs.writeSync(2, `\n${line}\n`);
|
|
50
|
+
} catch {
|
|
51
|
+
// stderr may be closed late in shutdown; never crash the agent for a
|
|
52
|
+
// warning. The violation still influences the exit path below.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const debug = (line: string): void => {
|
|
57
|
+
if (process.env.OMNIRUSH_DEBUG === "1") warnStderr(`omnirush debug: ${line}`);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function reportViolation(strictSota: boolean, expected: string, served: string): void {
|
|
61
|
+
warnStderr(formatSotaWarning(expected, served));
|
|
62
|
+
if (strictSota) {
|
|
63
|
+
// Deliberately abrupt: in --print mode the non-zero exit is the
|
|
64
|
+
// contract (pipelines must not ship traces from a downgraded model);
|
|
65
|
+
// in the TUI refusing the turn is the honest behavior.
|
|
66
|
+
process.exit(STRICT_EXIT_CODE);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function bearerFromInit(init: any): string {
|
|
71
|
+
const headers = init?.headers;
|
|
72
|
+
try {
|
|
73
|
+
let authorization = "";
|
|
74
|
+
if (headers && typeof headers.get === "function") {
|
|
75
|
+
authorization = headers.get("authorization") ?? "";
|
|
76
|
+
} else if (Array.isArray(headers)) {
|
|
77
|
+
authorization =
|
|
78
|
+
headers.find(([name]: any) => String(name).toLowerCase() === "authorization")?.[1] ?? "";
|
|
79
|
+
} else if (headers && typeof headers === "object") {
|
|
80
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
81
|
+
if (name.toLowerCase() === "authorization") authorization = String(value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return authorization.startsWith("Bearer ") ? authorization.slice(7).trim() : "";
|
|
85
|
+
} catch {
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function withBearer(init: any, token: string): any {
|
|
91
|
+
const headers = new Headers();
|
|
92
|
+
const original = init?.headers;
|
|
93
|
+
try {
|
|
94
|
+
if (original) {
|
|
95
|
+
if (typeof original.forEach === "function" && !Array.isArray(original)) {
|
|
96
|
+
original.forEach((value: any, name: string) => headers.set(name, value));
|
|
97
|
+
} else if (Array.isArray(original)) {
|
|
98
|
+
for (const [name, value] of original) headers.set(String(name), String(value));
|
|
99
|
+
} else {
|
|
100
|
+
for (const [name, value] of Object.entries(original)) headers.set(name, String(value));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
/* fall through with just the auth header */
|
|
105
|
+
}
|
|
106
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
107
|
+
return { ...(init ?? {}), headers };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Wrap a fetch implementation so that:
|
|
112
|
+
* - 401 responses trigger one single-flight device-token refresh and a
|
|
113
|
+
* single retry with the rotated token (gateway-broker pattern), and
|
|
114
|
+
* - SSE response bodies stream through a pass-through tap that observes
|
|
115
|
+
* the served model. Bytes are forwarded unmodified and non-SSE
|
|
116
|
+
* responses (errors, JSON) are returned as-is.
|
|
117
|
+
*/
|
|
118
|
+
function tapFetch(
|
|
119
|
+
baseFetch: any,
|
|
120
|
+
expectedModelId: string,
|
|
121
|
+
onViolation: (expected: string, served: string) => void,
|
|
122
|
+
): any {
|
|
123
|
+
return async (input: any, init: any) => {
|
|
124
|
+
let response = await baseFetch(input, init);
|
|
125
|
+
debug(`provider fetch ${typeof input === "string" ? input : input?.url} -> ${response?.status}`);
|
|
126
|
+
if (response?.status === 401) {
|
|
127
|
+
const tokenUsed = bearerFromInit(init) || bearerFromInit(input);
|
|
128
|
+
debug(`401 seen; bearer present: ${Boolean(tokenUsed)}`);
|
|
129
|
+
if (tokenUsed) {
|
|
130
|
+
const refresher = sharedRefresher();
|
|
131
|
+
const refreshed = await refresher.refresh(tokenUsed).catch((error) => {
|
|
132
|
+
debug(`refresh threw: ${error?.message ?? error}`);
|
|
133
|
+
return false;
|
|
134
|
+
});
|
|
135
|
+
const next = refresher.auth?.accessToken;
|
|
136
|
+
debug(`refresh ok: ${refreshed}, next token present: ${Boolean(next)}`);
|
|
137
|
+
if (refreshed && next) {
|
|
138
|
+
response = await baseFetch(input, withBearer(init, next));
|
|
139
|
+
debug(`retry status: ${response?.status}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const contentType = String(response?.headers?.get?.("content-type") ?? "");
|
|
145
|
+
if (!response?.body || !contentType.includes("text/event-stream")) {
|
|
146
|
+
return response;
|
|
147
|
+
}
|
|
148
|
+
let reported = false;
|
|
149
|
+
const onData = (data: string) => {
|
|
150
|
+
if (reported) return;
|
|
151
|
+
const served = servedModelFromSseData(data);
|
|
152
|
+
if (served === undefined || served === expectedModelId) return;
|
|
153
|
+
reported = true;
|
|
154
|
+
onViolation(expectedModelId, served);
|
|
155
|
+
};
|
|
156
|
+
const scanner = createSseDataScanner(onData);
|
|
157
|
+
const decoder = new TextDecoder();
|
|
158
|
+
const passthrough = new TransformStream({
|
|
159
|
+
transform(chunk: any, controller: any) {
|
|
160
|
+
controller.enqueue(chunk);
|
|
161
|
+
scanner(decoder.decode(chunk, { stream: true }));
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
const body = response.body.pipeThrough(passthrough);
|
|
165
|
+
return new Response(body, {
|
|
166
|
+
status: response.status,
|
|
167
|
+
statusText: response.statusText,
|
|
168
|
+
headers: response.headers,
|
|
169
|
+
});
|
|
170
|
+
} catch {
|
|
171
|
+
// A tap failure must never break the request itself.
|
|
172
|
+
return response;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default function (pi: any) {
|
|
178
|
+
pi.registerFlag("strict-sota", {
|
|
179
|
+
description:
|
|
180
|
+
"Exit non-zero if the gateway serves a different model than the requested Omnirush SOTA model",
|
|
181
|
+
type: "boolean",
|
|
182
|
+
default: false,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const responses = openAIResponsesApi();
|
|
186
|
+
const strictSota = (): boolean => pi.getFlag("strict-sota") === true;
|
|
187
|
+
|
|
188
|
+
const streamWith =
|
|
189
|
+
(simple: boolean) =>
|
|
190
|
+
(model: any, context: any, options: any) => {
|
|
191
|
+
const baseFetch = options?.fetch ?? globalThis.fetch;
|
|
192
|
+
const tapped = {
|
|
193
|
+
...(options ?? {}),
|
|
194
|
+
fetch: tapFetch(baseFetch, model.id, (expected, served) =>
|
|
195
|
+
reportViolation(strictSota(), expected, served),
|
|
196
|
+
),
|
|
197
|
+
};
|
|
198
|
+
return simple
|
|
199
|
+
? responses.streamSimple(model, context, tapped)
|
|
200
|
+
: responses.stream(model, context, tapped);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
pi.registerProvider({
|
|
204
|
+
id: PROVIDER_ID,
|
|
205
|
+
name: "Omnirush",
|
|
206
|
+
baseUrl: process.env.OMNIRUSH_GATEWAY_URL || GATEWAY_DEFAULT,
|
|
207
|
+
auth: {
|
|
208
|
+
apiKey: {
|
|
209
|
+
name: "Omnirush token",
|
|
210
|
+
async resolve() {
|
|
211
|
+
const key = process.env.OMNIRUSH_TOKEN;
|
|
212
|
+
if (!key) return undefined;
|
|
213
|
+
return { auth: { apiKey: key }, source: "environment" };
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
getModels: () => [CATALOG_MODEL],
|
|
218
|
+
stream: streamWith(false),
|
|
219
|
+
streamSimple: streamWith(true),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Latest x-omnirush-* gateway headers (grant/usage info the manager
|
|
2
|
+
// attaches to /v1 responses). Kept for /grant and trace events; never
|
|
3
|
+
// contains credentials.
|
|
4
|
+
|
|
5
|
+
let latest: Record<string, string> | null = null;
|
|
6
|
+
|
|
7
|
+
export function recordGatewayUsage(headers: unknown): Record<string, string> | null {
|
|
8
|
+
if (!headers || typeof headers !== "object") return null;
|
|
9
|
+
const found: Record<string, string> = {};
|
|
10
|
+
for (const [name, value] of Object.entries(headers as Record<string, unknown>)) {
|
|
11
|
+
if (!name.toLowerCase().startsWith("x-omnirush-")) continue;
|
|
12
|
+
found[name] = String(value);
|
|
13
|
+
}
|
|
14
|
+
const hasAny = Object.keys(found).length > 0;
|
|
15
|
+
latest = hasAny ? found : latest;
|
|
16
|
+
return hasAny ? found : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function gatewayUsage(): Record<string, string> | null {
|
|
20
|
+
return latest;
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"providers": {
|
|
3
|
+
"omnirush": {
|
|
4
|
+
"name": "Omnirush",
|
|
5
|
+
"baseUrl": "https://omnirush.ai/omnirush/v1",
|
|
6
|
+
"api": "openai-responses",
|
|
7
|
+
"apiKey": "$OMNIRUSH_TOKEN",
|
|
8
|
+
"models": [
|
|
9
|
+
{
|
|
10
|
+
"id": "gpt-6-astra",
|
|
11
|
+
"name": "GPT-6 Astra",
|
|
12
|
+
"reasoning": true,
|
|
13
|
+
"input": ["text", "image"],
|
|
14
|
+
"cost": { "input": 10, "output": 50, "cacheRead": 2.5, "cacheWrite": 12.5 },
|
|
15
|
+
"contextWindow": 400000,
|
|
16
|
+
"compat": { "supportsMaxOutputTokens": false },
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "omnirush",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Omnirush \u2014 free daily tokens for the most powerful coding model on earth.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"omnirush": "src/bin.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --test",
|
|
15
|
+
"postinstall": "node scripts/postinstall.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@earendil-works/pi-coding-agent": "0.86.1"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/omnirush-ai/omnirush-cli.git"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"ai",
|
|
26
|
+
"coding-agent",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"files": [
|
|
30
|
+
"src",
|
|
31
|
+
"assets",
|
|
32
|
+
"scripts",
|
|
33
|
+
"README.md"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build a per-platform omnirush-cli package from this repo (local use).
|
|
3
|
+
# Usage: scripts/package.sh [platform] (linux-x64|darwin-aarch64|darwin-x64|windows-x64)
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
PLATFORM="${1:-linux-x64}"
|
|
6
|
+
STAGE="omnirush-cli-$PLATFORM"
|
|
7
|
+
rm -rf "$STAGE" && mkdir -p "$STAGE"
|
|
8
|
+
cp -r src assets package.json "$STAGE/"
|
|
9
|
+
mkdir -p "$STAGE/.runtime/bin"
|
|
10
|
+
if [ "$PLATFORM" = "linux-x64" ]; then
|
|
11
|
+
cp .runtime/bin/bun "$STAGE/.runtime/bin/bun"
|
|
12
|
+
elif [ "$PLATFORM" = "windows-x64" ]; then
|
|
13
|
+
cp ".runtime/bun-windows-x64/bin/bun.exe" "$STAGE/.runtime/bin/bun.exe"
|
|
14
|
+
else
|
|
15
|
+
cp ".runtime/bun-$PLATFORM/bin/bun" "$STAGE/.runtime/bin/bun"
|
|
16
|
+
fi
|
|
17
|
+
if [[ "$PLATFORM" == windows* ]]; then
|
|
18
|
+
zip -q -r "$STAGE.zip" "$STAGE"
|
|
19
|
+
else
|
|
20
|
+
tar -czf "$STAGE.tar.gz" "$STAGE"
|
|
21
|
+
fi
|
|
22
|
+
echo "built $STAGE archive"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Downloads the platform bun runtime (v1.4.2) on install so `omnirush`
|
|
2
|
+
// works immediately on linux / macOS / Windows.
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import https from "node:https";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const VERSION = "bun-v1.4.2";
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
12
|
+
const RUNTIME = path.join(ROOT, ".runtime", "bin");
|
|
13
|
+
|
|
14
|
+
function platform() {
|
|
15
|
+
const plat = process.platform; // linux | darwin | win32
|
|
16
|
+
const arch = process.arch === "arm64" ? "aarch64" : "x64";
|
|
17
|
+
if (plat === "win32") return "windows-x64";
|
|
18
|
+
return `${plat}-${arch}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function get(url, redirects = 0) {
|
|
22
|
+
if (redirects > 5) throw new Error("too many redirects");
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
https.get(url, (res) => {
|
|
25
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
26
|
+
res.resume();
|
|
27
|
+
return resolve(get(res.headers.location, redirects + 1));
|
|
28
|
+
}
|
|
29
|
+
if (res.statusCode !== 200) {
|
|
30
|
+
res.resume();
|
|
31
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
32
|
+
}
|
|
33
|
+
const chunks = [];
|
|
34
|
+
res.on("data", (c) => chunks.push(c));
|
|
35
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
36
|
+
res.on("error", reject);
|
|
37
|
+
}).on("error", reject);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractZip(buf, dest) {
|
|
42
|
+
// Zero-dependency zip extraction via the system unzip/tar when present,
|
|
43
|
+
// else fall back to PowerShell on Windows.
|
|
44
|
+
const tmp = path.join(dest, "omnirush-bun.zip");
|
|
45
|
+
fs.writeFileSync(tmp, buf);
|
|
46
|
+
const isWin = process.platform === "win32";
|
|
47
|
+
try {
|
|
48
|
+
if (isWin) {
|
|
49
|
+
execFileSync("powershell", ["-NoProfile", "-Command",
|
|
50
|
+
`Expand-Archive -Path '${tmp}' -DestinationPath '${dest}' -Force`]);
|
|
51
|
+
} else {
|
|
52
|
+
execFileSync("python3", ["-m", "zipfile", "-e", tmp, dest]);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
execFileSync("unzip", ["-o", "-q", tmp, "-d", dest]);
|
|
56
|
+
}
|
|
57
|
+
fs.rmSync(tmp, { force: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main() {
|
|
61
|
+
// Skip when bundled (GitHub release packages ship .runtime prebuilt).
|
|
62
|
+
const existing = path.join(RUNTIME, process.platform === "win32" ? "bun.exe" : "bun");
|
|
63
|
+
if (fs.existsSync(existing)) return;
|
|
64
|
+
const plat = platform();
|
|
65
|
+
const zipUrl = `https://github.com/oven-sh/bun/releases/download/${VERSION}/bun-${plat}.zip`;
|
|
66
|
+
process.stdout.write(`omnirush: downloading agent runtime (bun for ${plat})...\n`);
|
|
67
|
+
const buf = await get(zipUrl);
|
|
68
|
+
fs.mkdirSync(RUNTIME, { recursive: true });
|
|
69
|
+
extractZip(buf, ROOT);
|
|
70
|
+
const exe = process.platform === "win32" ? "bun.exe" : "bun";
|
|
71
|
+
const src = path.join(ROOT, `bun-${plat}`, exe);
|
|
72
|
+
fs.copyFileSync(src, existing);
|
|
73
|
+
if (process.platform !== "win32") fs.chmodSync(existing, 0o755);
|
|
74
|
+
fs.rmSync(path.join(ROOT, `bun-${plat}`), { recursive: true, force: true });
|
|
75
|
+
console.log("omnirush: runtime ready");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main().catch((e) => { console.error("omnirush postinstall:", e.message); process.exit(1); });
|