Setup browser bundling for Agent library

This commit is contained in:
Morgan Dean
2025-08-21 11:55:02 +01:00
parent faec64029b
commit 8277c722b2
6 changed files with 940 additions and 50 deletions

View File

@@ -39,15 +39,15 @@
},
"dependencies": {
"@trycua/core": "^0.1.2",
"pino": "^9.7.0",
"peerjs": "^1.5.4"
"peerjs": "^1.5.4",
"pino": "^9.7.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/node": "^22.15.17",
"bumpp": "^10.1.0",
"happy-dom": "^17.4.7",
"tsdown": "^0.3.0",
"tsdown": "^0.14.1",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
}

View File

@@ -1,13 +1,17 @@
import pkg from 'peerjs';
const { Peer, DataConnection } = pkg;
import type { AgentRequest, AgentResponse, ConnectionType, AgentClientOptions } from './types.js';
import {Peer} from "peerjs";
import type {
AgentRequest,
AgentResponse,
ConnectionType,
AgentClientOptions,
} from "./types.js";
export class AgentClient {
private url: string;
private connectionType: ConnectionType;
private options: AgentClientOptions;
private peer?: Peer;
private connection?: DataConnection;
private connection?: any;
constructor(url: string, options: AgentClientOptions = {}) {
this.url = url;
@@ -16,14 +20,16 @@ export class AgentClient {
retries: 3,
...options,
};
// Determine connection type from URL
if (url.startsWith('http://') || url.startsWith('https://')) {
this.connectionType = url.startsWith('https://') ? 'https' : 'http';
} else if (url.startsWith('peer://')) {
this.connectionType = 'peer';
if (url.startsWith("http://") || url.startsWith("https://")) {
this.connectionType = url.startsWith("https://") ? "https" : "http";
} else if (url.startsWith("peer://")) {
this.connectionType = "peer";
} else {
throw new Error('Invalid URL format. Must start with http://, https://, or peer://');
throw new Error(
"Invalid URL format. Must start with http://, https://, or peer://"
);
}
}
@@ -31,15 +37,15 @@ export class AgentClient {
public responses = {
create: async (request: AgentRequest): Promise<AgentResponse> => {
return this.sendRequest(request);
}
},
};
private async sendRequest(request: AgentRequest): Promise<AgentResponse> {
switch (this.connectionType) {
case 'http':
case 'https':
case "http":
case "https":
return this.sendHttpRequest(request);
case 'peer':
case "peer":
return this.sendPeerRequest(request);
default:
throw new Error(`Unsupported connection type: ${this.connectionType}`);
@@ -48,13 +54,16 @@ export class AgentClient {
private async sendHttpRequest(request: AgentRequest): Promise<AgentResponse> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.options.timeout);
const timeoutId = setTimeout(
() => controller.abort(),
this.options.timeout
);
try {
const response = await fetch(`${this.url}/responses`, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify(request),
signal: controller.signal,
@@ -79,43 +88,44 @@ export class AgentClient {
private async sendPeerRequest(request: AgentRequest): Promise<AgentResponse> {
// Extract peer ID from peer:// URL
const peerId = this.url.replace('peer://', '');
const peerId = this.url.replace("peer://", "");
if (!this.peer) {
// Initialize peer connection with default options as requested
this.peer = new Peer();
return new Promise<AgentResponse>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Peer connection timeout'));
reject(new Error("Peer connection timeout"));
}, this.options.timeout);
this.peer!.on('open', () => {
this.peer!.on("open", () => {
// Connect to the target peer
this.connection = this.peer!.connect(peerId);
this.connection.on('open', () => {
this.connection.on("open", () => {
// Send the request
this.connection!.send(JSON.stringify(request));
});
this.connection.on('data', (data: any) => {
this.connection.on("data", (data: any) => {
clearTimeout(timeout);
try {
const response = typeof data === 'string' ? JSON.parse(data) : data;
const response =
typeof data === "string" ? JSON.parse(data) : data;
resolve(response as AgentResponse);
} catch (error) {
reject(new Error('Failed to parse peer response'));
reject(new Error("Failed to parse peer response"));
}
});
this.connection.on('error', (error: any) => {
this.connection.on("error", (error: any) => {
clearTimeout(timeout);
reject(new Error(`Peer connection error: ${error}`));
});
});
this.peer!.on('error', (error: any) => {
this.peer!.on("error", (error: any) => {
clearTimeout(timeout);
reject(new Error(`Peer error: ${error}`));
});
@@ -124,27 +134,28 @@ export class AgentClient {
// Reuse existing connection
return new Promise<AgentResponse>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Peer request timeout'));
reject(new Error("Peer request timeout"));
}, this.options.timeout);
if (this.connection && this.connection.open) {
this.connection.send(JSON.stringify(request));
const handleData = (data: any) => {
clearTimeout(timeout);
this.connection!.off('data', handleData);
this.connection!.off("data", handleData);
try {
const response = typeof data === 'string' ? JSON.parse(data) : data;
const response =
typeof data === "string" ? JSON.parse(data) : data;
resolve(response as AgentResponse);
} catch (error) {
reject(new Error('Failed to parse peer response'));
reject(new Error("Failed to parse peer response"));
}
};
this.connection.on('data', handleData);
this.connection.on("data", handleData);
} else {
clearTimeout(timeout);
reject(new Error('Peer connection not available'));
reject(new Error("Peer connection not available"));
}
});
}
@@ -152,18 +163,18 @@ export class AgentClient {
// Health check method
async health(): Promise<{ status: string }> {
if (this.connectionType === 'peer') {
return { status: this.peer?.open ? 'connected' : 'disconnected' };
if (this.connectionType === "peer") {
return { status: this.peer?.open ? "connected" : "disconnected" };
}
try {
const response = await fetch(`${this.url}/health`);
if (response.ok) {
return { status: 'healthy' };
return { status: "healthy" };
}
return { status: 'unhealthy' };
return { status: "unhealthy" };
} catch {
return { status: 'unreachable' };
return { status: "unreachable" };
}
}

View File

@@ -1,8 +1,10 @@
import { defineConfig } from 'tsdown'
import { defineConfig } from "tsdown";
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
entry: ["src/index.ts"],
format: ["module"],
platform: "browser",
dts: true,
clean: true,
})
noExternal: ['peerjs']
});

View File

@@ -23,7 +23,10 @@
"pnpm": {
"onlyBuiltDependencies": [
"@biomejs/biome",
"esbuild"
"esbuild",
"protobufjs",
"sharp",
"unrs-resolver"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
packages:
- "computer"
- "core"
- "agent"