From 71bde7d9e4fa6bf1f1ee5127b5405cf65e97c8fb Mon Sep 17 00:00:00 2001 From: Huarch Date: Thu, 6 Aug 2026 10:40:13 +0800 Subject: [PATCH] refactor(chat): centralize agent session URLs --- .../chat/hooks/useAgentChatSession.ts | 46 +++++++++++-- src/lib/chatStream.ts | 69 ++++++++++++------- 2 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/components/chat/hooks/useAgentChatSession.ts b/src/components/chat/hooks/useAgentChatSession.ts index 090a8d1..c8ba427 100644 --- a/src/components/chat/hooks/useAgentChatSession.ts +++ b/src/components/chat/hooks/useAgentChatSession.ts @@ -3,14 +3,50 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useSession } from "next-auth/react"; -import { abortAgentChat, forkAgentChat, rejectAgentQuestion, replyAgentCredentialRefresh, replyAgentPermission, replyAgentQuestion, resumeAgentChatStream, streamAgentChat } from "@/lib/chatStream"; +import { + abortAgentChat, + forkAgentChat, + rejectAgentQuestion, + replyAgentCredentialRefresh, + replyAgentPermission, + replyAgentQuestion, + resumeAgentChatStream, + streamAgentChat, +} from "@/lib/chatStream"; import type { PermissionReply, StreamEvent } from "@/lib/chatStream"; import { useAuthStore } from "@/store/authStore"; -import type { AgentArtifact, ChatSessionSummary, Message } from "../GlobalChatbox.types"; +import type { + AgentArtifact, + ChatSessionSummary, + Message, +} from "../GlobalChatbox.types"; import { cloneMessages } from "../globalChatboxUtils"; -import { createEmptyChatState, deleteChatSession, listChatSessions, loadChatSessionById, updateChatSessionTitle } from "../chatStorage"; -import { applyQuestionResponse, cancelRunningTodos, completeRunningProgress, createAssistantMessage, createTodoUpdateFromEvent, createUserMessage, dedupeQuestionsAcrossMessages, finalizeAssistantMessageAfterAbort, normalizeSessionTodos, toPermissionStatus, upsertPermission, upsertProgress, upsertQuestionAcrossMessages } from "./agentChatSessionState"; -import type { PromptRunOptions, UseAgentChatSessionOptions } from "./useAgentChatSession.types"; +import { + createEmptyChatState, + deleteChatSession, + listChatSessions, + loadChatSessionById, + updateChatSessionTitle, +} from "../chatStorage"; +import { + applyQuestionResponse, + cancelRunningTodos, + completeRunningProgress, + createAssistantMessage, + createTodoUpdateFromEvent, + createUserMessage, + dedupeQuestionsAcrossMessages, + finalizeAssistantMessageAfterAbort, + normalizeSessionTodos, + toPermissionStatus, + upsertPermission, + upsertProgress, + upsertQuestionAcrossMessages, +} from "./agentChatSessionState"; +import type { + PromptRunOptions, + UseAgentChatSessionOptions, +} from "./useAgentChatSession.types"; const TOKEN_PLAYBACK_INTERVAL_MS = 16; const TOKEN_PLAYBACK_BASE_CHARS = 28; diff --git a/src/lib/chatStream.ts b/src/lib/chatStream.ts index 8ba977d..fce65ea 100644 --- a/src/lib/chatStream.ts +++ b/src/lib/chatStream.ts @@ -1,6 +1,11 @@ import { apiFetch } from "@/lib/apiFetch"; import { config } from "@config/config"; +const AGENT_SESSIONS_URL = `${config.AGENT_URL}/api/v1/agent/sessions`; + +const getAgentSessionUrl = (sessionId: string, suffix = "") => + `${AGENT_SESSIONS_URL}/${encodeURIComponent(sessionId)}${suffix}`; + export type AgentModel = string; export type PermissionReply = "once" | "always" | "reject"; @@ -509,7 +514,7 @@ const readStreamEvents = async ( const ensureAgentSession = async (sessionId?: string) => { if (sessionId) return sessionId; - const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/sessions`, { + const response = await apiFetch(AGENT_SESSIONS_URL, { method: "POST", headers: { "Content-Type": "application/json", @@ -519,7 +524,10 @@ const ensureAgentSession = async (sessionId?: string) => { skipAuthRedirect: true, }); if (!response.ok) { - throw new Error((await response.text()) || `session creation failed: ${response.status}`); + throw new Error( + (await response.text()) || + `session creation failed: ${response.status}`, + ); } const payload = (await response.json()) as { session_id?: string }; if (!payload.session_id) { @@ -540,7 +548,7 @@ export const streamAgentChat = async ({ try { const effectiveSessionId = await ensureAgentSession(sessionId); response = await apiFetch( - `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(effectiveSessionId)}/runs`, + getAgentSessionUrl(effectiveSessionId, "/runs"), { method: "POST", signal, @@ -597,7 +605,7 @@ export const resumeAgentChatStream = async ({ let response: Response; try { response = await apiFetch( - `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/runs/current/events`, + getAgentSessionUrl(sessionId, "/runs/current/events"), { method: "GET", signal, @@ -638,11 +646,14 @@ export const abortAgentChat = async (sessionId?: string) => { return; } - const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/runs/current`, { - method: "DELETE", - projectHeaderMode: "include", - skipAuthRedirect: true, - }); + const response = await apiFetch( + getAgentSessionUrl(sessionId, "/runs/current"), + { + method: "DELETE", + projectHeaderMode: "include", + skipAuthRedirect: true, + }, + ); if (!response.ok) { const detail = await response.text(); @@ -656,7 +667,7 @@ export const replyAgentPermission = async ( reply: PermissionReply, ) => { const response = await apiFetch( - `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/permission-responses`, + getAgentSessionUrl(sessionId, "/permission-responses"), { method: "POST", headers: { @@ -682,7 +693,7 @@ export const replyAgentCredentialRefresh = async ( requestId: string, ) => { const response = await apiFetch( - `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/credential-refreshes`, + getAgentSessionUrl(sessionId, "/credential-refreshes"), { method: "POST", headers: { "Content-Type": "application/json" }, @@ -693,7 +704,9 @@ export const replyAgentCredentialRefresh = async ( ); if (!response.ok) { const detail = await response.text(); - throw new Error(detail || `credential refresh reply failed: ${response.status}`); + throw new Error( + detail || `credential refresh reply failed: ${response.status}`, + ); } }; @@ -703,7 +716,7 @@ export const replyAgentQuestion = async ( answers: string[][], ) => { const response = await apiFetch( - `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/question-responses`, + getAgentSessionUrl(sessionId, "/question-responses"), { method: "POST", headers: { @@ -730,7 +743,7 @@ export const rejectAgentQuestion = async ( requestId: string, ) => { const response = await apiFetch( - `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/question-responses`, + getAgentSessionUrl(sessionId, "/question-responses"), { method: "POST", headers: { @@ -751,18 +764,24 @@ export const rejectAgentQuestion = async ( } }; -export const forkAgentChat = async (sessionId: string | undefined, keepMessageCount: number) => { - const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId ?? "")}/forks`, { - method: "POST", - headers: { - "Content-Type": "application/json", +export const forkAgentChat = async ( + sessionId: string | undefined, + keepMessageCount: number, +) => { + const response = await apiFetch( + getAgentSessionUrl(sessionId ?? "", "/forks"), + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + keep_message_count: keepMessageCount, + }), + projectHeaderMode: "include", + skipAuthRedirect: true, }, - body: JSON.stringify({ - keep_message_count: keepMessageCount, - }), - projectHeaderMode: "include", - skipAuthRedirect: true, - }); + ); if (!response.ok) { const detail = await response.text();