@mupt-ai/dari-router 0.2.2 → 0.2.4

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.
Files changed (75) hide show
  1. package/README.md +14 -1
  2. package/dist/anonymous_actions.d.ts +17 -8
  3. package/dist/anonymous_actions.js +110 -44
  4. package/dist/anonymous_actions.js.map +1 -1
  5. package/dist/cache_behavior.js +1 -1
  6. package/dist/cache_behavior.js.map +1 -1
  7. package/dist/cache_scope.js +1 -0
  8. package/dist/cache_scope.js.map +1 -1
  9. package/dist/continuation_state.d.ts +1 -0
  10. package/dist/continuation_state.js +6 -1
  11. package/dist/continuation_state.js.map +1 -1
  12. package/dist/cost.d.ts +1 -0
  13. package/dist/cost.js +8 -4
  14. package/dist/cost.js.map +1 -1
  15. package/dist/eval_score_imputation.d.ts +14 -3
  16. package/dist/eval_score_imputation.js +83 -59
  17. package/dist/eval_score_imputation.js.map +1 -1
  18. package/dist/fixed_turn_cost_config.d.ts +2 -0
  19. package/dist/fixed_turn_cost_config.js +8 -0
  20. package/dist/fixed_turn_cost_config.js.map +1 -1
  21. package/dist/index.d.ts +0 -2
  22. package/dist/index.js +0 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/jev_client.d.ts +31 -0
  25. package/dist/jev_client.js +115 -0
  26. package/dist/jev_client.js.map +1 -0
  27. package/dist/jev_eval_relevance.d.ts +49 -0
  28. package/dist/jev_eval_relevance.js +176 -0
  29. package/dist/jev_eval_relevance.js.map +1 -0
  30. package/dist/jev_selector.d.ts +3 -27
  31. package/dist/jev_selector.js +31 -221
  32. package/dist/jev_selector.js.map +1 -1
  33. package/dist/model_ids.js +10 -3
  34. package/dist/model_ids.js.map +1 -1
  35. package/dist/pi_context.js.map +1 -1
  36. package/dist/pi_options.js +83 -26
  37. package/dist/pi_options.js.map +1 -1
  38. package/dist/pi_output.js +2 -2
  39. package/dist/pi_output.js.map +1 -1
  40. package/dist/policy-engine.d.ts +2 -0
  41. package/dist/policy-engine.js +2 -0
  42. package/dist/policy-engine.js.map +1 -1
  43. package/dist/prompts/anonymous_action.md +29 -0
  44. package/dist/prompts/anonymous_rules.md +5 -0
  45. package/dist/prompts/custom_rules.md +10 -0
  46. package/dist/prompts/jev_eval_relevance.json +12 -0
  47. package/dist/prompts/jev_selector.json +22 -0
  48. package/dist/prompts/selector.md +21 -0
  49. package/dist/prompts.d.ts +28 -3
  50. package/dist/prompts.js +59 -80
  51. package/dist/prompts.js.map +1 -1
  52. package/dist/route.d.ts +1 -0
  53. package/dist/route.js +21 -0
  54. package/dist/route.js.map +1 -1
  55. package/dist/routing_history.d.ts +3 -0
  56. package/dist/routing_history.js +90 -55
  57. package/dist/routing_history.js.map +1 -1
  58. package/dist/selector_input.d.ts +0 -2
  59. package/dist/selector_input.js +15 -7
  60. package/dist/selector_input.js.map +1 -1
  61. package/dist/selector_request.d.ts +0 -1
  62. package/dist/selector_request.js +14 -37
  63. package/dist/selector_request.js.map +1 -1
  64. package/dist/selector_truncation.d.ts +0 -1
  65. package/dist/selector_truncation.js +6 -39
  66. package/dist/selector_truncation.js.map +1 -1
  67. package/dist/thinking_level_graph.d.ts +10 -0
  68. package/dist/thinking_level_graph.js +64 -0
  69. package/dist/thinking_level_graph.js.map +1 -0
  70. package/dist/types.d.ts +1 -0
  71. package/package.json +4 -5
  72. package/dist/jev_router.d.ts +0 -5
  73. package/dist/jev_router.js +0 -22
  74. package/dist/jev_router.js.map +0 -1
  75. package/examples/jev_router.ts +0 -50
@@ -0,0 +1,49 @@
1
+ import type { JsonValue } from "./json.js";
2
+ import { type ResolvedJevConfig } from "./jev_client.js";
3
+ import { type ChatMessage, type RouterEval } from "./types.js";
4
+ export declare const JEV_EVAL_RELEVANCE_THRESHOLD = 0.3;
5
+ export declare const JEV_EVAL_RELEVANCE_BATCH_SIZE = 100;
6
+ export declare const JEV_EVAL_RELEVANCE_HISTORY_CHAR_BUDGET = 16000;
7
+ export type JevEvalRelevanceState = {
8
+ task: ChatMessage | null;
9
+ conversation: ChatMessage[];
10
+ };
11
+ export type JevNoulQuestion = {
12
+ type: "noul";
13
+ instructions: JsonValue;
14
+ criteria: {
15
+ true: string;
16
+ false: string;
17
+ };
18
+ };
19
+ export type JevEvalRelevanceRequest = {
20
+ model: string;
21
+ state: JsonValue;
22
+ questions: Record<string, JevNoulQuestion>;
23
+ };
24
+ export type JevEvalRelevanceCard = {
25
+ id: string;
26
+ name: string;
27
+ relevance: number;
28
+ kept: boolean;
29
+ };
30
+ export type JevEvalRelevanceSelection = {
31
+ evals: RouterEval[];
32
+ cards: JevEvalRelevanceCard[];
33
+ model: string;
34
+ usage: {
35
+ input_tokens: number;
36
+ output_tokens: number;
37
+ };
38
+ requests: JevEvalRelevanceRequest[];
39
+ };
40
+ export declare function buildJevEvalRelevanceRequest(cards: readonly RouterEval[], state: JevEvalRelevanceState, model: string): JevEvalRelevanceRequest;
41
+ export declare function parseJevEvalRelevanceResponse(body: unknown, cards: readonly RouterEval[]): {
42
+ cards: JevEvalRelevanceCard[];
43
+ model: string;
44
+ usage: {
45
+ input_tokens: number;
46
+ output_tokens: number;
47
+ };
48
+ };
49
+ export declare function selectRelevantEvals(cards: readonly RouterEval[], state: JevEvalRelevanceState, config: ResolvedJevConfig, signal?: AbortSignal): Promise<JevEvalRelevanceSelection>;
@@ -0,0 +1,176 @@
1
+ // Jev-decided eval relevance. Instead of the router owner curating which
2
+ // benchmark scorecards the selector sees, every candidate card is put to Jev
3
+ // as one yes/no (Noul) question — does this benchmark measure the kind of
4
+ // work the agent is doing? — all cards in a single System One call, and the
5
+ // cards Jev answers yes to become the selector's imported_evals.
6
+ //
7
+ // Jev is literal, so the question names the exact condition and the criteria
8
+ // carry the boundary: a benchmark of the same kind of work (coding, legal,
9
+ // visual design, ...) is relevant even when its exact task differs; a
10
+ // benchmark of a different kind of work is not, however capable a model
11
+ // scoring well on it might be.
12
+ import { JEV_EVAL_RELEVANCE_PROMPT } from "./prompts.js";
13
+ import { invalidJevResponse, postSystemOne } from "./jev_client.js";
14
+ import { compactRoutingHistory, contentText, elideMiddle, ROUTER_USER_MESSAGE_CHAR_CAP, } from "./routing_history.js";
15
+ import { isRecord } from "./types.js";
16
+ // A card is kept when Jev's probability that it is relevant exceeds this.
17
+ // Below even odds on purpose: an irrelevant card is noise the selector can
18
+ // discount, while a missed card is evidence it never sees.
19
+ export const JEV_EVAL_RELEVANCE_THRESHOLD = 0.3;
20
+ // Cards per System One call. Jev evaluates every question against the
21
+ // state in one pass and its latency grows slowly with question count, but
22
+ // each question also counts toward the request's token cap alongside the
23
+ // state; probed against the API, 100 cards plus a 64k-char conversation fit
24
+ // with room to spare while 400 cards alone did not.
25
+ export const JEV_EVAL_RELEVANCE_BATCH_SIZE = 100;
26
+ export const JEV_EVAL_RELEVANCE_HISTORY_CHAR_BUDGET = 16_000;
27
+ export function buildJevEvalRelevanceRequest(cards, state, model) {
28
+ const questions = {};
29
+ for (const card of cards) {
30
+ questions[card.id] = {
31
+ type: "noul",
32
+ instructions: {
33
+ question: JEV_EVAL_RELEVANCE_PROMPT.question,
34
+ benchmark: {
35
+ name: card.name,
36
+ ...(card.description ? { description: card.description } : {}),
37
+ },
38
+ rules: JEV_EVAL_RELEVANCE_PROMPT.rules,
39
+ },
40
+ criteria: JEV_EVAL_RELEVANCE_PROMPT.criteria,
41
+ };
42
+ }
43
+ return {
44
+ model,
45
+ state: {
46
+ task: state.task,
47
+ conversation: state.conversation,
48
+ },
49
+ questions,
50
+ };
51
+ }
52
+ // Maps a System One body back onto the cards it was asked about. Every card
53
+ // must be answered; a missing or non-Noul answer is a malformed response.
54
+ export function parseJevEvalRelevanceResponse(body, cards) {
55
+ if (!isRecord(body) || typeof body.model !== "string" || !isRecord(body.answers)) {
56
+ throw invalidJevResponse("Jev returned an unrecognized eval relevance response body.");
57
+ }
58
+ const usage = body.usage;
59
+ if (!isRecord(usage)
60
+ || typeof usage.input_tokens !== "number"
61
+ || typeof usage.output_tokens !== "number") {
62
+ throw invalidJevResponse("Jev eval relevance response is missing token usage.");
63
+ }
64
+ const answers = body.answers;
65
+ const scored = cards.map((card) => {
66
+ const answer = answers[card.id];
67
+ if (!isRecord(answer)
68
+ || answer.type !== "noul"
69
+ || typeof answer.noul !== "number"
70
+ || !Number.isFinite(answer.noul)
71
+ || answer.noul < 0
72
+ || answer.noul > 1) {
73
+ throw invalidJevResponse(`Jev eval relevance response has an invalid noul answer for ${card.id}.`);
74
+ }
75
+ return {
76
+ id: card.id,
77
+ name: card.name,
78
+ relevance: answer.noul,
79
+ kept: answer.noul > JEV_EVAL_RELEVANCE_THRESHOLD,
80
+ };
81
+ });
82
+ return {
83
+ cards: scored,
84
+ model: body.model,
85
+ usage: { input_tokens: usage.input_tokens, output_tokens: usage.output_tokens },
86
+ };
87
+ }
88
+ // Asks Jev about every card (batched, batches in parallel) and returns the
89
+ // kept cards in their original order plus the per-card probabilities.
90
+ export async function selectRelevantEvals(cards, state, config, signal) {
91
+ const unique = dedupeById(cards);
92
+ if (unique.length === 0) {
93
+ return {
94
+ evals: [],
95
+ cards: [],
96
+ model: config.model,
97
+ usage: { input_tokens: 0, output_tokens: 0 },
98
+ requests: [],
99
+ };
100
+ }
101
+ const batches = [];
102
+ for (let start = 0; start < unique.length; start += JEV_EVAL_RELEVANCE_BATCH_SIZE) {
103
+ batches.push(unique.slice(start, start + JEV_EVAL_RELEVANCE_BATCH_SIZE));
104
+ }
105
+ // History compaction leaves a lone message and the leading instructions
106
+ // verbatim, so every message is also capped: one large paste must not push
107
+ // the request past Jev's token limit.
108
+ const boundedState = {
109
+ task: state.task === null ? null : boundedMessage(state.task, ROUTER_USER_MESSAGE_CHAR_CAP),
110
+ conversation: compactRoutingHistory(state.conversation, JEV_EVAL_RELEVANCE_HISTORY_CHAR_BUDGET).map((message) => boundedMessage(message, JEV_EVAL_RELEVANCE_HISTORY_CHAR_BUDGET)),
111
+ };
112
+ const requests = batches.map((batch) => buildJevEvalRelevanceRequest(batch, boundedState, config.model));
113
+ const parsed = await Promise.all(requests.map(async (request, index) => parseJevEvalRelevanceResponse(await postSystemOne(request, config, signal), batches[index])));
114
+ const scored = parsed.flatMap((result) => result.cards);
115
+ const kept = new Set(scored.filter((card) => card.kept).map((card) => card.id));
116
+ return {
117
+ evals: unique.filter((card) => kept.has(card.id)),
118
+ cards: scored,
119
+ model: parsed[0].model,
120
+ usage: parsed.reduce((total, result) => ({
121
+ input_tokens: total.input_tokens + result.usage.input_tokens,
122
+ output_tokens: total.output_tokens + result.usage.output_tokens,
123
+ }), { input_tokens: 0, output_tokens: 0 }),
124
+ requests,
125
+ };
126
+ }
127
+ // Jev only needs the words, so an oversized message becomes its text with the
128
+ // middle of each long part elided, shrinking until the JSON (escapes
129
+ // included) fits.
130
+ function boundedMessage(message, maxChars) {
131
+ if (JSON.stringify(message).length <= maxChars)
132
+ return message;
133
+ const parts = typeof message.content === "string"
134
+ ? [message.content]
135
+ : Array.isArray(message.content)
136
+ ? message.content.map((part) => contentText([part])).filter((text) => text !== "")
137
+ : [];
138
+ for (let keepChars = maxChars;; keepChars = Math.floor(keepChars * 0.75)) {
139
+ const bounded = { role: message.role, content: elideParts(parts, keepChars) };
140
+ if (keepChars === 0 || JSON.stringify(bounded).length <= maxChars)
141
+ return bounded;
142
+ }
143
+ }
144
+ // Shares the budget across text parts so a short ask between long harness
145
+ // parts (reminders, environment dumps) survives: parts within an even share
146
+ // stay whole and the longer parts split what remains.
147
+ function elideParts(parts, maxChars) {
148
+ const limits = new Array(parts.length).fill(0);
149
+ let remaining = Math.max(0, maxChars - (parts.length - 1));
150
+ const bySize = parts.map((_, index) => index).sort((left, right) => parts[left].length - parts[right].length);
151
+ for (let open = 0; open < bySize.length; open += 1) {
152
+ const share = Math.floor(remaining / (bySize.length - open));
153
+ const index = bySize[open];
154
+ if (parts[index].length > share) {
155
+ for (const rest of bySize.slice(open))
156
+ limits[rest] = share;
157
+ break;
158
+ }
159
+ limits[index] = parts[index].length;
160
+ remaining -= parts[index].length;
161
+ }
162
+ return parts.map((part, index) => elideMiddle(part, limits[index])).join("\n");
163
+ }
164
+ // The candidate pool is the router's own cards plus the public catalog; a
165
+ // card the router imported from the catalog appears in both, and the first
166
+ // occurrence (the router's copy) wins.
167
+ function dedupeById(cards) {
168
+ const seen = new Set();
169
+ return cards.filter((card) => {
170
+ if (seen.has(card.id))
171
+ return false;
172
+ seen.add(card.id);
173
+ return true;
174
+ });
175
+ }
176
+ //# sourceMappingURL=jev_eval_relevance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jev_eval_relevance.js","sourceRoot":"","sources":["../src/jev_eval_relevance.ts"],"names":[],"mappings":"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"}
@@ -1,25 +1,11 @@
1
1
  import { type AnonymousActionSlot, type AnonymousSelectorInput } from "./anonymous_actions.js";
2
- import { RouterCoreError } from "./errors.js";
2
+ import { type JevTokenUsage, type ResolvedJevConfig } from "./jev_client.js";
3
3
  import type { JsonValue } from "./json.js";
4
- import type { Selector } from "./route.js";
5
4
  import type { CustomSelectorInput, SelectorInput } from "./selector_input.js";
6
5
  import { type RoutingDecision } from "./types.js";
7
- export declare const DEFAULT_JEV_ENDPOINT = "https://api.typesafe.ai/v1";
8
- export declare const DEFAULT_JEV_MODEL = "jev-latest";
9
6
  export declare const JEV_SELECTOR_CONTEXT_WINDOW_CHARS = 64000;
10
- export type JevSelectorConfig = {
11
- apiKey: string;
12
- endpoint?: string;
13
- model?: string;
14
- leaseTurns?: readonly number[];
15
- timeoutMs?: number;
16
- };
17
- export type ResolvedJevSelectorConfig = {
18
- apiKey: string;
19
- endpoint: string;
20
- model: string;
7
+ export type JevSelectorConfig = ResolvedJevConfig & {
21
8
  leaseTurns: readonly number[];
22
- timeoutMs: number;
23
9
  };
24
10
  export type JevChoiceQuestion = {
25
11
  type: "choice";
@@ -40,10 +26,6 @@ export type JevChoiceAnswer = {
40
26
  confidence: number;
41
27
  probabilities: Record<string, number>;
42
28
  };
43
- export type JevTokenUsage = {
44
- input_tokens: number;
45
- output_tokens: number;
46
- };
47
29
  export type JevSystemOneResponse = {
48
30
  model: string;
49
31
  answers: {
@@ -52,16 +34,11 @@ export type JevSystemOneResponse = {
52
34
  };
53
35
  usage: JevTokenUsage;
54
36
  };
55
- export declare class JevSelectorOutputError extends RouterCoreError {
56
- readonly usage: JevTokenUsage | null;
57
- constructor(message: string, code: string, usage?: JevTokenUsage | null);
58
- }
59
37
  export type JevSelection = {
60
38
  decision: RoutingDecision;
61
39
  request: JevSystemOneRequest;
62
40
  response: JevSystemOneResponse;
63
41
  };
64
- export declare function resolveJevSelectorConfig(input: JevSelectorConfig): ResolvedJevSelectorConfig;
65
42
  export declare function buildJevSelectorRequest(anonymousInput: AnonymousSelectorInput, options: {
66
43
  model: string;
67
44
  leaseTurns: readonly number[];
@@ -73,5 +50,4 @@ export declare function parseJevSelectorResponse(body: unknown, args: {
73
50
  decision: RoutingDecision;
74
51
  response: JevSystemOneResponse;
75
52
  };
76
- export declare function selectWithJev(selectorInput: SelectorInput | CustomSelectorInput, slots: readonly AnonymousActionSlot[], config: ResolvedJevSelectorConfig, signal?: AbortSignal): Promise<JevSelection>;
77
- export declare function createJevSelector(input: JevSelectorConfig): Selector;
53
+ export declare function selectWithJev(selectorInput: SelectorInput | CustomSelectorInput, slots: readonly AnonymousActionSlot[], config: JevSelectorConfig, signal?: AbortSignal): Promise<JevSelection>;
@@ -1,74 +1,25 @@
1
- // TypeSafe Jev selector over the anonymous-action protocol. Jev
2
- // (docs.typesafe.ai) is a System One model: it answers typed questions about
3
- // a state instead of generating text, so the routing decision is two Choice
4
- // questions over the same state — which anonymous candidate action serves
5
- // the next turns, and how long a lease to commit to — and the answer comes
6
- // back as calibrated probabilities rather than parsed JSON.
1
+ // TypeSafe Jev as a routing selector over the anonymous-action protocol.
2
+ // Jev answers two Choice questions about the de-identified selector input —
3
+ // which anonymous candidate action serves the next turns, and how long a
4
+ // lease to commit to — and the answers come back as calibrated
5
+ // probabilities rather than parsed JSON. The hosted router uses it as the
6
+ // fallback when Dari's policy endpoint is unavailable.
7
7
  //
8
- // This is an alternative to Dari's trained routing policy. Candidates are
9
- // de-identified through the same anonymous-action protocol Dari uses
10
- // (anonymous_actions.ts), so Jev decides on benchmark and cost evidence and
11
- // never sees a model identity; comparisons Jev is weak at (cost ordering,
12
- // benchmark standing) are computed here and passed as text.
13
- import { anonymizeSelectorInput, assignAnonymousActions, sessionRng, } from "./anonymous_actions.js";
14
- import { RouterCoreError } from "./errors.js";
15
- import { RouterFrameworkError } from "./framework_error.js";
16
- import { FIXED_TURN_COST_COMPARISON_TURNS, SELECTOR_LEASE_TURNS } from "./fixed_turn_cost_config.js";
17
- import { isRecord, } from "./types.js";
18
- export const DEFAULT_JEV_ENDPOINT = "https://api.typesafe.ai/v1";
19
- export const DEFAULT_JEV_MODEL = "jev-latest";
20
- const DEFAULT_JEV_TIMEOUT_MS = 30_000;
8
+ // Candidates are de-identified through the same anonymous-action protocol
9
+ // Dari uses (anonymous_actions.ts), so Jev decides on benchmark and cost
10
+ // evidence and never sees a model identity; comparisons Jev is weak at (cost
11
+ // ordering, benchmark standing) are computed here and passed as text.
12
+ import { anonymizeSelectorInput, } from "./anonymous_actions.js";
13
+ import { FIXED_TURN_COST_COMPARISON_TURNS } from "./fixed_turn_cost_config.js";
14
+ import { invalidJevResponse, JevOutputError, postSystemOne, } from "./jev_client.js";
15
+ import { fillPrompt, JEV_SELECTOR_PROMPT } from "./prompts.js";
16
+ import { isRecord } from "./types.js";
21
17
  // Jev evaluates up to 32k tokens of state plus the longest question, and
22
18
  // JSON-shaped conversation state tokenizes at roughly 2.7 characters per
23
19
  // token. The selector request is sized on its JSON-escaped messages, which
24
20
  // overstate the state Jev receives, so this budget leaves room for the
25
21
  // questions and the action descriptions.
26
22
  export const JEV_SELECTOR_CONTEXT_WINDOW_CHARS = 64_000;
27
- // Rate-limit and overload responses get one backoff retry; anything else is
28
- // the caller's failure to surface.
29
- const JEV_RETRY_STATUSES = new Set([429, 529]);
30
- const JEV_ATTEMPTS = 2;
31
- const JEV_RETRY_DELAY_MS = 500;
32
- const JEV_RETRY_MAX_DELAY_MS = 5_000;
33
- // selector_output after TypeSafe returned 200. Token counts are on the body
34
- // even when the action or lease answer is rejected, so hosts can still bill
35
- // the selector call.
36
- export class JevSelectorOutputError extends RouterCoreError {
37
- usage;
38
- constructor(message, code, usage = null) {
39
- super("selector_output", message, code);
40
- this.name = "JevSelectorOutputError";
41
- this.usage = usage;
42
- }
43
- }
44
- export function resolveJevSelectorConfig(input) {
45
- const apiKey = input.apiKey?.trim() ?? "";
46
- if (!apiKey)
47
- throw configurationError("Jev selector requires an apiKey.", "apiKey");
48
- const endpoint = (input.endpoint ?? DEFAULT_JEV_ENDPOINT).trim().replace(/\/+$/, "");
49
- let url;
50
- try {
51
- url = new URL(endpoint);
52
- }
53
- catch {
54
- throw configurationError("Jev selector endpoint must be a valid URL.", "endpoint");
55
- }
56
- if ((url.protocol !== "https:" && url.protocol !== "http:") || url.username || url.password) {
57
- throw configurationError("Jev selector endpoint must be an HTTP(S) URL without embedded credentials.", "endpoint");
58
- }
59
- const model = (input.model ?? DEFAULT_JEV_MODEL).trim();
60
- if (!model)
61
- throw configurationError("Jev selector model must not be empty.", "model");
62
- const leaseTurns = input.leaseTurns ?? SELECTOR_LEASE_TURNS;
63
- if (leaseTurns.some((turns, index) => !Number.isSafeInteger(turns) || turns <= 0 || (index > 0 && turns <= leaseTurns[index - 1]))) {
64
- throw configurationError("Jev selector leaseTurns must be ascending positive integers.", "leaseTurns");
65
- }
66
- const timeoutMs = input.timeoutMs ?? DEFAULT_JEV_TIMEOUT_MS;
67
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
68
- throw configurationError("Jev selector timeoutMs must be a positive integer.", "timeoutMs");
69
- }
70
- return { apiKey, endpoint, model, leaseTurns, timeoutMs };
71
- }
72
23
  // Renders the de-identified selector input as a Jev request. Every candidate
73
24
  // identity has already been replaced by its action letter; the only evidence
74
25
  // an action carries is its projected cost and its benchmark standing.
@@ -95,7 +46,6 @@ export function buildJevSelectorRequest(anonymousInput, options) {
95
46
  };
96
47
  }
97
48
  const state = {
98
- task: anonymousInput.task,
99
49
  previous_action: anonymousInput.previous_action === null
100
50
  ? null
101
51
  : anonymousInput.previous_action.action,
@@ -113,15 +63,14 @@ export function buildJevSelectorRequest(anonymousInput, options) {
113
63
  action: {
114
64
  type: "choice",
115
65
  instructions: {
116
- question: "Which action should serve the coding agent's next turns?",
117
- rules: [
118
- "Capability first: the action must be able to handle the task; capability is never traded away for cost.",
119
- "Judge the task's difficulty and how much work remains from `task`, `lease_history`, and the latest turns of `conversation`.",
120
- "Use each action's benchmark scores and ranks as evidence of capability. A missing benchmark means the action was not measured on it, not that it scored poorly.",
121
- "When several actions are capable enough, choose the one with the best (lowest-numbered) cost rank.",
122
- "Prefer `previous_action` while the task and its current phase have not changed.",
123
- ],
66
+ question: JEV_SELECTOR_PROMPT.action.question,
67
+ rules: anonymousInput.router_rules === undefined
68
+ ? JEV_SELECTOR_PROMPT.action.rules
69
+ : [...JEV_SELECTOR_PROMPT.action.rules, JEV_SELECTOR_PROMPT.action.router_rules_rule],
124
70
  ...(Object.keys(glossary).length === 0 ? {} : { benchmarks: glossary }),
71
+ ...(anonymousInput.router_rules === undefined
72
+ ? {}
73
+ : { router_rules: anonymousInput.router_rules }),
125
74
  },
126
75
  criteria,
127
76
  },
@@ -130,23 +79,18 @@ export function buildJevSelectorRequest(anonymousInput, options) {
130
79
  return { model: options.model, state, questions };
131
80
  }
132
81
  function leaseQuestion(leaseTurns) {
82
+ const { question, guidance, criteria: templates } = JEV_SELECTOR_PROMPT.lease;
133
83
  const last = leaseTurns.length - 1;
134
84
  const criteria = {};
135
85
  leaseTurns.forEach((turns, index) => {
136
- criteria[String(turns)] = index === 0
137
- ? `${turns} turns: the task is nearly done, or the next step may change what the agent needs.`
86
+ const template = index === 0
87
+ ? templates.shortest
138
88
  : index === last
139
- ? `${turns} turns: a long stretch of similar work remains, such as a large implementation or many edit-and-test cycles.`
140
- : `${turns} turns: a bounded stretch of similar work remains.`;
89
+ ? templates.longest
90
+ : templates.middle;
91
+ criteria[String(turns)] = fillPrompt(template, { turns: String(turns) });
141
92
  });
142
- return {
143
- type: "choice",
144
- instructions: {
145
- question: "How many consecutive turns should the chosen action serve before the router reconsiders?",
146
- guidance: "A longer lease amortizes the cold start paid on its first turn across warm cache hits on the turns that follow. The lease ends early only if the task finishes or the provider fails. Judge from how much similar work remains after the latest turn of `conversation`.",
147
- },
148
- criteria,
149
- };
93
+ return { type: "choice", instructions: { question, guidance }, criteria };
150
94
  }
151
95
  export function parseJevSelectorResponse(body, args) {
152
96
  if (!isRecord(body) || typeof body.model !== "string" || !isRecord(body.answers)) {
@@ -165,7 +109,7 @@ export function parseJevSelectorResponse(body, args) {
165
109
  const action = choiceAnswer(body.answers.action, "action", tokenUsage);
166
110
  const slot = args.slots.find((entry) => entry.action === action.choice);
167
111
  if (slot === undefined) {
168
- throw new JevSelectorOutputError("Jev selected an action outside this router's candidates.", "selector_invalid_candidate", tokenUsage);
112
+ throw new JevOutputError("Jev selected an action outside this router's candidates.", "selector_invalid_candidate", tokenUsage);
169
113
  }
170
114
  let lease;
171
115
  let turns;
@@ -203,9 +147,6 @@ function decisionReason(action, turns, lease) {
203
147
  // Anonymizes the named selector input against the slots, asks Jev, and maps
204
148
  // the answers back onto the slots' candidates.
205
149
  export async function selectWithJev(selectorInput, slots, config, signal) {
206
- if ("custom_rules" in selectorInput || "default_target" in selectorInput) {
207
- throw new RouterCoreError("configuration", "The Jev selector serves only the default routing policy and cannot apply custom routing rules.", "custom_rules_not_supported");
208
- }
209
150
  const anonymousInput = anonymizeSelectorInput(selectorInput, slots);
210
151
  const request = buildJevSelectorRequest(anonymousInput, config);
211
152
  const body = await postSystemOne(request, config, signal);
@@ -221,126 +162,6 @@ export async function selectWithJev(selectorInput, slots, config, signal) {
221
162
  // Letters are assigned per call: the framework path carries no conversation
222
163
  // identity, and every candidate-bearing field is re-lettered together, so
223
164
  // correctness never depends on cross-turn stability.
224
- export function createJevSelector(input) {
225
- const config = resolveJevSelectorConfig(input);
226
- return {
227
- async select(request, signal) {
228
- const selectorInput = selectorInputFromRequest(request);
229
- const single = singleCandidate(selectorInput);
230
- if (single !== undefined) {
231
- return JSON.stringify({
232
- selected_model: single.model,
233
- reasoning_effort: single.reasoningEffort,
234
- reason: "The router has only one compatible model/thinking-level pair.",
235
- });
236
- }
237
- const candidates = selectorInput.candidate_pairs.map((pair) => ({
238
- model: pair.model,
239
- reasoningEffort: pair.thinking_level,
240
- }));
241
- const rng = typeof request.prompt_cache_key === "string" && request.prompt_cache_key !== ""
242
- ? sessionRng(request.prompt_cache_key)
243
- : Math.random;
244
- let selection;
245
- try {
246
- selection = await selectWithJev(selectorInput, assignAnonymousActions(candidates, rng), config, signal);
247
- }
248
- catch (error) {
249
- throw frameworkError(error);
250
- }
251
- const { decision, response } = selection;
252
- return JSON.stringify({
253
- selected_model: decision.selectedModel,
254
- reasoning_effort: decision.reasoningEffort,
255
- reason: decision.reason,
256
- ...(decision.leaseTurnsRemaining === undefined
257
- ? {}
258
- : { lease_turns_remaining: decision.leaseTurnsRemaining }),
259
- jev: response,
260
- });
261
- },
262
- };
263
- }
264
- function singleCandidate(selectorInput) {
265
- if (selectorInput.candidate_pairs.length !== 1)
266
- return undefined;
267
- const pair = selectorInput.candidate_pairs[0];
268
- return { model: pair.model, reasoningEffort: pair.thinking_level };
269
- }
270
- async function postSystemOne(request, config, signal) {
271
- for (let attempt = 1;; attempt += 1) {
272
- const response = await fetchSystemOne(request, config, signal);
273
- if (response.ok) {
274
- try {
275
- return await response.json();
276
- }
277
- catch {
278
- throw invalidResponse("Jev returned a non-JSON response body.");
279
- }
280
- }
281
- if (attempt < JEV_ATTEMPTS && JEV_RETRY_STATUSES.has(response.status)) {
282
- await delay(retryDelayMs(response.headers.get("retry-after")), signal);
283
- continue;
284
- }
285
- const body = await response.text().catch(() => "");
286
- throw new RouterFrameworkError("policy", `Jev returned HTTP ${response.status}: ${body}`, "jev_http_error");
287
- }
288
- }
289
- async function fetchSystemOne(request, config, signal) {
290
- const timeout = AbortSignal.timeout(config.timeoutMs);
291
- try {
292
- return await fetch(`${config.endpoint}/systemone`, {
293
- method: "POST",
294
- headers: {
295
- Authorization: `Bearer ${config.apiKey}`,
296
- "Content-Type": "application/json",
297
- },
298
- body: JSON.stringify(request),
299
- signal: signal === undefined ? timeout : AbortSignal.any([signal, timeout]),
300
- });
301
- }
302
- catch (error) {
303
- if (signal?.aborted)
304
- throw error;
305
- throw new RouterFrameworkError("policy", `Jev request failed: ${error instanceof Error ? error.message : String(error)}`, "jev_request_failed", undefined, { cause: error });
306
- }
307
- }
308
- function retryDelayMs(retryAfter) {
309
- const seconds = Number(retryAfter);
310
- if (retryAfter === null || !Number.isFinite(seconds) || seconds <= 0)
311
- return JEV_RETRY_DELAY_MS;
312
- return Math.min(seconds * 1000, JEV_RETRY_MAX_DELAY_MS);
313
- }
314
- function delay(ms, signal) {
315
- return new Promise((resolve, reject) => {
316
- const timer = setTimeout(() => {
317
- signal?.removeEventListener("abort", onAbort);
318
- resolve();
319
- }, ms);
320
- const onAbort = () => {
321
- clearTimeout(timer);
322
- reject(signal?.reason);
323
- };
324
- signal?.addEventListener("abort", onAbort, { once: true });
325
- });
326
- }
327
- function selectorInputFromRequest(request) {
328
- const user = request.messages?.find((message) => message.role === "user");
329
- if (typeof user?.content !== "string") {
330
- throw new RouterCoreError("invalid_request", "Selector request is missing the JSON user message.", "invalid_request_error");
331
- }
332
- let parsed;
333
- try {
334
- parsed = JSON.parse(user.content);
335
- }
336
- catch {
337
- throw new RouterCoreError("invalid_request", "Selector request user message is not valid JSON.", "invalid_request_error");
338
- }
339
- if (!isRecord(parsed) || !Array.isArray(parsed.candidate_pairs) || !Array.isArray(parsed.messages)) {
340
- throw new RouterCoreError("invalid_request", "Selector input is missing candidate_pairs or messages.", "invalid_request_error");
341
- }
342
- return parsed;
343
- }
344
165
  // Cost per action as text Jev can compare literally: the loop projections at
345
166
  // each lease length, plus a rank across actions on the comparison horizon.
346
167
  function actionCosts(costs) {
@@ -403,16 +224,5 @@ function usd(value) {
403
224
  function percent(value) {
404
225
  return `${Math.round(value * 100)}%`;
405
226
  }
406
- function invalidResponse(message, usage = null) {
407
- return new JevSelectorOutputError(message, "selector_invalid_response", usage);
408
- }
409
- function configurationError(message, param) {
410
- return new RouterCoreError("configuration", message, "jev_configuration_invalid", param);
411
- }
412
- function frameworkError(error) {
413
- if (!(error instanceof RouterCoreError))
414
- return error;
415
- const kind = error.kind === "selector_output" ? "policy" : error.kind;
416
- return new RouterFrameworkError(kind, error.message, error.code, error.param, { cause: error });
417
- }
227
+ const invalidResponse = invalidJevResponse;
418
228
  //# sourceMappingURL=jev_selector.js.map