Skip to content

Commit 98a4b67

Browse files
authored
fix(models): never use Fable as the default model for new tasks (#3298)
1 parent 4419c2f commit 98a4b67

7 files changed

Lines changed: 59 additions & 14 deletions

File tree

packages/shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export {
127127
mentionsToPlainText,
128128
splitMentionSegments,
129129
} from "./mentions";
130+
export { defaultEligibleModel } from "./models";
130131
export {
131132
getOauthClientIdFromRegion,
132133
OAUTH_SCOPE_VERSION,

packages/shared/src/models.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, expect, it } from "vitest";
2+
import { defaultEligibleModel } from "./models";
3+
4+
describe("defaultEligibleModel", () => {
5+
it.each([
6+
["claude-fable-5", undefined],
7+
["anthropic/claude-fable-5", undefined],
8+
["CLAUDE-FABLE-5", undefined],
9+
["claude-fable-5-20260601", undefined],
10+
["claude-opus-4-8", "claude-opus-4-8"],
11+
["claude-sonnet-5", "claude-sonnet-5"],
12+
["gpt-5.5", "gpt-5.5"],
13+
["@cf/zai-org/glm-5.2", "@cf/zai-org/glm-5.2"],
14+
// Contains "fable" as a substring but is not the Fable family.
15+
["gpt-affable-1", "gpt-affable-1"],
16+
["", undefined],
17+
[null, undefined],
18+
[undefined, undefined],
19+
] as const)("%s -> %s", (modelId, expected) => {
20+
expect(defaultEligibleModel(modelId)).toBe(expected);
21+
});
22+
});

packages/shared/src/models.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Returns the id unless it's a premium family (currently Fable) that must be
3+
* an explicit per-task pick and never the implicit default for a new task.
4+
*/
5+
export function defaultEligibleModel(
6+
modelId: string | null | undefined,
7+
): string | undefined {
8+
if (!modelId) return undefined;
9+
const family = modelId.toLowerCase().split("/").pop() ?? "";
10+
return family.startsWith("claude-fable") ? undefined : modelId;
11+
}

packages/ui/src/features/home/hooks/useRunWorkstreamAction.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { TaskService } from "@posthog/core/task-detail/taskService";
99
import { useService } from "@posthog/di/react";
1010
import {
1111
ANALYTICS_EVENTS,
12+
defaultEligibleModel,
1213
getCloudUrlFromRegion,
1314
type TaskCreationInput,
1415
} from "@posthog/shared";
@@ -103,7 +104,8 @@ export function useRunWorkstreamAction(): RunWorkstreamAction {
103104
// honoured if the gateway still offers it (the resolver validates it),
104105
// so a stale persisted/pinned id can't reach the run and 403.
105106
const adapter = action.adapter ?? lastUsedAdapter;
106-
const preferredModel = action.model ?? lastUsedModel ?? undefined;
107+
const preferredModel =
108+
action.model ?? defaultEligibleModel(lastUsedModel);
107109
let model = preferredModel;
108110
if (cloudRegion) {
109111
// The resolver swallows transient failures and returns undefined; fall

packages/ui/src/features/inbox/components/ConfigureAgentsSection.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import {
1111
} from "@posthog/core/task-detail/taskService";
1212
import { useService } from "@posthog/di/react";
1313
import { Button } from "@posthog/quill";
14-
import { ANALYTICS_EVENTS, getCloudUrlFromRegion } from "@posthog/shared";
14+
import {
15+
ANALYTICS_EVENTS,
16+
defaultEligibleModel,
17+
getCloudUrlFromRegion,
18+
} from "@posthog/shared";
1519
import { SELF_DRIVING_SETUP_TASK_FLAG } from "@posthog/shared/constants";
1620
import { useTrackAgentsViewed } from "@posthog/ui/features/agents/hooks/useTrackAgentsViewed";
1721
import { useAuthStateValue } from "@posthog/ui/features/auth/store";
@@ -302,17 +306,18 @@ function SetupTaskSection() {
302306
const settings = useSettingsStore.getState();
303307
const adapter = settings.lastUsedAdapter ?? "claude";
304308
const apiHost = getCloudUrlFromRegion(cloudRegion);
309+
const preferredModel = defaultEligibleModel(settings.lastUsedModel);
305310
const resolvedModel = await resolveDefaultModel(
306311
queryClient,
307312
apiHost,
308313
adapter,
309314
modelResolver,
310-
settings.lastUsedModel,
315+
preferredModel,
311316
);
312317
// The resolver returns undefined on a transient failure; fall back to the
313318
// persisted id so a gateway outage degrades gracefully rather than blocking
314319
// setup for a user whose persisted model was valid.
315-
const model = resolvedModel ?? settings.lastUsedModel;
320+
const model = resolvedModel ?? preferredModel;
316321

317322
if (!model) {
318323
toast.dismiss(toastId);

packages/ui/src/features/inbox/hooks/useInboxCloudTaskRunner.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useService } from "@posthog/di/react";
1313
import {
1414
type Adapter,
1515
ANALYTICS_EVENTS,
16+
defaultEligibleModel,
1617
getCloudUrlFromRegion,
1718
} from "@posthog/shared";
1819
import { useAuthStateValue } from "@posthog/ui/features/auth/store";
@@ -170,16 +171,17 @@ export function useInboxCloudTaskRunner({
170171
// resolver keeps it only if the gateway still offers it, otherwise it falls
171172
// back to the server default. A stale id (e.g. one later de-listed for the
172173
// org) would otherwise be sent here and fail the run with a gateway 403.
174+
const preferredModel = defaultEligibleModel(settings.lastUsedModel);
173175
const resolvedModel = await resolveDefaultModel(
174176
queryClient,
175177
apiHost,
176178
adapter,
177179
modelResolver,
178-
settings.lastUsedModel,
180+
preferredModel,
179181
);
180182
// The resolver returns undefined on a transient failure; fall back to the
181183
// persisted id so a gateway outage degrades gracefully rather than blocking.
182-
const model = resolvedModel ?? settings.lastUsedModel;
184+
const model = resolvedModel ?? preferredModel;
183185

184186
if (!model) {
185187
toast.dismiss(toastId);

packages/ui/src/features/task-detail/hooks/usePreviewConfig.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { useHostTRPCClient } from "@posthog/host-router/react";
99
import {
1010
type Adapter,
11+
defaultEligibleModel,
1112
GLM_MODEL_FLAG,
1213
getCloudUrlFromRegion,
1314
} from "@posthog/shared";
@@ -106,22 +107,23 @@ export function usePreviewConfig(adapter: Adapter): PreviewConfigResult {
106107
);
107108

108109
// The server always returns its default model as the current value, so
109-
// without this the user's last pick (e.g. fable) is lost on every
110-
// refetch/remount. Restore it through applyConfigChange so the dependent
111-
// effort options are recomputed for the restored model.
110+
// without this the user's last (default-eligible) pick is lost on every
111+
// refetch/remount. Restore it through applyConfigChange so the
112+
// dependent effort options are recomputed for the restored model.
112113
const modelOpt = getOptionByCategory(initial, "model");
114+
const restorableModel = defaultEligibleModel(lastUsedModel);
113115
if (
114-
lastUsedModel &&
116+
restorableModel &&
115117
modelOpt?.type === "select" &&
116-
modelOpt.currentValue !== lastUsedModel &&
117-
flattenConfigValues(modelOpt).includes(lastUsedModel)
118+
modelOpt.currentValue !== restorableModel &&
119+
flattenConfigValues(modelOpt).includes(restorableModel)
118120
) {
119121
initial = applyConfigChange(initial, {
120122
adapter,
121123
configId: modelOpt.id,
122-
value: lastUsedModel,
124+
value: restorableModel,
123125
effortOptions:
124-
getReasoningEffortOptions(adapter, lastUsedModel) ?? undefined,
126+
getReasoningEffortOptions(adapter, restorableModel) ?? undefined,
125127
settings: {
126128
defaultInitialTaskMode: "",
127129
lastUsedInitialTaskMode: undefined,

0 commit comments

Comments
 (0)