diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index b0e8114c..4adc2084 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -311,7 +311,6 @@ class AgentLoop: self._running = False self._mcp_servers = mcp_servers or {} self._mcp_stacks: dict[str, AsyncExitStack] = {} - self._mcp_connected = False self._mcp_connecting = False self._active_tasks: dict[str, list[asyncio.Task]] = {} # session_key -> tasks self._background_tasks: list[asyncio.Task] = [] diff --git a/nanobot/agent/tools/mcp.py b/nanobot/agent/tools/mcp.py index 53193a0d..e4dfad55 100644 --- a/nanobot/agent/tools/mcp.py +++ b/nanobot/agent/tools/mcp.py @@ -526,8 +526,6 @@ class MCPToolWrapper(_MCPWrapperBase): f"(MCP tool returned malformed content: {type(exc).__name__})" ) - return "(MCP tool call failed)" # Unreachable, but satisfies type checkers - def _render_call_result(self, content: Any, arguments: Mapping[str, Any]) -> str: """Turn MCP content blocks into a tool result string. @@ -679,8 +677,6 @@ class MCPResourceWrapper(_MCPWrapperBase): parts.append(str(block)) return "\n".join(parts) or "(no output)" - return "(MCP resource read failed)" # Unreachable - class MCPPromptWrapper(_MCPWrapperBase): """Wraps an MCP prompt as a read-only nanobot Tool.""" @@ -815,8 +811,6 @@ class MCPPromptWrapper(_MCPWrapperBase): parts.append(str(content)) return "\n".join(parts) or "(no output)" - return "(MCP prompt call failed)" # Unreachable - async def connect_mcp_servers( mcp_servers: dict, registry: ToolRegistry @@ -1140,17 +1134,14 @@ async def connect_missing_servers(state: Any, registry: ToolRegistry) -> None: connected = await connect_mcp_servers(missing_servers, registry) state._mcp_stacks.update(connected) _attach_reconnect_handlers(state, registry, connected) - state._mcp_connected = bool(state._mcp_stacks) if connected: logger.info("MCP connected servers: {}", sorted(connected)) else: logger.warning("No MCP servers connected successfully (will retry next message)") except asyncio.CancelledError: logger.warning("MCP connection cancelled (will retry next message)") - state._mcp_connected = bool(state._mcp_stacks) except BaseException as e: logger.warning("Failed to connect MCP servers (will retry next message): {}", e) - state._mcp_connected = bool(state._mcp_stacks) finally: state._mcp_connecting = False @@ -1202,7 +1193,6 @@ async def reload_servers(state: Any, registry: ToolRegistry) -> dict[str, Any]: state._mcp_stacks.update(connected) _attach_reconnect_handlers(state, registry, connected) - state._mcp_connected = bool(state._mcp_stacks) failed = sorted(set(to_connect) - set(connected)) unchanged = not removed and not added and not changed and not retry_missing ok = not failed @@ -1356,7 +1346,6 @@ async def _refresh_terminated_server( connected = await connect_mcp_servers({server_name: cfg}, registry) state._mcp_stacks.update(connected) _attach_reconnect_handlers(state, registry, connected) - state._mcp_connected = bool(state._mcp_stacks) if server_name not in connected: logger.warning("MCP server '{}' reconnect failed after session termination", server_name) return None diff --git a/nanobot/providers/image_generation.py b/nanobot/providers/image_generation.py index 5d729e23..91d08919 100644 --- a/nanobot/providers/image_generation.py +++ b/nanobot/providers/image_generation.py @@ -1395,25 +1395,6 @@ async def _openai_images_from_payload( return images -def _codex_responses_images_from_payload(payload: dict[str, Any]) -> list[str]: - """Extract images from Codex Responses API ``image_generation_call`` output.""" - images: list[str] = [] - for item in payload.get("output") or []: - if not isinstance(item, dict): - continue - if item.get("type") != "image_generation_call": - continue - result = item.get("result") - if isinstance(result, str): - images.append(result if result.startswith("data:image/") else _b64_image_data_url(result)) - continue - if isinstance(result, dict): - image_url = result.get("image_url") or result.get("image") or "" - if isinstance(image_url, str): - images.append(image_url if image_url.startswith("data:image/") else _b64_image_data_url(image_url)) - return images - - async def _parse_codex_sse_images( response: httpx.Response, ) -> tuple[list[str], str]: diff --git a/nanobot/utils/llm_runtime.py b/nanobot/utils/llm_runtime.py index a74f0d8c..be7ab704 100644 --- a/nanobot/utils/llm_runtime.py +++ b/nanobot/utils/llm_runtime.py @@ -2,7 +2,6 @@ from __future__ import annotations -from collections.abc import Callable from dataclasses import dataclass from nanobot.providers.base import LLMProvider @@ -12,11 +11,3 @@ from nanobot.providers.base import LLMProvider class LLMRuntime: provider: LLMProvider model: str - - -LLMRuntimeResolver = Callable[[], LLMRuntime] - - -def static_llm_runtime(provider: LLMProvider, model: str) -> LLMRuntimeResolver: - runtime = LLMRuntime(provider=provider, model=model) - return lambda: runtime diff --git a/nanobot/webui/http_utils.py b/nanobot/webui/http_utils.py index 24fd265d..22a36d2e 100644 --- a/nanobot/webui/http_utils.py +++ b/nanobot/webui/http_utils.py @@ -107,10 +107,6 @@ def parse_request_path(path_with_query: str) -> tuple[str, QueryParams]: return path, parse_qs(parsed.query, keep_blank_values=True) -def normalize_http_path(path_with_query: str) -> str: - return parse_request_path(path_with_query)[0] - - def parse_query(path_with_query: str) -> QueryParams: return parse_request_path(path_with_query)[1] diff --git a/tests/agent/test_mcp_connection.py b/tests/agent/test_mcp_connection.py index 45f4bd47..d5de1343 100644 --- a/tests/agent/test_mcp_connection.py +++ b/tests/agent/test_mcp_connection.py @@ -131,7 +131,6 @@ async def test_connect_mcp_retries_when_no_servers_connect(tmp_path, monkeypatch await loop._connect_mcp() assert attempts == 2 - assert loop._mcp_connected is False assert loop._mcp_stacks == {} diff --git a/tests/channels/test_websocket_channel.py b/tests/channels/test_websocket_channel.py index 5636320b..a5c33892 100644 --- a/tests/channels/test_websocket_channel.py +++ b/tests/channels/test_websocket_channel.py @@ -43,9 +43,6 @@ from nanobot.webui.http_utils import ( from nanobot.webui.http_utils import ( normalize_config_path as _normalize_config_path, ) -from nanobot.webui.http_utils import ( - normalize_http_path as _normalize_http_path, -) from nanobot.webui.http_utils import ( parse_query as _parse_query, ) @@ -227,15 +224,15 @@ def _sent_ws_payloads(mock_ws: AsyncMock) -> list[dict[str, Any]]: return [json.loads(call.args[0]) for call in mock_ws.send.await_args_list] -def test_normalize_http_path_strips_trailing_slash_except_root() -> None: - assert _normalize_http_path("/chat/") == "/chat" - assert _normalize_http_path("/chat?x=1") == "/chat" - assert _normalize_http_path("/") == "/" +def test_parse_request_path_strips_trailing_slash_except_root() -> None: + assert _parse_request_path("/chat/")[0] == "/chat" + assert _parse_request_path("/chat?x=1")[0] == "/chat" + assert _parse_request_path("/")[0] == "/" -def test_parse_request_path_matches_normalize_and_query() -> None: +def test_parse_request_path_matches_query() -> None: path, query = _parse_request_path("/ws/?token=secret&client_id=u1") - assert path == _normalize_http_path("/ws/?token=secret&client_id=u1") + assert path == "/ws" assert query == _parse_query("/ws/?token=secret&client_id=u1") diff --git a/webui/src/components/CliAppMentionText.tsx b/webui/src/components/CliAppMentionText.tsx index 33a9114f..d90df223 100644 --- a/webui/src/components/CliAppMentionText.tsx +++ b/webui/src/components/CliAppMentionText.tsx @@ -4,7 +4,7 @@ import { logoFallbackUrls } from "@/lib/provider-brand"; import type { CliAppInfo, McpPresetInfo } from "@/lib/types"; import { cn } from "@/lib/utils"; -export type CliAppMentionSegment = +type CliAppMentionSegment = | { kind: "text"; text: string } | { kind: "cli"; text: string; app: CliAppInfo }; @@ -36,42 +36,6 @@ export function mcpPresetInitials(preset: Pick app.installed) - .map((app) => [app.name.toLowerCase(), app]), - ); - if (appsByName.size === 0) return [{ kind: "text", text: value }]; - - const segments: CliAppMentionSegment[] = []; - const mentionRe = /(^|[\s([{])@([a-z0-9_-]+)\b/gi; - let cursor = 0; - let match: RegExpExecArray | null; - while ((match = mentionRe.exec(value)) !== null) { - const prefix = match[1] ?? ""; - const name = match[2] ?? ""; - const app = appsByName.get(name.toLowerCase()); - if (!app) continue; - - const mentionStart = match.index + prefix.length; - const mentionEnd = mentionStart + name.length + 1; - if (mentionStart > cursor) { - segments.push({ kind: "text", text: value.slice(cursor, mentionStart) }); - } - segments.push({ kind: "cli", text: value.slice(mentionStart, mentionEnd), app }); - cursor = mentionEnd; - } - if (cursor < value.length) { - segments.push({ kind: "text", text: value.slice(cursor) }); - } - return segments.length ? segments : [{ kind: "text", text: value }]; -} - export function splitCapabilityMentionSegments( value: string, cliApps: CliAppInfo[], diff --git a/webui/src/components/thread/ThreadMessages.tsx b/webui/src/components/thread/ThreadMessages.tsx index 45f0f5ff..2e4703f9 100644 --- a/webui/src/components/thread/ThreadMessages.tsx +++ b/webui/src/components/thread/ThreadMessages.tsx @@ -19,21 +19,6 @@ interface ThreadMessagesProps { export type DisplayUnit = TurnUnit; -/** True when this unit index is the last assistant text slice before the next user message (or end of thread). */ -export function isFinalAssistantSliceBeforeNextUser( - units: DisplayUnit[], - index: number, -): boolean { - const u = units[index]; - if (u.type !== "message" || u.message.role !== "assistant") return true; - for (let j = index + 1; j < units.length; j++) { - const v = units[j]; - if (v.type === "message" && v.message.role === "user") break; - return false; - } - return true; -} - export function buildDisplayUnits( messages: UIMessage[], isStreaming = false, diff --git a/webui/src/i18n/config.ts b/webui/src/i18n/config.ts index 2b8df1bd..02a704a5 100644 --- a/webui/src/i18n/config.ts +++ b/webui/src/i18n/config.ts @@ -57,19 +57,6 @@ export function readStoredLocale(): SupportedLocale | null { } } -export function detectNavigatorLocale(): SupportedLocale { - if (typeof navigator === "undefined") return defaultLocale; - const candidates = [ - ...(navigator.languages ?? []), - navigator.language, - ].filter(Boolean); - for (const locale of candidates) { - const normalized = normalizeLocale(locale); - if (normalized) return normalized; - } - return defaultLocale; -} - export function resolveInitialLocale(): SupportedLocale { return readStoredLocale() ?? defaultLocale; } diff --git a/webui/src/lib/imageEncode.ts b/webui/src/lib/imageEncode.ts index 9606807f..848c712a 100644 --- a/webui/src/lib/imageEncode.ts +++ b/webui/src/lib/imageEncode.ts @@ -12,7 +12,6 @@ import { } from "@/workers/imageEncode.worker"; export type { EncodeResponse, EncodeSuccess, EncodeFailure } from "@/workers/imageEncode.worker"; -export { TARGET_MAX_BYTES } from "@/workers/imageEncode.worker"; type Pending = { resolve: (r: EncodeResponse) => void; @@ -82,16 +81,3 @@ export async function encodeImage(file: File): Promise { } }); } - -/** Release the singleton Worker (tests / teardown). */ -export function disposeImageEncoder(): void { - if (worker) { - worker.terminate(); - worker = null; - } - bootAttempted = false; - for (const [, entry] of pending) { - entry.reject(new Error("image encoder disposed")); - } - pending.clear(); -} diff --git a/webui/src/lib/tool-traces.ts b/webui/src/lib/tool-traces.ts index 10210a9a..e6aee619 100644 --- a/webui/src/lib/tool-traces.ts +++ b/webui/src/lib/tool-traces.ts @@ -1,26 +1,5 @@ import type { ToolProgressEvent } from "@/lib/types"; -/** Drop duplicate tool_call objects (same id or identical formatted trace). */ -export function dedupeToolCallsForUi(calls: unknown): unknown[] { - if (!Array.isArray(calls) || calls.length === 0) return []; - const seen = new Set(); - const out: unknown[] = []; - for (const c of calls) { - let key: string | null = null; - if (c && typeof c === "object" && "id" in c) { - const id = (c as { id?: unknown }).id; - if (typeof id === "string" && id.length > 0) key = `id:${id}`; - } - if (key == null) { - key = formatToolCallTrace(c) ?? ""; - } - if (!key || seen.has(key)) continue; - seen.add(key); - out.push(c); - } - return out; -} - export function formatToolCallTrace(call: unknown): string | null { if (!call || typeof call !== "object") return null; const item = call as {