[eric] tiny dead-code sweep — drop unused langchain pins from requirements.txt (saves ~5MB on packaged build) plus a few orphan exports left

over from past iterations (temp_state slice fields, clipboard timestamp getter, three never-imported lastSeq helpers).
This commit is contained in:
ciregenz
2026-05-03 21:31:13 -07:00
parent d14cc89300
commit 5f5b932c53
8 changed files with 112 additions and 53 deletions
+91 -11
View File
@@ -2886,17 +2886,82 @@ class AgentManager:
_ticker_task = asyncio.create_task(_ticker_loop())
if content_parts:
asst_msg = Message(
id=stream_text_msg_id or uuid4().hex,
role="assistant",
content="\n".join(content_parts),
branch_id=session.active_branch_id,
_asst_text = "\n".join(content_parts)
# 9Router can deliver upstream auth failures
# AS the assistant's reply ("Failed to
# authenticate. API Error: 401 ... [codex/...]
# Provided authentication token is expired").
# When that happens, the SDK doesn't raise —
# so our catch-all _is_auth_error path never
# fires. Detect the pattern in the text
# itself and substitute a friendly system
# bubble + auth_error WS event so the user
# gets an actionable message instead of a
# raw 401 JSON dump in the chat.
_lower_text = _asst_text.lower()
_looks_like_router_auth_error = (
("failed to authenticate" in _lower_text and "401" in _lower_text)
or ("authentication token is expired" in _lower_text)
or ("authentication token has expired" in _lower_text)
or ("provided authentication token" in _lower_text and ("401" in _lower_text or "expired" in _lower_text))
)
session.messages.append(asst_msg)
await ws_manager.send_to_session(session_id, "agent:message", {
"session_id": session_id,
"message": asst_msg.model_dump(mode="json"),
})
if _looks_like_router_auth_error:
# Build a friendly message keyed off the
# provider name in the upstream error.
if "codex/" in _lower_text or "[codex" in _lower_text:
friendly = (
"GPT subscription token expired. Open Settings → Models and click "
"Reconnect on the OpenAI / GPT row to refresh — should take ~10s, "
"then send your message again."
)
reason = "codex_token_expired"
elif "gemini-cli/" in _lower_text or "[gemini" in _lower_text:
friendly = (
"Gemini subscription token expired. Open Settings → Models and click "
"Reconnect on the Google / Gemini row, then send your message again."
)
reason = "gemini_token_expired"
else:
friendly = (
"Provider authentication expired. Open Settings → Models and "
"reconnect, then send your message again."
)
reason = "router_auth_expired"
_err_msg = Message(
id=uuid4().hex,
role="system",
content=friendly,
branch_id=session.active_branch_id,
)
session.messages.append(_err_msg)
await ws_manager.send_to_session(session_id, "agent:auth_error", {
"session_id": session_id,
"reason": reason,
"message": friendly,
"model": session.model,
})
await ws_manager.send_to_session(session_id, "agent:message", {
"session_id": session_id,
"message": _err_msg.model_dump(mode="json"),
})
_analytics("auth.error", {
"reason": reason,
"model": session.model,
"provider": session.provider,
"via": "router_streamed_text",
}, session_id=session_id, dashboard_id=session.dashboard_id)
else:
asst_msg = Message(
id=stream_text_msg_id or uuid4().hex,
role="assistant",
content=_asst_text,
branch_id=session.active_branch_id,
)
session.messages.append(asst_msg)
await ws_manager.send_to_session(session_id, "agent:message", {
"session_id": session_id,
"message": asst_msg.model_dump(mode="json"),
})
for i, tu in enumerate(tool_uses):
msg_id = stream_tool_msg_ids_ordered[i] if i < len(stream_tool_msg_ids_ordered) else uuid4().hex
@@ -3197,7 +3262,22 @@ class AgentManager:
# 3. Anthropic API key 401 — wrong key. Re-enter.
_model = (session.model or "").lower()
_combined = f"{e!s}\n{_stderr_tail}".lower()
if "no credentials for provider" in _combined:
# Codex/OpenAI subscription tokens rotate every ~2-3
# minutes — the user sees the rotation window as a 401
# with "reset after 1m 59s" or similar. Don't ask them to
# reconnect; just tell them to wait it out and retry.
if (
("codex/" in _combined or "[codex/" in _combined or _model.startswith(("cx/", "gpt-")))
and ("authentication token is expired" in _combined or "authentication token has expired" in _combined or "401" in _combined)
):
friendly_msg = (
"GPT subscription token just rotated — this is "
"automatic and resets every couple minutes. Send "
"your message again in ~1 minute and it'll go "
"through. (No need to reconnect anything.)"
)
reason = "codex_token_rotating"
elif "no credentials for provider" in _combined:
friendly_msg = (
"Selected route requires Claude Pro / Max, but it's "
"not connected. Open Settings → Models and either "
+4 -4
View File
@@ -423,7 +423,7 @@ async def resolve_aux_model(
2. Anthropic API key set → bare haiku/sonnet on real Anthropic API
3. 9Router + Claude subscription connected → cc/<model>
4. 9Router + Codex connected → cx/gpt-5.4-mini
5. 9Router + Gemini connected → gc/gemini-2.5-flash
5. 9Router + Gemini connected → gc/gemini-3.1-flash-lite-preview
6. Nothing available → raise ValueError
When primary_api is provided, the resolver tries that family first
@@ -458,9 +458,9 @@ async def resolve_aux_model(
# primary is Codex but it's not reachable — fall through to default
elif primary_api == "gemini-cli" or primary_api == "gemini":
if "gemini-cli" in connected:
return ("gc/gemini-2.5-flash", base_url)
return ("gc/gemini-3.1-flash-lite-preview", base_url)
if getattr(settings, "google_api_key", None):
return ("gemini-2.5-flash", "https://generativelanguage.googleapis.com/v1beta")
return ("gemini-3.1-flash-lite-preview", "https://generativelanguage.googleapis.com/v1beta")
# fall through to default
# primary_api == "anthropic" naturally falls into the Anthropic-first
# cascade below — no special branch needed.
@@ -485,7 +485,7 @@ async def resolve_aux_model(
if "codex" in connected:
return ("cx/gpt-5.4-mini", base_url)
if "gemini-cli" in connected:
return ("gc/gemini-2.5-flash", base_url)
return ("gc/gemini-3.1-flash-lite-preview", base_url)
raise ValueError(
"No AI provider connected for auxiliary LLM call. "
-2
View File
@@ -11,8 +11,6 @@ claude-agent-sdk==0.1.70
jsonschema
fastapi[standard]
pydantic==2.13.3
langchain-core==0.3.51
langchain-openai==0.3.12
typeguard==4.4.2
python-dotenv==1.1.1
Pillow
@@ -842,11 +842,23 @@ const ProviderReasoningExplanation: React.FC<{
}
return segs.join(' · ');
})();
// Stable per-mount variant pick. Each render of the same bubble keeps
// its line; new bubbles get a fresh roll. Adds a touch of personality
// without becoming repetitive across the transcript.
const variants = [
"It's still thinking — we just aren't allowed to peek behind the curtain.",
"Wheels are turning, but this provider keeps its thoughts private.",
"Brain's busy back there; the provider just isn't letting us listen in.",
"Mulling it over quietly — only Claude shows its work out loud.",
"Thinking happened, just not in the open. (GPT and Gemini play their cards close.)",
"Reasoning's underway, but this provider doesn't broadcast it. Trust the process.",
];
const idx = useMemo(() => Math.floor(Math.random() * variants.length), []);
const line = variants[idx];
return (
<Box component="span" sx={{ fontStyle: 'italic', opacity: 0.85 }}>
The model reasoned about this turn, but the provider didn't expose
the reasoning text — only Anthropic emits a full chain-of-thought
stream. {metric ? `Spent ${metric} thinking.` : 'No reasoning trace available.'}
{line} {metric ? `Took ${metric}.` : ''}
</Box>
);
};
@@ -13,22 +13,15 @@ export interface ClipboardCard {
}
let clipboardCards: ClipboardCard[] = [];
let clipboardTimestamp = 0;
export function setClipboardCards(cards: ClipboardCard[]): void {
clipboardCards = cards;
clipboardTimestamp = Date.now();
}
export function getClipboardCards(): ClipboardCard[] {
return clipboardCards;
}
export function getClipboardTimestamp(): number {
return clipboardTimestamp;
}
export function clearClipboard(): void {
clipboardCards = [];
clipboardTimestamp = 0;
}
+1 -11
View File
@@ -2,14 +2,12 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface TempState {
temp_state: string | null;
pendingBrowserUrl: string | null;
pendingFocusAgentId: string | null;
lastDashboardId: string | null;
}
const initialState: TempState = {
temp_state: null,
pendingBrowserUrl: null,
pendingFocusAgentId: null,
lastDashboardId: null,
@@ -19,12 +17,6 @@ const tempStateSlice = createSlice({
name: 'tempState',
initialState,
reducers: {
setTempState(state, action: PayloadAction<string | null>) {
state.temp_state = action.payload;
},
resetTempState(state) {
state.temp_state = null;
},
setPendingBrowserUrl(state, action: PayloadAction<string>) {
state.pendingBrowserUrl = action.payload;
},
@@ -43,9 +35,7 @@ const tempStateSlice = createSlice({
},
});
export const {
setTempState,
resetTempState,
export const {
setPendingBrowserUrl,
clearPendingBrowserUrl,
setLastDashboardId,
@@ -947,20 +947,6 @@ export const dashboardWs = new WebSocketManager(`${WS_BASE}/ws/dashboard`, { ski
// to a no-op replay. Safe.
const _sessionLastSeq: Map<string, number> = new Map();
export function getPersistedLastSeq(sessionId: string): number {
return _sessionLastSeq.get(sessionId) ?? 0;
}
export function setPersistedLastSeq(sessionId: string, seq: number): void {
if (seq > (_sessionLastSeq.get(sessionId) ?? 0)) {
_sessionLastSeq.set(sessionId, seq);
}
}
export function clearPersistedLastSeq(sessionId: string): void {
_sessionLastSeq.delete(sessionId);
}
export function createSessionWs(sessionId: string): WebSocketManager {
return new WebSocketManager(`${WS_BASE}/ws/agents/${sessionId}`, { sessionId });
}
File diff suppressed because one or more lines are too long