create-quorum-router 0.1.5 → 0.1.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/README.md +22 -2
- package/bin/create-quorum-router.js +25 -4
- package/package.json +1 -1
- package/templates/basic/README.md +72 -6
- package/templates/basic/deno.json +2 -0
- package/templates/basic/router.config.example.json +11 -0
- package/templates/basic/src/auth_session.ts +1 -0
- package/templates/basic/src/best_route.ts +10 -1
- package/templates/basic/src/calibration.ts +249 -0
- package/templates/basic/src/calibration_demo.ts +25 -0
- package/templates/basic/src/cli.ts +14 -2
- package/templates/basic/src/cost_aware.ts +150 -0
- package/templates/basic/src/provider_registry.ts +14 -7
- package/templates/basic/src/schema.ts +87 -0
- package/templates/basic/src/supabase.ts +462 -0
- package/templates/basic/src/trace.ts +3 -0
- package/templates/basic/src/wrapper_client.ts +46 -13
- package/templates/basic/supabase/migrations/20260701130000_workflow_access_audit.sql +125 -0
- package/templates/basic/supabase/migrations/20260712211500_workflow_access_audit_limits.sql +94 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { readRouterEnv } from "./env.ts";
|
|
2
|
+
import type { CostAwareTrace, ModelInventoryEntry } from "./schema.ts";
|
|
3
|
+
|
|
4
|
+
export type CostExclusion = {
|
|
5
|
+
model_id: string;
|
|
6
|
+
estimated_cost_usd?: number;
|
|
7
|
+
reason: "missing_estimate" | "budget_exceeded";
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type CostAwareSelection =
|
|
11
|
+
| CostAwareTrace
|
|
12
|
+
| {
|
|
13
|
+
enabled: false;
|
|
14
|
+
selected_model_ids: string[];
|
|
15
|
+
excluded: CostExclusion[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type CostAwareConfig = {
|
|
19
|
+
maxBudgetUsd: number;
|
|
20
|
+
estimates: Map<string, number>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function normalizedModelId(value: string): string {
|
|
24
|
+
return value.trim().toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parsePositiveBudget(raw: string): number {
|
|
28
|
+
const value = Number(raw);
|
|
29
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"QuorumRouter blocked: QUORUM_ROUTER_MAX_BUDGET_USD must be a finite number > 0",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseEstimates(raw: string): Map<string, number> {
|
|
38
|
+
let parsed: unknown;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(raw);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"QuorumRouter blocked: QUORUM_ROUTER_ESTIMATED_COSTS_JSON must be valid JSON",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"QuorumRouter blocked: QUORUM_ROUTER_ESTIMATED_COSTS_JSON must be a model-id to USD object",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const estimates = new Map<string, number>();
|
|
53
|
+
for (const [rawModelId, rawCost] of Object.entries(parsed)) {
|
|
54
|
+
const modelId = normalizedModelId(rawModelId);
|
|
55
|
+
if (
|
|
56
|
+
!modelId || typeof rawCost !== "number" || !Number.isFinite(rawCost) ||
|
|
57
|
+
rawCost < 0
|
|
58
|
+
) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"QuorumRouter blocked: every cost estimate must use a non-empty model id and a finite USD number >= 0",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (estimates.has(modelId)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`QuorumRouter blocked: duplicate normalized cost estimate for ${modelId}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
estimates.set(modelId, rawCost);
|
|
69
|
+
}
|
|
70
|
+
if (estimates.size === 0) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"QuorumRouter blocked: QUORUM_ROUTER_ESTIMATED_COSTS_JSON must contain at least one estimate",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return estimates;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function readCostAwareConfig(): CostAwareConfig | undefined {
|
|
79
|
+
const rawBudget = readRouterEnv("QUORUM_ROUTER_MAX_BUDGET_USD");
|
|
80
|
+
const rawEstimates = readRouterEnv("QUORUM_ROUTER_ESTIMATED_COSTS_JSON");
|
|
81
|
+
if (rawBudget === undefined && rawEstimates === undefined) return undefined;
|
|
82
|
+
if (rawBudget === undefined || rawEstimates === undefined) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
"QuorumRouter blocked: cost-aware routing requires both QUORUM_ROUTER_MAX_BUDGET_USD and QUORUM_ROUTER_ESTIMATED_COSTS_JSON",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
maxBudgetUsd: parsePositiveBudget(rawBudget),
|
|
89
|
+
estimates: parseEstimates(rawEstimates),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function selectCandidatesWithinBudget(
|
|
94
|
+
candidates: ModelInventoryEntry[],
|
|
95
|
+
config = readCostAwareConfig(),
|
|
96
|
+
): { candidates: ModelInventoryEntry[]; cost: CostAwareSelection } {
|
|
97
|
+
if (!config) {
|
|
98
|
+
return {
|
|
99
|
+
candidates,
|
|
100
|
+
cost: {
|
|
101
|
+
enabled: false,
|
|
102
|
+
selected_model_ids: candidates.map((candidate) => candidate.model_id),
|
|
103
|
+
excluded: [],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const selected: ModelInventoryEntry[] = [];
|
|
109
|
+
const excluded: CostExclusion[] = [];
|
|
110
|
+
let estimatedTotalUsd = 0;
|
|
111
|
+
for (const candidate of candidates) {
|
|
112
|
+
const estimate = config.estimates.get(
|
|
113
|
+
normalizedModelId(candidate.model_id),
|
|
114
|
+
);
|
|
115
|
+
if (estimate === undefined) {
|
|
116
|
+
excluded.push({
|
|
117
|
+
model_id: candidate.model_id,
|
|
118
|
+
reason: "missing_estimate",
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (estimatedTotalUsd + estimate > config.maxBudgetUsd) {
|
|
123
|
+
excluded.push({
|
|
124
|
+
model_id: candidate.model_id,
|
|
125
|
+
estimated_cost_usd: estimate,
|
|
126
|
+
reason: "budget_exceeded",
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
selected.push(candidate);
|
|
131
|
+
estimatedTotalUsd += estimate;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (selected.length === 0) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
"QuorumRouter blocked: cost-aware routing selected no candidates within the configured budget",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
candidates: selected,
|
|
141
|
+
cost: {
|
|
142
|
+
enabled: true,
|
|
143
|
+
pricing_source: "configured_estimate_not_live_billing",
|
|
144
|
+
max_budget_usd: config.maxBudgetUsd,
|
|
145
|
+
estimated_total_usd: estimatedTotalUsd,
|
|
146
|
+
selected_model_ids: selected.map((candidate) => candidate.model_id),
|
|
147
|
+
excluded,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -9,6 +9,7 @@ export type ProviderSpec = {
|
|
|
9
9
|
source: "wrapper" | "oauth_session" | "local_cli" | "env_fallback";
|
|
10
10
|
command?: string;
|
|
11
11
|
args_template?: string[];
|
|
12
|
+
prompt_transport?: "stdin" | "file";
|
|
12
13
|
list_models_args?: string[];
|
|
13
14
|
list_blocked_reason?: string;
|
|
14
15
|
notes: string[];
|
|
@@ -145,8 +146,9 @@ export const LOCAL_PROVIDER_SPECS: ProviderSpec[] = [
|
|
|
145
146
|
"__CWD__",
|
|
146
147
|
"--output-last-message",
|
|
147
148
|
"__OUT__",
|
|
148
|
-
"
|
|
149
|
+
"-",
|
|
149
150
|
],
|
|
151
|
+
prompt_transport: "stdin",
|
|
150
152
|
list_blocked_reason:
|
|
151
153
|
"codex models is tty/stdin dependent in this environment; model catalog listing is unavailable from non-interactive Deno.",
|
|
152
154
|
notes: [
|
|
@@ -160,7 +162,8 @@ export const LOCAL_PROVIDER_SPECS: ProviderSpec[] = [
|
|
|
160
162
|
auth_mode: "oauth",
|
|
161
163
|
source: "oauth_session",
|
|
162
164
|
command: "claude",
|
|
163
|
-
args_template: ["-p"
|
|
165
|
+
args_template: ["-p"],
|
|
166
|
+
prompt_transport: "stdin",
|
|
164
167
|
list_blocked_reason:
|
|
165
168
|
"Claude Code model listing is blocked by organization policy / disabled subscription access in this environment.",
|
|
166
169
|
notes: [
|
|
@@ -174,7 +177,8 @@ export const LOCAL_PROVIDER_SPECS: ProviderSpec[] = [
|
|
|
174
177
|
auth_mode: "oauth",
|
|
175
178
|
source: "oauth_session",
|
|
176
179
|
command: "gemini",
|
|
177
|
-
args_template: ["-p", "
|
|
180
|
+
args_template: ["-p", ""],
|
|
181
|
+
prompt_transport: "stdin",
|
|
178
182
|
list_blocked_reason:
|
|
179
183
|
"Gemini CLI list/invoke paths require a trusted directory in non-interactive runs unless the user opts into the trust setting.",
|
|
180
184
|
notes: [
|
|
@@ -189,14 +193,15 @@ export const LOCAL_PROVIDER_SPECS: ProviderSpec[] = [
|
|
|
189
193
|
source: "oauth_session",
|
|
190
194
|
command: "grok",
|
|
191
195
|
args_template: [
|
|
192
|
-
"-
|
|
193
|
-
"
|
|
196
|
+
"--prompt-file",
|
|
197
|
+
"__PROMPT_FILE__",
|
|
194
198
|
"--output-format",
|
|
195
199
|
"plain",
|
|
196
200
|
"--permission-mode",
|
|
197
201
|
"plan",
|
|
198
202
|
"--disable-web-search",
|
|
199
203
|
],
|
|
204
|
+
prompt_transport: "file",
|
|
200
205
|
list_models_args: ["models"],
|
|
201
206
|
notes: [
|
|
202
207
|
"Uses existing Grok CLI session; `grok models` is safe list-only discovery.",
|
|
@@ -209,7 +214,8 @@ export const LOCAL_PROVIDER_SPECS: ProviderSpec[] = [
|
|
|
209
214
|
auth_mode: "session",
|
|
210
215
|
source: "local_cli",
|
|
211
216
|
command: "devin",
|
|
212
|
-
args_template: ["-p", "
|
|
217
|
+
args_template: ["-p", "--prompt-file", "__PROMPT_FILE__"],
|
|
218
|
+
prompt_transport: "file",
|
|
213
219
|
list_blocked_reason:
|
|
214
220
|
"Devin CLI does not expose a models subcommand in this environment.",
|
|
215
221
|
notes: ["Uses existing Devin CLI session."],
|
|
@@ -221,7 +227,8 @@ export const LOCAL_PROVIDER_SPECS: ProviderSpec[] = [
|
|
|
221
227
|
auth_mode: "session",
|
|
222
228
|
source: "local_cli",
|
|
223
229
|
command: "qwen",
|
|
224
|
-
args_template: ["-p", "
|
|
230
|
+
args_template: ["-p", ""],
|
|
231
|
+
prompt_transport: "stdin",
|
|
225
232
|
list_blocked_reason:
|
|
226
233
|
"Qwen CLI model listing path is stdin/API-key dependent in this environment.",
|
|
227
234
|
notes: ["Uses existing local Qwen CLI session."],
|
|
@@ -1,3 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const SupabaseAuditModeSchema = z.enum([
|
|
4
|
+
"disabled",
|
|
5
|
+
"optional",
|
|
6
|
+
"required",
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export type SupabaseAuditMode = z.infer<typeof SupabaseAuditModeSchema>;
|
|
10
|
+
|
|
11
|
+
export const SupabaseFeatureConfigSchema = z.object({
|
|
12
|
+
audit: z.object({
|
|
13
|
+
mode: SupabaseAuditModeSchema,
|
|
14
|
+
}).strict(),
|
|
15
|
+
}).strict();
|
|
16
|
+
|
|
17
|
+
export type SupabaseFeatureConfig = z.infer<
|
|
18
|
+
typeof SupabaseFeatureConfigSchema
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
export const SelectiveFeatureConfigSchema = z.object({
|
|
22
|
+
supabase: SupabaseFeatureConfigSchema.optional(),
|
|
23
|
+
}).strict();
|
|
24
|
+
|
|
25
|
+
const SECRET_CONFIG_KEY =
|
|
26
|
+
/^(?:quorum_router_)?supabase_(?:url|anon_key|publishable_key|session_jwt|access_token|service_role_key|admin_key)$/i;
|
|
27
|
+
|
|
28
|
+
function normalizedConfigKey(key: string): string {
|
|
29
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9]+/gi, "_")
|
|
30
|
+
.toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function findSupabaseCredentialPath(
|
|
34
|
+
value: unknown,
|
|
35
|
+
path: Array<string | number> = [],
|
|
36
|
+
): Array<string | number> | undefined {
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
39
|
+
const found = findSupabaseCredentialPath(value[index], [...path, index]);
|
|
40
|
+
if (found) return found;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (!value || typeof value !== "object") return undefined;
|
|
45
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
46
|
+
const normalized = normalizedConfigKey(key);
|
|
47
|
+
if (
|
|
48
|
+
SECRET_CONFIG_KEY.test(normalized) ||
|
|
49
|
+
(/supabase/.test(normalized) &&
|
|
50
|
+
/(secret|token|key|credential|password|url)/.test(normalized))
|
|
51
|
+
) {
|
|
52
|
+
return [...path, key];
|
|
53
|
+
}
|
|
54
|
+
const found = findSupabaseCredentialPath(nested, [...path, key]);
|
|
55
|
+
if (found) return found;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const GeneratedRouterConfigSchema = z.object({
|
|
61
|
+
features: SelectiveFeatureConfigSchema.optional(),
|
|
62
|
+
}).passthrough().superRefine((value, context) => {
|
|
63
|
+
const path = findSupabaseCredentialPath(value);
|
|
64
|
+
if (path) {
|
|
65
|
+
context.addIssue({
|
|
66
|
+
code: z.ZodIssueCode.custom,
|
|
67
|
+
path,
|
|
68
|
+
message: "Supabase runtime credentials belong in the environment only",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
1
73
|
export type AuthMode = "auto" | "wrapper" | "oauth" | "session" | "env";
|
|
2
74
|
export type InventorySource =
|
|
3
75
|
| "wrapper"
|
|
@@ -21,6 +93,7 @@ export type ModelInventoryEntry = {
|
|
|
21
93
|
notes: string[];
|
|
22
94
|
command?: string;
|
|
23
95
|
args_template?: string[];
|
|
96
|
+
prompt_transport?: "stdin" | "file";
|
|
24
97
|
invocation_model?: string;
|
|
25
98
|
};
|
|
26
99
|
|
|
@@ -82,6 +155,19 @@ export type PromptContextTrace = {
|
|
|
82
155
|
context_fetch_error?: string;
|
|
83
156
|
};
|
|
84
157
|
|
|
158
|
+
export type CostAwareTrace = {
|
|
159
|
+
enabled: boolean;
|
|
160
|
+
pricing_source: "configured_estimate_not_live_billing";
|
|
161
|
+
max_budget_usd?: number;
|
|
162
|
+
estimated_total_usd?: number;
|
|
163
|
+
selected_model_ids: string[];
|
|
164
|
+
excluded: Array<{
|
|
165
|
+
model_id: string;
|
|
166
|
+
estimated_cost_usd?: number;
|
|
167
|
+
reason: "missing_estimate" | "budget_exceeded";
|
|
168
|
+
}>;
|
|
169
|
+
};
|
|
170
|
+
|
|
85
171
|
export type DogfoodTrace = {
|
|
86
172
|
run_id: string;
|
|
87
173
|
timestamp: string;
|
|
@@ -99,6 +185,7 @@ export type DogfoodTrace = {
|
|
|
99
185
|
prompt_hash?: string;
|
|
100
186
|
prompt_summary?: string;
|
|
101
187
|
prompt_context?: PromptContextTrace;
|
|
188
|
+
cost_aware?: CostAwareTrace;
|
|
102
189
|
response_summary?: string;
|
|
103
190
|
schema_valid: boolean;
|
|
104
191
|
redaction_ok: boolean;
|