Repository navigation
Expand file tree
/
Copy pathindex.ts
More file actions
147 lines (133 loc) · 5.47 KB
/
Copy pathindex.ts
File metadata and controls
147 lines (133 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Resolves routing; model additions also require mint allowlists and gateway prompt/transport support.
import { GPT5_6_SOL_MODEL, Harness, Sequence } from '@shared/constants';
import { resolveHarness } from './harness';
import { triageModelFor, type EffortLevel } from './models';
import { resolveSequence } from './sequence';
// ── Shared machinery ────────────────────────────────────────────────────
/** Which precedence rung decided each axis. Stamped by middlewares as they assert. */
export interface SwitchboardTrace {
harness?: 'cli' | 'flag' | 'binding';
model?: 'cli' | 'flag' | 'binding';
sequence?:
| 'cli'
| 'composed'
| 'runtask-clamp'
| 'payload'
| 'flag'
| 'binding';
}
/** Everything a resolver middleware may branch on. Built once per run. */
export interface SwitchboardCtx {
program: string;
/** The program's own binding: the base every override and flag lands on. */
binding: ProgramBinding;
/** Composed sub-run (a dependency inside a parent program). Structurally linear — no override can orchestrate it. */
composed?: boolean;
flags: Record<string, string>;
/** Flag payloads from the same snapshot (payload-carrying flags, e.g. self-driving pi). */
flagPayloads?: Record<string, unknown>;
/** CLI override (`--harness`). Wins over `flags`. */
cliHarness?: Harness;
/** CLI override (`--sequence`). Wins over `flags`. */
cliSequence?: Sequence;
/** CLI override (`--model`, gateway id). Wins over the binding's model. */
cliModel?: string;
/** Filled during resolution; read by the caller for telemetry. */
trace?: SwitchboardTrace;
}
/** A resolver middleware: defer via `next()`, or assert by returning a value. */
export type Middleware<D> = (ctx: SwitchboardCtx, next: () => D) => D;
/**
* Run a middleware chain over `ctx`. Each middleware receives `next` (which
* runs the rest of the chain) and can either:
* - defer: call `next()` and optionally modify its result (overlay pattern)
* - short-circuit: return a value without calling `next()` (skip the rest)
*
* **Earlier in the array = higher precedence.** Index 0 runs first and can
* short-circuit the rest; index 1 only runs if index 0 deferred. So
* `[cliSequenceMw, orchestratorFeatureFlagMw]` means CLI takes precedence over the
* flag, not the other way around.
*
* `fallback` runs at the end — reached only when every middleware deferred.
* Typically the map read for the base value.
*/
export function runChain<D>(
chain: Middleware<D>[],
ctx: SwitchboardCtx,
fallback: () => D,
): D {
function step(index: number): D {
if (index >= chain.length) return fallback();
const middleware = chain[index];
const next = () => step(index + 1);
return middleware(ctx, next);
}
return step(0);
}
// ── Data model ──────────────────────────────────────────────────────────
/** Harness + model for one leaf of agent work. */
export interface HarnessPick {
harness: Harness;
/** Gateway model id (string). */
model: string;
/** Reasoning-effort override. Absent → the model's table default. */
thinkingLevel?: EffortLevel;
}
export interface ProgramBinding {
sequence: Sequence;
harness: Harness;
model: string;
/** Reasoning-effort override for the model. Absent → the model's table default. */
thinkingLevel?: EffortLevel;
/**
* Per-role overrides applied only in orchestrator mode — keys are
* agent-prompt `type` values published by context-mill (`'seed'`,
* `'install'`, `'capture'`, etc.). Linear runs use role `'default'` and
* skip this map.
*/
contextMillOverride?: Record<string, Partial<HarnessPick>>;
}
// Legacy fallback; new programs should explicitly choose Pi and prefer orchestration.
export const DEFAULT_BINDING: ProgramBinding = {
sequence: Sequence.linear,
harness: Harness.pi,
model: GPT5_6_SOL_MODEL,
thinkingLevel: 'medium',
};
// ── Unified resolver ────────────────────────────────────────────────────
/** Compose both axes. Callers needing only one axis use the per-axis resolver. */
export function resolveBinding(
ctx: SwitchboardCtx,
role = 'default',
): ProgramBinding {
ctx.trace ??= {};
const sequence = resolveSequence(ctx);
const { harness, model, thinkingLevel } = resolveHarness(ctx, role);
return { sequence, harness, model, thinkingLevel };
}
/**
* A scan's binding: linear on a triage model. The first attempt takes the
* harness the run resolves to; the retry runs the SDK, a second provider,
* whatever the flags say.
*/
export function resolveScanBinding(
ctx: SwitchboardCtx,
attempt: 'first' | 'retry',
): ProgramBinding {
const harness =
attempt === 'retry' ? Harness.anthropic : resolveBinding(ctx).harness;
return { sequence: Sequence.linear, harness, model: triageModelFor(harness) };
}
// ── Unified re-export surface ───────────────────────────────────────────
export { HARNESS_OPTIONS, getHarness, resolveHarness } from './harness';
export {
SEQUENCE_OPTIONS,
getSequence,
resolveSequence,
type SequenceRunner,
} from './sequence';
export {
isOrchestratorEnabled,
areSeededTasksEnabled,
resolveStageOverrides,
} from './flags';