pi-switchback 0.0.0-stage → 2026.10.14
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/index.ts +633 -0
- package/package.json +54 -5
- package/src/build-route.ts +74 -0
- package/src/dialogue-classifier.ts +672 -0
- package/src/dialogue-ui.ts +201 -0
- package/src/dialogue.ts +425 -0
- package/src/index.ts +18 -0
- package/src/local-classifier.ts +125 -0
- package/src/model-picker.ts +228 -0
- package/src/secrets.ts +328 -0
- package/src/thinking-support.ts +35 -0
- package/README.md +0 -3
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a routing decision into the request pi dispatches.
|
|
3
|
+
*
|
|
4
|
+
* `packages/core/src/routing.ts` decides *where* a request should go and
|
|
5
|
+
* `packages/core/src/dispatch.ts` settles the reasoning level; this module expresses that
|
|
6
|
+
* as pi's `ModelRoute`: it resolves the chosen id back to a pi `Model`, clamps the
|
|
7
|
+
* level to what that model actually implements, and attaches the router state.
|
|
8
|
+
*
|
|
9
|
+
* The decision types themselves live in `packages/core/src/decision.ts`: they are the one
|
|
10
|
+
* thing the core and every host adapter must agree on, and a core module cannot
|
|
11
|
+
* import them from a pi module.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { clampThinkingLevel } from "@earendil-works/pi-ai";
|
|
15
|
+
import type { ModelRoute } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { lookupModel } from "switchback-core/availability.ts";
|
|
17
|
+
import type { Decision } from "switchback-core/decision.ts";
|
|
18
|
+
import type { ThinkingLevelId } from "switchback-core/ports.ts";
|
|
19
|
+
import type { SwitchbackState } from "switchback-core/types.ts";
|
|
20
|
+
import type { PiModelLookup } from "./thinking-support.ts";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The slice of pi's model registry this module reads: the catalog lookup owned
|
|
24
|
+
* by `./thinking-support.ts`. Narrower than pi's whole registry, but `find` still
|
|
25
|
+
* hands back pi's own `Model`, which is what `ModelRoute` and pi's
|
|
26
|
+
* `clampThinkingLevel` take.
|
|
27
|
+
*/
|
|
28
|
+
type PiRegistry = PiModelLookup;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a `ModelRoute` from a `Decision`.
|
|
32
|
+
*
|
|
33
|
+
* This function NEVER throws for "config-invalid" decisions or for decisions on
|
|
34
|
+
* greyed entries. It only throws when the picked effective model was lost between
|
|
35
|
+
* `decide()` and `buildRoute()` (e.g. registry state changed mid-call) - in that
|
|
36
|
+
* case the caller's availability resolution was stale and the caller should retry.
|
|
37
|
+
*/
|
|
38
|
+
export function buildRoute(
|
|
39
|
+
registry: PiRegistry,
|
|
40
|
+
decision: Decision,
|
|
41
|
+
thinkingLevel: ThinkingLevelId,
|
|
42
|
+
nextState: SwitchbackState | undefined,
|
|
43
|
+
): ModelRoute<SwitchbackState> {
|
|
44
|
+
if (decision.kind === "config-invalid") {
|
|
45
|
+
throw new ConfigInvalidError(decision);
|
|
46
|
+
}
|
|
47
|
+
if (decision.kind === "exhausted") {
|
|
48
|
+
throw new Error(`switchback: ${decision.reason}`);
|
|
49
|
+
}
|
|
50
|
+
const model = lookupModel(registry, decision.modelId);
|
|
51
|
+
if (!model)
|
|
52
|
+
throw new Error(
|
|
53
|
+
`switchback: model "${decision.modelId}" not in catalog (lost between decide() and buildRoute())`,
|
|
54
|
+
);
|
|
55
|
+
// Clamp to the routed model. A virtual level the physical model does not implement
|
|
56
|
+
// (zai has no "medium" and cannot disable thinking at all) is otherwise sent as the
|
|
57
|
+
// provider default, which is what makes "I changed reasoning effort and nothing
|
|
58
|
+
// happened" look like a bug. Pi clamps too; doing it here keeps the returned route
|
|
59
|
+
// honest for every caller.
|
|
60
|
+
const route: ModelRoute<SwitchbackState> = { model, thinkingLevel: clampThinkingLevel(model, thinkingLevel) };
|
|
61
|
+
if (nextState !== undefined) route.state = nextState;
|
|
62
|
+
return route;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class ConfigInvalidError extends Error {
|
|
66
|
+
readonly decision: Extract<Decision, { kind: "config-invalid" }>;
|
|
67
|
+
constructor(decision: Extract<Decision, { kind: "config-invalid" }>) {
|
|
68
|
+
super(
|
|
69
|
+
`switchback: config invalid (${decision.reason}); effective=[${decision.effective.join(", ")}] greyed=[${decision.greyed.map((g) => `${g.id}:${g.reason}`).join(", ")}]`,
|
|
70
|
+
);
|
|
71
|
+
this.name = "ConfigInvalidError";
|
|
72
|
+
this.decision = decision;
|
|
73
|
+
}
|
|
74
|
+
}
|