@agentuity/frontend 0.0.100 → 0.0.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- # Agent Guidelines for @agentuity/web
1
+ # Agent Guidelines for @agentuity/frontend
2
2
 
3
3
  ## Package Overview
4
4
 
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @agentuity/web
1
+ # @agentuity/frontend
2
2
 
3
3
  Generic web utilities for building Agentuity frontend applications. Provides framework-agnostic utilities for URL building, serialization, reconnection logic, and type definitions.
4
4
 
@@ -7,7 +7,7 @@ This package contains reusable JavaScript logic that can be shared across differ
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @agentuity/web
10
+ npm install @agentuity/frontend
11
11
  ```
12
12
 
13
13
  ## Features
@@ -24,7 +24,7 @@ npm install @agentuity/web
24
24
  ### URL Building
25
25
 
26
26
  ```typescript
27
- import { buildUrl, defaultBaseUrl } from '@agentuity/web';
27
+ import { buildUrl, defaultBaseUrl } from '@agentuity/frontend';
28
28
 
29
29
  const url = buildUrl(
30
30
  'https://api.example.com',
@@ -38,7 +38,7 @@ const url = buildUrl(
38
38
  ### Reconnection Manager
39
39
 
40
40
  ```typescript
41
- import { createReconnectManager } from '@agentuity/web';
41
+ import { createReconnectManager } from '@agentuity/frontend';
42
42
 
43
43
  const reconnect = createReconnectManager({
44
44
  onReconnect: () => console.log('Reconnecting...'),
@@ -55,7 +55,7 @@ reconnect.recordFailure();
55
55
  ### Serialization
56
56
 
57
57
  ```typescript
58
- import { deserializeData } from '@agentuity/web';
58
+ import { deserializeData } from '@agentuity/frontend';
59
59
 
60
60
  const data = deserializeData<MyType>('{"key":"value"}');
61
61
  ```
@@ -0,0 +1,12 @@
1
+ import type { EventStreamClient } from './types';
2
+ /**
3
+ * Create an EventSource (SSE) client wrapper with event-based API.
4
+ *
5
+ * Note: Native EventSource has limited authentication support.
6
+ * - withCredentials: true will send cookies and HTTP auth headers
7
+ * - For custom Authorization headers, consider using @microsoft/fetch-event-source
8
+ */
9
+ export declare function createEventStreamClient(url: string, options?: {
10
+ withCredentials?: boolean;
11
+ }): EventStreamClient;
12
+ //# sourceMappingURL=eventstream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventstream.d.ts","sourceRoot":"","sources":["../../src/client/eventstream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE/D;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACrC,iBAAiB,CAuCnB"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Create an EventSource (SSE) client wrapper with event-based API.
3
+ *
4
+ * Note: Native EventSource has limited authentication support.
5
+ * - withCredentials: true will send cookies and HTTP auth headers
6
+ * - For custom Authorization headers, consider using @microsoft/fetch-event-source
7
+ */
8
+ export function createEventStreamClient(url, options) {
9
+ const eventSource = new EventSource(url, {
10
+ withCredentials: options?.withCredentials ?? false,
11
+ });
12
+ const handlers = {
13
+ message: new Set(),
14
+ open: new Set(),
15
+ error: new Set(),
16
+ };
17
+ // Set up native EventSource event listeners
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ eventSource.onmessage = (event) => {
20
+ handlers.message.forEach((handler) => handler(event));
21
+ };
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ eventSource.onopen = (event) => {
24
+ handlers.open.forEach((handler) => handler(event));
25
+ };
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ eventSource.onerror = (event) => {
28
+ handlers.error.forEach((handler) => handler(event));
29
+ };
30
+ return {
31
+ on: ((event, handler) => {
32
+ handlers[event].add(handler);
33
+ }),
34
+ close() {
35
+ eventSource.close();
36
+ },
37
+ };
38
+ }
39
+ //# sourceMappingURL=eventstream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventstream.js","sourceRoot":"","sources":["../../src/client/eventstream.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACtC,GAAW,EACX,OAAuC;IAEvC,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,GAAG,EAAE;QACxC,eAAe,EAAE,OAAO,EAAE,eAAe,IAAI,KAAK;KAClD,CAAC,CAAC;IACH,MAAM,QAAQ,GAIV;QACH,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,IAAI,EAAE,IAAI,GAAG,EAAE;QACf,KAAK,EAAE,IAAI,GAAG,EAAE;KAChB,CAAC;IAEF,4CAA4C;IAC5C,8DAA8D;IAC9D,WAAW,CAAC,SAAS,GAAG,CAAC,KAAU,EAAE,EAAE;QACtC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,8DAA8D;IAC9D,WAAW,CAAC,MAAM,GAAG,CAAC,KAAU,EAAE,EAAE;QACnC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC;IAEF,8DAA8D;IAC9D,WAAW,CAAC,OAAO,GAAG,CAAC,KAAU,EAAE,EAAE;QACpC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC;IAEF,OAAO;QACN,EAAE,EAAE,CAAC,CAAC,KAAmC,EAAE,OAAqB,EAAE,EAAE;YACnE,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC,CAA4B;QAE7B,KAAK;YACJ,WAAW,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Client, ClientOptions } from './types';
2
+ /**
3
+ * Create a type-safe API client from a RouteRegistry.
4
+ *
5
+ * Uses a Proxy to build up the path as you navigate the object,
6
+ * then executes the request when you call a terminal method (.post(), .get(), .websocket(), etc.).
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { createClient } from '@agentuity/frontend';
11
+ * import type { RPCRouteRegistry } from './generated/routes';
12
+ *
13
+ * const client = createClient<RPCRouteRegistry>();
14
+ *
15
+ * // Type-safe API call
16
+ * const result = await client.hello.post({ name: 'World' });
17
+ *
18
+ * // WebSocket
19
+ * const ws = client.chat.websocket();
20
+ * ws.on('message', (msg) => console.log(msg));
21
+ *
22
+ * // Server-Sent Events
23
+ * const es = client.events.eventstream();
24
+ * es.on('message', (event) => console.log(event.data));
25
+ *
26
+ * // Streaming response
27
+ * const stream = await client.data.stream({ query: 'foo' });
28
+ * stream.on('chunk', (chunk) => console.log(chunk));
29
+ * ```
30
+ */
31
+ export declare function createClient<R>(options?: ClientOptions, metadata?: unknown): Client<R>;
32
+ export type { ClientOptions, Client, RouteEndpoint, WebSocketClient, EventStreamClient, StreamClient, EventHandler, } from './types';
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA0BrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAE,aAAkB,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAwG1F;AAGD,YAAY,EACX,aAAa,EACb,MAAM,EACN,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACZ,MAAM,SAAS,CAAC"}
@@ -0,0 +1,149 @@
1
+ import { createWebSocketClient } from './websocket';
2
+ import { createEventStreamClient } from './eventstream';
3
+ import { createStreamClient } from './stream';
4
+ /**
5
+ * Default base URL (empty = relative URLs).
6
+ */
7
+ const defaultBaseUrl = '';
8
+ /**
9
+ * Resolve baseUrl - if it's a function, call it; otherwise return the string.
10
+ */
11
+ function resolveBaseUrl(baseUrl) {
12
+ return typeof baseUrl === 'function' ? baseUrl() : baseUrl;
13
+ }
14
+ /**
15
+ * Resolve headers - if it's a function, call it; otherwise return the object.
16
+ */
17
+ function resolveHeaders(headers) {
18
+ return typeof headers === 'function' ? headers() : headers;
19
+ }
20
+ /**
21
+ * Create a type-safe API client from a RouteRegistry.
22
+ *
23
+ * Uses a Proxy to build up the path as you navigate the object,
24
+ * then executes the request when you call a terminal method (.post(), .get(), .websocket(), etc.).
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { createClient } from '@agentuity/frontend';
29
+ * import type { RPCRouteRegistry } from './generated/routes';
30
+ *
31
+ * const client = createClient<RPCRouteRegistry>();
32
+ *
33
+ * // Type-safe API call
34
+ * const result = await client.hello.post({ name: 'World' });
35
+ *
36
+ * // WebSocket
37
+ * const ws = client.chat.websocket();
38
+ * ws.on('message', (msg) => console.log(msg));
39
+ *
40
+ * // Server-Sent Events
41
+ * const es = client.events.eventstream();
42
+ * es.on('message', (event) => console.log(event.data));
43
+ *
44
+ * // Streaming response
45
+ * const stream = await client.data.stream({ query: 'foo' });
46
+ * stream.on('chunk', (chunk) => console.log(chunk));
47
+ * ```
48
+ */
49
+ export function createClient(options = {}, metadata) {
50
+ const { baseUrl = defaultBaseUrl, headers, contentType = 'application/json', signal } = options;
51
+ // Default headers to empty object if not provided
52
+ const defaultHeaders = headers || {};
53
+ const handler = {
54
+ get(target, prop) {
55
+ const currentPath = target.__path;
56
+ const newPath = [...currentPath, prop];
57
+ // Check if this is a terminal method
58
+ const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
59
+ const streamMethods = ['websocket', 'eventstream', 'stream'];
60
+ const isHttpMethod = httpMethods.includes(prop);
61
+ const isStreamMethod = streamMethods.includes(prop);
62
+ const isTerminalMethod = isHttpMethod || isStreamMethod;
63
+ // Terminal: method at the end (minimum 1 path segment required)
64
+ // Examples: client.hello.post(), client.echo.websocket(), client.events.eventstream()
65
+ if (isTerminalMethod && currentPath.length >= 1) {
66
+ const method = prop;
67
+ const pathSegments = currentPath;
68
+ const urlPath = '/api/' + pathSegments.join('/');
69
+ // Determine route type
70
+ let routeType = 'api';
71
+ if (isStreamMethod) {
72
+ // Stream methods directly specify the route type
73
+ if (method === 'websocket')
74
+ routeType = 'websocket';
75
+ else if (method === 'eventstream')
76
+ routeType = 'sse';
77
+ else if (method === 'stream')
78
+ routeType = 'stream';
79
+ }
80
+ else if (metadata) {
81
+ // Look up route type from metadata for HTTP methods
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ let metaNode = metadata;
84
+ for (const segment of pathSegments) {
85
+ if (metaNode && typeof metaNode === 'object') {
86
+ metaNode = metaNode[segment];
87
+ }
88
+ }
89
+ if (metaNode && typeof metaNode === 'object' && metaNode[method]?.type) {
90
+ routeType = metaNode[method].type;
91
+ }
92
+ }
93
+ return (input) => {
94
+ const resolvedBaseUrl = resolveBaseUrl(baseUrl);
95
+ const resolvedHeaders = resolveHeaders(defaultHeaders);
96
+ // WebSocket endpoint
97
+ if (routeType === 'websocket') {
98
+ const wsBaseUrl = resolvedBaseUrl.replace(/^http/, 'ws');
99
+ const wsUrl = `${wsBaseUrl}${urlPath}`;
100
+ const ws = createWebSocketClient(wsUrl);
101
+ if (input) {
102
+ ws.on('open', () => ws.send(input));
103
+ }
104
+ return ws;
105
+ }
106
+ // SSE endpoint
107
+ if (routeType === 'sse') {
108
+ const sseUrl = `${resolvedBaseUrl}${urlPath}`;
109
+ // Note: Native EventSource doesn't support custom headers
110
+ // For auth, use withCredentials or consider fetch-based alternatives like @microsoft/fetch-event-source
111
+ return createEventStreamClient(sseUrl, {
112
+ withCredentials: Object.keys(resolvedHeaders).length > 0,
113
+ });
114
+ }
115
+ // Stream endpoint
116
+ if (routeType === 'stream') {
117
+ return fetch(`${resolvedBaseUrl}${urlPath}`, {
118
+ method: method.toUpperCase(),
119
+ headers: { 'Content-Type': contentType, ...resolvedHeaders },
120
+ body: input ? JSON.stringify(input) : undefined,
121
+ signal,
122
+ }).then((res) => {
123
+ if (!res.ok)
124
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
125
+ return createStreamClient(res);
126
+ });
127
+ }
128
+ // Regular API endpoint
129
+ return fetch(`${resolvedBaseUrl}${urlPath}`, {
130
+ method: method.toUpperCase(),
131
+ headers: { 'Content-Type': contentType, ...resolvedHeaders },
132
+ body: method.toUpperCase() !== 'GET' && input ? JSON.stringify(input) : undefined,
133
+ signal,
134
+ }).then(async (res) => {
135
+ if (!res.ok)
136
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
137
+ if (res.status === 204)
138
+ return undefined;
139
+ return res.json();
140
+ });
141
+ };
142
+ }
143
+ // Otherwise, return a new proxy with extended path
144
+ return new Proxy({ __path: newPath }, handler);
145
+ },
146
+ };
147
+ return new Proxy({ __path: [] }, handler);
148
+ }
149
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { StreamClient } from './types';
2
+ /**
3
+ * Create a streaming response reader with event-based API.
4
+ */
5
+ export declare function createStreamClient(response: Response): StreamClient;
6
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/client/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,SAAS,CAAC;AAE1D;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,YAAY,CAuDnE"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Create a streaming response reader with event-based API.
3
+ */
4
+ export function createStreamClient(response) {
5
+ const handlers = {
6
+ chunk: new Set(),
7
+ close: new Set(),
8
+ error: new Set(),
9
+ };
10
+ const reader = response.body?.getReader();
11
+ let cancelled = false;
12
+ // Start reading the stream
13
+ const readStream = async () => {
14
+ if (!reader) {
15
+ const error = new Error('Response body is not readable');
16
+ handlers.error.forEach((handler) => handler(error));
17
+ return;
18
+ }
19
+ try {
20
+ while (!cancelled) {
21
+ const { done, value } = await reader.read();
22
+ if (done) {
23
+ handlers.close.forEach((handler) => handler());
24
+ break;
25
+ }
26
+ if (value) {
27
+ handlers.chunk.forEach((handler) => handler(value));
28
+ }
29
+ }
30
+ }
31
+ catch (error) {
32
+ const err = error instanceof Error ? error : new Error(String(error));
33
+ handlers.error.forEach((handler) => handler(err));
34
+ }
35
+ };
36
+ // Start reading immediately
37
+ readStream();
38
+ return {
39
+ on: ((event, handler) => {
40
+ handlers[event].add(handler);
41
+ }),
42
+ async cancel() {
43
+ cancelled = true;
44
+ await reader?.cancel();
45
+ handlers.close.forEach((handler) => handler());
46
+ },
47
+ };
48
+ }
49
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/client/stream.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAkB;IACpD,MAAM,QAAQ,GAIV;QACH,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,KAAK,EAAE,IAAI,GAAG,EAAE;KAChB,CAAC;IAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;IAC1C,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,2BAA2B;IAC3B,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC7B,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACzD,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YACpD,OAAO;QACR,CAAC;QAED,IAAI,CAAC;YACJ,OAAO,CAAC,SAAS,EAAE,CAAC;gBACnB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAE5C,IAAI,IAAI,EAAE,CAAC;oBACV,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;oBAC/C,MAAM;gBACP,CAAC;gBAED,IAAI,KAAK,EAAE,CAAC;oBACX,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;gBACrD,CAAC;YACF,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACtE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,CAAC;IACF,CAAC,CAAC;IAEF,4BAA4B;IAC5B,UAAU,EAAE,CAAC;IAEb,OAAO;QACN,EAAE,EAAE,CAAC,CAAC,KAAkC,EAAE,OAAqB,EAAE,EAAE;YAClE,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAkD,CAAC,CAAC;QACzE,CAAC,CAAuB;QAExB,KAAK,CAAC,MAAM;YACX,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM,MAAM,EAAE,MAAM,EAAE,CAAC;YACvB,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Type-safe API client types for RPC-style invocations.
3
+ */
4
+ /**
5
+ * Options for creating a client.
6
+ */
7
+ export interface ClientOptions {
8
+ /**
9
+ * Base URL for API requests (e.g., "https://p1234567890.agenuity.run").
10
+ * Defaults to empty string (relative URLs).
11
+ * Can be a string or a function that returns a string for lazy resolution.
12
+ */
13
+ baseUrl?: string | (() => string);
14
+ /**
15
+ * Default headers to include in all requests.
16
+ * Can be a static object or a function that returns headers for dynamic resolution (e.g., auth tokens).
17
+ */
18
+ headers?: Record<string, string> | (() => Record<string, string>);
19
+ /**
20
+ * Content-Type header for request bodies.
21
+ * @default "application/json"
22
+ */
23
+ contentType?: string;
24
+ /**
25
+ * AbortSignal for request cancellation.
26
+ */
27
+ signal?: AbortSignal;
28
+ }
29
+ /**
30
+ * Event handler for streaming responses.
31
+ */
32
+ export type EventHandler<T = unknown> = (data: T) => void;
33
+ /**
34
+ * WebSocket wrapper with event-based API.
35
+ */
36
+ export interface WebSocketClient {
37
+ /**
38
+ * Register an event handler.
39
+ */
40
+ on: {
41
+ (event: 'message', handler: EventHandler<unknown>): void;
42
+ (event: 'open', handler: EventHandler<Event>): void;
43
+ (event: 'close', handler: EventHandler<CloseEvent>): void;
44
+ (event: 'error', handler: EventHandler<Event>): void;
45
+ };
46
+ /**
47
+ * Send data through the WebSocket.
48
+ */
49
+ send(data: unknown): void;
50
+ /**
51
+ * Close the WebSocket connection.
52
+ */
53
+ close(code?: number, reason?: string): void;
54
+ }
55
+ /**
56
+ * Server-Sent Events (SSE) client with event-based API.
57
+ */
58
+ export interface EventStreamClient {
59
+ /**
60
+ * Register an event handler.
61
+ */
62
+ on: {
63
+ (event: 'message', handler: EventHandler<MessageEvent>): void;
64
+ (event: 'open', handler: EventHandler<Event>): void;
65
+ (event: 'error', handler: EventHandler<Event>): void;
66
+ };
67
+ /**
68
+ * Close the EventSource connection.
69
+ */
70
+ close(): void;
71
+ }
72
+ /**
73
+ * Streaming response reader with event-based API.
74
+ */
75
+ export interface StreamClient {
76
+ /**
77
+ * Register an event handler.
78
+ */
79
+ on: {
80
+ (event: 'chunk', handler: EventHandler<Uint8Array>): void;
81
+ (event: 'close', handler: EventHandler<void>): void;
82
+ (event: 'error', handler: EventHandler<Error>): void;
83
+ };
84
+ /**
85
+ * Cancel the stream.
86
+ */
87
+ cancel(): Promise<void>;
88
+ }
89
+ /**
90
+ * API endpoint - callable function for regular HTTP calls.
91
+ */
92
+ export type APIEndpoint<Input = unknown, Output = unknown> = (input?: Input) => Promise<Output>;
93
+ /**
94
+ * WebSocket endpoint - callable function that returns WebSocket client.
95
+ */
96
+ export type WebSocketEndpoint<Input = unknown, _Output = unknown> = (input?: Input) => WebSocketClient;
97
+ /**
98
+ * Server-Sent Events endpoint - callable function that returns EventStream client.
99
+ */
100
+ export type SSEEndpoint<Input = unknown, _Output = unknown> = (input?: Input) => EventStreamClient;
101
+ /**
102
+ * Streaming endpoint - callable function that returns Stream client.
103
+ */
104
+ export type StreamEndpoint<Input = unknown, _Output = unknown> = (input?: Input) => StreamClient;
105
+ /**
106
+ * Route endpoint - discriminated union based on route type.
107
+ */
108
+ export type RouteEndpoint<Input = unknown, Output = unknown, Type extends string = 'api'> = Type extends 'websocket' ? WebSocketEndpoint<Input, Output> : Type extends 'sse' ? SSEEndpoint<Input, Output> : Type extends 'stream' ? StreamEndpoint<Input, Output> : APIEndpoint<Input, Output>;
109
+ /**
110
+ * Recursively build the client proxy type from a RouteRegistry.
111
+ */
112
+ export type Client<R> = {
113
+ [K in keyof R]: R[K] extends {
114
+ input: infer I;
115
+ output: infer O;
116
+ type: infer T;
117
+ } ? RouteEndpoint<I, O, T extends string ? T : 'api'> : R[K] extends {
118
+ input: infer I;
119
+ output: infer O;
120
+ } ? RouteEndpoint<I, O, 'api'> : R[K] extends object ? Client<R[K]> : never;
121
+ };
122
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Type-safe API client types for RPC-style invocations.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,6 @@
1
+ import type { WebSocketClient } from './types';
2
+ /**
3
+ * Create a WebSocket client wrapper with event-based API.
4
+ */
5
+ export declare function createWebSocketClient(url: string, protocols?: string | string[]): WebSocketClient;
6
+ //# sourceMappingURL=websocket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/client/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,SAAS,CAAC;AAE7D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,eAAe,CAuDjG"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Create a WebSocket client wrapper with event-based API.
3
+ */
4
+ export function createWebSocketClient(url, protocols) {
5
+ const ws = new WebSocket(url, protocols);
6
+ const handlers = {
7
+ message: new Set(),
8
+ open: new Set(),
9
+ close: new Set(),
10
+ error: new Set(),
11
+ };
12
+ // Set up native WebSocket event listeners
13
+ ws.addEventListener('message', (event) => {
14
+ let data = event.data;
15
+ // Try to parse JSON if data is a string
16
+ if (typeof event.data === 'string') {
17
+ try {
18
+ data = JSON.parse(event.data);
19
+ }
20
+ catch {
21
+ // Keep as string if not valid JSON
22
+ data = event.data;
23
+ }
24
+ }
25
+ handlers.message.forEach((handler) => handler(data));
26
+ });
27
+ ws.addEventListener('open', (event) => {
28
+ handlers.open.forEach((handler) => handler(event));
29
+ });
30
+ ws.addEventListener('close', (event) => {
31
+ handlers.close.forEach((handler) => handler(event));
32
+ });
33
+ ws.addEventListener('error', (event) => {
34
+ handlers.error.forEach((handler) => handler(event));
35
+ });
36
+ return {
37
+ on: ((event, handler) => {
38
+ handlers[event].add(handler);
39
+ }),
40
+ send(data) {
41
+ const payload = typeof data === 'string' ? data : JSON.stringify(data);
42
+ ws.send(payload);
43
+ },
44
+ close(code, reason) {
45
+ ws.close(code, reason);
46
+ },
47
+ };
48
+ }
49
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/client/websocket.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW,EAAE,SAA6B;IAC/E,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,QAAQ,GAKV;QACH,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,IAAI,EAAE,IAAI,GAAG,EAAE;QACf,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,KAAK,EAAE,IAAI,GAAG,EAAE;KAChB,CAAC;IAEF,0CAA0C;IAC1C,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAmB,EAAE,EAAE;QACtD,IAAI,IAAI,GAAY,KAAK,CAAC,IAAI,CAAC;QAC/B,wCAAwC;QACxC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACpC,IAAI,CAAC;gBACJ,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACR,mCAAmC;gBACnC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;YACnB,CAAC;QACF,CAAC;QACD,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,KAAY,EAAE,EAAE;QAC5C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAiB,EAAE,EAAE;QAClD,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;QAC7C,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,OAAO;QACN,EAAE,EAAE,CAAC,CAAC,KAA6C,EAAE,OAAqB,EAAE,EAAE;YAC7E,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAqD,CAAC,CAAC;QAC5E,CAAC,CAA0B;QAE3B,IAAI,CAAC,IAAa;YACjB,MAAM,OAAO,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACvE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClB,CAAC;QAED,KAAK,CAAC,IAAa,EAAE,MAAe;YACnC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxB,CAAC;KACD,CAAC;AACH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -6,4 +6,6 @@ export { type RouteRegistry, type WebSocketRouteRegistry, type SSERouteRegistry
6
6
  export { jsonEqual } from './memo';
7
7
  export { WebSocketManager, type MessageHandler as WebSocketMessageHandler, type WebSocketCallbacks, type WebSocketManagerOptions, type WebSocketManagerState, } from './websocket-manager';
8
8
  export { EventStreamManager, type MessageHandler as EventStreamMessageHandler, type EventStreamCallbacks, type EventStreamManagerOptions, type EventStreamManagerState, } from './eventstream-manager';
9
+ export { createClient } from './client/index';
10
+ export type { Client, ClientOptions, RouteEndpoint, WebSocketClient, EventStreamClient, StreamClient, EventHandler, } from './client/types';
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EACN,gBAAgB,EAChB,KAAK,cAAc,IAAI,uBAAuB,EAC9C,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,kBAAkB,EAClB,KAAK,cAAc,IAAI,yBAAyB,EAChD,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,GAC5B,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EACN,gBAAgB,EAChB,KAAK,cAAc,IAAI,uBAAuB,EAC9C,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,kBAAkB,EAClB,KAAK,cAAc,IAAI,yBAAyB,EAChD,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,GAC5B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACX,MAAM,EACN,aAAa,EACb,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACZ,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -5,4 +5,6 @@ export { createReconnectManager } from './reconnect';
5
5
  export { jsonEqual } from './memo';
6
6
  export { WebSocketManager, } from './websocket-manager';
7
7
  export { EventStreamManager, } from './eventstream-manager';
8
+ // Export client implementation (local to this package)
9
+ export { createClient } from './client/index';
8
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAgD,MAAM,aAAa,CAAC;AAEnG,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EACN,gBAAgB,GAKhB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,kBAAkB,GAKlB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAgD,MAAM,aAAa,CAAC;AAEnG,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EACN,gBAAgB,GAKhB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,kBAAkB,GAKlB,MAAM,uBAAuB,CAAC;AAE/B,uDAAuD;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentuity/frontend",
3
- "version": "0.0.100",
3
+ "version": "0.0.101",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Agentuity employees and contributors",
6
6
  "type": "module",
@@ -25,9 +25,11 @@
25
25
  "test": "bun test",
26
26
  "prepublishOnly": "bun run clean && bun run build"
27
27
  },
28
- "dependencies": {},
28
+ "dependencies": {
29
+ "@agentuity/core": "0.0.101"
30
+ },
29
31
  "devDependencies": {
30
- "@agentuity/test-utils": "workspace:*",
32
+ "@agentuity/test-utils": "0.0.101",
31
33
  "@types/bun": "latest",
32
34
  "bun-types": "latest",
33
35
  "typescript": "^5.9.0"
@@ -0,0 +1,52 @@
1
+ import type { EventHandler, EventStreamClient } from './types';
2
+
3
+ /**
4
+ * Create an EventSource (SSE) client wrapper with event-based API.
5
+ *
6
+ * Note: Native EventSource has limited authentication support.
7
+ * - withCredentials: true will send cookies and HTTP auth headers
8
+ * - For custom Authorization headers, consider using @microsoft/fetch-event-source
9
+ */
10
+ export function createEventStreamClient(
11
+ url: string,
12
+ options?: { withCredentials?: boolean }
13
+ ): EventStreamClient {
14
+ const eventSource = new EventSource(url, {
15
+ withCredentials: options?.withCredentials ?? false,
16
+ });
17
+ const handlers: {
18
+ message: Set<EventHandler<MessageEvent>>;
19
+ open: Set<EventHandler<Event>>;
20
+ error: Set<EventHandler<Event>>;
21
+ } = {
22
+ message: new Set(),
23
+ open: new Set(),
24
+ error: new Set(),
25
+ };
26
+
27
+ // Set up native EventSource event listeners
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ eventSource.onmessage = (event: any) => {
30
+ handlers.message.forEach((handler) => handler(event));
31
+ };
32
+
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ eventSource.onopen = (event: any) => {
35
+ handlers.open.forEach((handler) => handler(event));
36
+ };
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ eventSource.onerror = (event: any) => {
40
+ handlers.error.forEach((handler) => handler(event));
41
+ };
42
+
43
+ return {
44
+ on: ((event: 'message' | 'open' | 'error', handler: EventHandler) => {
45
+ handlers[event].add(handler);
46
+ }) as EventStreamClient['on'],
47
+
48
+ close() {
49
+ eventSource.close();
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,171 @@
1
+ import type { Client, ClientOptions } from './types';
2
+ import { createWebSocketClient } from './websocket';
3
+ import { createEventStreamClient } from './eventstream';
4
+ import { createStreamClient } from './stream';
5
+
6
+ /**
7
+ * Default base URL (empty = relative URLs).
8
+ */
9
+ const defaultBaseUrl = '';
10
+
11
+ /**
12
+ * Resolve baseUrl - if it's a function, call it; otherwise return the string.
13
+ */
14
+ function resolveBaseUrl(baseUrl: string | (() => string)): string {
15
+ return typeof baseUrl === 'function' ? baseUrl() : baseUrl;
16
+ }
17
+
18
+ /**
19
+ * Resolve headers - if it's a function, call it; otherwise return the object.
20
+ */
21
+ function resolveHeaders(
22
+ headers: Record<string, string> | (() => Record<string, string>)
23
+ ): Record<string, string> {
24
+ return typeof headers === 'function' ? headers() : headers;
25
+ }
26
+
27
+ /**
28
+ * Create a type-safe API client from a RouteRegistry.
29
+ *
30
+ * Uses a Proxy to build up the path as you navigate the object,
31
+ * then executes the request when you call a terminal method (.post(), .get(), .websocket(), etc.).
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { createClient } from '@agentuity/frontend';
36
+ * import type { RPCRouteRegistry } from './generated/routes';
37
+ *
38
+ * const client = createClient<RPCRouteRegistry>();
39
+ *
40
+ * // Type-safe API call
41
+ * const result = await client.hello.post({ name: 'World' });
42
+ *
43
+ * // WebSocket
44
+ * const ws = client.chat.websocket();
45
+ * ws.on('message', (msg) => console.log(msg));
46
+ *
47
+ * // Server-Sent Events
48
+ * const es = client.events.eventstream();
49
+ * es.on('message', (event) => console.log(event.data));
50
+ *
51
+ * // Streaming response
52
+ * const stream = await client.data.stream({ query: 'foo' });
53
+ * stream.on('chunk', (chunk) => console.log(chunk));
54
+ * ```
55
+ */
56
+ export function createClient<R>(options: ClientOptions = {}, metadata?: unknown): Client<R> {
57
+ const { baseUrl = defaultBaseUrl, headers, contentType = 'application/json', signal } = options;
58
+
59
+ // Default headers to empty object if not provided
60
+ const defaultHeaders = headers || {};
61
+
62
+ const handler: ProxyHandler<{ __path: string[]; __type?: string }> = {
63
+ get(target, prop: string) {
64
+ const currentPath = target.__path;
65
+ const newPath = [...currentPath, prop];
66
+
67
+ // Check if this is a terminal method
68
+ const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
69
+ const streamMethods = ['websocket', 'eventstream', 'stream'];
70
+ const isHttpMethod = httpMethods.includes(prop);
71
+ const isStreamMethod = streamMethods.includes(prop);
72
+ const isTerminalMethod = isHttpMethod || isStreamMethod;
73
+
74
+ // Terminal: method at the end (minimum 1 path segment required)
75
+ // Examples: client.hello.post(), client.echo.websocket(), client.events.eventstream()
76
+ if (isTerminalMethod && currentPath.length >= 1) {
77
+ const method = prop;
78
+ const pathSegments = currentPath;
79
+ const urlPath = '/api/' + pathSegments.join('/');
80
+
81
+ // Determine route type
82
+ let routeType = 'api';
83
+ if (isStreamMethod) {
84
+ // Stream methods directly specify the route type
85
+ if (method === 'websocket') routeType = 'websocket';
86
+ else if (method === 'eventstream') routeType = 'sse';
87
+ else if (method === 'stream') routeType = 'stream';
88
+ } else if (metadata) {
89
+ // Look up route type from metadata for HTTP methods
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ let metaNode: any = metadata;
92
+ for (const segment of pathSegments) {
93
+ if (metaNode && typeof metaNode === 'object') {
94
+ metaNode = metaNode[segment];
95
+ }
96
+ }
97
+ if (metaNode && typeof metaNode === 'object' && metaNode[method]?.type) {
98
+ routeType = metaNode[method].type;
99
+ }
100
+ }
101
+
102
+ return (input?: unknown) => {
103
+ const resolvedBaseUrl = resolveBaseUrl(baseUrl);
104
+ const resolvedHeaders = resolveHeaders(defaultHeaders);
105
+
106
+ // WebSocket endpoint
107
+ if (routeType === 'websocket') {
108
+ const wsBaseUrl = resolvedBaseUrl.replace(/^http/, 'ws');
109
+ const wsUrl = `${wsBaseUrl}${urlPath}`;
110
+ const ws = createWebSocketClient(wsUrl);
111
+ if (input) {
112
+ ws.on('open', () => ws.send(input));
113
+ }
114
+ return ws;
115
+ }
116
+
117
+ // SSE endpoint
118
+ if (routeType === 'sse') {
119
+ const sseUrl = `${resolvedBaseUrl}${urlPath}`;
120
+ // Note: Native EventSource doesn't support custom headers
121
+ // For auth, use withCredentials or consider fetch-based alternatives like @microsoft/fetch-event-source
122
+ return createEventStreamClient(sseUrl, {
123
+ withCredentials: Object.keys(resolvedHeaders).length > 0,
124
+ });
125
+ }
126
+
127
+ // Stream endpoint
128
+ if (routeType === 'stream') {
129
+ return fetch(`${resolvedBaseUrl}${urlPath}`, {
130
+ method: method.toUpperCase(),
131
+ headers: { 'Content-Type': contentType, ...resolvedHeaders },
132
+ body: input ? JSON.stringify(input) : undefined,
133
+ signal,
134
+ }).then((res) => {
135
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
136
+ return createStreamClient(res);
137
+ });
138
+ }
139
+
140
+ // Regular API endpoint
141
+ return fetch(`${resolvedBaseUrl}${urlPath}`, {
142
+ method: method.toUpperCase(),
143
+ headers: { 'Content-Type': contentType, ...resolvedHeaders },
144
+ body: method.toUpperCase() !== 'GET' && input ? JSON.stringify(input) : undefined,
145
+ signal,
146
+ }).then(async (res) => {
147
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
148
+ if (res.status === 204) return undefined;
149
+ return res.json();
150
+ });
151
+ };
152
+ }
153
+
154
+ // Otherwise, return a new proxy with extended path
155
+ return new Proxy({ __path: newPath }, handler);
156
+ },
157
+ };
158
+
159
+ return new Proxy({ __path: [] }, handler) as unknown as Client<R>;
160
+ }
161
+
162
+ // Re-export types
163
+ export type {
164
+ ClientOptions,
165
+ Client,
166
+ RouteEndpoint,
167
+ WebSocketClient,
168
+ EventStreamClient,
169
+ StreamClient,
170
+ EventHandler,
171
+ } from './types';
@@ -0,0 +1,61 @@
1
+ import type { EventHandler, StreamClient } from './types';
2
+
3
+ /**
4
+ * Create a streaming response reader with event-based API.
5
+ */
6
+ export function createStreamClient(response: Response): StreamClient {
7
+ const handlers: {
8
+ chunk: Set<EventHandler<Uint8Array>>;
9
+ close: Set<EventHandler<void>>;
10
+ error: Set<EventHandler<Error>>;
11
+ } = {
12
+ chunk: new Set(),
13
+ close: new Set(),
14
+ error: new Set(),
15
+ };
16
+
17
+ const reader = response.body?.getReader();
18
+ let cancelled = false;
19
+
20
+ // Start reading the stream
21
+ const readStream = async () => {
22
+ if (!reader) {
23
+ const error = new Error('Response body is not readable');
24
+ handlers.error.forEach((handler) => handler(error));
25
+ return;
26
+ }
27
+
28
+ try {
29
+ while (!cancelled) {
30
+ const { done, value } = await reader.read();
31
+
32
+ if (done) {
33
+ handlers.close.forEach((handler) => handler());
34
+ break;
35
+ }
36
+
37
+ if (value) {
38
+ handlers.chunk.forEach((handler) => handler(value));
39
+ }
40
+ }
41
+ } catch (error) {
42
+ const err = error instanceof Error ? error : new Error(String(error));
43
+ handlers.error.forEach((handler) => handler(err));
44
+ }
45
+ };
46
+
47
+ // Start reading immediately
48
+ readStream();
49
+
50
+ return {
51
+ on: ((event: 'chunk' | 'close' | 'error', handler: EventHandler) => {
52
+ handlers[event].add(handler as EventHandler<Uint8Array | void | Error>);
53
+ }) as StreamClient['on'],
54
+
55
+ async cancel() {
56
+ cancelled = true;
57
+ await reader?.cancel();
58
+ handlers.close.forEach((handler) => handler());
59
+ },
60
+ };
61
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Type-safe API client types for RPC-style invocations.
3
+ */
4
+
5
+ /**
6
+ * Options for creating a client.
7
+ */
8
+ export interface ClientOptions {
9
+ /**
10
+ * Base URL for API requests (e.g., "https://p1234567890.agenuity.run").
11
+ * Defaults to empty string (relative URLs).
12
+ * Can be a string or a function that returns a string for lazy resolution.
13
+ */
14
+ baseUrl?: string | (() => string);
15
+
16
+ /**
17
+ * Default headers to include in all requests.
18
+ * Can be a static object or a function that returns headers for dynamic resolution (e.g., auth tokens).
19
+ */
20
+ headers?: Record<string, string> | (() => Record<string, string>);
21
+
22
+ /**
23
+ * Content-Type header for request bodies.
24
+ * @default "application/json"
25
+ */
26
+ contentType?: string;
27
+
28
+ /**
29
+ * AbortSignal for request cancellation.
30
+ */
31
+ signal?: AbortSignal;
32
+ }
33
+
34
+ /**
35
+ * Event handler for streaming responses.
36
+ */
37
+ export type EventHandler<T = unknown> = (data: T) => void;
38
+
39
+ /**
40
+ * WebSocket wrapper with event-based API.
41
+ */
42
+ export interface WebSocketClient {
43
+ /**
44
+ * Register an event handler.
45
+ */
46
+ on: {
47
+ (event: 'message', handler: EventHandler<unknown>): void;
48
+ (event: 'open', handler: EventHandler<Event>): void;
49
+ (event: 'close', handler: EventHandler<CloseEvent>): void;
50
+ (event: 'error', handler: EventHandler<Event>): void;
51
+ };
52
+
53
+ /**
54
+ * Send data through the WebSocket.
55
+ */
56
+ send(data: unknown): void;
57
+
58
+ /**
59
+ * Close the WebSocket connection.
60
+ */
61
+ close(code?: number, reason?: string): void;
62
+ }
63
+
64
+ /**
65
+ * Server-Sent Events (SSE) client with event-based API.
66
+ */
67
+ export interface EventStreamClient {
68
+ /**
69
+ * Register an event handler.
70
+ */
71
+ on: {
72
+ (event: 'message', handler: EventHandler<MessageEvent>): void;
73
+ (event: 'open', handler: EventHandler<Event>): void;
74
+ (event: 'error', handler: EventHandler<Event>): void;
75
+ };
76
+
77
+ /**
78
+ * Close the EventSource connection.
79
+ */
80
+ close(): void;
81
+ }
82
+
83
+ /**
84
+ * Streaming response reader with event-based API.
85
+ */
86
+ export interface StreamClient {
87
+ /**
88
+ * Register an event handler.
89
+ */
90
+ on: {
91
+ (event: 'chunk', handler: EventHandler<Uint8Array>): void;
92
+ (event: 'close', handler: EventHandler<void>): void;
93
+ (event: 'error', handler: EventHandler<Error>): void;
94
+ };
95
+
96
+ /**
97
+ * Cancel the stream.
98
+ */
99
+ cancel(): Promise<void>;
100
+ }
101
+
102
+ /**
103
+ * API endpoint - callable function for regular HTTP calls.
104
+ */
105
+ export type APIEndpoint<Input = unknown, Output = unknown> = (input?: Input) => Promise<Output>;
106
+
107
+ /**
108
+ * WebSocket endpoint - callable function that returns WebSocket client.
109
+ */
110
+ export type WebSocketEndpoint<Input = unknown, _Output = unknown> = (
111
+ input?: Input
112
+ ) => WebSocketClient;
113
+
114
+ /**
115
+ * Server-Sent Events endpoint - callable function that returns EventStream client.
116
+ */
117
+ export type SSEEndpoint<Input = unknown, _Output = unknown> = (input?: Input) => EventStreamClient;
118
+
119
+ /**
120
+ * Streaming endpoint - callable function that returns Stream client.
121
+ */
122
+ export type StreamEndpoint<Input = unknown, _Output = unknown> = (input?: Input) => StreamClient;
123
+
124
+ /**
125
+ * Route endpoint - discriminated union based on route type.
126
+ */
127
+ export type RouteEndpoint<
128
+ Input = unknown,
129
+ Output = unknown,
130
+ Type extends string = 'api',
131
+ > = Type extends 'websocket'
132
+ ? WebSocketEndpoint<Input, Output>
133
+ : Type extends 'sse'
134
+ ? SSEEndpoint<Input, Output>
135
+ : Type extends 'stream'
136
+ ? StreamEndpoint<Input, Output>
137
+ : APIEndpoint<Input, Output>;
138
+
139
+ /**
140
+ * Recursively build the client proxy type from a RouteRegistry.
141
+ */
142
+ export type Client<R> = {
143
+ [K in keyof R]: R[K] extends { input: infer I; output: infer O; type: infer T }
144
+ ? RouteEndpoint<I, O, T extends string ? T : 'api'>
145
+ : R[K] extends { input: infer I; output: infer O }
146
+ ? RouteEndpoint<I, O, 'api'>
147
+ : R[K] extends object
148
+ ? Client<R[K]>
149
+ : never;
150
+ };
@@ -0,0 +1,61 @@
1
+ import type { EventHandler, WebSocketClient } from './types';
2
+
3
+ /**
4
+ * Create a WebSocket client wrapper with event-based API.
5
+ */
6
+ export function createWebSocketClient(url: string, protocols?: string | string[]): WebSocketClient {
7
+ const ws = new WebSocket(url, protocols);
8
+ const handlers: {
9
+ message: Set<EventHandler<unknown>>;
10
+ open: Set<EventHandler<Event>>;
11
+ close: Set<EventHandler<CloseEvent>>;
12
+ error: Set<EventHandler<Event>>;
13
+ } = {
14
+ message: new Set(),
15
+ open: new Set(),
16
+ close: new Set(),
17
+ error: new Set(),
18
+ };
19
+
20
+ // Set up native WebSocket event listeners
21
+ ws.addEventListener('message', (event: MessageEvent) => {
22
+ let data: unknown = event.data;
23
+ // Try to parse JSON if data is a string
24
+ if (typeof event.data === 'string') {
25
+ try {
26
+ data = JSON.parse(event.data);
27
+ } catch {
28
+ // Keep as string if not valid JSON
29
+ data = event.data;
30
+ }
31
+ }
32
+ handlers.message.forEach((handler) => handler(data));
33
+ });
34
+
35
+ ws.addEventListener('open', (event: Event) => {
36
+ handlers.open.forEach((handler) => handler(event));
37
+ });
38
+
39
+ ws.addEventListener('close', (event: CloseEvent) => {
40
+ handlers.close.forEach((handler) => handler(event));
41
+ });
42
+
43
+ ws.addEventListener('error', (event: Event) => {
44
+ handlers.error.forEach((handler) => handler(event));
45
+ });
46
+
47
+ return {
48
+ on: ((event: 'message' | 'open' | 'close' | 'error', handler: EventHandler) => {
49
+ handlers[event].add(handler as EventHandler<unknown | Event | CloseEvent>);
50
+ }) as WebSocketClient['on'],
51
+
52
+ send(data: unknown) {
53
+ const payload = typeof data === 'string' ? data : JSON.stringify(data);
54
+ ws.send(payload);
55
+ },
56
+
57
+ close(code?: number, reason?: string) {
58
+ ws.close(code, reason);
59
+ },
60
+ };
61
+ }
package/src/index.ts CHANGED
@@ -18,3 +18,15 @@ export {
18
18
  type EventStreamManagerOptions,
19
19
  type EventStreamManagerState,
20
20
  } from './eventstream-manager';
21
+
22
+ // Export client implementation (local to this package)
23
+ export { createClient } from './client/index';
24
+ export type {
25
+ Client,
26
+ ClientOptions,
27
+ RouteEndpoint,
28
+ WebSocketClient,
29
+ EventStreamClient,
30
+ StreamClient,
31
+ EventHandler,
32
+ } from './client/types';