Files
boocode/apps/coder/web/src/api/client.ts
indifferentketchup 792bbb9da3 v2.3.0-sampling-params-ask-user: agent sampling params, ask_user_input in CoderPane, UX polish
Add top_p/top_k/min_p/presence_penalty to AGENTS.md frontmatter and thread
through inference (agents.ts parser → Agent type → stream-phase → sentinel
summaries). Null means omit from request body, preserving provider defaults.

Wire ask_user_input interactive card into both BooCoder frontends: the
CoderPane in BooChat's SPA (CoderMessageList now renders AskUserInputCard
instead of ToolCallLine for ask_user_input tool calls) and the standalone
coder SPA (MessageBubble + new AskUserInputCard + shadcn ui primitives).

Additional fixes: SessionLandingPage uses ChatInput with slash-command
support and lazy chat creation; Session.tsx hydrate-race fix for empty pane
promotion; AgentPicker wider dropdown with line-clamp; ModelPicker min-width;
Textarea converted to forwardRef; Recon agent added to AGENTS.md; codecontext
host port exposed in docker-compose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 21:02:21 +00:00

102 lines
3.2 KiB
TypeScript

import type { Project, Session, Chat, Message, PendingChange, AskUserAnswer } from './types';
export class ApiError extends Error {
constructor(
public status: number,
public body: unknown,
) {
super(
typeof body === 'object' && body && 'error' in body
? String((body as { error: unknown }).error)
: `HTTP ${status}`,
);
}
}
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(path, {
...init,
headers: {
'Content-Type': 'application/json',
...(init.headers ?? {}),
},
});
if (res.status === 204) return undefined as T;
const text = await res.text();
const data = text ? JSON.parse(text) : undefined;
if (!res.ok) throw new ApiError(res.status, data);
return data as T;
}
export const api = {
health: () => request<{ ok: boolean; db: boolean; tools: number }>('/api/health'),
projects: {
list: (params?: { status?: 'open' | 'archived' }) =>
request<Project[]>(`/api/projects${params?.status ? `?status=${params.status}` : ''}`),
},
sessions: {
listForProject: (projectId: string, status?: 'open' | 'archived') =>
request<Session[]>(
`/api/projects/${projectId}/sessions${status ? `?status=${status}` : ''}`,
),
get: (id: string) => request<Session>(`/api/sessions/${id}`),
},
chats: {
listForSession: (sessionId: string) =>
request<Chat[]>(`/api/sessions/${sessionId}/chats`),
create: (sessionId: string, body?: { name?: string }) =>
request<Chat>(`/api/sessions/${sessionId}/chats`, {
method: 'POST',
body: JSON.stringify(body ?? {}),
}),
answerUserInput: (chatId: string, toolCallId: string, answers: AskUserAnswer[]) =>
request<{ tool_message_id: string; assistant_message_id: string }>(
`/api/chats/${chatId}/answer_user_input`,
{
method: 'POST',
body: JSON.stringify({ tool_call_id: toolCallId, answers }),
},
),
},
messages: {
send: (sessionId: string, chatId: string, content: string) =>
request<{ user_message_id: string; assistant_message_id: string }>(
`/api/sessions/${sessionId}/messages`,
{
method: 'POST',
body: JSON.stringify({ content, chat_id: chatId }),
},
),
stop: (sessionId: string) =>
request<{ cancelled: boolean }>(`/api/sessions/${sessionId}/stop`, {
method: 'POST',
}),
},
pending: {
list: (sessionId: string) =>
request<PendingChange[]>(`/api/sessions/${sessionId}/pending`),
applyAll: (sessionId: string) =>
request<{ results: Array<{ id: string; success: boolean; error?: string }> }>(
`/api/sessions/${sessionId}/pending/apply`,
{ method: 'POST' },
),
applyOne: (changeId: string) =>
request<{ success: boolean; error?: string }>(`/api/pending/${changeId}/apply`, {
method: 'POST',
}),
rejectOne: (changeId: string) =>
request<{ ok: boolean }>(`/api/pending/${changeId}/reject`, {
method: 'POST',
}),
rewindOne: (changeId: string) =>
request<{ success: boolean; error?: string }>(`/api/pending/${changeId}/rewind`, {
method: 'POST',
}),
},
};