From 160b18c8667f8370f46708d273d368648316b200 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:47:45 +0200 Subject: [PATCH] fix(pi): keep thinking defaults specific to each model (#17835) --- docs/user/providers-pi.md | 3 +- .../provider-pi/src/server/driver.test.ts | 55 +++++++++++++++++-- packages/provider-pi/src/server/status.ts | 20 ++++--- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/docs/user/providers-pi.md b/docs/user/providers-pi.md index f86f9923ef8d60dc039f974e4a8c2925d891be48..123258f6ccb43a777110a4a63baa569516684375 100644 --- a/docs/user/providers-pi.md +++ b/docs/user/providers-pi.md @@ -36,7 +36,8 @@ update it through that manager instead. For example, a Nix profile installation ## What Carries Over T3 Code discovers the models reported by Pi and exposes their supported thinking levels. The -thinking picker marks Pi's current configured level as the default without overriding it. Threads +thinking picker marks the discovered active model's configured level as its default without +overriding it. Other models keep their Pi defaults when no level is selected. Threads use Pi's native session files for resume, rollback, and forks within the same Pi instance. Forks preserve the native conversation through the selected turn in the destination workspace. Switching providers uses portable conversation context. Extension diff --git a/packages/provider-pi/src/server/driver.test.ts b/packages/provider-pi/src/server/driver.test.ts index d0a108bc5cc13ea2a4ed439b628716b90a56ee83..84e6d86b0ddf7ad185146249e4ee30af8f87e75b 100644 --- a/packages/provider-pi/src/server/driver.test.ts +++ b/packages/provider-pi/src/server/driver.test.ts @@ -47,7 +47,9 @@ const personalSkill = { }; // Respond through the real stdio transport, with a distinct command catalog for each cwd. -const makePiSpawner = Effect.gen(function* () { +const makePiSpawner = Effect.fnUntraced(function* ( + discovery: { readonly state?: unknown; readonly models?: ReadonlyArray } = {}, +) { const pendingCommand = yield* Deferred.make(); const launches: Array = []; const spawner = ChildProcessSpawner.make((command) => @@ -92,8 +94,10 @@ const makePiSpawner = Effect.gen(function* () { ], } : request.type === "get_available_models" - ? { models: [{ provider: "test", id: "model" }] } - : {}; + ? { models: discovery.models ?? [{ provider: "test", id: "model" }] } + : request.type === "get_state" + ? (discovery.state ?? {}) + : {}; return Queue.offer( stdout, encoder.encode( @@ -125,7 +129,7 @@ const create = (config: Partial = {}, enabled = true) => it.layer(layerTest)("PiDriver workspace discovery", (it) => { it.effect("keeps each workspace's skills and commands separate from the machine catalog", () => Effect.gen(function* () { - const { spawner, launches } = yield* makePiSpawner; + const { spawner, launches } = yield* makePiSpawner(); const instance = yield* create({ launchArgs: '--approve --skill "extra skill"' }).pipe( Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), ); @@ -186,11 +190,50 @@ it.layer(layerTest)("PiDriver workspace discovery", (it) => { }).pipe(Effect.scoped), ); + it.effect.each([true, false])( + "limits discovered thinking defaults to the known active model, known=%s", + (known) => + Effect.gen(function* () { + const { spawner } = yield* makePiSpawner({ + state: { + thinkingLevel: "high", + ...(known ? { model: { provider: "test", id: "model" } } : {}), + }, + models: [ + { provider: "test", id: "model", reasoning: true }, + { provider: "other", id: "model", reasoning: true }, + { provider: "test", id: "other", reasoning: true }, + ], + }); + const instance = yield* create().pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + ); + yield* instance.snapshot.refresh; + const snapshot = yield* instance.snapshot.getSnapshot; + assert.deepEqual( + snapshot.models.map((model) => [ + model.slug, + (model.capabilities?.optionDescriptors ?? []).flatMap((descriptor) => + descriptor.type === "select" + ? descriptor.options.filter((option) => option.isDefault).map((option) => option.id) + : [], + ), + ]), + [ + ["default", []], + ["test/model", known ? ["high"] : []], + ["other/model", []], + ["test/other", []], + ], + ); + }).pipe(Effect.scoped), + ); + it.effect( "fails command discovery instead of returning an empty successful workspace catalog", () => Effect.gen(function* () { - const { spawner } = yield* makePiSpawner; + const { spawner } = yield* makePiSpawner(); const instance = yield* create().pipe( Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), ); @@ -208,7 +251,7 @@ it.layer(layerTest)("PiDriver workspace discovery", (it) => { it.effect("times out workspace discovery that needs interactive input", () => Effect.gen(function* () { - const { spawner, pendingCommand } = yield* makePiSpawner; + const { spawner, pendingCommand } = yield* makePiSpawner(); const instance = yield* create().pipe( Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), ); diff --git a/packages/provider-pi/src/server/status.ts b/packages/provider-pi/src/server/status.ts index ab6d6f7d9874badb5365f40b68b492efae21c5ab..03240655ba583441e79f42f567f2cb397d2163c0 100644 --- a/packages/provider-pi/src/server/status.ts +++ b/packages/provider-pi/src/server/status.ts @@ -101,12 +101,12 @@ function piModelsFromSettings( ); } -function parseDiscoveredModels( - data: unknown, - defaultThinkingLevel: unknown, -): ReadonlyArray { +function parseDiscoveredModels(data: unknown, state: unknown): ReadonlyArray { const models = recordField(data, "models"); if (!Array.isArray(models)) return []; + const activeModel = recordField(state, "model"); + const activeProvider = recordString(activeModel, "provider"); + const activeModelId = recordString(activeModel, "id"); const seen = new Set(); const parsed: Array = []; for (const model of models) { @@ -121,7 +121,12 @@ function parseDiscoveredModels( name: recordString(model, "name") ?? slug, subProvider: provider, isCustom: false, - capabilities: thinkingCapabilitiesForPiModel(model, defaultThinkingLevel), + capabilities: thinkingCapabilitiesForPiModel( + model, + provider === activeProvider && id === activeModelId + ? recordString(state, "thinkingLevel") + : undefined, + ), }); } return parsed; @@ -167,10 +172,7 @@ const discoverPiViaRpc = ( const commandsData = yield* connection .request({ type: "get_commands" }) .pipe(Effect.orElseSucceed(() => undefined)); - const discoveredModels = parseDiscoveredModels( - modelsData, - recordString(stateData, "thinkingLevel"), - ); + const discoveredModels = parseDiscoveredModels(modelsData, stateData); const { slashCommands, skills } = parsePiDiscoveredCommands(commandsData); return { models: discoveredModels, -- 2.54.0