@firststep-studio/sdk 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ UCPClient: () => UCPClient,
24
+ UCPError: () => UCPError,
25
+ classifyWithUCP: () => classifyWithUCP,
26
+ createAuthHeader: () => createAuthHeader,
27
+ createRequestSignature: () => createRequestSignature,
28
+ createUCPClient: () => createUCPClient,
29
+ isValidToken: () => isValidToken,
30
+ verifyRequestSignature: () => verifyRequestSignature
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/ucp/client.ts
35
+ var DEFAULT_TIMEOUT = 3e4;
36
+ var UCPClient = class _UCPClient {
37
+ constructor(config) {
38
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
39
+ this.classifierId = config.classifierId;
40
+ this.apiKey = config.apiKey;
41
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
42
+ this.headers = config.headers ?? {};
43
+ }
44
+ /**
45
+ * Create client from a full endpoint URL
46
+ *
47
+ * URL format: {baseUrl}/classifiers/{classifierId}
48
+ * Example: https://api.example.com/ucp/v1/classifiers/abc123
49
+ */
50
+ static fromEndpoint(endpoint, options) {
51
+ const url = new URL(endpoint);
52
+ const pathParts = url.pathname.split("/");
53
+ const classifiersIndex = pathParts.indexOf("classifiers");
54
+ if (classifiersIndex === -1 || classifiersIndex >= pathParts.length - 1) {
55
+ throw new Error(
56
+ `Invalid UCP endpoint URL: ${endpoint}. Expected format: {baseUrl}/classifiers/{classifierId}`
57
+ );
58
+ }
59
+ const classifierId = pathParts[classifiersIndex + 1];
60
+ const baseUrlPath = pathParts.slice(0, classifiersIndex).join("/");
61
+ const baseUrl = `${url.protocol}//${url.host}${baseUrlPath}`;
62
+ return new _UCPClient({
63
+ baseUrl,
64
+ classifierId,
65
+ apiKey: options?.apiKey,
66
+ timeout: options?.timeout
67
+ });
68
+ }
69
+ /**
70
+ * Get the classify endpoint URL
71
+ */
72
+ get classifyUrl() {
73
+ return `${this.baseUrl}/classifiers/${this.classifierId}/classify`;
74
+ }
75
+ /**
76
+ * Get the info endpoint URL
77
+ */
78
+ get infoUrl() {
79
+ return `${this.baseUrl}/classifiers/${this.classifierId}/info`;
80
+ }
81
+ /**
82
+ * Classify messages using UCP protocol
83
+ */
84
+ async classify(messages) {
85
+ const request = { messages };
86
+ const response = await this.fetch(
87
+ this.classifyUrl,
88
+ {
89
+ method: "POST",
90
+ body: JSON.stringify(request)
91
+ }
92
+ );
93
+ return {
94
+ classifierId: this.classifierId,
95
+ category: response.category,
96
+ level: response.level,
97
+ confidence: response.score / 100,
98
+ // Normalize to 0-1
99
+ reasoning: response.rationale,
100
+ raw: response
101
+ };
102
+ }
103
+ /**
104
+ * Classify ChatMessage array (convenience method)
105
+ * Converts ChatMessage to UCPMessage format
106
+ */
107
+ async classifyChat(messages) {
108
+ const ucpMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").map((m, index) => ({
109
+ id: `msg-${index}`,
110
+ role: m.role,
111
+ content: m.content,
112
+ timestamp: m.timestamp ? m.timestamp.getTime() : Date.now(),
113
+ metadata: m.metadata
114
+ }));
115
+ return this.classify(ucpMessages);
116
+ }
117
+ /**
118
+ * Get classifier info
119
+ */
120
+ async getInfo() {
121
+ return this.fetch(this.infoUrl, {
122
+ method: "GET"
123
+ });
124
+ }
125
+ /**
126
+ * Check if the classifier endpoint is reachable
127
+ */
128
+ async healthCheck() {
129
+ try {
130
+ const healthUrl = `${this.baseUrl}/health`;
131
+ const response = await fetch(healthUrl, {
132
+ method: "GET",
133
+ signal: AbortSignal.timeout(5e3)
134
+ });
135
+ return response.ok;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+ /**
141
+ * Internal fetch helper with error handling
142
+ */
143
+ async fetch(url, options) {
144
+ const headers = {
145
+ "Content-Type": "application/json",
146
+ ...this.headers
147
+ };
148
+ if (this.apiKey) {
149
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
150
+ }
151
+ const controller = new AbortController();
152
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
153
+ try {
154
+ const response = await fetch(url, {
155
+ ...options,
156
+ headers,
157
+ signal: controller.signal
158
+ });
159
+ clearTimeout(timeoutId);
160
+ if (!response.ok) {
161
+ const errorData = await response.json().catch(() => ({}));
162
+ const ucpError = errorData;
163
+ throw new UCPError(
164
+ ucpError.error?.code || `HTTP_${response.status}`,
165
+ ucpError.error?.message || `HTTP ${response.status}: ${response.statusText}`,
166
+ ucpError.error?.details
167
+ );
168
+ }
169
+ return response.json();
170
+ } catch (error) {
171
+ clearTimeout(timeoutId);
172
+ if (error instanceof UCPError) {
173
+ throw error;
174
+ }
175
+ if (error instanceof Error) {
176
+ if (error.name === "AbortError") {
177
+ throw new UCPError("TIMEOUT", `Request timed out after ${this.timeout}ms`);
178
+ }
179
+ throw new UCPError("NETWORK_ERROR", error.message);
180
+ }
181
+ throw new UCPError("UNKNOWN_ERROR", "An unknown error occurred");
182
+ }
183
+ }
184
+ };
185
+ var UCPError = class extends Error {
186
+ constructor(code, message, details) {
187
+ super(message);
188
+ this.code = code;
189
+ this.details = details;
190
+ this.name = "UCPError";
191
+ }
192
+ };
193
+ function createUCPClient(endpoint, options) {
194
+ return UCPClient.fromEndpoint(endpoint, options);
195
+ }
196
+ async function classifyWithUCP(endpoint, messages, options) {
197
+ const client = createUCPClient(endpoint, options);
198
+ return client.classifyChat(messages);
199
+ }
200
+
201
+ // src/auth.ts
202
+ var import_crypto = require("crypto");
203
+ var TOKEN_PREFIX = "fst_";
204
+ var TOKEN_LENGTH = 44;
205
+ function verifyRequestSignature(token, payload, signature) {
206
+ try {
207
+ const expected = (0, import_crypto.createHmac)("sha256", token).update(payload).digest("hex");
208
+ const expectedBuffer = Buffer.from(expected, "hex");
209
+ const signatureBuffer = Buffer.from(signature, "hex");
210
+ if (expectedBuffer.length !== signatureBuffer.length) {
211
+ return false;
212
+ }
213
+ return (0, import_crypto.timingSafeEqual)(expectedBuffer, signatureBuffer);
214
+ } catch {
215
+ return false;
216
+ }
217
+ }
218
+ function createRequestSignature(token, payload) {
219
+ return (0, import_crypto.createHmac)("sha256", token).update(payload).digest("hex");
220
+ }
221
+ function createAuthHeader(token) {
222
+ return `Bearer ${token}`;
223
+ }
224
+ function isValidToken(token) {
225
+ return typeof token === "string" && token.startsWith(TOKEN_PREFIX) && token.length === TOKEN_LENGTH;
226
+ }
227
+ // Annotate the CommonJS export names for ESM import in node:
228
+ 0 && (module.exports = {
229
+ UCPClient,
230
+ UCPError,
231
+ classifyWithUCP,
232
+ createAuthHeader,
233
+ createRequestSignature,
234
+ createUCPClient,
235
+ isValidToken,
236
+ verifyRequestSignature
237
+ });
238
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/ucp/client.ts","../src/auth.ts"],"sourcesContent":["/**\n * FirstStep SDK\n *\n * SDK for building protocol handlers that integrate with FirstStep Studio.\n *\n * @example\n * ```typescript\n * import { ProtocolHandler, ProtocolRequest, ProtocolResponse, ProtocolContext } from '@firststep-studio/sdk';\n *\n * class MyHandler implements ProtocolHandler {\n * async handleMessage(request: ProtocolRequest, context: ProtocolContext): Promise<ProtocolResponse> {\n * // Your implementation\n * }\n *\n * getCapabilities() {\n * return { streaming: false, formQuestions: false, knowledgeActions: false, integrations: false };\n * }\n * }\n * ```\n */\n\n// Core types\nexport type {\n ProtocolRequest,\n ProtocolResponse,\n SessionStatus,\n} from './types';\n\n// Form types\nexport type {\n ProtocolForm,\n ProtocolFormField,\n ProtocolFormOption,\n ProtocolFieldValidation,\n} from './types';\n\n// Streaming types\nexport type {\n ProtocolStreamChunk,\n ProtocolError,\n} from './types';\n\n// Handler interface\nexport type {\n ProtocolHandler,\n ProtocolCapabilities,\n HandlerInfo,\n} from './types';\n\n// Context types\nexport type {\n ProtocolContext,\n SessionContext,\n SessionState,\n ChatMessage,\n KnowledgeContext,\n KnowledgeResult,\n IntegrationContext,\n IntegrationResult,\n HelplineSearchOptions,\n HelplineResult,\n Helpline,\n LoggerContext,\n RoutingDecision,\n DeploymentInfo,\n ChatbotInfo,\n ClassifierConfig,\n} from './types';\n\n// ============================================\n// Platform Standard Data Types\n// ============================================\n\nexport type {\n // Form Data\n FormData,\n FormFieldValue,\n\n // Routing Logs\n RoutingLog,\n\n // Session Metadata\n SessionMetadata,\n\n // Analytics\n AnalyticsContext,\n InteractionEvent,\n InteractionEventType,\n\n // Form Schema (Protocol Registration)\n FormSchema,\n FormFieldDefinition,\n FormFieldType,\n\n // Protocol Registration\n ProtocolRegistration,\n} from './types';\n\n// ============================================\n// UCP (Universal Classification Protocol)\n// ============================================\n\n// UCP Types\nexport type {\n UCPMessage,\n UCPClassifyRequest,\n UCPClassifyResponse,\n UCPErrorResponse,\n UCPInfoResponse,\n UCPClientConfig,\n UCPEndpointConfig,\n ClassificationResult,\n} from './ucp';\n\n// UCP Client\nexport {\n UCPClient,\n UCPError,\n createUCPClient,\n classifyWithUCP,\n} from './ucp';\n\n// ============================================\n// Auth Utilities\n// ============================================\n\nexport {\n verifyRequestSignature,\n createRequestSignature,\n createAuthHeader,\n isValidToken,\n} from './auth';\n","/**\n * UCP (Universal Classification Protocol) Client\n *\n * Client for communicating with UCP-compliant classifier endpoints.\n * Supports both full configuration and simple endpoint URL.\n *\n * @example\n * ```typescript\n * // Simple usage with endpoint URL\n * const client = UCPClient.fromEndpoint('https://api.example.com/ucp/v1/classifiers/abc123');\n *\n * // With API key\n * const client = UCPClient.fromEndpoint(\n * 'https://api.example.com/ucp/v1/classifiers/abc123',\n * { apiKey: 'your-api-key' }\n * );\n *\n * // Classify messages\n * const result = await client.classify(messages);\n * ```\n */\n\nimport type {\n UCPMessage,\n UCPClassifyRequest,\n UCPClassifyResponse,\n UCPErrorResponse,\n UCPInfoResponse,\n UCPClientConfig,\n UCPEndpointConfig,\n ClassificationResult,\n} from './types';\nimport type { ChatMessage } from '../types';\n\n// Default timeout for requests\nconst DEFAULT_TIMEOUT = 30000;\n\n/**\n * UCP Client for classifier communication\n */\nexport class UCPClient {\n private baseUrl: string;\n private classifierId: string;\n private apiKey?: string;\n private timeout: number;\n private headers: Record<string, string>;\n\n constructor(config: UCPClientConfig) {\n // Normalize base URL (remove trailing slash)\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.classifierId = config.classifierId;\n this.apiKey = config.apiKey;\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.headers = config.headers ?? {};\n }\n\n /**\n * Create client from a full endpoint URL\n *\n * URL format: {baseUrl}/classifiers/{classifierId}\n * Example: https://api.example.com/ucp/v1/classifiers/abc123\n */\n static fromEndpoint(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n ): UCPClient {\n // Parse endpoint URL to extract baseUrl and classifierId\n const url = new URL(endpoint);\n const pathParts = url.pathname.split('/');\n\n // Find \"classifiers\" in path and get the ID after it\n const classifiersIndex = pathParts.indexOf('classifiers');\n if (classifiersIndex === -1 || classifiersIndex >= pathParts.length - 1) {\n throw new Error(\n `Invalid UCP endpoint URL: ${endpoint}. Expected format: {baseUrl}/classifiers/{classifierId}`\n );\n }\n\n const classifierId = pathParts[classifiersIndex + 1];\n const baseUrlPath = pathParts.slice(0, classifiersIndex).join('/');\n const baseUrl = `${url.protocol}//${url.host}${baseUrlPath}`;\n\n return new UCPClient({\n baseUrl,\n classifierId,\n apiKey: options?.apiKey,\n timeout: options?.timeout,\n });\n }\n\n /**\n * Get the classify endpoint URL\n */\n get classifyUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/classify`;\n }\n\n /**\n * Get the info endpoint URL\n */\n get infoUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/info`;\n }\n\n /**\n * Classify messages using UCP protocol\n */\n async classify(messages: UCPMessage[]): Promise<ClassificationResult> {\n const request: UCPClassifyRequest = { messages };\n\n const response = await this.fetch<UCPClassifyResponse>(\n this.classifyUrl,\n {\n method: 'POST',\n body: JSON.stringify(request),\n }\n );\n\n // Normalize to SDK format\n return {\n classifierId: this.classifierId,\n category: response.category,\n level: response.level,\n confidence: response.score / 100, // Normalize to 0-1\n reasoning: response.rationale,\n raw: response,\n };\n }\n\n /**\n * Classify ChatMessage array (convenience method)\n * Converts ChatMessage to UCPMessage format\n */\n async classifyChat(messages: ChatMessage[]): Promise<ClassificationResult> {\n const ucpMessages: UCPMessage[] = messages\n .filter(m => m.role === 'user' || m.role === 'assistant')\n .map((m, index) => ({\n id: `msg-${index}`,\n role: m.role as 'user' | 'assistant',\n content: m.content,\n timestamp: m.timestamp ? m.timestamp.getTime() : Date.now(),\n metadata: m.metadata,\n }));\n\n return this.classify(ucpMessages);\n }\n\n /**\n * Get classifier info\n */\n async getInfo(): Promise<UCPInfoResponse> {\n return this.fetch<UCPInfoResponse>(this.infoUrl, {\n method: 'GET',\n });\n }\n\n /**\n * Check if the classifier endpoint is reachable\n */\n async healthCheck(): Promise<boolean> {\n try {\n const healthUrl = `${this.baseUrl}/health`;\n const response = await fetch(healthUrl, {\n method: 'GET',\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Internal fetch helper with error handling\n */\n private async fetch<T>(url: string, options: RequestInit): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.headers,\n };\n\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const ucpError = errorData as UCPErrorResponse;\n\n throw new UCPError(\n ucpError.error?.code || `HTTP_${response.status}`,\n ucpError.error?.message || `HTTP ${response.status}: ${response.statusText}`,\n ucpError.error?.details\n );\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof UCPError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new UCPError('TIMEOUT', `Request timed out after ${this.timeout}ms`);\n }\n throw new UCPError('NETWORK_ERROR', error.message);\n }\n\n throw new UCPError('UNKNOWN_ERROR', 'An unknown error occurred');\n }\n }\n}\n\n/**\n * UCP-specific error class\n */\nexport class UCPError extends Error {\n constructor(\n public code: string,\n message: string,\n public details?: unknown\n ) {\n super(message);\n this.name = 'UCPError';\n }\n}\n\n// ============================================\n// Convenience Functions\n// ============================================\n\n/**\n * Create a UCP client from an endpoint URL\n */\nexport function createUCPClient(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n): UCPClient {\n return UCPClient.fromEndpoint(endpoint, options);\n}\n\n/**\n * Classify messages using a UCP endpoint\n * One-shot function for simple usage\n */\nexport async function classifyWithUCP(\n endpoint: string,\n messages: ChatMessage[],\n options?: { apiKey?: string; timeout?: number }\n): Promise<ClassificationResult> {\n const client = createUCPClient(endpoint, options);\n return client.classifyChat(messages);\n}\n","import { createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Verify an HMAC-SHA256 request signature.\n *\n * The handler server can use this to verify that incoming webhook\n * requests were signed by the FirstStep platform using the shared token.\n *\n * @param token - The API token (FIRSTSTEP_TOKEN)\n * @param payload - The raw request body string\n * @param signature - The signature from the X-FirstStep-Signature header\n * @returns true if the signature is valid\n *\n * @example\n * ```typescript\n * import { verifyRequestSignature } from '@firststep-studio/sdk';\n *\n * app.post('/webhook', (req, res) => {\n * const signature = req.headers['x-firststep-signature'] as string;\n * if (!verifyRequestSignature(process.env.FIRSTSTEP_TOKEN!, req.body, signature)) {\n * return res.status(401).send('Invalid signature');\n * }\n * // Process the request...\n * });\n * ```\n */\nexport function verifyRequestSignature(\n token: string,\n payload: string,\n signature: string\n): boolean {\n try {\n const expected = createHmac('sha256', token)\n .update(payload)\n .digest('hex');\n const expectedBuffer = Buffer.from(expected, 'hex');\n const signatureBuffer = Buffer.from(signature, 'hex');\n\n if (expectedBuffer.length !== signatureBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(expectedBuffer, signatureBuffer);\n } catch {\n return false;\n }\n}\n\n/**\n * Create an HMAC-SHA256 signature for a payload.\n *\n * Used by the platform to sign outgoing requests to handler servers.\n *\n * @param token - The API token\n * @param payload - The request body string to sign\n * @returns The hex-encoded HMAC signature\n */\nexport function createRequestSignature(\n token: string,\n payload: string\n): string {\n return createHmac('sha256', token).update(payload).digest('hex');\n}\n\n/**\n * Create an Authorization header value for API requests.\n *\n * @param token - The API token (fst_xxx)\n * @returns The header value, e.g. \"Bearer fst_xxx\"\n *\n * @example\n * ```typescript\n * import { createAuthHeader } from '@firststep-studio/sdk';\n *\n * const response = await fetch('https://api.firststep.ai/api/projects', {\n * headers: {\n * 'Authorization': createAuthHeader(process.env.FIRSTSTEP_TOKEN!),\n * },\n * });\n * ```\n */\nexport function createAuthHeader(token: string): string {\n return `Bearer ${token}`;\n}\n\n/**\n * Validate that a token has the correct format (fst_ prefix + 40 hex chars).\n *\n * @param token - The token string to validate\n * @returns true if the token matches the expected format\n */\nexport function isValidToken(token: string): boolean {\n return (\n typeof token === 'string' &&\n token.startsWith(TOKEN_PREFIX) &&\n token.length === TOKEN_LENGTH\n );\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,204 @@
1
+ // src/ucp/client.ts
2
+ var DEFAULT_TIMEOUT = 3e4;
3
+ var UCPClient = class _UCPClient {
4
+ constructor(config) {
5
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
6
+ this.classifierId = config.classifierId;
7
+ this.apiKey = config.apiKey;
8
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
9
+ this.headers = config.headers ?? {};
10
+ }
11
+ /**
12
+ * Create client from a full endpoint URL
13
+ *
14
+ * URL format: {baseUrl}/classifiers/{classifierId}
15
+ * Example: https://api.example.com/ucp/v1/classifiers/abc123
16
+ */
17
+ static fromEndpoint(endpoint, options) {
18
+ const url = new URL(endpoint);
19
+ const pathParts = url.pathname.split("/");
20
+ const classifiersIndex = pathParts.indexOf("classifiers");
21
+ if (classifiersIndex === -1 || classifiersIndex >= pathParts.length - 1) {
22
+ throw new Error(
23
+ `Invalid UCP endpoint URL: ${endpoint}. Expected format: {baseUrl}/classifiers/{classifierId}`
24
+ );
25
+ }
26
+ const classifierId = pathParts[classifiersIndex + 1];
27
+ const baseUrlPath = pathParts.slice(0, classifiersIndex).join("/");
28
+ const baseUrl = `${url.protocol}//${url.host}${baseUrlPath}`;
29
+ return new _UCPClient({
30
+ baseUrl,
31
+ classifierId,
32
+ apiKey: options?.apiKey,
33
+ timeout: options?.timeout
34
+ });
35
+ }
36
+ /**
37
+ * Get the classify endpoint URL
38
+ */
39
+ get classifyUrl() {
40
+ return `${this.baseUrl}/classifiers/${this.classifierId}/classify`;
41
+ }
42
+ /**
43
+ * Get the info endpoint URL
44
+ */
45
+ get infoUrl() {
46
+ return `${this.baseUrl}/classifiers/${this.classifierId}/info`;
47
+ }
48
+ /**
49
+ * Classify messages using UCP protocol
50
+ */
51
+ async classify(messages) {
52
+ const request = { messages };
53
+ const response = await this.fetch(
54
+ this.classifyUrl,
55
+ {
56
+ method: "POST",
57
+ body: JSON.stringify(request)
58
+ }
59
+ );
60
+ return {
61
+ classifierId: this.classifierId,
62
+ category: response.category,
63
+ level: response.level,
64
+ confidence: response.score / 100,
65
+ // Normalize to 0-1
66
+ reasoning: response.rationale,
67
+ raw: response
68
+ };
69
+ }
70
+ /**
71
+ * Classify ChatMessage array (convenience method)
72
+ * Converts ChatMessage to UCPMessage format
73
+ */
74
+ async classifyChat(messages) {
75
+ const ucpMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").map((m, index) => ({
76
+ id: `msg-${index}`,
77
+ role: m.role,
78
+ content: m.content,
79
+ timestamp: m.timestamp ? m.timestamp.getTime() : Date.now(),
80
+ metadata: m.metadata
81
+ }));
82
+ return this.classify(ucpMessages);
83
+ }
84
+ /**
85
+ * Get classifier info
86
+ */
87
+ async getInfo() {
88
+ return this.fetch(this.infoUrl, {
89
+ method: "GET"
90
+ });
91
+ }
92
+ /**
93
+ * Check if the classifier endpoint is reachable
94
+ */
95
+ async healthCheck() {
96
+ try {
97
+ const healthUrl = `${this.baseUrl}/health`;
98
+ const response = await fetch(healthUrl, {
99
+ method: "GET",
100
+ signal: AbortSignal.timeout(5e3)
101
+ });
102
+ return response.ok;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+ /**
108
+ * Internal fetch helper with error handling
109
+ */
110
+ async fetch(url, options) {
111
+ const headers = {
112
+ "Content-Type": "application/json",
113
+ ...this.headers
114
+ };
115
+ if (this.apiKey) {
116
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
117
+ }
118
+ const controller = new AbortController();
119
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
120
+ try {
121
+ const response = await fetch(url, {
122
+ ...options,
123
+ headers,
124
+ signal: controller.signal
125
+ });
126
+ clearTimeout(timeoutId);
127
+ if (!response.ok) {
128
+ const errorData = await response.json().catch(() => ({}));
129
+ const ucpError = errorData;
130
+ throw new UCPError(
131
+ ucpError.error?.code || `HTTP_${response.status}`,
132
+ ucpError.error?.message || `HTTP ${response.status}: ${response.statusText}`,
133
+ ucpError.error?.details
134
+ );
135
+ }
136
+ return response.json();
137
+ } catch (error) {
138
+ clearTimeout(timeoutId);
139
+ if (error instanceof UCPError) {
140
+ throw error;
141
+ }
142
+ if (error instanceof Error) {
143
+ if (error.name === "AbortError") {
144
+ throw new UCPError("TIMEOUT", `Request timed out after ${this.timeout}ms`);
145
+ }
146
+ throw new UCPError("NETWORK_ERROR", error.message);
147
+ }
148
+ throw new UCPError("UNKNOWN_ERROR", "An unknown error occurred");
149
+ }
150
+ }
151
+ };
152
+ var UCPError = class extends Error {
153
+ constructor(code, message, details) {
154
+ super(message);
155
+ this.code = code;
156
+ this.details = details;
157
+ this.name = "UCPError";
158
+ }
159
+ };
160
+ function createUCPClient(endpoint, options) {
161
+ return UCPClient.fromEndpoint(endpoint, options);
162
+ }
163
+ async function classifyWithUCP(endpoint, messages, options) {
164
+ const client = createUCPClient(endpoint, options);
165
+ return client.classifyChat(messages);
166
+ }
167
+
168
+ // src/auth.ts
169
+ import { createHmac, timingSafeEqual } from "crypto";
170
+ var TOKEN_PREFIX = "fst_";
171
+ var TOKEN_LENGTH = 44;
172
+ function verifyRequestSignature(token, payload, signature) {
173
+ try {
174
+ const expected = createHmac("sha256", token).update(payload).digest("hex");
175
+ const expectedBuffer = Buffer.from(expected, "hex");
176
+ const signatureBuffer = Buffer.from(signature, "hex");
177
+ if (expectedBuffer.length !== signatureBuffer.length) {
178
+ return false;
179
+ }
180
+ return timingSafeEqual(expectedBuffer, signatureBuffer);
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+ function createRequestSignature(token, payload) {
186
+ return createHmac("sha256", token).update(payload).digest("hex");
187
+ }
188
+ function createAuthHeader(token) {
189
+ return `Bearer ${token}`;
190
+ }
191
+ function isValidToken(token) {
192
+ return typeof token === "string" && token.startsWith(TOKEN_PREFIX) && token.length === TOKEN_LENGTH;
193
+ }
194
+ export {
195
+ UCPClient,
196
+ UCPError,
197
+ classifyWithUCP,
198
+ createAuthHeader,
199
+ createRequestSignature,
200
+ createUCPClient,
201
+ isValidToken,
202
+ verifyRequestSignature
203
+ };
204
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ucp/client.ts","../src/auth.ts"],"sourcesContent":["/**\n * UCP (Universal Classification Protocol) Client\n *\n * Client for communicating with UCP-compliant classifier endpoints.\n * Supports both full configuration and simple endpoint URL.\n *\n * @example\n * ```typescript\n * // Simple usage with endpoint URL\n * const client = UCPClient.fromEndpoint('https://api.example.com/ucp/v1/classifiers/abc123');\n *\n * // With API key\n * const client = UCPClient.fromEndpoint(\n * 'https://api.example.com/ucp/v1/classifiers/abc123',\n * { apiKey: 'your-api-key' }\n * );\n *\n * // Classify messages\n * const result = await client.classify(messages);\n * ```\n */\n\nimport type {\n UCPMessage,\n UCPClassifyRequest,\n UCPClassifyResponse,\n UCPErrorResponse,\n UCPInfoResponse,\n UCPClientConfig,\n UCPEndpointConfig,\n ClassificationResult,\n} from './types';\nimport type { ChatMessage } from '../types';\n\n// Default timeout for requests\nconst DEFAULT_TIMEOUT = 30000;\n\n/**\n * UCP Client for classifier communication\n */\nexport class UCPClient {\n private baseUrl: string;\n private classifierId: string;\n private apiKey?: string;\n private timeout: number;\n private headers: Record<string, string>;\n\n constructor(config: UCPClientConfig) {\n // Normalize base URL (remove trailing slash)\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.classifierId = config.classifierId;\n this.apiKey = config.apiKey;\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.headers = config.headers ?? {};\n }\n\n /**\n * Create client from a full endpoint URL\n *\n * URL format: {baseUrl}/classifiers/{classifierId}\n * Example: https://api.example.com/ucp/v1/classifiers/abc123\n */\n static fromEndpoint(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n ): UCPClient {\n // Parse endpoint URL to extract baseUrl and classifierId\n const url = new URL(endpoint);\n const pathParts = url.pathname.split('/');\n\n // Find \"classifiers\" in path and get the ID after it\n const classifiersIndex = pathParts.indexOf('classifiers');\n if (classifiersIndex === -1 || classifiersIndex >= pathParts.length - 1) {\n throw new Error(\n `Invalid UCP endpoint URL: ${endpoint}. Expected format: {baseUrl}/classifiers/{classifierId}`\n );\n }\n\n const classifierId = pathParts[classifiersIndex + 1];\n const baseUrlPath = pathParts.slice(0, classifiersIndex).join('/');\n const baseUrl = `${url.protocol}//${url.host}${baseUrlPath}`;\n\n return new UCPClient({\n baseUrl,\n classifierId,\n apiKey: options?.apiKey,\n timeout: options?.timeout,\n });\n }\n\n /**\n * Get the classify endpoint URL\n */\n get classifyUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/classify`;\n }\n\n /**\n * Get the info endpoint URL\n */\n get infoUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/info`;\n }\n\n /**\n * Classify messages using UCP protocol\n */\n async classify(messages: UCPMessage[]): Promise<ClassificationResult> {\n const request: UCPClassifyRequest = { messages };\n\n const response = await this.fetch<UCPClassifyResponse>(\n this.classifyUrl,\n {\n method: 'POST',\n body: JSON.stringify(request),\n }\n );\n\n // Normalize to SDK format\n return {\n classifierId: this.classifierId,\n category: response.category,\n level: response.level,\n confidence: response.score / 100, // Normalize to 0-1\n reasoning: response.rationale,\n raw: response,\n };\n }\n\n /**\n * Classify ChatMessage array (convenience method)\n * Converts ChatMessage to UCPMessage format\n */\n async classifyChat(messages: ChatMessage[]): Promise<ClassificationResult> {\n const ucpMessages: UCPMessage[] = messages\n .filter(m => m.role === 'user' || m.role === 'assistant')\n .map((m, index) => ({\n id: `msg-${index}`,\n role: m.role as 'user' | 'assistant',\n content: m.content,\n timestamp: m.timestamp ? m.timestamp.getTime() : Date.now(),\n metadata: m.metadata,\n }));\n\n return this.classify(ucpMessages);\n }\n\n /**\n * Get classifier info\n */\n async getInfo(): Promise<UCPInfoResponse> {\n return this.fetch<UCPInfoResponse>(this.infoUrl, {\n method: 'GET',\n });\n }\n\n /**\n * Check if the classifier endpoint is reachable\n */\n async healthCheck(): Promise<boolean> {\n try {\n const healthUrl = `${this.baseUrl}/health`;\n const response = await fetch(healthUrl, {\n method: 'GET',\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Internal fetch helper with error handling\n */\n private async fetch<T>(url: string, options: RequestInit): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.headers,\n };\n\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const ucpError = errorData as UCPErrorResponse;\n\n throw new UCPError(\n ucpError.error?.code || `HTTP_${response.status}`,\n ucpError.error?.message || `HTTP ${response.status}: ${response.statusText}`,\n ucpError.error?.details\n );\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof UCPError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new UCPError('TIMEOUT', `Request timed out after ${this.timeout}ms`);\n }\n throw new UCPError('NETWORK_ERROR', error.message);\n }\n\n throw new UCPError('UNKNOWN_ERROR', 'An unknown error occurred');\n }\n }\n}\n\n/**\n * UCP-specific error class\n */\nexport class UCPError extends Error {\n constructor(\n public code: string,\n message: string,\n public details?: unknown\n ) {\n super(message);\n this.name = 'UCPError';\n }\n}\n\n// ============================================\n// Convenience Functions\n// ============================================\n\n/**\n * Create a UCP client from an endpoint URL\n */\nexport function createUCPClient(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n): UCPClient {\n return UCPClient.fromEndpoint(endpoint, options);\n}\n\n/**\n * Classify messages using a UCP endpoint\n * One-shot function for simple usage\n */\nexport async function classifyWithUCP(\n endpoint: string,\n messages: ChatMessage[],\n options?: { apiKey?: string; timeout?: number }\n): Promise<ClassificationResult> {\n const client = createUCPClient(endpoint, options);\n return client.classifyChat(messages);\n}\n","import { createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Verify an HMAC-SHA256 request signature.\n *\n * The handler server can use this to verify that incoming webhook\n * requests were signed by the FirstStep platform using the shared token.\n *\n * @param token - The API token (FIRSTSTEP_TOKEN)\n * @param payload - The raw request body string\n * @param signature - The signature from the X-FirstStep-Signature header\n * @returns true if the signature is valid\n *\n * @example\n * ```typescript\n * import { verifyRequestSignature } from '@firststep-studio/sdk';\n *\n * app.post('/webhook', (req, res) => {\n * const signature = req.headers['x-firststep-signature'] as string;\n * if (!verifyRequestSignature(process.env.FIRSTSTEP_TOKEN!, req.body, signature)) {\n * return res.status(401).send('Invalid signature');\n * }\n * // Process the request...\n * });\n * ```\n */\nexport function verifyRequestSignature(\n token: string,\n payload: string,\n signature: string\n): boolean {\n try {\n const expected = createHmac('sha256', token)\n .update(payload)\n .digest('hex');\n const expectedBuffer = Buffer.from(expected, 'hex');\n const signatureBuffer = Buffer.from(signature, 'hex');\n\n if (expectedBuffer.length !== signatureBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(expectedBuffer, signatureBuffer);\n } catch {\n return false;\n }\n}\n\n/**\n * Create an HMAC-SHA256 signature for a payload.\n *\n * Used by the platform to sign outgoing requests to handler servers.\n *\n * @param token - The API token\n * @param payload - The request body string to sign\n * @returns The hex-encoded HMAC signature\n */\nexport function createRequestSignature(\n token: string,\n payload: string\n): string {\n return createHmac('sha256', token).update(payload).digest('hex');\n}\n\n/**\n * Create an Authorization header value for API requests.\n *\n * @param token - The API token (fst_xxx)\n * @returns The header value, e.g. \"Bearer fst_xxx\"\n *\n * @example\n * ```typescript\n * import { createAuthHeader } from '@firststep-studio/sdk';\n *\n * const response = await fetch('https://api.firststep.ai/api/projects', {\n * headers: {\n * 'Authorization': createAuthHeader(process.env.FIRSTSTEP_TOKEN!),\n * },\n * });\n * ```\n */\nexport function createAuthHeader(token: string): string {\n return `Bearer ${token}`;\n}\n\n/**\n * Validate that a token has the correct format (fst_ prefix + 40 hex chars).\n *\n * @param token - The token string to validate\n * @returns true if the token matches the expected format\n */\nexport function isValidToken(token: string): boolean {\n return (\n typeof token === 'string' &&\n token.startsWith(TOKEN_PREFIX) &&\n token.length === TOKEN_LENGTH\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,77 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { h as ProtocolHandler } from './types-Bm98aHcd.mjs';
3
+
4
+ /**
5
+ * FirstStep SDK Server
6
+ *
7
+ * Standalone HTTP server for protocol handlers.
8
+ * Zero external dependencies, uses Node.js built-in `http` module.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createServer } from '@firststep-studio/sdk/server';
13
+ * import type { ProtocolHandler } from '@firststep-studio/sdk';
14
+ *
15
+ * const handler: ProtocolHandler = {
16
+ * async handleMessage(request, context) {
17
+ * return {
18
+ * message: 'Hello from my handler!',
19
+ * sessionId: request.sessionId || 'new',
20
+ * agentId: 'main',
21
+ * sessionStatus: 'active',
22
+ * };
23
+ * },
24
+ * getCapabilities() {
25
+ * return { streaming: false, formQuestions: false, knowledgeActions: false, integrations: false };
26
+ * },
27
+ * };
28
+ *
29
+ * const server = createServer(handler, {
30
+ * token: process.env.FIRSTSTEP_TOKEN!,
31
+ * port: 3001,
32
+ * });
33
+ *
34
+ * server.start();
35
+ * ```
36
+ */
37
+
38
+ interface ServerConfig {
39
+ /** API token (FIRSTSTEP_TOKEN). Used for request signature verification. */
40
+ token: string;
41
+ /** Port to listen on. Defaults to 3001, or the PORT env variable. */
42
+ port?: number;
43
+ /** Host to bind to. Defaults to '0.0.0.0'. */
44
+ host?: string;
45
+ /** Skip signature verification (for local development only). */
46
+ skipSignatureVerification?: boolean;
47
+ }
48
+ interface FirstStepServer {
49
+ /** Start the server */
50
+ start(): Promise<void>;
51
+ /** Stop the server gracefully */
52
+ stop(): Promise<void>;
53
+ /**
54
+ * Get the request handler function.
55
+ * Use this to integrate with Express, Fastify, or other frameworks.
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Express
60
+ * const server = createServer(handler, { token: '...' });
61
+ * app.post('/handshake', server.getRequestHandler());
62
+ * app.post('/message', server.getRequestHandler());
63
+ * ```
64
+ */
65
+ getRequestHandler(): (req: IncomingMessage, res: ServerResponse) => void;
66
+ }
67
+ /**
68
+ * Create a standalone HTTP server for a protocol handler.
69
+ *
70
+ * The server exposes three endpoints:
71
+ * - `GET /health` - Health check (always 200)
72
+ * - `POST /handshake` - Returns handler capabilities (signature verified)
73
+ * - `POST /message` - Handles a chat message (signature verified)
74
+ */
75
+ declare function createServer(handler: ProtocolHandler, config: ServerConfig): FirstStepServer;
76
+
77
+ export { type FirstStepServer, type ServerConfig, createServer };
@@ -0,0 +1,77 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { h as ProtocolHandler } from './types-Bm98aHcd.js';
3
+
4
+ /**
5
+ * FirstStep SDK Server
6
+ *
7
+ * Standalone HTTP server for protocol handlers.
8
+ * Zero external dependencies, uses Node.js built-in `http` module.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createServer } from '@firststep-studio/sdk/server';
13
+ * import type { ProtocolHandler } from '@firststep-studio/sdk';
14
+ *
15
+ * const handler: ProtocolHandler = {
16
+ * async handleMessage(request, context) {
17
+ * return {
18
+ * message: 'Hello from my handler!',
19
+ * sessionId: request.sessionId || 'new',
20
+ * agentId: 'main',
21
+ * sessionStatus: 'active',
22
+ * };
23
+ * },
24
+ * getCapabilities() {
25
+ * return { streaming: false, formQuestions: false, knowledgeActions: false, integrations: false };
26
+ * },
27
+ * };
28
+ *
29
+ * const server = createServer(handler, {
30
+ * token: process.env.FIRSTSTEP_TOKEN!,
31
+ * port: 3001,
32
+ * });
33
+ *
34
+ * server.start();
35
+ * ```
36
+ */
37
+
38
+ interface ServerConfig {
39
+ /** API token (FIRSTSTEP_TOKEN). Used for request signature verification. */
40
+ token: string;
41
+ /** Port to listen on. Defaults to 3001, or the PORT env variable. */
42
+ port?: number;
43
+ /** Host to bind to. Defaults to '0.0.0.0'. */
44
+ host?: string;
45
+ /** Skip signature verification (for local development only). */
46
+ skipSignatureVerification?: boolean;
47
+ }
48
+ interface FirstStepServer {
49
+ /** Start the server */
50
+ start(): Promise<void>;
51
+ /** Stop the server gracefully */
52
+ stop(): Promise<void>;
53
+ /**
54
+ * Get the request handler function.
55
+ * Use this to integrate with Express, Fastify, or other frameworks.
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Express
60
+ * const server = createServer(handler, { token: '...' });
61
+ * app.post('/handshake', server.getRequestHandler());
62
+ * app.post('/message', server.getRequestHandler());
63
+ * ```
64
+ */
65
+ getRequestHandler(): (req: IncomingMessage, res: ServerResponse) => void;
66
+ }
67
+ /**
68
+ * Create a standalone HTTP server for a protocol handler.
69
+ *
70
+ * The server exposes three endpoints:
71
+ * - `GET /health` - Health check (always 200)
72
+ * - `POST /handshake` - Returns handler capabilities (signature verified)
73
+ * - `POST /message` - Handles a chat message (signature verified)
74
+ */
75
+ declare function createServer(handler: ProtocolHandler, config: ServerConfig): FirstStepServer;
76
+
77
+ export { type FirstStepServer, type ServerConfig, createServer };