Files
pikasTech-HWLAB/web/hwlab-cloud-web/src/api/agent.ts
T
2026-06-18 15:24:16 +08:00

94 lines
5.9 KiB
TypeScript

// SPEC: PJ2026-010403 API契约 draft-2026-06-17-r0; PJ2026-010401 Web工作台 draft-2026-06-17-r0.
// Responsibility: Code Agent HTTP client for send, steer, turn, trace, session and cancel APIs.
import { fetchJson, type ApiRequestOptions } from "./client";
import type { AgentChatResponse, AgentChatResultResponse, ApiResult } from "@/types";
export interface AgentTraceRequestOptions {
sinceSeq?: number | null;
limit?: number | null;
}
function workbenchTracePath(traceId: string, options: AgentTraceRequestOptions = {}): string {
const params = new URLSearchParams();
if (Number.isFinite(options.sinceSeq)) params.set("sinceSeq", String(Math.max(0, Math.trunc(Number(options.sinceSeq)))));
if (Number.isFinite(options.limit)) params.set("limit", String(Math.max(1, Math.trunc(Number(options.limit)))));
const query = params.toString();
return `/v1/workbench/traces/${encodeURIComponent(traceId)}/events${query ? `?${query}` : ""}`;
}
export const agentAPI = {
sendAgentMessage: (payload: Record<string, unknown>, timeoutMs: number, activityRef?: ApiRequestOptions["activityRef"]): Promise<ApiResult<AgentChatResponse>> => fetchJson("/v1/agent/chat", { method: "POST", body: JSON.stringify(payload), timeoutMs, timeoutName: "Code Agent", activityRef }),
steerAgentMessage: (payload: Record<string, unknown>, timeoutMs: number, activityRef?: ApiRequestOptions["activityRef"]): Promise<ApiResult<AgentChatResponse>> => fetchJson("/v1/agent/chat/steer", { method: "POST", body: JSON.stringify(payload), timeoutMs, timeoutName: "Code Agent steer", activityRef }),
getAgentTurn: async (traceId: string, timeoutMs = 8000, activityRef?: ApiRequestOptions["activityRef"]): Promise<ApiResult<AgentChatResultResponse>> => normalizeWorkbenchTurnResult(await fetchJson<Record<string, unknown>>(`/v1/workbench/turns/${encodeURIComponent(traceId)}`, { timeoutMs, timeoutName: "Code Agent turn", activityRef }), traceId),
getAgentTrace: async (traceId: string, timeoutMs = 8000, activityRef?: ApiRequestOptions["activityRef"], options?: AgentTraceRequestOptions): Promise<ApiResult<AgentChatResultResponse>> => normalizeWorkbenchTraceResult(await fetchJson<Record<string, unknown>>(workbenchTracePath(traceId, options), { timeoutMs, timeoutName: "Code Agent trace", activityRef }), traceId),
createAgentSession: (payload: Record<string, unknown> = {}): Promise<ApiResult<{ session?: Record<string, unknown> }>> => fetchJson("/v1/agent/sessions", { method: "POST", body: JSON.stringify(payload), timeoutMs: 30000, timeoutName: "Code Agent session create" }),
getAgentSession: (sessionId: string): Promise<ApiResult<{ session?: Record<string, unknown> }>> => fetchJson(`/v1/agent/sessions/${encodeURIComponent(sessionId)}`, { timeoutMs: 8000, timeoutName: "Code Agent session" }),
cancelAgentMessage: (payload: Record<string, unknown>): Promise<ApiResult<{ ok?: boolean; canceled?: boolean; error?: { code?: string; message?: string; userMessage?: string } }>> => fetchJson("/v1/agent/chat/cancel", { method: "POST", body: JSON.stringify(payload), timeoutMs: 30000, timeoutName: "Code Agent cancel" })
};
function normalizeWorkbenchTurnResult(result: ApiResult<Record<string, unknown>>, traceId: string): ApiResult<AgentChatResultResponse> {
if (!result.ok || !result.data) return result as ApiResult<AgentChatResultResponse>;
const turn = recordValue(result.data.turn) ?? result.data;
return {
...result,
data: {
...turn,
traceId: textValue(turn.traceId) ?? traceId,
status: textValue(turn.status) ?? textValue(result.data.status) ?? "unknown",
running: turn.running === true,
terminal: turn.terminal === true,
sessionId: textValue(turn.sessionId) ?? undefined,
threadId: textValue(turn.threadId) ?? undefined,
agentRun: recordValue(turn.agentRun) ?? undefined,
updatedAt: textValue(turn.updatedAt) ?? textValue(result.data.observedAt) ?? undefined
} as AgentChatResultResponse
};
}
function normalizeWorkbenchTraceResult(result: ApiResult<Record<string, unknown>>, traceId: string): ApiResult<AgentChatResultResponse> {
if (!result.ok || !result.data) return result as ApiResult<AgentChatResultResponse>;
const events = Array.isArray(result.data.events) ? result.data.events : [];
const nextSeq = Number(result.data.nextSeq ?? result.data.nextSinceSeq);
return {
...result,
data: {
...result.data,
traceId: textValue(result.data.traceId) ?? traceId,
status: textValue(result.data.traceStatus, result.data.status) ?? "unknown",
traceStatus: textValue(result.data.traceStatus, result.data.status) ?? undefined,
events,
traceEvents: events,
eventCount: Number.isFinite(Number(result.data.eventCount)) ? Number(result.data.eventCount) : events.length,
hasMore: result.data.hasMore === true,
fullTraceLoaded: result.data.hasMore !== true,
nextSinceSeq: Number.isFinite(nextSeq) ? Math.trunc(nextSeq) : null,
range: recordValue(result.data.range) ?? undefined,
runnerTrace: {
traceId: textValue(result.data.traceId) ?? traceId,
status: textValue(result.data.traceStatus, result.data.status) ?? undefined,
events,
eventCount: Number.isFinite(Number(result.data.eventCount)) ? Number(result.data.eventCount) : events.length,
hasMore: result.data.hasMore === true,
fullTraceLoaded: result.data.hasMore !== true,
nextSinceSeq: Number.isFinite(nextSeq) ? Math.trunc(nextSeq) : null,
range: recordValue(result.data.range) ?? undefined,
eventSource: "trace-api"
}
} as AgentChatResultResponse
};
}
function recordValue(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" ? value as Record<string, unknown> : null;
}
function textValue(...values: unknown[]): string | null {
for (const value of values) {
if (typeof value !== "string") continue;
const text = value.trim();
if (text) return text;
}
return null;
}