Add thinking level management to runtime and socket handlers
This commit is contained in:
@@ -16,6 +16,7 @@ import type {
|
|||||||
ModelListPayload,
|
ModelListPayload,
|
||||||
ModelRef,
|
ModelRef,
|
||||||
ModelSummary,
|
ModelSummary,
|
||||||
|
ThinkingLevel,
|
||||||
PersistedSessionInfo,
|
PersistedSessionInfo,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
@@ -53,6 +54,8 @@ export class RuntimeRegistry {
|
|||||||
lastMessage: lastMessagePreview(session.messages),
|
lastMessage: lastMessagePreview(session.messages),
|
||||||
isStreaming: session.isStreaming,
|
isStreaming: session.isStreaming,
|
||||||
currentModel: toModelRef(session.model),
|
currentModel: toModelRef(session.model),
|
||||||
|
thinkingLevel: session.thinkingLevel as ThinkingLevel,
|
||||||
|
availableThinkingLevels: session.getAvailableThinkingLevels() as ThinkingLevel[],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -76,7 +79,12 @@ export class RuntimeRegistry {
|
|||||||
return this.runtimes.get(runtimeId);
|
return this.runtimes.get(runtimeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createRuntime(cwd: string, name?: string, modelRef?: ModelRef): Promise<LiveRuntimeInfo> {
|
async createRuntime(
|
||||||
|
cwd: string,
|
||||||
|
name?: string,
|
||||||
|
modelRef?: ModelRef,
|
||||||
|
thinkingLevel?: ThinkingLevel,
|
||||||
|
): Promise<LiveRuntimeInfo> {
|
||||||
const sessionManager = SessionManager.create(cwd);
|
const sessionManager = SessionManager.create(cwd);
|
||||||
const runtime = await createAgentSessionRuntime(createRuntimeFactory, {
|
const runtime = await createAgentSessionRuntime(createRuntimeFactory, {
|
||||||
cwd,
|
cwd,
|
||||||
@@ -87,6 +95,9 @@ export class RuntimeRegistry {
|
|||||||
if (modelRef) {
|
if (modelRef) {
|
||||||
await this.applyModel(runtime, modelRef);
|
await this.applyModel(runtime, modelRef);
|
||||||
}
|
}
|
||||||
|
if (thinkingLevel) {
|
||||||
|
runtime.session.setThinkingLevel(thinkingLevel);
|
||||||
|
}
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
runtime.session.sessionManager.appendSessionInfo(name);
|
runtime.session.sessionManager.appendSessionInfo(name);
|
||||||
@@ -147,6 +158,12 @@ export class RuntimeRegistry {
|
|||||||
.getAvailable()
|
.getAvailable()
|
||||||
.map((model) => toModelSummary(model, registry)),
|
.map((model) => toModelSummary(model, registry)),
|
||||||
currentModel: record ? toModelRef(record.runtime.session.model) : undefined,
|
currentModel: record ? toModelRef(record.runtime.session.model) : undefined,
|
||||||
|
currentThinkingLevel: record
|
||||||
|
? (record.runtime.session.thinkingLevel as ThinkingLevel)
|
||||||
|
: undefined,
|
||||||
|
availableThinkingLevels: record
|
||||||
|
? (record.runtime.session.getAvailableThinkingLevels() as ThinkingLevel[])
|
||||||
|
: undefined,
|
||||||
loadError: registry.getError(),
|
loadError: registry.getError(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -157,6 +174,18 @@ export class RuntimeRegistry {
|
|||||||
return this.getLiveInfo(runtimeId, record);
|
return this.getLiveInfo(runtimeId, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setThinkingLevel(runtimeId: string, level: ThinkingLevel): LiveRuntimeInfo {
|
||||||
|
const record = this.requireRuntime(runtimeId);
|
||||||
|
record.runtime.session.setThinkingLevel(level);
|
||||||
|
return this.getLiveInfo(runtimeId, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
cycleThinkingLevel(runtimeId: string): LiveRuntimeInfo {
|
||||||
|
const record = this.requireRuntime(runtimeId);
|
||||||
|
record.runtime.session.cycleThinkingLevel();
|
||||||
|
return this.getLiveInfo(runtimeId, record);
|
||||||
|
}
|
||||||
|
|
||||||
dispose(runtimeId: string): void {
|
dispose(runtimeId: string): void {
|
||||||
const record = this.runtimes.get(runtimeId);
|
const record = this.runtimes.get(runtimeId);
|
||||||
if (!record) {
|
if (!record) {
|
||||||
@@ -189,6 +218,8 @@ export class RuntimeRegistry {
|
|||||||
lastMessage: lastMessagePreview(session.messages),
|
lastMessage: lastMessagePreview(session.messages),
|
||||||
isStreaming: session.isStreaming,
|
isStreaming: session.isStreaming,
|
||||||
currentModel: toModelRef(session.model),
|
currentModel: toModelRef(session.model),
|
||||||
|
thinkingLevel: session.thinkingLevel as ThinkingLevel,
|
||||||
|
availableThinkingLevels: session.getAvailableThinkingLevels() as ThinkingLevel[],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +234,8 @@ export class RuntimeRegistry {
|
|||||||
lastMessage: lastMessagePreview(session.messages),
|
lastMessage: lastMessagePreview(session.messages),
|
||||||
isStreaming: session.isStreaming,
|
isStreaming: session.isStreaming,
|
||||||
currentModel: toModelRef(session.model),
|
currentModel: toModelRef(session.model),
|
||||||
|
thinkingLevel: session.thinkingLevel as ThinkingLevel,
|
||||||
|
availableThinkingLevels: session.getAvailableThinkingLevels() as ThinkingLevel[],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
ModelListPayload,
|
ModelListPayload,
|
||||||
ModelRef,
|
ModelRef,
|
||||||
RuntimeCreatedPayload,
|
RuntimeCreatedPayload,
|
||||||
|
ThinkingLevel,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
type AttachState = {
|
type AttachState = {
|
||||||
@@ -64,9 +65,51 @@ export function registerSocketHandlers(io: Server, registry: RuntimeRegistry) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
socket.on("runtime:create", async (payload: { cwd: string; name?: string; model?: ModelRef }) => {
|
socket.on(
|
||||||
|
"runtime:thinking:set",
|
||||||
|
async (
|
||||||
|
payload: { runtimeId: string; level: ThinkingLevel },
|
||||||
|
ack?: (response: { runtime: RuntimeCreatedPayload } | { error: string }) => void,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const runtime = registry.setThinkingLevel(payload.runtimeId, payload.level);
|
||||||
|
ack?.({ runtime });
|
||||||
|
} catch (error) {
|
||||||
|
ack?.({ error: errorMessage(error) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.on(
|
||||||
|
"runtime:thinking:cycle",
|
||||||
|
async (
|
||||||
|
payload: { runtimeId: string },
|
||||||
|
ack?: (response: { runtime: RuntimeCreatedPayload } | { error: string }) => void,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const runtime = registry.cycleThinkingLevel(payload.runtimeId);
|
||||||
|
ack?.({ runtime });
|
||||||
|
} catch (error) {
|
||||||
|
ack?.({ error: errorMessage(error) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.on(
|
||||||
|
"runtime:create",
|
||||||
|
async (payload: {
|
||||||
|
cwd: string;
|
||||||
|
name?: string;
|
||||||
|
model?: ModelRef;
|
||||||
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const info = await registry.createRuntime(payload.cwd, payload.name, payload.model);
|
const info = await registry.createRuntime(
|
||||||
|
payload.cwd,
|
||||||
|
payload.name,
|
||||||
|
payload.model,
|
||||||
|
payload.thinkingLevel,
|
||||||
|
);
|
||||||
const created: RuntimeCreatedPayload = info;
|
const created: RuntimeCreatedPayload = info;
|
||||||
socket.emit("runtime:created", created);
|
socket.emit("runtime:created", created);
|
||||||
io.emit("sessions:list:result", {
|
io.emit("sessions:list:result", {
|
||||||
@@ -76,7 +119,8 @@ export function registerSocketHandlers(io: Server, registry: RuntimeRegistry) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
emitError(socket, error);
|
emitError(socket, error);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
socket.on("runtime:open", async (payload: { sessionFile: string }) => {
|
socket.on("runtime:open", async (payload: { sessionFile: string }) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export type ModelRef = {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||||
|
|
||||||
export type ModelSummary = ModelRef & {
|
export type ModelSummary = ModelRef & {
|
||||||
name?: string;
|
name?: string;
|
||||||
providerDisplayName?: string;
|
providerDisplayName?: string;
|
||||||
@@ -17,6 +19,8 @@ export type ModelSummary = ModelRef & {
|
|||||||
export type ModelListPayload = {
|
export type ModelListPayload = {
|
||||||
models: ModelSummary[];
|
models: ModelSummary[];
|
||||||
currentModel?: ModelRef;
|
currentModel?: ModelRef;
|
||||||
|
currentThinkingLevel?: ThinkingLevel;
|
||||||
|
availableThinkingLevels?: ThinkingLevel[];
|
||||||
loadError?: string;
|
loadError?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,6 +33,8 @@ export type LiveRuntimeInfo = {
|
|||||||
lastMessage?: string;
|
lastMessage?: string;
|
||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
currentModel?: ModelRef;
|
currentModel?: ModelRef;
|
||||||
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
availableThinkingLevels?: ThinkingLevel[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PersistedSessionInfo = {
|
export type PersistedSessionInfo = {
|
||||||
@@ -72,4 +78,5 @@ export type RuntimeCreatedPayload = {
|
|||||||
sessionFile?: string;
|
sessionFile?: string;
|
||||||
lastMessage?: string;
|
lastMessage?: string;
|
||||||
currentModel?: ModelRef;
|
currentModel?: ModelRef;
|
||||||
|
thinkingLevel?: ThinkingLevel;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,13 +15,22 @@ import {
|
|||||||
import { AssistantMessage } from "@/components/AssistantMessage";
|
import { AssistantMessage } from "@/components/AssistantMessage";
|
||||||
import { MessageRow, type Sender } from "@/components/MessageRow";
|
import { MessageRow, type Sender } from "@/components/MessageRow";
|
||||||
import { ModelSelector } from "@/components/ModelSelector";
|
import { ModelSelector } from "@/components/ModelSelector";
|
||||||
|
import { ReasoningEffortButton } from "@/components/ReasoningEffortButton";
|
||||||
import { ToolCallCard } from "@/components/ToolCallCard";
|
import { ToolCallCard } from "@/components/ToolCallCard";
|
||||||
import { UserMessage } from "@/components/UserMessage";
|
import { UserMessage } from "@/components/UserMessage";
|
||||||
import { useSocket } from "@/contexts/SocketContext";
|
import { useSocket } from "@/contexts/SocketContext";
|
||||||
import { saveLastModel } from "@/lib/api";
|
import { saveLastModel } from "@/lib/api";
|
||||||
import { extractResultText, toolArgsPreview, truncateDetail } from "@/lib/tool-format";
|
import { extractResultText, toolArgsPreview, truncateDetail } from "@/lib/tool-format";
|
||||||
import { colors, monoFont } from "@/lib/theme";
|
import { colors, monoFont } from "@/lib/theme";
|
||||||
import type { ChatHistoryMessage, ChatItem, ModelRef, ModelSummary } from "@/lib/types";
|
import type {
|
||||||
|
ChatHistoryMessage,
|
||||||
|
ChatItem,
|
||||||
|
ModelRef,
|
||||||
|
ModelSummary,
|
||||||
|
ThinkingLevel,
|
||||||
|
} from "@/lib/types";
|
||||||
|
|
||||||
|
const ALL_THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
||||||
|
|
||||||
function extractText(content: unknown): string {
|
function extractText(content: unknown): string {
|
||||||
if (typeof content === "string") {
|
if (typeof content === "string") {
|
||||||
@@ -127,6 +136,7 @@ export default function ChatScreen() {
|
|||||||
listModels,
|
listModels,
|
||||||
sendPrompt,
|
sendPrompt,
|
||||||
setRuntimeModel,
|
setRuntimeModel,
|
||||||
|
setRuntimeThinkingLevel,
|
||||||
abortPrompt,
|
abortPrompt,
|
||||||
subscribeChat,
|
subscribeChat,
|
||||||
subscribeChatHistory,
|
subscribeChatHistory,
|
||||||
@@ -143,6 +153,9 @@ export default function ChatScreen() {
|
|||||||
const [selectedModel, setSelectedModel] = useState<ModelRef | undefined>();
|
const [selectedModel, setSelectedModel] = useState<ModelRef | undefined>();
|
||||||
const [modelLoading, setModelLoading] = useState(false);
|
const [modelLoading, setModelLoading] = useState(false);
|
||||||
const [modelError, setModelError] = useState<string | null>(null);
|
const [modelError, setModelError] = useState<string | null>(null);
|
||||||
|
const [thinkingLevel, setThinkingLevel] = useState<ThinkingLevel>("off");
|
||||||
|
const [availableThinkingLevels, setAvailableThinkingLevels] =
|
||||||
|
useState<ThinkingLevel[]>(ALL_THINKING_LEVELS);
|
||||||
const assistantIdRef = useRef<string | null>(null);
|
const assistantIdRef = useRef<string | null>(null);
|
||||||
const toolIdsRef = useRef(new Map<string, string>());
|
const toolIdsRef = useRef(new Map<string, string>());
|
||||||
const pendingMessageRef = useRef<string | null>(null);
|
const pendingMessageRef = useRef<string | null>(null);
|
||||||
@@ -171,6 +184,17 @@ export default function ChatScreen() {
|
|||||||
persistedModel ??
|
persistedModel ??
|
||||||
(payload.models[0] ? { provider: payload.models[0].provider, id: payload.models[0].id } : undefined),
|
(payload.models[0] ? { provider: payload.models[0].provider, id: payload.models[0].id } : undefined),
|
||||||
);
|
);
|
||||||
|
const selected = payload.currentModel ?? persistedModel ?? payload.models[0];
|
||||||
|
const selectedSummary = selected
|
||||||
|
? payload.models.find(
|
||||||
|
(model) => model.provider === selected.provider && model.id === selected.id,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
setAvailableThinkingLevels(
|
||||||
|
payload.availableThinkingLevels ??
|
||||||
|
(selectedSummary?.reasoning ? ALL_THINKING_LEVELS : ["off"]),
|
||||||
|
);
|
||||||
|
setThinkingLevel(payload.currentThinkingLevel ?? "off");
|
||||||
if (payload.loadError) {
|
if (payload.loadError) {
|
||||||
setModelError(payload.loadError);
|
setModelError(payload.loadError);
|
||||||
}
|
}
|
||||||
@@ -396,7 +420,7 @@ export default function ChatScreen() {
|
|||||||
|
|
||||||
pendingMessageRef.current = message;
|
pendingMessageRef.current = message;
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
createRuntime(projectCwd, undefined, selectedModel);
|
createRuntime(projectCwd, undefined, selectedModel, thinkingLevel);
|
||||||
try {
|
try {
|
||||||
const runtime = await waitForRuntime("created");
|
const runtime = await waitForRuntime("created");
|
||||||
setActiveRuntimeId(runtime.runtimeId);
|
setActiveRuntimeId(runtime.runtimeId);
|
||||||
@@ -421,17 +445,47 @@ export default function ChatScreen() {
|
|||||||
setSelectedModel(model);
|
setSelectedModel(model);
|
||||||
setModelError(null);
|
setModelError(null);
|
||||||
if (!activeRuntimeId) {
|
if (!activeRuntimeId) {
|
||||||
|
const summary = models.find(
|
||||||
|
(entry) => entry.provider === model.provider && entry.id === model.id,
|
||||||
|
);
|
||||||
|
const nextLevels = summary?.reasoning ? ALL_THINKING_LEVELS : ["off" as ThinkingLevel];
|
||||||
|
setAvailableThinkingLevels(nextLevels);
|
||||||
|
if (!nextLevels.includes(thinkingLevel)) {
|
||||||
|
setThinkingLevel("off");
|
||||||
|
}
|
||||||
void saveLastModel(model);
|
void saveLastModel(model);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await setRuntimeModel(activeRuntimeId, model);
|
const runtime = await setRuntimeModel(activeRuntimeId, model);
|
||||||
|
setThinkingLevel(runtime.thinkingLevel ?? "off");
|
||||||
|
setAvailableThinkingLevels(runtime.availableThinkingLevels ?? ["off"]);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setSelectedModel(previous);
|
setSelectedModel(previous);
|
||||||
setModelError(error instanceof Error ? error.message : "Failed to set model");
|
setModelError(error instanceof Error ? error.message : "Failed to set model");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cycleReasoningEffort = async () => {
|
||||||
|
if (streaming || availableThinkingLevels.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentIndex = availableThinkingLevels.indexOf(thinkingLevel);
|
||||||
|
const nextLevel =
|
||||||
|
availableThinkingLevels[(currentIndex + 1) % availableThinkingLevels.length] ?? "off";
|
||||||
|
setThinkingLevel(nextLevel);
|
||||||
|
if (!activeRuntimeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const runtime = await setRuntimeThinkingLevel(activeRuntimeId, nextLevel);
|
||||||
|
setThinkingLevel(runtime.thinkingLevel ?? nextLevel);
|
||||||
|
setAvailableThinkingLevels(runtime.availableThinkingLevels ?? availableThinkingLevels);
|
||||||
|
} catch {
|
||||||
|
setThinkingLevel(thinkingLevel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleScroll = (scrollEvent: NativeSyntheticEvent<NativeScrollEvent>) => {
|
const handleScroll = (scrollEvent: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
const { contentOffset, contentSize, layoutMeasurement } = scrollEvent.nativeEvent;
|
const { contentOffset, contentSize, layoutMeasurement } = scrollEvent.nativeEvent;
|
||||||
atBottomRef.current =
|
atBottomRef.current =
|
||||||
@@ -469,14 +523,23 @@ export default function ChatScreen() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={styles.composer}>
|
<View style={styles.composer}>
|
||||||
<ModelSelector
|
<View style={styles.selectorRow}>
|
||||||
models={models}
|
<View style={styles.modelSelector}>
|
||||||
selected={selectedModel}
|
<ModelSelector
|
||||||
loading={modelLoading}
|
models={models}
|
||||||
error={modelError}
|
selected={selectedModel}
|
||||||
disabled={streaming || modelLoading}
|
loading={modelLoading}
|
||||||
onSelect={(model) => void selectModel(model)}
|
error={modelError}
|
||||||
/>
|
disabled={streaming || modelLoading}
|
||||||
|
onSelect={(model) => void selectModel(model)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<ReasoningEffortButton
|
||||||
|
level={thinkingLevel}
|
||||||
|
disabled={streaming || modelLoading || availableThinkingLevels.length < 2}
|
||||||
|
onPress={() => void cycleReasoningEffort()}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
<View style={styles.inputRow}>
|
<View style={styles.inputRow}>
|
||||||
{streaming ? (
|
{streaming ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -534,6 +597,15 @@ const styles = StyleSheet.create({
|
|||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
gap: 8,
|
gap: 8,
|
||||||
},
|
},
|
||||||
|
selectorRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "stretch",
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
modelSelector: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
},
|
||||||
inputRow: {
|
inputRow: {
|
||||||
gap: 8,
|
gap: 8,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { Pressable, StyleSheet, Text } from "react-native";
|
||||||
|
import { colors, monoFont } from "@/lib/theme";
|
||||||
|
import type { ThinkingLevel } from "@/lib/types";
|
||||||
|
|
||||||
|
type ReasoningEffortButtonProps = {
|
||||||
|
level: ThinkingLevel;
|
||||||
|
disabled?: boolean;
|
||||||
|
onPress: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const labels: Record<ThinkingLevel, string> = {
|
||||||
|
off: "Off",
|
||||||
|
minimal: "Minimal",
|
||||||
|
low: "Low",
|
||||||
|
medium: "Medium",
|
||||||
|
high: "High",
|
||||||
|
xhigh: "X-high",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ReasoningEffortButton({
|
||||||
|
level,
|
||||||
|
disabled,
|
||||||
|
onPress,
|
||||||
|
}: ReasoningEffortButtonProps) {
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
accessibilityLabel={`Reasoning effort ${labels[level]}. Tap to change.`}
|
||||||
|
style={({ pressed }) => [
|
||||||
|
styles.button,
|
||||||
|
pressed && styles.pressed,
|
||||||
|
disabled && styles.disabled,
|
||||||
|
level !== "off" && styles.active,
|
||||||
|
]}
|
||||||
|
onPress={onPress}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="sparkles-outline"
|
||||||
|
size={14}
|
||||||
|
color={level === "off" ? colors.textMuted : colors.accent}
|
||||||
|
/>
|
||||||
|
<Text style={styles.label}>{labels[level]}</Text>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
minHeight: 40,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 6,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
backgroundColor: colors.surfaceDeep,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
borderColor: "rgba(147, 197, 253, 0.45)",
|
||||||
|
backgroundColor: "rgba(147, 197, 253, 0.1)",
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
color: colors.text,
|
||||||
|
fontFamily: monoFont,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "700",
|
||||||
|
},
|
||||||
|
pressed: { backgroundColor: colors.surface },
|
||||||
|
disabled: { opacity: 0.45 },
|
||||||
|
});
|
||||||
@@ -20,7 +20,12 @@ import {
|
|||||||
saveLastModel,
|
saveLastModel,
|
||||||
upsertSavedComputer,
|
upsertSavedComputer,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
import { createSocket, listModels as requestModels, setRuntimeModel as requestSetRuntimeModel } from "@/lib/socket";
|
import {
|
||||||
|
createSocket,
|
||||||
|
listModels as requestModels,
|
||||||
|
setRuntimeModel as requestSetRuntimeModel,
|
||||||
|
setRuntimeThinkingLevel as requestSetRuntimeThinkingLevel,
|
||||||
|
} from "@/lib/socket";
|
||||||
import type {
|
import type {
|
||||||
ChatEvent,
|
ChatEvent,
|
||||||
ChatHistoryPayload,
|
ChatHistoryPayload,
|
||||||
@@ -30,6 +35,7 @@ import type {
|
|||||||
PersistedSessionInfo,
|
PersistedSessionInfo,
|
||||||
RuntimeCreatedPayload,
|
RuntimeCreatedPayload,
|
||||||
SavedComputer,
|
SavedComputer,
|
||||||
|
ThinkingLevel,
|
||||||
} from "@/lib/types";
|
} from "@/lib/types";
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
|
|
||||||
@@ -55,10 +61,16 @@ type SocketContextValue = {
|
|||||||
removeSavedComputerEntry: (id: string) => Promise<void>;
|
removeSavedComputerEntry: (id: string) => Promise<void>;
|
||||||
disconnect: () => void;
|
disconnect: () => void;
|
||||||
refreshSessions: () => void;
|
refreshSessions: () => void;
|
||||||
createRuntime: (cwd: string, name?: string, model?: ModelRef) => void;
|
createRuntime: (
|
||||||
|
cwd: string,
|
||||||
|
name?: string,
|
||||||
|
model?: ModelRef,
|
||||||
|
thinkingLevel?: ThinkingLevel,
|
||||||
|
) => void;
|
||||||
lastModel: ModelRef | null;
|
lastModel: ModelRef | null;
|
||||||
listModels: (payload: { runtimeId?: string; cwd?: string }) => Promise<ModelListPayload>;
|
listModels: (payload: { runtimeId?: string; cwd?: string }) => Promise<ModelListPayload>;
|
||||||
setRuntimeModel: (runtimeId: string, model: ModelRef) => Promise<void>;
|
setRuntimeModel: (runtimeId: string, model: ModelRef) => Promise<LiveRuntimeInfo>;
|
||||||
|
setRuntimeThinkingLevel: (runtimeId: string, level: ThinkingLevel) => Promise<LiveRuntimeInfo>;
|
||||||
openRuntime: (sessionFile: string) => void;
|
openRuntime: (sessionFile: string) => void;
|
||||||
attachRuntime: (runtimeId: string) => void;
|
attachRuntime: (runtimeId: string) => void;
|
||||||
sendPrompt: (runtimeId: string, message: string) => void;
|
sendPrompt: (runtimeId: string, message: string) => void;
|
||||||
@@ -249,8 +261,13 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
socketRef.current?.emit("sessions:list");
|
socketRef.current?.emit("sessions:list");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const createRuntime = useCallback((cwd: string, name?: string, model?: ModelRef) => {
|
const createRuntime = useCallback((
|
||||||
socketRef.current?.emit("runtime:create", { cwd, name, model });
|
cwd: string,
|
||||||
|
name?: string,
|
||||||
|
model?: ModelRef,
|
||||||
|
thinkingLevel?: ThinkingLevel,
|
||||||
|
) => {
|
||||||
|
socketRef.current?.emit("runtime:create", { cwd, name, model, thinkingLevel });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listModels = useCallback(
|
const listModels = useCallback(
|
||||||
@@ -282,6 +299,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
setLiveRuntimes((current) =>
|
setLiveRuntimes((current) =>
|
||||||
current.map((entry) => (entry.runtimeId === runtime.runtimeId ? runtime : entry)),
|
current.map((entry) => (entry.runtimeId === runtime.runtimeId ? runtime : entry)),
|
||||||
);
|
);
|
||||||
|
return runtime;
|
||||||
} catch (requestError) {
|
} catch (requestError) {
|
||||||
const message = requestError instanceof Error ? requestError.message : "Failed to set model";
|
const message = requestError instanceof Error ? requestError.message : "Failed to set model";
|
||||||
setError(message);
|
setError(message);
|
||||||
@@ -289,6 +307,28 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const setRuntimeThinkingLevel = useCallback(
|
||||||
|
async (runtimeId: string, level: ThinkingLevel) => {
|
||||||
|
const socket = socketRef.current;
|
||||||
|
if (!socket) {
|
||||||
|
throw new Error("Not connected");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const runtime = await requestSetRuntimeThinkingLevel(socket, runtimeId, level);
|
||||||
|
setLiveRuntimes((current) =>
|
||||||
|
current.map((entry) => (entry.runtimeId === runtime.runtimeId ? runtime : entry)),
|
||||||
|
);
|
||||||
|
return runtime;
|
||||||
|
} catch (requestError) {
|
||||||
|
const message =
|
||||||
|
requestError instanceof Error ? requestError.message : "Failed to set reasoning effort";
|
||||||
|
setError(message);
|
||||||
|
throw requestError;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const openRuntime = useCallback((sessionFile: string) => {
|
const openRuntime = useCallback((sessionFile: string) => {
|
||||||
socketRef.current?.emit("runtime:open", { sessionFile });
|
socketRef.current?.emit("runtime:open", { sessionFile });
|
||||||
}, []);
|
}, []);
|
||||||
@@ -366,6 +406,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
createRuntime,
|
createRuntime,
|
||||||
listModels,
|
listModels,
|
||||||
setRuntimeModel,
|
setRuntimeModel,
|
||||||
|
setRuntimeThinkingLevel,
|
||||||
openRuntime,
|
openRuntime,
|
||||||
attachRuntime,
|
attachRuntime,
|
||||||
sendPrompt,
|
sendPrompt,
|
||||||
@@ -397,6 +438,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
createRuntime,
|
createRuntime,
|
||||||
listModels,
|
listModels,
|
||||||
setRuntimeModel,
|
setRuntimeModel,
|
||||||
|
setRuntimeThinkingLevel,
|
||||||
openRuntime,
|
openRuntime,
|
||||||
attachRuntime,
|
attachRuntime,
|
||||||
sendPrompt,
|
sendPrompt,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
ModelRef,
|
ModelRef,
|
||||||
PersistedSessionInfo,
|
PersistedSessionInfo,
|
||||||
RuntimeCreatedPayload,
|
RuntimeCreatedPayload,
|
||||||
|
ThinkingLevel,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export type SocketCallbacks = {
|
export type SocketCallbacks = {
|
||||||
@@ -90,3 +91,26 @@ export function setRuntimeModel(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setRuntimeThinkingLevel(
|
||||||
|
socket: Socket,
|
||||||
|
runtimeId: string,
|
||||||
|
level: ThinkingLevel,
|
||||||
|
timeoutMs = 10000,
|
||||||
|
): Promise<LiveRuntimeInfo> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
socket.timeout(timeoutMs).emit(
|
||||||
|
"runtime:thinking:set",
|
||||||
|
{ runtimeId, level },
|
||||||
|
(error: Error | null, response: { runtime: LiveRuntimeInfo } | { error: string }) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error("Timed out setting reasoning effort"));
|
||||||
|
} else if ("error" in response) {
|
||||||
|
reject(new Error(response.error));
|
||||||
|
} else {
|
||||||
|
resolve(response.runtime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ export type ModelRef = {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||||
|
|
||||||
export type ModelSummary = ModelRef & {
|
export type ModelSummary = ModelRef & {
|
||||||
name?: string;
|
name?: string;
|
||||||
providerDisplayName?: string;
|
providerDisplayName?: string;
|
||||||
@@ -15,6 +17,8 @@ export type ModelSummary = ModelRef & {
|
|||||||
export type ModelListPayload = {
|
export type ModelListPayload = {
|
||||||
models: ModelSummary[];
|
models: ModelSummary[];
|
||||||
currentModel?: ModelRef;
|
currentModel?: ModelRef;
|
||||||
|
currentThinkingLevel?: ThinkingLevel;
|
||||||
|
availableThinkingLevels?: ThinkingLevel[];
|
||||||
loadError?: string;
|
loadError?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,6 +31,8 @@ export type LiveRuntimeInfo = {
|
|||||||
lastMessage?: string;
|
lastMessage?: string;
|
||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
currentModel?: ModelRef;
|
currentModel?: ModelRef;
|
||||||
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
availableThinkingLevels?: ThinkingLevel[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PersistedSessionInfo = {
|
export type PersistedSessionInfo = {
|
||||||
@@ -58,6 +64,7 @@ export type RuntimeCreatedPayload = {
|
|||||||
sessionFile?: string;
|
sessionFile?: string;
|
||||||
lastMessage?: string;
|
lastMessage?: string;
|
||||||
currentModel?: ModelRef;
|
currentModel?: ModelRef;
|
||||||
|
thinkingLevel?: ThinkingLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChatEvent = {
|
export type ChatEvent = {
|
||||||
|
|||||||
Reference in New Issue
Block a user