authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-10-11 00:47:45+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-10 17:47:45-05:00
log160b18c8667f8370f46708d273d368648316b200
tree754ce8729eee5a6b74d46ddf68680e7053fa103a
parent7e8071f1f1566220fb52cbfaaf97a2bc4e93a193
signature Signed by PGP key B5690EEEBB952194

fix(pi): keep thinking defaults specific to each model (#17835)


3 files changed, 62 insertions(+), 16 deletions(-)

docs/user/providers-pi.md+2-1
......@@ -36,7 +36,8 @@ update it through that manager instead. For example, a Nix profile installation
3636## What Carries Over
3737
3838T3 Code discovers the models reported by Pi and exposes their supported thinking levels. The
39thinking picker marks Pi's current configured level as the default without overriding it. Threads
39thinking picker marks the discovered active model's configured level as its default without
40overriding it. Other models keep their Pi defaults when no level is selected. Threads
4041use Pi's native session files for resume, rollback, and forks within the same Pi instance. Forks
4142preserve the native conversation through the selected turn in the destination workspace.
4243Switching providers uses portable conversation context. Extension
packages/provider-pi/src/server/driver.test.ts+49-6
......@@ -47,7 +47,9 @@ const personalSkill = {
4747};
4848
4949// Respond through the real stdio transport, with a distinct command catalog for each cwd.
50const makePiSpawner = Effect.gen(function* () {
50const makePiSpawner = Effect.fnUntraced(function* (
51 discovery: { readonly state?: unknown; readonly models?: ReadonlyArray<unknown> } = {},
52) {
5153 const pendingCommand = yield* Deferred.make<void>();
5254 const launches: Array<ChildProcess.StandardCommand> = [];
5355 const spawner = ChildProcessSpawner.make((command) =>
......@@ -92,8 +94,10 @@ const makePiSpawner = Effect.gen(function* () {
9294 ],
9395 }
9496 : request.type === "get_available_models"
95 ? { models: [{ provider: "test", id: "model" }] }
96 : {};
97 ? { models: discovery.models ?? [{ provider: "test", id: "model" }] }
98 : request.type === "get_state"
99 ? (discovery.state ?? {})
100 : {};
97101 return Queue.offer(
98102 stdout,
99103 encoder.encode(
......@@ -125,7 +129,7 @@ const create = (config: Partial<PiSettings> = {}, enabled = true) =>
125129it.layer(layerTest)("PiDriver workspace discovery", (it) => {
126130 it.effect("keeps each workspace's skills and commands separate from the machine catalog", () =>
127131 Effect.gen(function* () {
128 const { spawner, launches } = yield* makePiSpawner;
132 const { spawner, launches } = yield* makePiSpawner();
129133 const instance = yield* create({ launchArgs: '--approve --skill "extra skill"' }).pipe(
130134 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
131135 );
......@@ -186,11 +190,50 @@ it.layer(layerTest)("PiDriver workspace discovery", (it) => {
186190 }).pipe(Effect.scoped),
187191 );
188192
193 it.effect.each([true, false])(
194 "limits discovered thinking defaults to the known active model, known=%s",
195 (known) =>
196 Effect.gen(function* () {
197 const { spawner } = yield* makePiSpawner({
198 state: {
199 thinkingLevel: "high",
200 ...(known ? { model: { provider: "test", id: "model" } } : {}),
201 },
202 models: [
203 { provider: "test", id: "model", reasoning: true },
204 { provider: "other", id: "model", reasoning: true },
205 { provider: "test", id: "other", reasoning: true },
206 ],
207 });
208 const instance = yield* create().pipe(
209 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
210 );
211 yield* instance.snapshot.refresh;
212 const snapshot = yield* instance.snapshot.getSnapshot;
213 assert.deepEqual(
214 snapshot.models.map((model) => [
215 model.slug,
216 (model.capabilities?.optionDescriptors ?? []).flatMap((descriptor) =>
217 descriptor.type === "select"
218 ? descriptor.options.filter((option) => option.isDefault).map((option) => option.id)
219 : [],
220 ),
221 ]),
222 [
223 ["default", []],
224 ["test/model", known ? ["high"] : []],
225 ["other/model", []],
226 ["test/other", []],
227 ],
228 );
229 }).pipe(Effect.scoped),
230 );
231
189232 it.effect(
190233 "fails command discovery instead of returning an empty successful workspace catalog",
191234 () =>
192235 Effect.gen(function* () {
193 const { spawner } = yield* makePiSpawner;
236 const { spawner } = yield* makePiSpawner();
194237 const instance = yield* create().pipe(
195238 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
196239 );
......@@ -208,7 +251,7 @@ it.layer(layerTest)("PiDriver workspace discovery", (it) => {
208251
209252 it.effect("times out workspace discovery that needs interactive input", () =>
210253 Effect.gen(function* () {
211 const { spawner, pendingCommand } = yield* makePiSpawner;
254 const { spawner, pendingCommand } = yield* makePiSpawner();
212255 const instance = yield* create().pipe(
213256 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
214257 );
packages/provider-pi/src/server/status.ts+11-9
......@@ -101,12 +101,12 @@ function piModelsFromSettings(
101101 );
102102}
103103
104function parseDiscoveredModels(
105 data: unknown,
106 defaultThinkingLevel: unknown,
107): ReadonlyArray<ServerProviderModel> {
104function parseDiscoveredModels(data: unknown, state: unknown): ReadonlyArray<ServerProviderModel> {
108105 const models = recordField(data, "models");
109106 if (!Array.isArray(models)) return [];
107 const activeModel = recordField(state, "model");
108 const activeProvider = recordString(activeModel, "provider");
109 const activeModelId = recordString(activeModel, "id");
110110 const seen = new Set<string>();
111111 const parsed: Array<ServerProviderModel> = [];
112112 for (const model of models) {
......@@ -121,7 +121,12 @@ function parseDiscoveredModels(
121121 name: recordString(model, "name") ?? slug,
122122 subProvider: provider,
123123 isCustom: false,
124 capabilities: thinkingCapabilitiesForPiModel(model, defaultThinkingLevel),
124 capabilities: thinkingCapabilitiesForPiModel(
125 model,
126 provider === activeProvider && id === activeModelId
127 ? recordString(state, "thinkingLevel")
128 : undefined,
129 ),
125130 });
126131 }
127132 return parsed;
......@@ -167,10 +172,7 @@ const discoverPiViaRpc = (
167172 const commandsData = yield* connection
168173 .request({ type: "get_commands" })
169174 .pipe(Effect.orElseSucceed(() => undefined));
170 const discoveredModels = parseDiscoveredModels(
171 modelsData,
172 recordString(stateData, "thinkingLevel"),
173 );
175 const discoveredModels = parseDiscoveredModels(modelsData, stateData);
174176 const { slashCommands, skills } = parsePiDiscoveredCommands(commandsData);
175177 return {
176178 models: discoveredModels,