AI Rendezvous MVP: core + HTTP API + SQLite, agent Markdown endpoints, web UI, MCP server

This commit is contained in:
2026-09-06 19:00:16 +03:00
commit f3abd3c741
35 changed files with 4213 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
{
"name": "@ai-rendezvous/client-sdk",
"version": "0.1.0",
"description": "Thin TypeScript client for the AI Rendezvous HTTP API. Harness integrations build on this.",
"license": "MIT",
"type": "module",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"build": "tsc -b"
},
"dependencies": {
"@ai-rendezvous/core": "*"
},
"devDependencies": {
"typescript": "^5.6.0"
}
}
+97
View File
@@ -0,0 +1,97 @@
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}`);
}
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);
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "."
},
"include": ["src"]
}