wip: pi working

This commit is contained in:
Franklin 2026-02-06 16:54:43 -05:00
commit a6064e7027
120 changed files with 15728 additions and 2301 deletions

View file

@ -3,6 +3,7 @@ import {
SandboxAgentError,
SandboxAgent,
type AgentInfo,
type AgentModelInfo,
type AgentModeInfo,
type PermissionEventData,
type QuestionEventData,
@ -31,18 +32,6 @@ type ItemDeltaEventData = {
delta: string;
};
const shouldHidePiStatusItem = (item: UniversalItem) => {
if (item.kind !== "status") return false;
const statusParts = (item.content ?? []).filter(
(part) => (part as { type?: string }).type === "status"
) as Array<{ label?: string }>;
if (statusParts.length === 0) return false;
return statusParts.every((part) => {
const label = part.label ?? "";
return label.startsWith("pi.turn_") || label.startsWith("pi.agent_");
});
};
const buildStubItem = (itemId: string, nativeItemId?: string | null): UniversalItem => {
return {
item_id: itemId,
@ -101,6 +90,8 @@ export default function App() {
const [agents, setAgents] = useState<AgentInfo[]>([]);
const [modesByAgent, setModesByAgent] = useState<Record<string, AgentModeInfo[]>>({});
const [modelsByAgent, setModelsByAgent] = useState<Record<string, AgentModelInfo[]>>({});
const [defaultModelByAgent, setDefaultModelByAgent] = useState<Record<string, string>>({});
const [sessions, setSessions] = useState<SessionInfo[]>([]);
const [agentsLoading, setAgentsLoading] = useState(false);
const [agentsError, setAgentsError] = useState<string | null>(null);
@ -108,6 +99,8 @@ export default function App() {
const [sessionsError, setSessionsError] = useState<string | null>(null);
const [modesLoadingByAgent, setModesLoadingByAgent] = useState<Record<string, boolean>>({});
const [modesErrorByAgent, setModesErrorByAgent] = useState<Record<string, string | null>>({});
const [modelsLoadingByAgent, setModelsLoadingByAgent] = useState<Record<string, boolean>>({});
const [modelsErrorByAgent, setModelsErrorByAgent] = useState<Record<string, string | null>>({});
const [agentId, setAgentId] = useState("claude");
const [agentMode, setAgentMode] = useState("");
@ -264,10 +257,14 @@ export default function App() {
stopTurnStream();
setAgents([]);
setSessions([]);
setModelsByAgent({});
setDefaultModelByAgent({});
setAgentsLoading(false);
setSessionsLoading(false);
setAgentsError(null);
setSessionsError(null);
setModelsLoadingByAgent({});
setModelsErrorByAgent({});
};
const refreshAgents = async () => {
@ -280,6 +277,7 @@ export default function App() {
for (const agent of agentList) {
if (agent.installed) {
loadModes(agent.id);
loadModels(agent.id);
}
}
} catch (error) {
@ -326,6 +324,29 @@ export default function App() {
}
};
const loadModels = async (targetId: string) => {
setModelsLoadingByAgent((prev) => ({ ...prev, [targetId]: true }));
setModelsErrorByAgent((prev) => ({ ...prev, [targetId]: null }));
try {
const data = await getClient().getAgentModels(targetId);
const models = data.models ?? [];
setModelsByAgent((prev) => ({ ...prev, [targetId]: models }));
if (data.defaultModel) {
setDefaultModelByAgent((prev) => ({ ...prev, [targetId]: data.defaultModel! }));
} else {
setDefaultModelByAgent((prev) => {
const next = { ...prev };
delete next[targetId];
return next;
});
}
} catch {
setModelsErrorByAgent((prev) => ({ ...prev, [targetId]: "Unable to load models." }));
} finally {
setModelsLoadingByAgent((prev) => ({ ...prev, [targetId]: false }));
}
};
const sendMessage = async () => {
const prompt = message.trim();
if (!prompt || !sessionId || turnStreaming) return;
@ -746,10 +767,7 @@ export default function App() {
}
}
return entries.filter((entry) => {
if (entry.kind !== "item" || !entry.item) return true;
return !shouldHidePiStatusItem(entry.item);
});
return entries;
}, [events]);
useEffect(() => {
@ -840,6 +858,12 @@ export default function App() {
}
}, [connected, agentId]);
useEffect(() => {
if (connected && agentId && !modelsByAgent[agentId]) {
loadModels(agentId);
}
}, [connected, agentId]);
useEffect(() => {
const modes = modesByAgent[agentId];
if (modes && modes.length > 0 && !agentMode) {
@ -851,6 +875,15 @@ export default function App() {
const activeModes = modesByAgent[agentId] ?? [];
const modesLoading = modesLoadingByAgent[agentId] ?? false;
const modesError = modesErrorByAgent[agentId] ?? null;
const modelOptions = modelsByAgent[agentId] ?? [];
const modelsLoading = modelsLoadingByAgent[agentId] ?? false;
const modelsError = modelsErrorByAgent[agentId] ?? null;
const defaultModel = defaultModelByAgent[agentId] ?? "";
const selectedModelId = model || defaultModel;
const selectedModel = modelOptions.find((entry) => entry.id === selectedModelId);
const variantOptions = selectedModel?.variants ?? [];
const defaultVariant = selectedModel?.defaultVariant ?? "";
const supportsVariants = Boolean(currentAgent?.capabilities?.variants);
const agentDisplayNames: Record<string, string> = {
claude: "Claude Code",
codex: "Codex",
@ -952,6 +985,13 @@ export default function App() {
permissionMode={permissionMode}
model={model}
variant={variant}
modelOptions={modelOptions}
defaultModel={defaultModel}
modelsLoading={modelsLoading}
modelsError={modelsError}
variantOptions={variantOptions}
defaultVariant={defaultVariant}
supportsVariants={supportsVariants}
streamMode={streamMode}
activeModes={activeModes}
currentAgentVersion={currentAgent?.version ?? null}

View file

@ -10,6 +10,7 @@ import {
GitBranch,
HelpCircle,
Image,
Layers,
MessageSquare,
Paperclip,
PlayCircle,
@ -37,7 +38,8 @@ const badges = [
{ key: "fileChanges", label: "File Changes", icon: FileDiff },
{ key: "mcpTools", label: "MCP", icon: Plug },
{ key: "streamingDeltas", label: "Deltas", icon: Activity },
{ key: "itemStarted", label: "Item Start", icon: CircleDot }
{ key: "itemStarted", label: "Item Start", icon: CircleDot },
{ key: "variants", label: "Variants", icon: Layers }
] as const;
type BadgeItem = (typeof badges)[number];

View file

@ -1,6 +1,6 @@
import { MessageSquare, PauseCircle, PlayCircle, Plus, Square, Terminal } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import type { AgentInfo, AgentModeInfo, PermissionEventData, QuestionEventData } from "sandbox-agent";
import type { AgentInfo, AgentModelInfo, AgentModeInfo, PermissionEventData, QuestionEventData } from "sandbox-agent";
import ApprovalsTab from "../debug/ApprovalsTab";
import ChatInput from "./ChatInput";
import ChatMessages from "./ChatMessages";
@ -28,6 +28,13 @@ const ChatPanel = ({
permissionMode,
model,
variant,
modelOptions,
defaultModel,
modelsLoading,
modelsError,
variantOptions,
defaultVariant,
supportsVariants,
streamMode,
activeModes,
currentAgentVersion,
@ -70,6 +77,13 @@ const ChatPanel = ({
permissionMode: string;
model: string;
variant: string;
modelOptions: AgentModelInfo[];
defaultModel: string;
modelsLoading: boolean;
modelsError: string | null;
variantOptions: string[];
defaultVariant: string;
supportsVariants: boolean;
streamMode: "poll" | "sse" | "turn";
activeModes: AgentModeInfo[];
currentAgentVersion?: string | null;
@ -278,6 +292,13 @@ const ChatPanel = ({
permissionMode={permissionMode}
model={model}
variant={variant}
modelOptions={modelOptions}
defaultModel={defaultModel}
modelsLoading={modelsLoading}
modelsError={modelsError}
variantOptions={variantOptions}
defaultVariant={defaultVariant}
supportsVariants={supportsVariants}
activeModes={activeModes}
modesLoading={modesLoading}
modesError={modesError}

View file

@ -1,10 +1,17 @@
import type { AgentModeInfo } from "sandbox-agent";
import type { AgentModelInfo, AgentModeInfo } from "sandbox-agent";
const ChatSetup = ({
agentMode,
permissionMode,
model,
variant,
modelOptions,
defaultModel,
modelsLoading,
modelsError,
variantOptions,
defaultVariant,
supportsVariants,
activeModes,
hasSession,
modesLoading,
@ -18,6 +25,13 @@ const ChatSetup = ({
permissionMode: string;
model: string;
variant: string;
modelOptions: AgentModelInfo[];
defaultModel: string;
modelsLoading: boolean;
modelsError: string | null;
variantOptions: string[];
defaultVariant: string;
supportsVariants: boolean;
activeModes: AgentModeInfo[];
hasSession: boolean;
modesLoading: boolean;
@ -27,6 +41,15 @@ const ChatSetup = ({
onModelChange: (value: string) => void;
onVariantChange: (value: string) => void;
}) => {
const hasModelOptions = modelOptions.length > 0;
const showModelSelect = hasModelOptions && !modelsError;
const hasVariantOptions = variantOptions.length > 0;
const showVariantSelect = supportsVariants && hasVariantOptions && !modelsError;
const modelCustom =
model && hasModelOptions && !modelOptions.some((entry) => entry.id === model);
const variantCustom =
variant && hasVariantOptions && !variantOptions.includes(variant);
return (
<div className="setup-row">
<div className="setup-field">
@ -71,26 +94,82 @@ const ChatSetup = ({
<div className="setup-field">
<span className="setup-label">Model</span>
<input
className="setup-input"
value={model}
onChange={(e) => onModelChange(e.target.value)}
placeholder="Model"
title="Model"
disabled={!hasSession}
/>
{showModelSelect ? (
<select
className="setup-select"
value={model}
onChange={(e) => onModelChange(e.target.value)}
title="Model"
disabled={!hasSession || modelsLoading || Boolean(modelsError)}
>
{modelsLoading ? (
<option value="">Loading models...</option>
) : modelsError ? (
<option value="">{modelsError}</option>
) : (
<>
<option value="">
{defaultModel ? `Default (${defaultModel})` : "Default"}
</option>
{modelCustom && <option value={model}>{model} (custom)</option>}
{modelOptions.map((entry) => (
<option key={entry.id} value={entry.id}>
{entry.name ?? entry.id}
</option>
))}
</>
)}
</select>
) : (
<input
className="setup-input"
value={model}
onChange={(e) => onModelChange(e.target.value)}
placeholder="Model"
title="Model"
disabled={!hasSession}
/>
)}
</div>
<div className="setup-field">
<span className="setup-label">Variant</span>
<input
className="setup-input"
value={variant}
onChange={(e) => onVariantChange(e.target.value)}
placeholder="Variant"
title="Variant"
disabled={!hasSession}
/>
{showVariantSelect ? (
<select
className="setup-select"
value={variant}
onChange={(e) => onVariantChange(e.target.value)}
title="Variant"
disabled={!hasSession || !supportsVariants || modelsLoading || Boolean(modelsError)}
>
{modelsLoading ? (
<option value="">Loading variants...</option>
) : modelsError ? (
<option value="">{modelsError}</option>
) : (
<>
<option value="">
{defaultVariant ? `Default (${defaultVariant})` : "Default"}
</option>
{variantCustom && <option value={variant}>{variant} (custom)</option>}
{variantOptions.map((entry) => (
<option key={entry} value={entry}>
{entry}
</option>
))}
</>
)}
</select>
) : (
<input
className="setup-input"
value={variant}
onChange={(e) => onVariantChange(e.target.value)}
placeholder={supportsVariants ? "Variant" : "Variants unsupported"}
title="Variant"
disabled={!hasSession || !supportsVariants}
/>
)}
</div>
</div>
);

View file

@ -14,6 +14,7 @@ export type FeatureCoverageView = AgentCapabilities & {
mcpTools?: boolean;
streamingDeltas?: boolean;
itemStarted?: boolean;
variants?: boolean;
};
export const emptyFeatureCoverage: FeatureCoverageView = {
@ -34,5 +35,6 @@ export const emptyFeatureCoverage: FeatureCoverageView = {
mcpTools: false,
streamingDeltas: false,
itemStarted: false,
variants: false,
sharedProcess: false
};

View file

@ -103,6 +103,9 @@ export function GetStarted() {
<p className="text-lg text-zinc-400">
Choose the installation method that works best for your use case.
</p>
<p className="mt-4 text-sm text-zinc-500">
Quick OpenCode attach: <span className="font-mono text-white">npx @sandbox-agent/gigacode</span>
</p>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">