feat(wave2): @mosaic/openclaw-context plugin migrated to monorepo (#3)

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #3.
This commit is contained in:
2026-03-07 00:33:20 +00:00
committed by jason.woltje
parent 2828a83b66
commit d7f200edd6
15 changed files with 2554 additions and 14 deletions

View File

@@ -0,0 +1,333 @@
import { OpenBrainConfigError, OpenBrainHttpError, OpenBrainRequestError } from "./errors.js";
export type OpenBrainThoughtMetadata = Record<string, unknown> & {
sessionId?: string;
turn?: number;
role?: string;
type?: string;
};
export type OpenBrainThought = {
id: string;
content: string;
source: string;
metadata: OpenBrainThoughtMetadata | undefined;
createdAt: string | undefined;
updatedAt: string | undefined;
score: number | undefined;
[key: string]: unknown;
};
export type OpenBrainThoughtInput = {
content: string;
source: string;
metadata?: OpenBrainThoughtMetadata;
};
export type OpenBrainSearchInput = {
query: string;
limit: number;
source?: string;
};
export type OpenBrainClientOptions = {
baseUrl: string;
apiKey: string;
fetchImpl?: typeof fetch;
};
export interface OpenBrainClientLike {
createThought(input: OpenBrainThoughtInput): Promise<OpenBrainThought>;
search(input: OpenBrainSearchInput): Promise<OpenBrainThought[]>;
listRecent(input: { limit: number; source?: string }): Promise<OpenBrainThought[]>;
updateThought(
id: string,
payload: { content?: string; metadata?: OpenBrainThoughtMetadata },
): Promise<OpenBrainThought>;
deleteThought(id: string): Promise<void>;
deleteThoughts(params: { source?: string; metadataId?: string }): Promise<void>;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function readString(record: Record<string, unknown>, key: string): string | undefined {
const value = record[key];
return typeof value === "string" ? value : undefined;
}
function readNumber(record: Record<string, unknown>, key: string): number | undefined {
const value = record[key];
return typeof value === "number" ? value : undefined;
}
function normalizeBaseUrl(baseUrl: string): string {
const normalized = baseUrl.trim().replace(/\/+$/, "");
if (normalized.length === 0) {
throw new OpenBrainConfigError("Missing required OpenBrain config: baseUrl");
}
return normalized;
}
function normalizeApiKey(apiKey: string): string {
const normalized = apiKey.trim();
if (normalized.length === 0) {
throw new OpenBrainConfigError("Missing required OpenBrain config: apiKey");
}
return normalized;
}
function normalizeHeaders(headers: unknown): Record<string, string> {
if (headers === undefined) {
return {};
}
if (Array.isArray(headers)) {
const normalized: Record<string, string> = {};
for (const pair of headers) {
if (!Array.isArray(pair) || pair.length < 2) {
continue;
}
const key = pair[0];
const value = pair[1];
if (typeof key !== "string" || typeof value !== "string") {
continue;
}
normalized[key] = value;
}
return normalized;
}
if (headers instanceof Headers) {
const normalized: Record<string, string> = {};
for (const [key, value] of headers.entries()) {
normalized[key] = value;
}
return normalized;
}
if (!isRecord(headers)) {
return {};
}
const normalized: Record<string, string> = {};
for (const [key, value] of Object.entries(headers)) {
if (typeof value === "string") {
normalized[key] = value;
continue;
}
if (Array.isArray(value)) {
normalized[key] = value.join(", ");
}
}
return normalized;
}
async function readResponseBody(response: Response): Promise<string | undefined> {
try {
const body = await response.text();
return body.length > 0 ? body : undefined;
} catch {
return undefined;
}
}
export class OpenBrainClient implements OpenBrainClientLike {
private readonly baseUrl: string;
private readonly apiKey: string;
private readonly fetchImpl: typeof fetch;
constructor(options: OpenBrainClientOptions) {
this.baseUrl = normalizeBaseUrl(options.baseUrl);
this.apiKey = normalizeApiKey(options.apiKey);
this.fetchImpl = options.fetchImpl ?? fetch;
}
async createThought(input: OpenBrainThoughtInput): Promise<OpenBrainThought> {
const payload = await this.request<unknown>("/v1/thoughts", {
method: "POST",
body: JSON.stringify(input),
});
return this.extractThought(payload);
}
async search(input: OpenBrainSearchInput): Promise<OpenBrainThought[]> {
const payload = await this.request<unknown>("/v1/search", {
method: "POST",
body: JSON.stringify(input),
});
return this.extractThoughtArray(payload);
}
async listRecent(input: { limit: number; source?: string }): Promise<OpenBrainThought[]> {
const params = new URLSearchParams({
limit: String(input.limit),
});
if (input.source !== undefined && input.source.length > 0) {
params.set("source", input.source);
}
const payload = await this.request<unknown>(`/v1/thoughts/recent?${params.toString()}`, {
method: "GET",
});
return this.extractThoughtArray(payload);
}
async updateThought(
id: string,
payload: { content?: string; metadata?: OpenBrainThoughtMetadata },
): Promise<OpenBrainThought> {
const responsePayload = await this.request<unknown>(`/v1/thoughts/${encodeURIComponent(id)}`, {
method: "PATCH",
body: JSON.stringify(payload),
});
return this.extractThought(responsePayload);
}
async deleteThought(id: string): Promise<void> {
await this.request<unknown>(`/v1/thoughts/${encodeURIComponent(id)}`, {
method: "DELETE",
});
}
async deleteThoughts(params: { source?: string; metadataId?: string }): Promise<void> {
const query = new URLSearchParams();
if (params.source !== undefined && params.source.length > 0) {
query.set("source", params.source);
}
if (params.metadataId !== undefined && params.metadataId.length > 0) {
query.set("metadata_id", params.metadataId);
}
const suffix = query.size > 0 ? `?${query.toString()}` : "";
await this.request<unknown>(`/v1/thoughts${suffix}`, {
method: "DELETE",
});
}
private async request<T>(endpoint: string, init: RequestInit): Promise<T> {
const headers = normalizeHeaders(init.headers);
headers.Authorization = `Bearer ${this.apiKey}`;
if (init.body !== undefined && headers["Content-Type"] === undefined) {
headers["Content-Type"] = "application/json";
}
const url = `${this.baseUrl}${endpoint}`;
let response: Response;
try {
response = await this.fetchImpl(url, {
...init,
headers,
});
} catch (error) {
throw new OpenBrainRequestError({ endpoint, cause: error });
}
if (!response.ok) {
throw new OpenBrainHttpError({
endpoint,
status: response.status,
responseBody: await readResponseBody(response),
});
}
if (response.status === 204) {
return undefined as T;
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.toLowerCase().includes("application/json")) {
return undefined as T;
}
return (await response.json()) as T;
}
private extractThoughtArray(payload: unknown): OpenBrainThought[] {
if (Array.isArray(payload)) {
return payload.map((item) => this.normalizeThought(item));
}
if (!isRecord(payload)) {
return [];
}
const candidates = [payload.thoughts, payload.data, payload.results, payload.items];
for (const candidate of candidates) {
if (Array.isArray(candidate)) {
return candidate.map((item) => this.normalizeThought(item));
}
}
return [];
}
private extractThought(payload: unknown): OpenBrainThought {
if (isRecord(payload)) {
const nested = payload.thought;
if (nested !== undefined) {
return this.normalizeThought(nested);
}
const data = payload.data;
if (data !== undefined && !Array.isArray(data)) {
return this.normalizeThought(data);
}
}
return this.normalizeThought(payload);
}
private normalizeThought(value: unknown): OpenBrainThought {
if (!isRecord(value)) {
return {
id: "",
content: "",
source: "",
metadata: undefined,
createdAt: undefined,
updatedAt: undefined,
score: undefined,
};
}
const metadataValue = value.metadata;
const metadata = isRecord(metadataValue)
? ({ ...metadataValue } as OpenBrainThoughtMetadata)
: undefined;
const id = readString(value, "id") ?? readString(value, "thought_id") ?? "";
const content =
readString(value, "content") ??
readString(value, "text") ??
(value.content === undefined ? "" : String(value.content));
const source = readString(value, "source") ?? "";
const createdAt = readString(value, "createdAt") ?? readString(value, "created_at");
const updatedAt = readString(value, "updatedAt") ?? readString(value, "updated_at");
const score = readNumber(value, "score");
return {
...value,
id,
content,
source,
metadata,
createdAt,
updatedAt,
score,
};
}
}
export { normalizeApiKey, normalizeBaseUrl };