640 lines
18 KiB
TypeScript
640 lines
18 KiB
TypeScript
/**
|
|
* Minimal gateway REST API client for the TUI and CLI commands.
|
|
*/
|
|
|
|
export interface ModelInfo {
|
|
id: string;
|
|
provider: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface ProviderInfo {
|
|
id: string;
|
|
name: string;
|
|
available: boolean;
|
|
models: ModelInfo[];
|
|
}
|
|
|
|
export interface SessionInfo {
|
|
id: string;
|
|
provider: string;
|
|
modelId: string;
|
|
createdAt: string;
|
|
promptCount: number;
|
|
channels: string[];
|
|
durationMs: number;
|
|
}
|
|
|
|
export interface SessionListResult {
|
|
sessions: SessionInfo[];
|
|
total: number;
|
|
}
|
|
|
|
// ── Agent Config types ──
|
|
|
|
export interface AgentConfigInfo {
|
|
id: string;
|
|
name: string;
|
|
provider: string;
|
|
model: string;
|
|
status: string;
|
|
projectId: string | null;
|
|
ownerId: string | null;
|
|
systemPrompt: string | null;
|
|
allowedTools: string[] | null;
|
|
skills: string[] | null;
|
|
isSystem: boolean;
|
|
config: Record<string, unknown> | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
// ── Project types ──
|
|
|
|
export interface ProjectInfo {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
status: string;
|
|
ownerId: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
// ── Mission types ──
|
|
|
|
export interface MissionInfo {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
status: string;
|
|
projectId: string | null;
|
|
userId: string | null;
|
|
phase: string | null;
|
|
milestones: Record<string, unknown>[] | null;
|
|
config: Record<string, unknown> | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
// ── Mission Task types ──
|
|
|
|
export interface MissionTaskInfo {
|
|
id: string;
|
|
missionId: string;
|
|
taskId: string | null;
|
|
userId: string;
|
|
status: string;
|
|
description: string | null;
|
|
notes: string | null;
|
|
pr: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
// ── Helpers ──
|
|
|
|
function headers(sessionCookie: string, gatewayUrl: string) {
|
|
return { Cookie: sessionCookie, Origin: gatewayUrl };
|
|
}
|
|
|
|
function jsonHeaders(sessionCookie: string, gatewayUrl: string) {
|
|
return { ...headers(sessionCookie, gatewayUrl), 'Content-Type': 'application/json' };
|
|
}
|
|
|
|
async function handleResponse<T>(res: Response, errorPrefix: string): Promise<T> {
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`${errorPrefix} (${res.status}): ${body}`);
|
|
}
|
|
return (await res.json()) as T;
|
|
}
|
|
|
|
// ── Conversation types ──
|
|
|
|
export interface ConversationInfo {
|
|
id: string;
|
|
title: string | null;
|
|
archived: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
// ── Conversation endpoints ──
|
|
|
|
export async function createConversation(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
data: { title?: string; projectId?: string } = {},
|
|
): Promise<ConversationInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/conversations`, {
|
|
method: 'POST',
|
|
headers: jsonHeaders(sessionCookie, gatewayUrl),
|
|
body: JSON.stringify(data),
|
|
});
|
|
return handleResponse<ConversationInfo>(res, 'Failed to create conversation');
|
|
}
|
|
|
|
// ── Provider / Model endpoints ──
|
|
|
|
export async function fetchAvailableModels(
|
|
gatewayUrl: string,
|
|
sessionCookie?: string,
|
|
): Promise<ModelInfo[]> {
|
|
try {
|
|
const res = await fetch(`${gatewayUrl}/api/providers/models`, {
|
|
headers: {
|
|
...(sessionCookie ? { Cookie: sessionCookie } : {}),
|
|
Origin: gatewayUrl,
|
|
},
|
|
});
|
|
if (!res.ok) return [];
|
|
const data = (await res.json()) as ModelInfo[];
|
|
return Array.isArray(data) ? data : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function fetchProviders(
|
|
gatewayUrl: string,
|
|
sessionCookie?: string,
|
|
): Promise<ProviderInfo[]> {
|
|
try {
|
|
const res = await fetch(`${gatewayUrl}/api/providers`, {
|
|
headers: {
|
|
...(sessionCookie ? { Cookie: sessionCookie } : {}),
|
|
Origin: gatewayUrl,
|
|
},
|
|
});
|
|
if (!res.ok) return [];
|
|
const data = (await res.json()) as ProviderInfo[];
|
|
return Array.isArray(data) ? data : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// ── Session endpoints ──
|
|
|
|
export async function fetchSessions(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
): Promise<SessionListResult> {
|
|
const res = await fetch(`${gatewayUrl}/api/sessions`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<SessionListResult>(res, 'Failed to list sessions');
|
|
}
|
|
|
|
export async function deleteSession(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
sessionId: string,
|
|
): Promise<void> {
|
|
const res = await fetch(`${gatewayUrl}/api/sessions/${encodeURIComponent(sessionId)}`, {
|
|
method: 'DELETE',
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
if (!res.ok && res.status !== 204) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`Failed to destroy session (${res.status}): ${body}`);
|
|
}
|
|
}
|
|
|
|
// ── Agent Config endpoints ──
|
|
|
|
export async function fetchAgentConfigs(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
): Promise<AgentConfigInfo[]> {
|
|
const res = await fetch(`${gatewayUrl}/api/agents`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<AgentConfigInfo[]>(res, 'Failed to list agents');
|
|
}
|
|
|
|
export async function fetchAgentConfig(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
id: string,
|
|
): Promise<AgentConfigInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/agents/${encodeURIComponent(id)}`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<AgentConfigInfo>(res, 'Failed to get agent');
|
|
}
|
|
|
|
export async function createAgentConfig(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
data: {
|
|
name: string;
|
|
provider: string;
|
|
model: string;
|
|
projectId?: string;
|
|
systemPrompt?: string;
|
|
allowedTools?: string[];
|
|
skills?: string[];
|
|
config?: Record<string, unknown>;
|
|
},
|
|
): Promise<AgentConfigInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/agents`, {
|
|
method: 'POST',
|
|
headers: jsonHeaders(sessionCookie, gatewayUrl),
|
|
body: JSON.stringify(data),
|
|
});
|
|
return handleResponse<AgentConfigInfo>(res, 'Failed to create agent');
|
|
}
|
|
|
|
export async function updateAgentConfig(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
id: string,
|
|
data: Record<string, unknown>,
|
|
): Promise<AgentConfigInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/agents/${encodeURIComponent(id)}`, {
|
|
method: 'PATCH',
|
|
headers: jsonHeaders(sessionCookie, gatewayUrl),
|
|
body: JSON.stringify(data),
|
|
});
|
|
return handleResponse<AgentConfigInfo>(res, 'Failed to update agent');
|
|
}
|
|
|
|
export async function deleteAgentConfig(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
id: string,
|
|
): Promise<void> {
|
|
const res = await fetch(`${gatewayUrl}/api/agents/${encodeURIComponent(id)}`, {
|
|
method: 'DELETE',
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
if (!res.ok && res.status !== 204) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`Failed to delete agent (${res.status}): ${body}`);
|
|
}
|
|
}
|
|
|
|
// ── Project endpoints ──
|
|
|
|
export async function fetchProjects(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
): Promise<ProjectInfo[]> {
|
|
const res = await fetch(`${gatewayUrl}/api/projects`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<ProjectInfo[]>(res, 'Failed to list projects');
|
|
}
|
|
|
|
// ── Mission endpoints ──
|
|
|
|
export async function fetchMissions(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
): Promise<MissionInfo[]> {
|
|
const res = await fetch(`${gatewayUrl}/api/missions`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<MissionInfo[]>(res, 'Failed to list missions');
|
|
}
|
|
|
|
export async function fetchMission(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
id: string,
|
|
): Promise<MissionInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/missions/${encodeURIComponent(id)}`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<MissionInfo>(res, 'Failed to get mission');
|
|
}
|
|
|
|
export async function createMission(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
data: {
|
|
name: string;
|
|
description?: string;
|
|
projectId?: string;
|
|
status?: string;
|
|
phase?: string;
|
|
milestones?: Record<string, unknown>[];
|
|
config?: Record<string, unknown>;
|
|
},
|
|
): Promise<MissionInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/missions`, {
|
|
method: 'POST',
|
|
headers: jsonHeaders(sessionCookie, gatewayUrl),
|
|
body: JSON.stringify(data),
|
|
});
|
|
return handleResponse<MissionInfo>(res, 'Failed to create mission');
|
|
}
|
|
|
|
export async function updateMission(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
id: string,
|
|
data: Record<string, unknown>,
|
|
): Promise<MissionInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/missions/${encodeURIComponent(id)}`, {
|
|
method: 'PATCH',
|
|
headers: jsonHeaders(sessionCookie, gatewayUrl),
|
|
body: JSON.stringify(data),
|
|
});
|
|
return handleResponse<MissionInfo>(res, 'Failed to update mission');
|
|
}
|
|
|
|
export async function deleteMission(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
id: string,
|
|
): Promise<void> {
|
|
const res = await fetch(`${gatewayUrl}/api/missions/${encodeURIComponent(id)}`, {
|
|
method: 'DELETE',
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
if (!res.ok && res.status !== 204) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`Failed to delete mission (${res.status}): ${body}`);
|
|
}
|
|
}
|
|
|
|
// ── Authenticated interaction runtime endpoints ──
|
|
|
|
export interface InteractionRequest {
|
|
agentName: string;
|
|
correlationId?: string;
|
|
}
|
|
|
|
function interactionHeaders(sessionCookie: string, gatewayUrl: string, correlationId?: string) {
|
|
return {
|
|
...jsonHeaders(sessionCookie, gatewayUrl),
|
|
...(correlationId ? { 'X-Correlation-Id': correlationId } : {}),
|
|
};
|
|
}
|
|
|
|
function interactionPath(agentName: string, suffix: string): string {
|
|
return `/api/interaction/${encodeURIComponent(agentName)}${suffix}`;
|
|
}
|
|
|
|
export async function fetchInteractionSessions(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & { providerId: string },
|
|
): Promise<unknown[]> {
|
|
const params = new URLSearchParams({ provider: request.providerId });
|
|
const res = await fetch(
|
|
`${gatewayUrl}${interactionPath(request.agentName, `/sessions?${params}`)}`,
|
|
{
|
|
headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId),
|
|
},
|
|
);
|
|
return handleResponse<unknown[]>(res, 'Failed to list interaction sessions');
|
|
}
|
|
|
|
export async function fetchInteractionTree(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & { providerId: string },
|
|
): Promise<unknown[]> {
|
|
const params = new URLSearchParams({ provider: request.providerId });
|
|
const res = await fetch(`${gatewayUrl}${interactionPath(request.agentName, `/tree?${params}`)}`, {
|
|
headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId),
|
|
});
|
|
return handleResponse<unknown[]>(res, 'Failed to get interaction session tree');
|
|
}
|
|
|
|
export async function enrollInteractionSession(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & {
|
|
sessionId: string;
|
|
providerId: string;
|
|
runtimeSessionId: string;
|
|
},
|
|
): Promise<{ status: string; sessionId: string }> {
|
|
const res = await fetch(
|
|
`${gatewayUrl}${interactionPath(request.agentName, `/sessions/${encodeURIComponent(request.sessionId)}/enroll`)}`,
|
|
{
|
|
method: 'POST',
|
|
headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId),
|
|
body: JSON.stringify({
|
|
providerId: request.providerId,
|
|
runtimeSessionId: request.runtimeSessionId,
|
|
}),
|
|
},
|
|
);
|
|
return handleResponse<{ status: string; sessionId: string }>(
|
|
res,
|
|
'Failed to enroll interaction session',
|
|
);
|
|
}
|
|
|
|
export async function attachInteractionSession(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & { sessionId: string; mode?: 'read' | 'control' },
|
|
): Promise<unknown> {
|
|
const res = await fetch(
|
|
`${gatewayUrl}${interactionPath(request.agentName, `/sessions/${encodeURIComponent(request.sessionId)}/attach`)}`,
|
|
{
|
|
method: 'POST',
|
|
headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId),
|
|
body: JSON.stringify({ mode: request.mode ?? 'read' }),
|
|
},
|
|
);
|
|
return handleResponse<unknown>(res, 'Failed to attach interaction session');
|
|
}
|
|
|
|
export async function* streamInteractionSession(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & { sessionId: string; cursor?: string },
|
|
): AsyncIterable<unknown> {
|
|
const params = request.cursor?.trim()
|
|
? `?${new URLSearchParams({ cursor: request.cursor.trim() }).toString()}`
|
|
: '';
|
|
const res = await fetch(
|
|
`${gatewayUrl}${interactionPath(request.agentName, `/sessions/${encodeURIComponent(request.sessionId)}/stream${params}`)}`,
|
|
{ headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId) },
|
|
);
|
|
if (!res.ok || !res.body) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`Failed to stream interaction session (${res.status}): ${body}`);
|
|
}
|
|
|
|
const reader = res.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let pending = '';
|
|
try {
|
|
for (;;) {
|
|
const chunk = await reader.read();
|
|
if (chunk.done) break;
|
|
pending += decoder.decode(chunk.value, { stream: true });
|
|
for (;;) {
|
|
const separator = pending.indexOf('\n\n');
|
|
if (separator < 0) break;
|
|
const frame = pending.slice(0, separator);
|
|
pending = pending.slice(separator + 2);
|
|
const data = frame
|
|
.split('\n')
|
|
.find((line: string): boolean => line.startsWith('data:'))
|
|
?.slice('data:'.length)
|
|
.trim();
|
|
if (data) yield JSON.parse(data) as unknown;
|
|
}
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
}
|
|
|
|
export async function sendInteractionMessage(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & { sessionId: string; content: string; idempotencyKey: string },
|
|
): Promise<{ status: string; sessionId: string }> {
|
|
const res = await fetch(
|
|
`${gatewayUrl}${interactionPath(request.agentName, `/sessions/${encodeURIComponent(request.sessionId)}/send`)}`,
|
|
{
|
|
method: 'POST',
|
|
headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId),
|
|
body: JSON.stringify({ content: request.content, idempotencyKey: request.idempotencyKey }),
|
|
},
|
|
);
|
|
return handleResponse<{ status: string; sessionId: string }>(
|
|
res,
|
|
'Failed to send interaction message',
|
|
);
|
|
}
|
|
|
|
export async function stopInteractionSession(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & { sessionId: string; approvalRef: string },
|
|
): Promise<{ status: string; sessionId: string }> {
|
|
const res = await fetch(
|
|
`${gatewayUrl}${interactionPath(request.agentName, `/sessions/${encodeURIComponent(request.sessionId)}/stop`)}`,
|
|
{
|
|
method: 'POST',
|
|
headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId),
|
|
body: JSON.stringify({ approvalRef: request.approvalRef }),
|
|
},
|
|
);
|
|
return handleResponse<{ status: string; sessionId: string }>(
|
|
res,
|
|
'Failed to stop interaction session',
|
|
);
|
|
}
|
|
|
|
export async function recoverInteractionSession(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
request: InteractionRequest & { sessionId: string },
|
|
): Promise<{ status: string; sessionId: string }> {
|
|
const res = await fetch(
|
|
`${gatewayUrl}${interactionPath(request.agentName, `/sessions/${encodeURIComponent(request.sessionId)}/recover`)}`,
|
|
{
|
|
method: 'POST',
|
|
headers: interactionHeaders(sessionCookie, gatewayUrl, request.correlationId),
|
|
},
|
|
);
|
|
return handleResponse<{ status: string; sessionId: string }>(
|
|
res,
|
|
'Failed to recover interaction session',
|
|
);
|
|
}
|
|
|
|
export async function fetchInteractionStatus(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
): Promise<unknown> {
|
|
const res = await fetch(`${gatewayUrl}/api/providers/status`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<unknown>(res, 'Failed to get effective interaction policy');
|
|
}
|
|
|
|
export async function fetchInteractionHealth(gatewayUrl: string): Promise<unknown> {
|
|
const res = await fetch(`${gatewayUrl}/health/ready`);
|
|
return handleResponse<unknown>(res, 'Failed to get interaction readiness');
|
|
}
|
|
|
|
// ── Conversation Message types ──
|
|
|
|
export interface ConversationMessage {
|
|
id: string;
|
|
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
content: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
// ── Conversation Message endpoints ──
|
|
|
|
export async function fetchConversationMessages(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
conversationId: string,
|
|
): Promise<ConversationMessage[]> {
|
|
const res = await fetch(
|
|
`${gatewayUrl}/api/conversations/${encodeURIComponent(conversationId)}/messages`,
|
|
{
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
},
|
|
);
|
|
return handleResponse<ConversationMessage[]>(res, 'Failed to fetch conversation messages');
|
|
}
|
|
|
|
// ── Mission Task endpoints ──
|
|
|
|
export async function fetchMissionTasks(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
missionId: string,
|
|
): Promise<MissionTaskInfo[]> {
|
|
const res = await fetch(`${gatewayUrl}/api/missions/${encodeURIComponent(missionId)}/tasks`, {
|
|
headers: headers(sessionCookie, gatewayUrl),
|
|
});
|
|
return handleResponse<MissionTaskInfo[]>(res, 'Failed to list mission tasks');
|
|
}
|
|
|
|
export async function createMissionTask(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
missionId: string,
|
|
data: {
|
|
description?: string;
|
|
status?: string;
|
|
notes?: string;
|
|
pr?: string;
|
|
taskId?: string;
|
|
},
|
|
): Promise<MissionTaskInfo> {
|
|
const res = await fetch(`${gatewayUrl}/api/missions/${encodeURIComponent(missionId)}/tasks`, {
|
|
method: 'POST',
|
|
headers: jsonHeaders(sessionCookie, gatewayUrl),
|
|
body: JSON.stringify(data),
|
|
});
|
|
return handleResponse<MissionTaskInfo>(res, 'Failed to create mission task');
|
|
}
|
|
|
|
export async function updateMissionTask(
|
|
gatewayUrl: string,
|
|
sessionCookie: string,
|
|
missionId: string,
|
|
taskId: string,
|
|
data: Record<string, unknown>,
|
|
): Promise<MissionTaskInfo> {
|
|
const res = await fetch(
|
|
`${gatewayUrl}/api/missions/${encodeURIComponent(missionId)}/tasks/${encodeURIComponent(taskId)}`,
|
|
{
|
|
method: 'PATCH',
|
|
headers: jsonHeaders(sessionCookie, gatewayUrl),
|
|
body: JSON.stringify(data),
|
|
},
|
|
);
|
|
return handleResponse<MissionTaskInfo>(res, 'Failed to update mission task');
|
|
}
|