@routerlab/core 0.0.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/LICENSE +201 -0
- package/README.md +92 -0
- package/dist/candidates.json +46 -0
- package/dist/cost.d.ts +120 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +496 -0
- package/dist/cost.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/quality_predictor.d.ts +94 -0
- package/dist/quality_predictor.d.ts.map +1 -0
- package/dist/quality_predictor.js +338 -0
- package/dist/quality_predictor.js.map +1 -0
- package/dist/quality_prior.d.ts +24 -0
- package/dist/quality_prior.d.ts.map +1 -0
- package/dist/quality_prior.js +109 -0
- package/dist/quality_prior.js.map +1 -0
- package/dist/router.d.ts +27 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +201 -0
- package/dist/router.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +27 -0
package/dist/router.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// router.ts — the routing engine. Given a task and a quality bar, pick the
|
|
2
|
+
// cheapest model that meets the bar and the caller's budget/latency caps.
|
|
3
|
+
//
|
|
4
|
+
// Differentiation from prior art:
|
|
5
|
+
//
|
|
6
|
+
// 1. Cost is grounded in *empirical* token economics, not offline
|
|
7
|
+
// tokenizer proxies. The atlas dataset (project 1 of this milestone)
|
|
8
|
+
// provides per-(provider, model, format) calibration between offline
|
|
9
|
+
// counters and empirical counts. RouterArena, RouteLLM, and other
|
|
10
|
+
// prior routers either use proxy "cost thresholds" in [0,1]
|
|
11
|
+
// (RouteLLM) or `offline_tokenizer_estimate * published_price`
|
|
12
|
+
// (RouterArena). Our cost line is `atlas_calibrated_token_count *
|
|
13
|
+
// published_price` — strictly more accurate.
|
|
14
|
+
//
|
|
15
|
+
// The atlas-grounded math lives in `cost.ts` (`estimateCost()`),
|
|
16
|
+
// imported below. `cost.ts` wraps tokenometer's empirical counters
|
|
17
|
+
// and atlas's per-(provider, model) calibration table; this router
|
|
18
|
+
// uses it for both the context-window check (`inputTokens`) and the
|
|
19
|
+
// budget check (`totalUsd`) so the two filters cannot drift.
|
|
20
|
+
//
|
|
21
|
+
// 2. Every decision returns a full triple — `chosen`, `fallbacks`, and
|
|
22
|
+
// `skipped` (with reasons). This makes the engine debuggable and
|
|
23
|
+
// reproducible: a downstream consumer can replay a decision and see
|
|
24
|
+
// exactly which constraints filtered each candidate. RouteLLM
|
|
25
|
+
// returns only a routing logit; LiteLLM's complexity router returns
|
|
26
|
+
// only a chosen model. Routerlab returns the full decision trace.
|
|
27
|
+
//
|
|
28
|
+
// 3. The default candidate pool is shipped *with* the package and is
|
|
29
|
+
// versioned alongside the engine in `candidates.json`. Callers can
|
|
30
|
+
// override it, but the default is reproducible and citable.
|
|
31
|
+
//
|
|
32
|
+
// No external network calls and no LLM invocations happen here. The
|
|
33
|
+
// engine produces decisions; the per-provider runners under `eval/runners`
|
|
34
|
+
// turn decisions into completions.
|
|
35
|
+
import candidatesData from "./candidates.json" with { type: "json" };
|
|
36
|
+
import { estimateCost } from "./cost.js";
|
|
37
|
+
// Phase 3: predictor reads from eval/results/quality_table.json when present
|
|
38
|
+
// and falls back to the seeded prior in quality_prior.ts otherwise. The
|
|
39
|
+
// `predictQuality` signature is intentionally unchanged so the router
|
|
40
|
+
// engine didn't need to be rewritten.
|
|
41
|
+
import { predictQuality } from "./quality_predictor.js";
|
|
42
|
+
const DEFAULT_CANDIDATES = Object.freeze(candidatesData.candidates.map((c) => Object.freeze({ ...c })));
|
|
43
|
+
/**
|
|
44
|
+
* Return the default candidate pool. Useful for callers that want to
|
|
45
|
+
* inspect the pool, filter it, or extend it before routing.
|
|
46
|
+
*/
|
|
47
|
+
export function getDefaultCandidates() {
|
|
48
|
+
return DEFAULT_CANDIDATES;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve which candidate pool to use for this request. Defaults to the
|
|
52
|
+
* shipped pool; callers can override with `request.candidates`.
|
|
53
|
+
*/
|
|
54
|
+
function resolveCandidates(request) {
|
|
55
|
+
if (request.candidates !== undefined) {
|
|
56
|
+
return request.candidates;
|
|
57
|
+
}
|
|
58
|
+
return DEFAULT_CANDIDATES;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate a request and throw a descriptive error if it's malformed. We
|
|
62
|
+
* fail loud here so misconfigured callers see the bug immediately rather
|
|
63
|
+
* than getting a silently-bad routing decision.
|
|
64
|
+
*/
|
|
65
|
+
function validateRequest(request) {
|
|
66
|
+
if (!Number.isFinite(request.qualityBar)) {
|
|
67
|
+
throw new Error("route(): qualityBar must be a finite number");
|
|
68
|
+
}
|
|
69
|
+
if (request.qualityBar < 0 || request.qualityBar > 1) {
|
|
70
|
+
throw new Error("route(): qualityBar must be in [0, 1]");
|
|
71
|
+
}
|
|
72
|
+
if (typeof request.prompt !== "string") {
|
|
73
|
+
throw new Error("route(): prompt must be a string");
|
|
74
|
+
}
|
|
75
|
+
if (request.maxCostUsd !== undefined &&
|
|
76
|
+
(!Number.isFinite(request.maxCostUsd) || request.maxCostUsd < 0)) {
|
|
77
|
+
throw new Error("route(): maxCostUsd must be a non-negative finite number");
|
|
78
|
+
}
|
|
79
|
+
if (request.maxLatencyMs !== undefined &&
|
|
80
|
+
(!Number.isFinite(request.maxLatencyMs) || request.maxLatencyMs < 0)) {
|
|
81
|
+
throw new Error("route(): maxLatencyMs must be a non-negative finite number");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Filter the candidate pool by quality bar, budget, and context window.
|
|
86
|
+
* Skipped candidates are returned alongside survivors with a recorded
|
|
87
|
+
* reason — this is what makes routing decisions auditable.
|
|
88
|
+
*/
|
|
89
|
+
function filterCandidates(request, pool) {
|
|
90
|
+
const kept = [];
|
|
91
|
+
const skipped = [];
|
|
92
|
+
for (const model of pool) {
|
|
93
|
+
const estimate = estimateCost({
|
|
94
|
+
prompt: request.prompt,
|
|
95
|
+
model: model.model,
|
|
96
|
+
provider: model.provider,
|
|
97
|
+
pricing: model.pricing,
|
|
98
|
+
taskClass: request.task,
|
|
99
|
+
});
|
|
100
|
+
if (estimate.inputTokens > model.contextWindow) {
|
|
101
|
+
skipped.push({
|
|
102
|
+
model,
|
|
103
|
+
reason: `prompt requires ~${estimate.inputTokens} input tokens, exceeds context window of ${model.contextWindow}`,
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const expectedQuality = predictQuality(request.task, model.model);
|
|
108
|
+
if (expectedQuality < request.qualityBar) {
|
|
109
|
+
skipped.push({
|
|
110
|
+
model,
|
|
111
|
+
reason: `expected quality ${expectedQuality.toFixed(3)} for task "${request.task}" is below quality bar ${request.qualityBar.toFixed(3)}`,
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (request.maxCostUsd !== undefined && estimate.totalUsd > request.maxCostUsd) {
|
|
116
|
+
skipped.push({
|
|
117
|
+
model,
|
|
118
|
+
reason: `expected cost $${estimate.totalUsd.toFixed(6)} exceeds budget $${request.maxCostUsd.toFixed(6)}`,
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
kept.push({ model, expectedCost: estimate.totalUsd, expectedQuality });
|
|
123
|
+
}
|
|
124
|
+
return { kept, skipped };
|
|
125
|
+
}
|
|
126
|
+
const MAX_FALLBACKS = 3;
|
|
127
|
+
/**
|
|
128
|
+
* Compose the winning pick + fallback list from a sorted-by-cost array.
|
|
129
|
+
*
|
|
130
|
+
* Sort tie-breaker: when expected costs are equal, prefer the higher
|
|
131
|
+
* expected quality. This makes routing deterministic and biases toward
|
|
132
|
+
* better-quality picks at no extra cost — defensible default for a
|
|
133
|
+
* cost-first router.
|
|
134
|
+
*/
|
|
135
|
+
function selectPickAndFallbacks(scored, request) {
|
|
136
|
+
const sorted = [...scored].sort((a, b) => {
|
|
137
|
+
if (a.expectedCost !== b.expectedCost) {
|
|
138
|
+
return a.expectedCost - b.expectedCost;
|
|
139
|
+
}
|
|
140
|
+
return b.expectedQuality - a.expectedQuality;
|
|
141
|
+
});
|
|
142
|
+
const winner = sorted[0];
|
|
143
|
+
if (winner === undefined) {
|
|
144
|
+
throw new Error("internal: selectPickAndFallbacks called with empty array");
|
|
145
|
+
}
|
|
146
|
+
const chosen = {
|
|
147
|
+
model: winner.model,
|
|
148
|
+
expectedCost: winner.expectedCost,
|
|
149
|
+
expectedQuality: winner.expectedQuality,
|
|
150
|
+
reasoning: buildReasoning(winner, sorted.length, request),
|
|
151
|
+
};
|
|
152
|
+
const fallbacks = sorted
|
|
153
|
+
.slice(1, 1 + MAX_FALLBACKS)
|
|
154
|
+
.map((c, idx) => ({
|
|
155
|
+
model: c.model,
|
|
156
|
+
reason: `cost-rank ${idx + 2} of ${sorted.length} qualifying candidates: expected cost $${c.expectedCost.toFixed(6)}, expected quality ${c.expectedQuality.toFixed(3)}`,
|
|
157
|
+
}));
|
|
158
|
+
return { chosen, fallbacks };
|
|
159
|
+
}
|
|
160
|
+
function buildReasoning(winner, poolSize, request) {
|
|
161
|
+
return [
|
|
162
|
+
`cheapest model meeting quality bar ${request.qualityBar.toFixed(3)} for task "${request.task}"`,
|
|
163
|
+
`of ${poolSize} qualifying candidates`,
|
|
164
|
+
`expected cost $${winner.expectedCost.toFixed(6)} (atlas-grounded estimator)`,
|
|
165
|
+
`expected quality ${winner.expectedQuality.toFixed(3)} (Phase 3 predictor; measured cells when available, seeded prior otherwise)`,
|
|
166
|
+
].join("; ");
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The public routing entrypoint.
|
|
170
|
+
*
|
|
171
|
+
* Pipeline:
|
|
172
|
+
* 1. Validate the request.
|
|
173
|
+
* 2. Resolve the candidate pool (caller override > shipped default).
|
|
174
|
+
* 3. Filter out candidates that fail the quality bar, the cost budget,
|
|
175
|
+
* or the model's context window. Record reasons.
|
|
176
|
+
* 4. Sort survivors by expected cost ascending (quality breaks ties).
|
|
177
|
+
* 5. Pick the cheapest survivor as `chosen`; the next three as
|
|
178
|
+
* `fallbacks`. Anything below that is in `skipped` only if it failed
|
|
179
|
+
* a constraint — extra cheap-survivors past the fallback list are
|
|
180
|
+
* simply not returned.
|
|
181
|
+
* 6. If no candidate survives, throw with the full skipped list so the
|
|
182
|
+
* caller can see exactly what went wrong.
|
|
183
|
+
*
|
|
184
|
+
* Sync return: this function does no I/O. Returning a promise would be
|
|
185
|
+
* misleading.
|
|
186
|
+
*/
|
|
187
|
+
export function route(request) {
|
|
188
|
+
validateRequest(request);
|
|
189
|
+
const pool = resolveCandidates(request);
|
|
190
|
+
if (pool.length === 0) {
|
|
191
|
+
throw new Error("route(): candidate pool is empty; pass `candidates` in the request or use the default pool");
|
|
192
|
+
}
|
|
193
|
+
const { kept, skipped } = filterCandidates(request, pool);
|
|
194
|
+
if (kept.length === 0) {
|
|
195
|
+
const reasons = skipped.map((s) => ` - ${s.model.model}: ${s.reason}`).join("\n");
|
|
196
|
+
throw new Error(`route(): no candidates passed filtering for task "${request.task}" at quality bar ${request.qualityBar}.\nSkipped:\n${reasons}`);
|
|
197
|
+
}
|
|
198
|
+
const { chosen, fallbacks } = selectPickAndFallbacks(kept, request);
|
|
199
|
+
return { chosen, fallbacks, skipped };
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of task classes routerlab routes for. Each class has its own
|
|
3
|
+
* quality prior table (see `quality_prior.ts`) and, downstream, its own
|
|
4
|
+
* Pareto frontier in `eval/results/frontier.json`.
|
|
5
|
+
*
|
|
6
|
+
* Keep this list small and stable — frontier reproducibility hinges on
|
|
7
|
+
* task-class identity surviving across runs.
|
|
8
|
+
*/
|
|
9
|
+
export type TaskClass = "qa" | "codegen" | "summarization" | "classification" | "reasoning";
|
|
10
|
+
/**
|
|
11
|
+
* The providers we can route across. Mirrors the candidate pool documented
|
|
12
|
+
* in the routerlab README and the per-provider runners under `eval/runners/`.
|
|
13
|
+
*/
|
|
14
|
+
export type Provider = "anthropic" | "openai" | "google" | "groq" | "together" | "hf" | "openrouter";
|
|
15
|
+
/**
|
|
16
|
+
* Per-million-token pricing for a model. Routerlab is cost-first, so this
|
|
17
|
+
* is the load-bearing field: every routing decision sorts on a cost
|
|
18
|
+
* computed from these numbers × atlas-grounded token estimates.
|
|
19
|
+
*/
|
|
20
|
+
export interface ModelPricing {
|
|
21
|
+
inputUsdPerMtok: number;
|
|
22
|
+
outputUsdPerMtok: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A single routable model. The pool of these is what `route()` searches
|
|
26
|
+
* over. Callers can override the default pool via `RouteRequest.candidates`.
|
|
27
|
+
*/
|
|
28
|
+
export interface ModelCandidate {
|
|
29
|
+
provider: Provider;
|
|
30
|
+
/** Model identifier, e.g. "claude-haiku-4-5", "llama-3.3-70b". */
|
|
31
|
+
model: string;
|
|
32
|
+
pricing: ModelPricing;
|
|
33
|
+
/** Maximum context window in tokens. */
|
|
34
|
+
contextWindow: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A routing request. `task` and `qualityBar` are required; everything else
|
|
38
|
+
* is optional and fills in sensible defaults.
|
|
39
|
+
*
|
|
40
|
+
* `qualityBar` is a value in [0,1] interpreted as "minimum acceptable
|
|
41
|
+
* expected quality on this task class." Candidates below the bar are
|
|
42
|
+
* filtered out before cost-sorting.
|
|
43
|
+
*/
|
|
44
|
+
export interface RouteRequest {
|
|
45
|
+
task: TaskClass;
|
|
46
|
+
prompt: string;
|
|
47
|
+
/** 0..1 — minimum acceptable expected quality. */
|
|
48
|
+
qualityBar: number;
|
|
49
|
+
/** Hard budget cap per request, in USD. */
|
|
50
|
+
maxCostUsd?: number;
|
|
51
|
+
/** Hard latency cap per request, in milliseconds. */
|
|
52
|
+
maxLatencyMs?: number;
|
|
53
|
+
/** Override the default candidate pool. */
|
|
54
|
+
candidates?: ModelCandidate[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A picked model with the engine's reasoning trace attached.
|
|
58
|
+
*
|
|
59
|
+
* `expectedCost` is in USD; `expectedQuality` is in [0,1].
|
|
60
|
+
*/
|
|
61
|
+
export interface RoutePick {
|
|
62
|
+
model: ModelCandidate;
|
|
63
|
+
expectedCost: number;
|
|
64
|
+
expectedQuality: number;
|
|
65
|
+
/** Human-readable explanation of why this model won. */
|
|
66
|
+
reasoning: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A non-chosen candidate retained as a fallback (e.g. for retries on
|
|
70
|
+
* provider failure). Sorted by ascending expected cost.
|
|
71
|
+
*/
|
|
72
|
+
export interface RouteFallback {
|
|
73
|
+
model: ModelCandidate;
|
|
74
|
+
reason: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A candidate filtered out before cost-sorting, with the failing constraint
|
|
78
|
+
* recorded. This is what makes routerlab debuggable — every rejection has
|
|
79
|
+
* a documented cause.
|
|
80
|
+
*/
|
|
81
|
+
export interface RouteSkipped {
|
|
82
|
+
model: ModelCandidate;
|
|
83
|
+
reason: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The full return value of `route()`. The triple (chosen, fallbacks,
|
|
87
|
+
* skipped) is a complete decision record: callers can reproduce why a
|
|
88
|
+
* particular model was picked from this object alone.
|
|
89
|
+
*/
|
|
90
|
+
export interface RouteDecision {
|
|
91
|
+
chosen: RoutePick;
|
|
92
|
+
fallbacks: RouteFallback[];
|
|
93
|
+
skipped: RouteSkipped[];
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GACjB,IAAI,GACJ,SAAS,GACT,eAAe,GACf,gBAAgB,GAChB,WAAW,CAAC;AAEhB;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAChB,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,UAAU,GACV,IAAI,GACJ,YAAY,CAAC;AAEjB;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,CAAC;IACtB,wCAAwC;IACxC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,cAAc,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @routerlab/core — public type surface for the routing engine.
|
|
2
|
+
//
|
|
3
|
+
// These types are intentionally narrow and dependency-free. They describe
|
|
4
|
+
// what a caller passes in to `route()` and what comes back. Everything else
|
|
5
|
+
// (cost computation, quality prediction, candidate pool composition) is an
|
|
6
|
+
// internal implementation detail of the engine.
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,2EAA2E;AAC3E,gDAAgD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@routerlab/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Routing engine for cost-quality LLM model selection.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Faraazuddin Mohammed",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/faraa2m/routerlab.git",
|
|
18
|
+
"directory": "packages/core"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"test": "bun test"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@tokenometer/core": "1.0.1"
|
|
26
|
+
}
|
|
27
|
+
}
|