1import { type ModelCapabilities, type ProviderOptionChoice } from "@t3tools/contracts";
2import { createModelCapabilities } from "@t3tools/shared/model";
3import * as Predicate from "effect/Predicate";
4
5/**
6 * Pi's full thinking ladder. Extra High (`xhigh`) and Max are opt-in per
7 * model via `thinkingLevelMap`; advertising them globally makes
8 * `set_thinking_level` fail on models that lack them.
9 */
10const PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
11
12export type PiThinkingLevel = (typeof PI_THINKING_LEVELS)[number];
13
14const PI_THINKING_LEVEL_LABELS: Record<PiThinkingLevel, string> = {
15 off: "Off",
16 minimal: "Minimal",
17 low: "Low",
18 medium: "Medium",
19 high: "High",
20 xhigh: "Extra High",
21 max: "Max",
22};
23
24export const EMPTY_PI_MODEL_CAPABILITIES: ModelCapabilities = createModelCapabilities({
25 optionDescriptors: [],
26});
27
28export function thinkingCapabilitiesForPiModel(
29 model: unknown,
30 defaultThinkingLevel: unknown,
31): ModelCapabilities {
32 const levels = supportedPiThinkingLevelsFromModel(model);
33 if (levels.length === 0) return EMPTY_PI_MODEL_CAPABILITIES;
34 const defaultLevel = clampPiThinkingLevel(defaultThinkingLevel, levels);
35 return createModelCapabilities({
36 optionDescriptors: [
37 {
38 id: "thinking",
39 label: "Thinking",
40 type: "select",
41 options: levels.map((level): ProviderOptionChoice => ({
42 id: level,
43 label: PI_THINKING_LEVEL_LABELS[level],
44 ...(level === defaultLevel ? { isDefault: true } : {}),
45 })),
46 },
47 ],
48 });
49}
50
51/** Mirrors `@earendil-works/pi-ai` `clampThinkingLevel`. */
52function clampPiThinkingLevel(
53 input: unknown,
54 availableLevels: ReadonlyArray<PiThinkingLevel>,
55): PiThinkingLevel | undefined {
56 if (typeof input !== "string") return undefined;
57 const requestedIndex = PI_THINKING_LEVELS.findIndex((level) => level === input);
58 if (requestedIndex === -1) return undefined;
59 const exact = availableLevels.find((level) => level === input);
60 if (exact !== undefined) return exact;
61 for (let index = requestedIndex + 1; index < PI_THINKING_LEVELS.length; index += 1) {
62 const higher = availableLevels.find((level) => level === PI_THINKING_LEVELS[index]);
63 if (higher !== undefined) return higher;
64 }
65 for (let index = requestedIndex - 1; index >= 0; index -= 1) {
66 const lower = availableLevels.find((level) => level === PI_THINKING_LEVELS[index]);
67 if (lower !== undefined) return lower;
68 }
69 return availableLevels[0];
70}
71
72/**
73 * Mirror of `@earendil-works/pi-ai` `getSupportedThinkingLevels`.
74 *
75 * A reasoning model always exposes off through high unless a map entry is
76 * `null`. Extra High and Max appear only when the map has a non-null entry.
77 */
78function supportedPiThinkingLevelsFromModel(model: unknown): ReadonlyArray<PiThinkingLevel> {
79 if (recordField(model, "reasoning") !== true) return [];
80 const thinkingLevelMap = thinkingLevelMapFromModel(model);
81 return PI_THINKING_LEVELS.filter((level) => {
82 const mapped = thinkingLevelMap?.[level];
83 if (mapped === null) return false;
84 if (level === "xhigh" || level === "max") return mapped !== undefined;
85 return true;
86 });
87}
88
89function thinkingLevelMapFromModel(model: unknown): Record<string, unknown> | undefined {
90 const value = recordField(model, "thinkingLevelMap");
91 if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
92 return value as Record<string, unknown>;
93}
94
95function recordField(input: unknown, key: string): unknown {
96 return Predicate.isObject(input) ? input[key] : undefined;
97}