Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/channel-platform-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@agentxjs/core": minor
"@agentxjs/server": minor
"@agentxjs/node-platform": minor
"agentxjs": minor
---

Inject channel server/client via Platform DI. Rename WebSocketFactory → ChannelClientFactory, webSocketFactory → channelClient. Server reads channelServer from Platform instead of importing WebSocketServer directly, enabling non-Node platforms (e.g. Cloudflare DO) to provide their own ChannelServer implementation.
2 changes: 1 addition & 1 deletion packages/agentx/src/RemoteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class RemoteClient implements AgentX {
// Create RPC client (WebSocket factory from platform if available)
this.rpcClient = new RpcClient({
url: config.serverUrl!,
createWebSocket: config.customPlatform?.webSocketFactory,
createWebSocket: config.customPlatform?.channelClient,
timeout: config.timeout ?? 30000,
autoReconnect: config.autoReconnect ?? true,
headers: config.headers as Record<string, string> | undefined,
Expand Down
8 changes: 4 additions & 4 deletions packages/agentx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ export async function createAgentX(config: AgentXConfig): Promise<AgentX> {
/**
* Resolve platform for remote mode
*
* In Node.js: auto-import node-platform to get webSocketFactory
* In Node.js: auto-import node-platform to get channelClient
* In browser: no platform needed (native WebSocket is the default)
*/
async function resolvePlatformForRemote(config: AgentXConfig): Promise<AgentXConfig> {
if (config.customPlatform?.webSocketFactory) {
if (config.customPlatform?.channelClient) {
return config;
}

Expand All @@ -82,14 +82,14 @@ async function resolvePlatformForRemote(config: AgentXConfig): Promise<AgentXCon
return config;
}

// Node.js — auto-resolve webSocketFactory from node-platform
// Node.js — auto-resolve channelClient from node-platform
try {
const { createNodeWebSocket } = await import("@agentxjs/node-platform/network");
return {
...config,
customPlatform: {
...config.customPlatform,
webSocketFactory: createNodeWebSocket,
channelClient: createNodeWebSocket,
} as any,
};
} catch {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/network/RpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ import {
// ============================================================================

/**
* Factory function for creating WebSocket instances.
* Platform layer provides the implementation:
* - Browser: native WebSocket (default)
* Factory for creating client-side WebSocket connections.
* Injected via Platform:
* - Browser: native WebSocket (default fallback)
* - Node.js: ws library (via @agentxjs/node-platform)
*/
export type WebSocketFactory = (url: string) => WebSocket;
export type ChannelClientFactory = (url: string) => WebSocket;

/**
* RpcClient configuration
Expand All @@ -64,7 +64,7 @@ export interface RpcClientConfig {
* Factory for creating WebSocket instances.
* If not provided, falls back to the global WebSocket constructor.
*/
createWebSocket?: WebSocketFactory;
createWebSocket?: ChannelClientFactory;

/**
* Request timeout in milliseconds (default: 30000)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/network/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export {
wrapMessage,
} from "./protocol";
// RPC Client
export type { RpcClientConfig, RpcClientState, WebSocketFactory } from "./RpcClient";
export type { ChannelClientFactory, RpcClientConfig, RpcClientState } from "./RpcClient";
export { RpcClient } from "./RpcClient";
// Types
export type {
Expand Down
16 changes: 13 additions & 3 deletions packages/core/src/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import type { BashProvider } from "../bash/types";
import type { ContainerRepository } from "../container/types";
import type { EventBus } from "../event/types";
import type { ImageRepository } from "../image/types";
import type { WebSocketFactory } from "../network/RpcClient";
import type { ChannelClientFactory } from "../network/RpcClient";
import type { ChannelServer } from "../network/types";
import type { SessionRepository } from "../session/types";

// ============================================================================
Expand Down Expand Up @@ -75,10 +76,19 @@ export interface AgentXPlatform {
readonly bashProvider?: BashProvider;

/**
* WebSocket factory for creating client connections
* Channel server for accepting client connections (server-side)
*
* Optional — only needed for server scenarios.
* Node.js platform provides ws-based implementation.
* Cloudflare platform provides DO Hibernation API implementation.
*/
readonly channelServer?: ChannelServer;

/**
* Channel client factory for creating connections (client-side)
*
* Optional — browser uses native WebSocket by default.
* Node.js platform provides ws-based implementation.
*/
readonly webSocketFactory?: WebSocketFactory;
readonly channelClient?: ChannelClientFactory;
}
12 changes: 10 additions & 2 deletions packages/node-platform/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,24 @@ export async function createNodePlatform(
// Create event bus
const eventBus = new EventBusImpl();

// Create WebSocket factory (uses ws library for Node.js)
// Create channel client factory (uses ws library for Node.js)
const { createNodeWebSocket } = await import("./network/WebSocketFactory");

// Create channel server (uses ws library for Node.js)
const { WebSocketServer } = await import("./network");
const channelServer = new WebSocketServer({
heartbeat: true,
heartbeatInterval: 30000,
});

return {
containerRepository: persistence.containers,
imageRepository: persistence.images,
sessionRepository: persistence.sessions,
eventBus,
bashProvider,
webSocketFactory: createNodeWebSocket,
channelServer,
channelClient: createNodeWebSocket,
};
}

Expand Down
8 changes: 4 additions & 4 deletions packages/node-platform/src/network/WebSocketFactory.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/**
* Node.js WebSocket factory using the ws library
* Node.js channel client factory using the ws library
*
* Provides WebSocketFactory implementation for @agentxjs/core RpcClient.
* Provides ChannelClientFactory implementation for @agentxjs/core RpcClient.
* Browser environments use native WebSocket (the default in RpcClient).
*/

import type { WebSocketFactory } from "@agentxjs/core/network";
import type { ChannelClientFactory } from "@agentxjs/core/network";

/**
* Create a WebSocket instance using the ws library (Node.js)
*/
export const createNodeWebSocket: WebSocketFactory = (url: string): WebSocket => {
export const createNodeWebSocket: ChannelClientFactory = (url: string): WebSocket => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { default: WS } = require("ws");
return new WS(url) as unknown as WebSocket;
Expand Down
19 changes: 8 additions & 11 deletions packages/server/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ import {
} from "@agentxjs/core/network";
import type { AgentXPlatform } from "@agentxjs/core/runtime";
import { createAgentXRuntime } from "@agentxjs/core/runtime";
import {
type DeferredPlatformConfig,
isDeferredPlatform,
WebSocketServer,
} from "@agentxjs/node-platform";
import { type DeferredPlatformConfig, isDeferredPlatform } from "@agentxjs/node-platform";
import { createLogger } from "commonxjs/logger";
import { CommandHandler } from "./CommandHandler";
import type { AgentXServer } from "./types";
Expand All @@ -51,6 +47,8 @@ interface ConnectionState {
export interface ServerConfig {
/**
* AgentX Platform (can be AgentXPlatform or DeferredPlatformConfig)
*
* Must provide `channelServer` for accepting WebSocket connections.
*/
platform: AgentXPlatform | DeferredPlatformConfig;

Expand Down Expand Up @@ -99,12 +97,11 @@ export async function createServer(config: ServerConfig): Promise<AgentXServer>
// Create runtime from platform + driver
const runtime = createAgentXRuntime(platform, config.createDriver);

// Create WebSocket server
const wsServer = new WebSocketServer({
heartbeat: true,
heartbeatInterval: 30000,
debug: config.debug,
});
// Get channel server from platform
const wsServer = platform.channelServer;
if (!wsServer) {
throw new Error("Platform must provide channelServer for server mode");
}

// Create command handler (no longer needs eventBus)
const commandHandler = new CommandHandler(runtime);
Expand Down