@utcp/http 1.0.0

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.
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ import { CallTemplate } from '@utcp/core/data/call_template';
3
+ import { Serializer } from '@utcp/core/interfaces/serializer';
4
+ /**
5
+ * REQUIRED
6
+ * Provider configuration for Server-Sent Events (SSE) tools.
7
+ *
8
+ * Enables real-time streaming of events from server to client using the
9
+ * Server-Sent Events protocol. Supports automatic reconnection and
10
+ * event type filtering. All tool arguments not mapped to URL body, headers
11
+ * or query pattern parameters are passed as query parameters using '?arg_name={arg_value}'.
12
+ *
13
+ * Attributes:
14
+ * call_template_type: Always "sse" for SSE providers.
15
+ * url: The SSE endpoint URL to connect to.
16
+ * event_type: Optional filter for specific event types. If None, all events are received.
17
+ * reconnect: Whether to automatically reconnect on connection loss.
18
+ * retry_timeout: Timeout in milliseconds before attempting reconnection.
19
+ * auth: Optional authentication configuration.
20
+ * headers: Optional static headers for the initial connection.
21
+ * body_field: Optional tool argument name to map to request body during connection.
22
+ * header_fields: List of tool argument names to map to HTTP headers during connection.
23
+ */
24
+ export interface SseCallTemplate extends CallTemplate {
25
+ call_template_type: 'sse';
26
+ url: string;
27
+ event_type?: string | null;
28
+ reconnect: boolean;
29
+ retry_timeout: number;
30
+ headers?: Record<string, string>;
31
+ body_field?: string | null;
32
+ header_fields?: string[] | null;
33
+ }
34
+ /**
35
+ * SSE Call Template schema.
36
+ */
37
+ export declare const SseCallTemplateSchema: z.ZodType<SseCallTemplate>;
38
+ /**
39
+ * REQUIRED
40
+ * Serializer for SseCallTemplate.
41
+ */
42
+ export declare class SseCallTemplateSerializer extends Serializer<SseCallTemplate> {
43
+ /**
44
+ * REQUIRED
45
+ * Convert SseCallTemplate to dictionary.
46
+ */
47
+ toDict(obj: SseCallTemplate): Record<string, unknown>;
48
+ /**
49
+ * REQUIRED
50
+ * Validate dictionary and convert to SseCallTemplate.
51
+ */
52
+ validateDict(obj: Record<string, unknown>): SseCallTemplate;
53
+ }
@@ -0,0 +1,55 @@
1
+ // packages/http/src/sse_call_template.ts
2
+ import { z } from 'zod';
3
+ import { AuthSchema } from '@utcp/core/data/auth';
4
+ import { Serializer } from '@utcp/core/interfaces/serializer';
5
+ /**
6
+ * SSE Call Template schema.
7
+ */
8
+ export const SseCallTemplateSchema = z.object({
9
+ name: z.string().optional(),
10
+ call_template_type: z.literal('sse'),
11
+ url: z.string().describe('The SSE endpoint URL to connect to.'),
12
+ event_type: z.string().nullable().optional().describe('Optional filter for specific event types. If null, all events are received.'),
13
+ reconnect: z.boolean().default(true).describe('Whether to automatically reconnect on connection loss.'),
14
+ retry_timeout: z.number().default(30000).describe('Timeout in milliseconds before attempting reconnection.'),
15
+ auth: AuthSchema.optional().describe('Optional authentication configuration.'),
16
+ headers: z.record(z.string(), z.string()).optional().describe('Optional static headers for the initial connection.'),
17
+ body_field: z.string().nullable().optional().describe('The name of the single input field to be sent as the request body.'),
18
+ header_fields: z.array(z.string()).nullable().optional().describe('List of input fields to be sent as request headers for the initial connection.'),
19
+ });
20
+ /**
21
+ * REQUIRED
22
+ * Serializer for SseCallTemplate.
23
+ */
24
+ export class SseCallTemplateSerializer extends Serializer {
25
+ /**
26
+ * REQUIRED
27
+ * Convert SseCallTemplate to dictionary.
28
+ */
29
+ toDict(obj) {
30
+ return {
31
+ name: obj.name,
32
+ call_template_type: obj.call_template_type,
33
+ url: obj.url,
34
+ event_type: obj.event_type,
35
+ reconnect: obj.reconnect,
36
+ retry_timeout: obj.retry_timeout,
37
+ auth: obj.auth,
38
+ headers: obj.headers,
39
+ body_field: obj.body_field,
40
+ header_fields: obj.header_fields,
41
+ };
42
+ }
43
+ /**
44
+ * REQUIRED
45
+ * Validate dictionary and convert to SseCallTemplate.
46
+ */
47
+ validateDict(obj) {
48
+ try {
49
+ return SseCallTemplateSchema.parse(obj);
50
+ }
51
+ catch (e) {
52
+ throw new Error(`Invalid SseCallTemplate: ${e.message}\n${e.stack || ''}`);
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Server-Sent Events (SSE) Communication Protocol for UTCP.
3
+ *
4
+ * Handles Server-Sent Events based tool providers with streaming capabilities.
5
+ */
6
+ import { CommunicationProtocol } from '@utcp/core/interfaces/communication_protocol';
7
+ import { RegisterManualResult } from '@utcp/core/data/register_manual_result';
8
+ import { CallTemplate } from '@utcp/core/data/call_template';
9
+ import { IUtcpClient } from '@utcp/core/interfaces/utcp_client_interface';
10
+ /**
11
+ * REQUIRED
12
+ * SSE communication protocol implementation for UTCP client.
13
+ *
14
+ * Handles Server-Sent Events based tool providers with streaming capabilities.
15
+ */
16
+ export declare class SseCommunicationProtocol implements CommunicationProtocol {
17
+ private oauthTokens;
18
+ private _logInfo;
19
+ private _logError;
20
+ private _applyAuth;
21
+ /**
22
+ * REQUIRED
23
+ * Register a manual and its tools from an SSE provider.
24
+ */
25
+ registerManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise<RegisterManualResult>;
26
+ /**
27
+ * REQUIRED
28
+ * Deregister a manual (no-op for SSE).
29
+ */
30
+ deregisterManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise<void>;
31
+ /**
32
+ * REQUIRED
33
+ * Call a tool using SSE (non-streaming).
34
+ */
35
+ callTool(caller: IUtcpClient, toolName: string, toolArgs: Record<string, any>, toolCallTemplate: CallTemplate): Promise<any>;
36
+ /**
37
+ * REQUIRED
38
+ * Call a tool using SSE streaming.
39
+ * Returns an async generator that yields SSE events.
40
+ */
41
+ callToolStreaming(caller: IUtcpClient, toolName: string, toolArgs: Record<string, any>, toolCallTemplate: CallTemplate): AsyncGenerator<any, void, unknown>;
42
+ /**
43
+ * REQUIRED
44
+ * Close all active connections and clear internal state.
45
+ */
46
+ close(): Promise<void>;
47
+ }
@@ -0,0 +1,157 @@
1
+ import { UtcpManualSerializer } from '@utcp/core/data/utcp_manual';
2
+ /**
3
+ * REQUIRED
4
+ * SSE communication protocol implementation for UTCP client.
5
+ *
6
+ * Handles Server-Sent Events based tool providers with streaming capabilities.
7
+ */
8
+ export class SseCommunicationProtocol {
9
+ oauthTokens = new Map();
10
+ _logInfo(message) {
11
+ console.log(`[SseCommunicationProtocol] ${message}`);
12
+ }
13
+ _logError(message) {
14
+ console.error(`[SseCommunicationProtocol] ${message}`);
15
+ }
16
+ _applyAuth(provider, headers, queryParams) {
17
+ let auth;
18
+ const cookies = {};
19
+ if (provider.auth) {
20
+ if ('api_key' in provider.auth) {
21
+ const apiKeyAuth = provider.auth;
22
+ if (apiKeyAuth.api_key) {
23
+ if (apiKeyAuth.location === 'header') {
24
+ headers[apiKeyAuth.var_name] = apiKeyAuth.api_key;
25
+ }
26
+ else if (apiKeyAuth.location === 'query') {
27
+ queryParams[apiKeyAuth.var_name] = apiKeyAuth.api_key;
28
+ }
29
+ else if (apiKeyAuth.location === 'cookie') {
30
+ cookies[apiKeyAuth.var_name] = apiKeyAuth.api_key;
31
+ }
32
+ }
33
+ else {
34
+ this._logError('API key not found for ApiKeyAuth.');
35
+ throw new Error('API key for ApiKeyAuth not found.');
36
+ }
37
+ }
38
+ else if ('username' in provider.auth && 'password' in provider.auth) {
39
+ const basicAuth = provider.auth;
40
+ auth = { username: basicAuth.username, password: basicAuth.password };
41
+ }
42
+ else if ('token_url' in provider.auth) {
43
+ // OAuth2 will be handled separately
44
+ }
45
+ }
46
+ return { auth, cookies };
47
+ }
48
+ /**
49
+ * REQUIRED
50
+ * Register a manual and its tools from an SSE provider.
51
+ */
52
+ async registerManual(caller, manualCallTemplate) {
53
+ if (manualCallTemplate.call_template_type !== 'sse') {
54
+ throw new Error('SseCommunicationProtocol can only be used with SseCallTemplate');
55
+ }
56
+ const provider = manualCallTemplate;
57
+ const url = provider.url;
58
+ // Security check: Enforce HTTPS or localhost to prevent MITM attacks
59
+ if (!url.startsWith('https://') &&
60
+ !url.startsWith('http://localhost') &&
61
+ !url.startsWith('http://127.0.0.1')) {
62
+ throw new Error(`Security error: URL must use HTTPS or start with 'http://localhost' or 'http://127.0.0.1'. Got: ${url}. ` +
63
+ 'Non-secure URLs are vulnerable to man-in-the-middle attacks.');
64
+ }
65
+ this._logInfo(`Discovering tools from '${provider.name}' (SSE) at ${url}`);
66
+ try {
67
+ const requestHeaders = provider.headers ? { ...provider.headers } : {};
68
+ const queryParams = {};
69
+ const { auth, cookies } = this._applyAuth(provider, requestHeaders, queryParams);
70
+ // Build URL with query parameters
71
+ const urlObj = new URL(url);
72
+ Object.entries(queryParams).forEach(([key, value]) => {
73
+ urlObj.searchParams.append(key, String(value));
74
+ });
75
+ // SSE requires Accept: text/event-stream
76
+ requestHeaders['Accept'] = 'text/event-stream';
77
+ // Build fetch options
78
+ const fetchOptions = {
79
+ method: 'GET',
80
+ headers: requestHeaders,
81
+ };
82
+ // Add basic auth if present
83
+ if (auth) {
84
+ const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString('base64');
85
+ fetchOptions.headers = {
86
+ ...fetchOptions.headers,
87
+ 'Authorization': `Basic ${credentials}`,
88
+ };
89
+ }
90
+ // Make discovery request
91
+ const response = await fetch(urlObj.toString(), fetchOptions);
92
+ if (!response.ok) {
93
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
94
+ }
95
+ // For SSE discovery, we expect the first event to contain the manual
96
+ // This is a simplified implementation
97
+ const responseText = await response.text();
98
+ const utcpManual = new UtcpManualSerializer().validateDict(JSON.parse(responseText));
99
+ this._logInfo(`Discovered ${utcpManual.tools.length} tools from '${provider.name}'`);
100
+ return {
101
+ manualCallTemplate: provider,
102
+ manual: utcpManual,
103
+ success: true,
104
+ errors: [],
105
+ };
106
+ }
107
+ catch (error) {
108
+ this._logError(`Error discovering tools from '${provider.name}': ${error.message}`);
109
+ return {
110
+ manualCallTemplate: provider,
111
+ manual: new UtcpManualSerializer().validateDict({ tools: [] }),
112
+ success: false,
113
+ errors: [error.message || String(error)],
114
+ };
115
+ }
116
+ }
117
+ /**
118
+ * REQUIRED
119
+ * Deregister a manual (no-op for SSE).
120
+ */
121
+ async deregisterManual(caller, manualCallTemplate) {
122
+ // No-op for SSE
123
+ }
124
+ /**
125
+ * REQUIRED
126
+ * Call a tool using SSE (non-streaming).
127
+ */
128
+ async callTool(caller, toolName, toolArgs, toolCallTemplate) {
129
+ // For SSE, we collect all events and return them
130
+ const events = [];
131
+ for await (const event of this.callToolStreaming(caller, toolName, toolArgs, toolCallTemplate)) {
132
+ events.push(event);
133
+ }
134
+ return events;
135
+ }
136
+ /**
137
+ * REQUIRED
138
+ * Call a tool using SSE streaming.
139
+ * Returns an async generator that yields SSE events.
140
+ */
141
+ async *callToolStreaming(caller, toolName, toolArgs, toolCallTemplate) {
142
+ const provider = toolCallTemplate;
143
+ // TODO: Implement actual SSE call logic
144
+ // This would involve connecting to the SSE endpoint and streaming events
145
+ this._logInfo(`Calling SSE tool '${toolName}' with args: ${JSON.stringify(toolArgs)}`);
146
+ // Placeholder implementation
147
+ yield `SSE event for tool: ${toolName}`;
148
+ }
149
+ /**
150
+ * REQUIRED
151
+ * Close all active connections and clear internal state.
152
+ */
153
+ async close() {
154
+ this._logInfo('Closing SseCommunicationProtocol.');
155
+ this.oauthTokens.clear();
156
+ }
157
+ }
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ import { CallTemplate } from '@utcp/core/data/call_template';
3
+ import { Serializer } from '@utcp/core/interfaces/serializer';
4
+ /**
5
+ * REQUIRED
6
+ * Provider configuration for HTTP streaming tools.
7
+ *
8
+ * Uses HTTP Chunked Transfer Encoding to enable streaming of large responses
9
+ * or real-time data. Useful for tools that return large datasets or provide
10
+ * progressive results. All tool arguments not mapped to URL body, headers
11
+ * or query pattern parameters are passed as query parameters using '?arg_name={arg_value}'.
12
+ *
13
+ * Attributes:
14
+ * call_template_type: Always "streamable_http" for HTTP streaming providers.
15
+ * url: The streaming HTTP endpoint URL. Supports path parameters.
16
+ * http_method: The HTTP method to use (GET or POST).
17
+ * content_type: The Content-Type header for requests.
18
+ * chunk_size: Size of each chunk in bytes for reading the stream.
19
+ * timeout: Request timeout in milliseconds.
20
+ * headers: Optional static headers to include in requests.
21
+ * auth: Optional authentication configuration.
22
+ * body_field: Optional tool argument name to map to HTTP request body.
23
+ * header_fields: List of tool argument names to map to HTTP request headers.
24
+ */
25
+ export interface StreamableHttpCallTemplate extends CallTemplate {
26
+ call_template_type: 'streamable_http';
27
+ url: string;
28
+ http_method: 'GET' | 'POST';
29
+ content_type: string;
30
+ chunk_size: number;
31
+ timeout: number;
32
+ headers?: Record<string, string>;
33
+ body_field?: string | null;
34
+ header_fields?: string[] | null;
35
+ }
36
+ /**
37
+ * Streamable HTTP Call Template schema.
38
+ */
39
+ export declare const StreamableHttpCallTemplateSchema: z.ZodType<StreamableHttpCallTemplate>;
40
+ /**
41
+ * REQUIRED
42
+ * Serializer for StreamableHttpCallTemplate.
43
+ */
44
+ export declare class StreamableHttpCallTemplateSerializer extends Serializer<StreamableHttpCallTemplate> {
45
+ /**
46
+ * REQUIRED
47
+ * Convert StreamableHttpCallTemplate to dictionary.
48
+ */
49
+ toDict(obj: StreamableHttpCallTemplate): Record<string, unknown>;
50
+ /**
51
+ * REQUIRED
52
+ * Validate dictionary and convert to StreamableHttpCallTemplate.
53
+ */
54
+ validateDict(obj: Record<string, unknown>): StreamableHttpCallTemplate;
55
+ }
@@ -0,0 +1,57 @@
1
+ // packages/http/src/streamable_http_call_template.ts
2
+ import { z } from 'zod';
3
+ import { AuthSchema } from '@utcp/core/data/auth';
4
+ import { Serializer } from '@utcp/core/interfaces/serializer';
5
+ /**
6
+ * Streamable HTTP Call Template schema.
7
+ */
8
+ export const StreamableHttpCallTemplateSchema = z.object({
9
+ name: z.string().optional(),
10
+ call_template_type: z.literal('streamable_http'),
11
+ url: z.string().describe('The streaming HTTP endpoint URL. Supports path parameters.'),
12
+ http_method: z.enum(['GET', 'POST']).default('GET'),
13
+ content_type: z.string().default('application/octet-stream').describe('The Content-Type header for requests.'),
14
+ chunk_size: z.number().default(4096).describe('Size of each chunk in bytes for reading the stream.'),
15
+ timeout: z.number().default(60000).describe('Request timeout in milliseconds.'),
16
+ auth: AuthSchema.optional().describe('Optional authentication configuration.'),
17
+ headers: z.record(z.string(), z.string()).optional().describe('Optional static headers to include in requests.'),
18
+ body_field: z.string().nullable().optional().describe('The name of the single input field to be sent as the request body.'),
19
+ header_fields: z.array(z.string()).nullable().optional().describe('List of input fields to be sent as request headers.'),
20
+ });
21
+ /**
22
+ * REQUIRED
23
+ * Serializer for StreamableHttpCallTemplate.
24
+ */
25
+ export class StreamableHttpCallTemplateSerializer extends Serializer {
26
+ /**
27
+ * REQUIRED
28
+ * Convert StreamableHttpCallTemplate to dictionary.
29
+ */
30
+ toDict(obj) {
31
+ return {
32
+ name: obj.name,
33
+ call_template_type: obj.call_template_type,
34
+ url: obj.url,
35
+ http_method: obj.http_method,
36
+ content_type: obj.content_type,
37
+ chunk_size: obj.chunk_size,
38
+ timeout: obj.timeout,
39
+ auth: obj.auth,
40
+ headers: obj.headers,
41
+ body_field: obj.body_field,
42
+ header_fields: obj.header_fields,
43
+ };
44
+ }
45
+ /**
46
+ * REQUIRED
47
+ * Validate dictionary and convert to StreamableHttpCallTemplate.
48
+ */
49
+ validateDict(obj) {
50
+ try {
51
+ return StreamableHttpCallTemplateSchema.parse(obj);
52
+ }
53
+ catch (e) {
54
+ throw new Error(`Invalid StreamableHttpCallTemplate: ${e.message}\n${e.stack || ''}`);
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Streamable HTTP Communication Protocol for UTCP.
3
+ *
4
+ * Handles HTTP streaming with chunked transfer encoding for real-time data.
5
+ */
6
+ import { CommunicationProtocol } from '@utcp/core/interfaces/communication_protocol';
7
+ import { RegisterManualResult } from '@utcp/core/data/register_manual_result';
8
+ import { CallTemplate } from '@utcp/core/data/call_template';
9
+ import { IUtcpClient } from '@utcp/core/interfaces/utcp_client_interface';
10
+ /**
11
+ * REQUIRED
12
+ * Streamable HTTP communication protocol implementation for UTCP client.
13
+ *
14
+ * Handles HTTP streaming with chunked transfer encoding for real-time data.
15
+ */
16
+ export declare class StreamableHttpCommunicationProtocol implements CommunicationProtocol {
17
+ private oauthTokens;
18
+ private _logInfo;
19
+ private _logError;
20
+ private _applyAuth;
21
+ /**
22
+ * REQUIRED
23
+ * Register a manual and its tools from a StreamableHttp provider.
24
+ */
25
+ registerManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise<RegisterManualResult>;
26
+ /**
27
+ * REQUIRED
28
+ * Deregister a manual (no-op for HTTP streaming).
29
+ */
30
+ deregisterManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise<void>;
31
+ /**
32
+ * REQUIRED
33
+ * Call a tool using HTTP (non-streaming).
34
+ */
35
+ callTool(caller: IUtcpClient, toolName: string, toolArgs: Record<string, any>, toolCallTemplate: CallTemplate): Promise<any>;
36
+ /**
37
+ * REQUIRED
38
+ * Call a tool using HTTP streaming.
39
+ * Returns an async generator that yields chunks of data.
40
+ */
41
+ callToolStreaming(caller: IUtcpClient, toolName: string, toolArgs: Record<string, any>, toolCallTemplate: CallTemplate): AsyncGenerator<any, void, unknown>;
42
+ /**
43
+ * REQUIRED
44
+ * Close all active connections and clear internal state.
45
+ */
46
+ close(): Promise<void>;
47
+ }
@@ -0,0 +1,154 @@
1
+ import { UtcpManualSerializer } from '@utcp/core/data/utcp_manual';
2
+ /**
3
+ * REQUIRED
4
+ * Streamable HTTP communication protocol implementation for UTCP client.
5
+ *
6
+ * Handles HTTP streaming with chunked transfer encoding for real-time data.
7
+ */
8
+ export class StreamableHttpCommunicationProtocol {
9
+ oauthTokens = new Map();
10
+ _logInfo(message) {
11
+ console.log(`[StreamableHttpCommunicationProtocol] ${message}`);
12
+ }
13
+ _logError(message) {
14
+ console.error(`[StreamableHttpCommunicationProtocol] ${message}`);
15
+ }
16
+ _applyAuth(provider, headers, queryParams) {
17
+ let auth;
18
+ const cookies = {};
19
+ if (provider.auth) {
20
+ if ('api_key' in provider.auth) {
21
+ const apiKeyAuth = provider.auth;
22
+ if (apiKeyAuth.api_key) {
23
+ if (apiKeyAuth.location === 'header') {
24
+ headers[apiKeyAuth.var_name] = apiKeyAuth.api_key;
25
+ }
26
+ else if (apiKeyAuth.location === 'query') {
27
+ queryParams[apiKeyAuth.var_name] = apiKeyAuth.api_key;
28
+ }
29
+ else if (apiKeyAuth.location === 'cookie') {
30
+ cookies[apiKeyAuth.var_name] = apiKeyAuth.api_key;
31
+ }
32
+ }
33
+ else {
34
+ this._logError('API key not found for ApiKeyAuth.');
35
+ throw new Error('API key for ApiKeyAuth not found.');
36
+ }
37
+ }
38
+ else if ('username' in provider.auth && 'password' in provider.auth) {
39
+ const basicAuth = provider.auth;
40
+ auth = { username: basicAuth.username, password: basicAuth.password };
41
+ }
42
+ else if ('token_url' in provider.auth) {
43
+ // OAuth2 will be handled separately
44
+ }
45
+ }
46
+ return { auth, cookies };
47
+ }
48
+ /**
49
+ * REQUIRED
50
+ * Register a manual and its tools from a StreamableHttp provider.
51
+ */
52
+ async registerManual(caller, manualCallTemplate) {
53
+ if (manualCallTemplate.call_template_type !== 'streamable_http') {
54
+ throw new Error('StreamableHttpCommunicationProtocol can only be used with StreamableHttpCallTemplate');
55
+ }
56
+ const provider = manualCallTemplate;
57
+ const url = provider.url;
58
+ // Security check: Enforce HTTPS or localhost to prevent MITM attacks
59
+ if (!url.startsWith('https://') &&
60
+ !url.startsWith('http://localhost') &&
61
+ !url.startsWith('http://127.0.0.1')) {
62
+ throw new Error(`Security error: URL must use HTTPS or start with 'http://localhost' or 'http://127.0.0.1'. Got: ${url}. ` +
63
+ 'Non-secure URLs are vulnerable to man-in-the-middle attacks.');
64
+ }
65
+ this._logInfo(`Discovering tools from '${provider.name}' (HTTP Stream) at ${url}`);
66
+ try {
67
+ const requestHeaders = provider.headers ? { ...provider.headers } : {};
68
+ const queryParams = {};
69
+ const { auth, cookies } = this._applyAuth(provider, requestHeaders, queryParams);
70
+ // Build URL with query parameters
71
+ const urlObj = new URL(url);
72
+ Object.entries(queryParams).forEach(([key, value]) => {
73
+ urlObj.searchParams.append(key, String(value));
74
+ });
75
+ // Build fetch options
76
+ const fetchOptions = {
77
+ method: provider.http_method || 'GET',
78
+ headers: requestHeaders,
79
+ };
80
+ // Add basic auth if present
81
+ if (auth) {
82
+ const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString('base64');
83
+ fetchOptions.headers = {
84
+ ...fetchOptions.headers,
85
+ 'Authorization': `Basic ${credentials}`,
86
+ };
87
+ }
88
+ // Make discovery request
89
+ const response = await fetch(urlObj.toString(), fetchOptions);
90
+ if (!response.ok) {
91
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
92
+ }
93
+ // Read response body
94
+ const responseText = await response.text();
95
+ const utcpManual = new UtcpManualSerializer().validateDict(JSON.parse(responseText));
96
+ this._logInfo(`Discovered ${utcpManual.tools.length} tools from '${provider.name}'`);
97
+ return {
98
+ manualCallTemplate: provider,
99
+ manual: utcpManual,
100
+ success: true,
101
+ errors: [],
102
+ };
103
+ }
104
+ catch (error) {
105
+ this._logError(`Error discovering tools from '${provider.name}': ${error.message}`);
106
+ return {
107
+ manualCallTemplate: provider,
108
+ manual: new UtcpManualSerializer().validateDict({ tools: [] }),
109
+ success: false,
110
+ errors: [error.message || String(error)],
111
+ };
112
+ }
113
+ }
114
+ /**
115
+ * REQUIRED
116
+ * Deregister a manual (no-op for HTTP streaming).
117
+ */
118
+ async deregisterManual(caller, manualCallTemplate) {
119
+ // No-op for HTTP streaming
120
+ }
121
+ /**
122
+ * REQUIRED
123
+ * Call a tool using HTTP (non-streaming).
124
+ */
125
+ async callTool(caller, toolName, toolArgs, toolCallTemplate) {
126
+ // For streamable HTTP, we collect all chunks and return the complete result
127
+ const chunks = [];
128
+ for await (const chunk of this.callToolStreaming(caller, toolName, toolArgs, toolCallTemplate)) {
129
+ chunks.push(chunk);
130
+ }
131
+ return chunks.join('');
132
+ }
133
+ /**
134
+ * REQUIRED
135
+ * Call a tool using HTTP streaming.
136
+ * Returns an async generator that yields chunks of data.
137
+ */
138
+ async *callToolStreaming(caller, toolName, toolArgs, toolCallTemplate) {
139
+ const provider = toolCallTemplate;
140
+ // TODO: Implement actual streaming call logic
141
+ // This would involve making an HTTP request and streaming the response in chunks
142
+ this._logInfo(`Calling streaming tool '${toolName}' with args: ${JSON.stringify(toolArgs)}`);
143
+ // Placeholder implementation
144
+ yield `Streaming response for tool: ${toolName}`;
145
+ }
146
+ /**
147
+ * REQUIRED
148
+ * Close all active connections and clear internal state.
149
+ */
150
+ async close() {
151
+ this._logInfo('Closing StreamableHttpCommunicationProtocol.');
152
+ this.oauthTokens.clear();
153
+ }
154
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@utcp/http",
3
+ "version": "1.0.0",
4
+ "description": "HTTP utilities for UTCP",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "license": "MPL-2.0",
9
+ "author": "UTCP Contributors",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/universal-tool-calling-protocol/typescript-utcp.git",
13
+ "directory": "packages/http"
14
+ },
15
+ "keywords": [
16
+ "utcp",
17
+ "universal-tool-calling-protocol",
18
+ "tools",
19
+ "api",
20
+ "typescript",
21
+ "tool calling",
22
+ "http",
23
+ "agent",
24
+ "ai",
25
+ "llm"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "exports": {
37
+ ".": {
38
+ "import": "./dist/index.js",
39
+ "require": "./dist/index.js",
40
+ "types": "./dist/index.d.ts"
41
+ }
42
+ },
43
+ "dependencies": {
44
+ "@utcp/sdk": "^1.0.0",
45
+ "axios": "^1.11.0",
46
+ "js-yaml": "^4.1.0"
47
+ },
48
+ "devDependencies": {
49
+ "bun-types": "latest",
50
+ "typescript": "^5.0.0",
51
+ "@types/bun": "latest"
52
+ }
53
+ }