deploy / deploy (push) Canceled after 0s
- POST /api/rooms/:id/join + rendezvous_join MCP tool; creator auto-joined - joined_at shown to agents, observer (invite not confirmed yet) and used by the turn indicator; what_you_should_do_next starts with joining - fix: participant list order is now stable (insertion order)
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import type { CreateRoomInput, CreatedRoom, RoomView } from '@ai-rendezvous/core';
|
|
|
|
export class ApiError extends Error {
|
|
constructor(readonly status: number, readonly code: string, message: string) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thin client for the AI Rendezvous HTTP API. Uses only fetch — no harness
|
|
* assumptions, suitable for integrations in any Node-based environment.
|
|
*/
|
|
export class RendezvousClient {
|
|
constructor(
|
|
readonly baseUrl: string,
|
|
readonly token?: string,
|
|
) {}
|
|
|
|
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
method,
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
|
|
},
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
const text = await res.text();
|
|
const data = text ? JSON.parse(text) : {};
|
|
if (!res.ok) {
|
|
const err = (data as { error?: { code?: string; message?: string } }).error ?? {};
|
|
throw new ApiError(res.status, err.code ?? 'unknown', err.message ?? res.statusText);
|
|
}
|
|
return data as T;
|
|
}
|
|
|
|
createRoom(input: CreateRoomInput): Promise<CreatedRoom> {
|
|
return this.request<CreatedRoom>('POST', '/api/rooms', input);
|
|
}
|
|
|
|
getRoom(roomId: string): Promise<RoomView> {
|
|
return this.request<RoomView>('GET', `/api/rooms/${roomId}`);
|
|
}
|
|
|
|
join(roomId: string): Promise<{ joined: boolean; participants: { role: string; joined_at: string | null }[] }> {
|
|
return this.request('POST', `/api/rooms/${roomId}/join`, {});
|
|
}
|
|
|
|
postMessage(roomId: string, content: string): Promise<unknown> {
|
|
return this.request('POST', `/api/rooms/${roomId}/messages`, { content });
|
|
}
|
|
|
|
ask(
|
|
roomId: string,
|
|
question: string,
|
|
opts: { blocking?: boolean; addressed_to_participant_id?: string } = {},
|
|
): Promise<unknown> {
|
|
return this.request('POST', `/api/rooms/${roomId}/questions`, {
|
|
question,
|
|
blocking: opts.blocking ?? true,
|
|
...(opts.addressed_to_participant_id
|
|
? { addressed_to_participant_id: opts.addressed_to_participant_id }
|
|
: {}),
|
|
});
|
|
}
|
|
|
|
resolve(roomId: string, questionId: string, resolution: string): Promise<unknown> {
|
|
return this.request('POST', `/api/rooms/${roomId}/questions/${questionId}/resolve`, { resolution });
|
|
}
|
|
|
|
proposeContract(roomId: string, markdown: string): Promise<{ version: number }> {
|
|
return this.request('PUT', `/api/rooms/${roomId}/contract`, { markdown });
|
|
}
|
|
|
|
agree(roomId: string): Promise<{
|
|
agreed_contract_version: number;
|
|
room_status: string;
|
|
everyone_agreed: boolean;
|
|
}> {
|
|
return this.request('POST', `/api/rooms/${roomId}/agree`, {});
|
|
}
|
|
|
|
async finalMarkdown(roomId: string): Promise<string> {
|
|
const res = await fetch(`${this.baseUrl}/api/rooms/${roomId}/final.md`, {
|
|
headers: this.token ? { authorization: `Bearer ${this.token}` } : {},
|
|
});
|
|
if (!res.ok) throw new ApiError(res.status, 'unknown', await res.text());
|
|
return res.text();
|
|
}
|
|
|
|
async inviteMarkdown(inviteUrl: string): Promise<string> {
|
|
const res = await fetch(`${inviteUrl}.md`);
|
|
if (!res.ok) throw new ApiError(res.status, 'unknown', await res.text());
|
|
return res.text();
|
|
}
|
|
|
|
/** Client for a specific participant. */
|
|
as(token: string): RendezvousClient {
|
|
return new RendezvousClient(this.baseUrl, token);
|
|
}
|
|
}
|