switchback-core 2026.10.14 → 2026.10.16

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/src/types.ts CHANGED
@@ -5,46 +5,57 @@
5
5
  /** A physical-model preference as "provider/id" (e.g. "zai/glm-4.7"). */
6
6
  export type ModelId = string;
7
7
 
8
- /** Jev classifier configuration (locked at the top of the user config). */
9
- export interface JevConfig {
10
- /** Provider id under which the classifier model is registered (e.g. "typesafe"). */
8
+ /**
9
+ * A decision-model address as "provider/model-id" - the same shape as a
10
+ * fallback entry, e.g. "typesafe/jev-latest" or "ollama/tev1:0.8b".
11
+ */
12
+ export type DecisionModelId = string;
13
+
14
+ /**
15
+ * A decision model after address resolution.
16
+ *
17
+ * With a `baseUrl` the host drives the SystemOne endpoint directly (both pi and
18
+ * OpenCode can do this). Without one the host resolves the address from its own
19
+ * classifier catalog - pi has one, OpenCode does not, so a catalog-only address
20
+ * stays an explicit "no classifier" cycle there.
21
+ */
22
+ export interface DecisionModelConfig {
23
+ /** Provider id (e.g. "typesafe", "ollama", "openrouter"). */
11
24
  provider: string;
12
- /** Classifier model id (e.g. "jev-latest"). */
25
+ /** Model id within that provider (e.g. "jev-latest", "tev1:0.8b"). */
13
26
  id: string;
14
- /**
15
- * Optional direct endpoint. When set, switchback registers this classifier
16
- * itself instead of looking it up in pi's catalog - for a local System One
17
- * server such as Ollama v0.35+ (`http://localhost:11434/v1`). This is
18
- * deliberately outside pi's normal provider/model configuration: exactly one
19
- * decision model, configured here, used only by this extension.
20
- */
27
+ /** Direct SystemOne endpoint. When set, no catalog lookup happens. */
21
28
  baseUrl?: string;
22
- /** Wire API for the direct endpoint. Only "typesafe-system-one" is supported today. */
23
- api?: string;
24
- /** Bearer token for the direct endpoint, or `secret:<name>` referencing the encrypted store. */
29
+ /** Bearer token for the direct endpoint, or `secret:<name>` referencing a secret store. */
25
30
  apiKey?: string;
26
31
  }
27
32
 
28
- /** A named, reusable decision-model entry (`decisionModels:` in the config). */
29
- export interface DecisionModelEntry extends JevConfig {
30
- /** Unique name `models[].jev.decisionModel` references. */
31
- name: string;
33
+ /**
34
+ * Connection details for one decision-model address - a `decisionModels:` map
35
+ * value. `baseUrl` and `apiKey` come as a pair: a direct endpoint needs a bearer
36
+ * token even when the endpoint ignores it (`apiKey: local`).
37
+ */
38
+ export interface DecisionModelEntry {
39
+ /** Direct SystemOne endpoint (http/https). */
40
+ baseUrl?: string;
41
+ /** Bearer token, or `secret:<name>`. Required with `baseUrl`. */
42
+ apiKey?: string;
32
43
  }
33
44
 
34
- /** A model's classifier: inline config, or a `decisionModel:` reference by name. */
35
- export type JevRef = JevConfig | { decisionModel: string };
36
-
37
45
  /**
38
- * A config entry after reference resolution: `jev` is always the inline form.
39
- * This is what the router and the extension consume; the reference form exists
40
- * only in the YAML file and the config editor.
46
+ * A config entry after address resolution: `decisionModel` is always the
47
+ * provider/id descriptor. This is what the router and the extension consume; the
48
+ * address form exists only in the YAML file and the config editor.
41
49
  */
42
- export type ResolvedSwitchbackConfig = Omit<SwitchbackConfig, "jev"> & { jev?: JevConfig };
50
+ export type ResolvedSwitchbackConfig = Omit<SwitchbackConfig, "decisionModel"> & {
51
+ decisionModel?: DecisionModelConfig;
52
+ };
43
53
 
44
- /** A config file after aggregation and reference resolution. */
54
+ /** A config file after aggregation and address resolution. */
45
55
  export interface ResolvedSwitchbackFileConfig {
46
56
  models: ResolvedSwitchbackConfig[];
47
- decisionModels?: DecisionModelEntry[];
57
+ /** Resolved `decisionModels:` entries, keyed by their address. */
58
+ decisionModels?: Record<DecisionModelId, DecisionModelConfig>;
48
59
  debug?: boolean;
49
60
  }
50
61
 
@@ -63,8 +74,12 @@ export interface SwitchbackConfig {
63
74
  name: string;
64
75
  /** Ordered preference of physical models ("provider/id"). First entry is the preferred model. */
65
76
  fallbacks: ModelId[];
66
- /** Inline classifier config or a `decisionModel:` name reference. Absent: report and blind-cycle. */
67
- jev?: JevRef;
77
+ /**
78
+ * Decision model as "provider/model-id". Connection details for direct
79
+ * endpoints live in the top-level `decisionModels:` map under the same
80
+ * address. Absent: report and blind-cycle.
81
+ */
82
+ decisionModel?: DecisionModelId;
68
83
  /** Return to the head of the list after this much idle time (default `never`). */
69
84
  idleReset?: IdleResetConfig;
70
85
  }
@@ -72,8 +87,12 @@ export interface SwitchbackConfig {
72
87
  /** Top-level switchback configuration. */
73
88
  export interface SwitchbackFileConfig {
74
89
  models: SwitchbackConfig[];
75
- /** Named, reusable decision models; `models[].jev.decisionModel` references these. */
76
- decisionModels?: DecisionModelEntry[];
90
+ /**
91
+ * Connection details for decision-model addresses that need a direct
92
+ * SystemOne endpoint, keyed by the same "provider/model-id" address a chain
93
+ * names in `decisionModel:`.
94
+ */
95
+ decisionModels?: Record<DecisionModelId, DecisionModelEntry>;
77
96
  /**
78
97
  * Emit one diagnostics notification per model switch (the classifier's verdict
79
98
  * and how the requested reasoning category resolved against the activated
@@ -149,5 +168,11 @@ export interface SwitchbackState {
149
168
  /** Cross-session blocked-until map. Keyed by "provider/id", value is epoch ms. */
150
169
  export type BlockedMap = Record<ModelId, number>;
151
170
 
171
+ /**
172
+ * Manually disabled models. Keyed by "provider/id", value is the epoch ms of the
173
+ * disable. Never expires; only an explicit enable removes an entry.
174
+ */
175
+ export type DisabledMap = Record<ModelId, number>;
176
+
152
177
  /** Reason for a route() decision. */
153
178
  export type RouteReason = "user" | "continuation" | "retry" | "direct";