@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/llms.txt ADDED
@@ -0,0 +1,597 @@
1
+ # @firststep-studio/sdk
2
+
3
+ SDK for building protocol handlers that integrate with FirstStep Studio.
4
+ A protocol handler is a standalone HTTP server that receives chat messages from FirstStep Studio and returns responses.
5
+
6
+ ## Quick Start
7
+
8
+ ```bash
9
+ npx create-firststep-app
10
+ cd my-handler
11
+ npm run dev
12
+ ```
13
+
14
+ This scaffolds a working echo bot with streaming, forms, and tunnel support.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @firststep-studio/sdk
20
+ ```
21
+
22
+ ## Architecture
23
+
24
+ FirstStep Studio sends chat messages to your handler via HTTP. Your handler processes them and returns responses.
25
+
26
+ ```
27
+ User -> FirstStep Studio -> POST /message -> Your Handler -> ProtocolResponse
28
+ <- JSON response <-
29
+ ```
30
+
31
+ The SDK provides:
32
+ 1. `ProtocolHandler` interface: what you implement
33
+ 2. `createServer()`: standalone HTTP server that handles routing, auth, and SSE streaming
34
+ 3. Type definitions for all request/response shapes
35
+ 4. Auth utilities for HMAC-SHA256 signature verification
36
+ 5. UCP client for classifier integration
37
+
38
+ ## Endpoints (handled by createServer)
39
+
40
+ - `GET /health` - always returns 200
41
+ - `POST /handshake` - returns handler capabilities (signature verified)
42
+ - `POST /message` - handles a chat message, returns ProtocolResponse (signature verified)
43
+ - `POST /message/stream` - handles a chat message via SSE stream (signature verified)
44
+
45
+ ## Core Interface: ProtocolHandler
46
+
47
+ This is the only interface you need to implement.
48
+
49
+ ```typescript
50
+ import { createServer } from '@firststep-studio/sdk/server';
51
+ import type {
52
+ ProtocolHandler,
53
+ ProtocolRequest,
54
+ ProtocolResponse,
55
+ ProtocolContext,
56
+ ProtocolCapabilities,
57
+ ProtocolStreamChunk,
58
+ HandlerInfo,
59
+ } from '@firststep-studio/sdk';
60
+
61
+ const handler: ProtocolHandler = {
62
+ // REQUIRED: handle a chat message
63
+ async handleMessage(
64
+ request: ProtocolRequest,
65
+ context: ProtocolContext
66
+ ): Promise<ProtocolResponse> {
67
+ // ...
68
+ },
69
+
70
+ // OPTIONAL: handle streaming responses
71
+ async *handleStream(
72
+ request: ProtocolRequest,
73
+ context: ProtocolContext
74
+ ): AsyncGenerator<ProtocolStreamChunk> {
75
+ // ...
76
+ },
77
+
78
+ // REQUIRED: declare capabilities
79
+ getCapabilities(): ProtocolCapabilities {
80
+ return {
81
+ streaming: true,
82
+ formQuestions: true,
83
+ knowledgeActions: false,
84
+ integrations: false,
85
+ };
86
+ },
87
+
88
+ // OPTIONAL: handler metadata for handshake auto-fill
89
+ getHandlerInfo(): HandlerInfo {
90
+ return {
91
+ name: 'My Handler',
92
+ description: 'Does something useful.',
93
+ };
94
+ },
95
+ };
96
+ ```
97
+
98
+ ## ProtocolRequest
99
+
100
+ Sent by FirstStep Studio to your handler.
101
+
102
+ ```typescript
103
+ interface ProtocolRequest {
104
+ message?: string; // User message. If absent, this is session init.
105
+ sessionId?: string; // Session ID. New session if absent.
106
+ deploymentSlug: string; // Deployment identifier.
107
+ metadata?: Record<string, unknown>;
108
+ }
109
+ ```
110
+
111
+ When `message` is undefined or empty, the handler should return an initial greeting/welcome message.
112
+
113
+ ## ProtocolResponse
114
+
115
+ What your handler returns.
116
+
117
+ ```typescript
118
+ interface ProtocolResponse {
119
+ message: string; // REQUIRED: response text
120
+ sessionId: string; // REQUIRED: session ID
121
+ agentId: string; // REQUIRED: current agent identifier
122
+ sessionStatus: SessionStatus; // REQUIRED: 'active' | 'completed' | 'error'
123
+ form?: ProtocolForm; // OPTIONAL: render a form in the UI
124
+ metadata?: Record<string, unknown>; // OPTIONAL: arbitrary metadata
125
+ }
126
+ ```
127
+
128
+ ### SessionStatus values
129
+
130
+ - `'active'` - conversation continues
131
+ - `'completed'` - session is done, no more messages expected
132
+ - `'error'` - something went wrong
133
+
134
+ ## Streaming
135
+
136
+ If your handler declares `streaming: true` in capabilities and implements `handleStream`, the server exposes `/message/stream` as an SSE endpoint.
137
+
138
+ Yield chunks one at a time:
139
+
140
+ ```typescript
141
+ async *handleStream(request, context) {
142
+ // Stream text word by word
143
+ const words = 'Hello world from streaming'.split(' ');
144
+ for (const word of words) {
145
+ yield { type: 'text', content: word + ' ' };
146
+ await new Promise(r => setTimeout(r, 50));
147
+ }
148
+
149
+ // Optionally yield metadata
150
+ yield { type: 'metadata', content: { processingTime: 123 } };
151
+
152
+ // Optionally yield a form
153
+ yield { type: 'form', content: { type: 'question', fields: [...] } };
154
+
155
+ // Always yield status last
156
+ yield { type: 'status', content: 'active' };
157
+ }
158
+ ```
159
+
160
+ ### ProtocolStreamChunk
161
+
162
+ ```typescript
163
+ interface ProtocolStreamChunk {
164
+ type: 'text' | 'form' | 'status' | 'error' | 'metadata';
165
+ content: string | ProtocolForm | SessionStatus | ProtocolError | Record<string, unknown>;
166
+ }
167
+ ```
168
+
169
+ SSE event format sent to the client:
170
+ ```
171
+ event: text
172
+ data: {"type":"text","content":"Hello ","sessionId":"abc"}
173
+
174
+ event: status
175
+ data: {"type":"status","content":"active","sessionId":"abc"}
176
+
177
+ event: done
178
+ data: {"sessionId":"abc"}
179
+ ```
180
+
181
+ If `handleStream` is not implemented, the server falls back to `handleMessage` and sends the full response as a single SSE burst.
182
+
183
+ ## Forms
184
+
185
+ Forms let your handler render structured input fields in the FirstStep Studio UI.
186
+
187
+ ```typescript
188
+ interface ProtocolForm {
189
+ type: 'question' | 'closing' | 'custom';
190
+ questionId?: string;
191
+ fields: ProtocolFormField[];
192
+ metadata?: Record<string, unknown>;
193
+ }
194
+
195
+ interface ProtocolFormField {
196
+ id: string;
197
+ type: 'text' | 'textarea' | 'select' | 'multiselect' | 'date' | 'number' | 'email' | 'phone';
198
+ label: string;
199
+ placeholder?: string;
200
+ options?: ProtocolFormOption[]; // required for select/multiselect
201
+ required?: boolean;
202
+ validation?: ProtocolFieldValidation;
203
+ }
204
+
205
+ interface ProtocolFormOption {
206
+ value: string;
207
+ label: string;
208
+ }
209
+
210
+ interface ProtocolFieldValidation {
211
+ pattern?: string;
212
+ minLength?: number;
213
+ maxLength?: number;
214
+ min?: number;
215
+ max?: number;
216
+ message?: string; // custom validation error message
217
+ }
218
+ ```
219
+
220
+ ### Form example
221
+
222
+ ```typescript
223
+ return {
224
+ message: 'Please fill out this form:',
225
+ sessionId,
226
+ agentId: 'main',
227
+ sessionStatus: 'active',
228
+ form: {
229
+ type: 'question',
230
+ questionId: 'contact-info',
231
+ fields: [
232
+ {
233
+ id: 'name',
234
+ type: 'text',
235
+ label: 'Your Name',
236
+ placeholder: 'Enter your name',
237
+ required: true,
238
+ },
239
+ {
240
+ id: 'email',
241
+ type: 'email',
242
+ label: 'Email Address',
243
+ required: true,
244
+ validation: {
245
+ pattern: '^[^@]+@[^@]+\\.[^@]+$',
246
+ message: 'Please enter a valid email.',
247
+ },
248
+ },
249
+ {
250
+ id: 'topic',
251
+ type: 'select',
252
+ label: 'What is this about?',
253
+ options: [
254
+ { value: 'support', label: 'Support' },
255
+ { value: 'sales', label: 'Sales' },
256
+ { value: 'feedback', label: 'Feedback' },
257
+ ],
258
+ required: true,
259
+ },
260
+ ],
261
+ },
262
+ };
263
+ ```
264
+
265
+ ## createServer
266
+
267
+ Creates a standalone HTTP server. Zero external dependencies (uses Node.js built-in `http` module).
268
+
269
+ ```typescript
270
+ import { createServer } from '@firststep-studio/sdk/server';
271
+
272
+ const server = createServer(handler, {
273
+ token: process.env.FIRSTSTEP_TOKEN || '',
274
+ port: 4001, // default: PORT env or 3001
275
+ host: '0.0.0.0', // default: '0.0.0.0'
276
+ skipSignatureVerification: false, // set true for local dev without token
277
+ });
278
+
279
+ server.start(); // returns Promise<void>
280
+ server.stop(); // graceful shutdown, returns Promise<void>
281
+ ```
282
+
283
+ ### ServerConfig
284
+
285
+ ```typescript
286
+ interface ServerConfig {
287
+ token: string; // FIRSTSTEP_TOKEN for HMAC verification
288
+ port?: number; // defaults to PORT env or 3001
289
+ host?: string; // defaults to '0.0.0.0'
290
+ skipSignatureVerification?: boolean; // for local dev only
291
+ }
292
+ ```
293
+
294
+ ### Integrating with Express/Fastify
295
+
296
+ If you already have an HTTP framework:
297
+
298
+ ```typescript
299
+ const server = createServer(handler, { token: '...' });
300
+ const requestHandler = server.getRequestHandler();
301
+
302
+ // Express
303
+ app.use('/firststep', requestHandler);
304
+
305
+ // Fastify
306
+ fastify.all('/firststep/*', (req, reply) => {
307
+ requestHandler(req.raw, reply.raw);
308
+ });
309
+ ```
310
+
311
+ ## Authentication
312
+
313
+ All requests from FirstStep Studio include an `X-FirstStep-Signature` header containing an HMAC-SHA256 signature of the request body, signed with your API token.
314
+
315
+ The `createServer` handles verification automatically. If you need manual verification:
316
+
317
+ ```typescript
318
+ import { verifyRequestSignature, createRequestSignature, isValidToken } from '@firststep-studio/sdk';
319
+
320
+ // Verify incoming request
321
+ const isValid = verifyRequestSignature(token, rawBody, signatureHeader);
322
+
323
+ // Create a signature (for testing)
324
+ const signature = createRequestSignature(token, JSON.stringify(payload));
325
+
326
+ // Validate token format (fst_ + 40 hex chars)
327
+ const valid = isValidToken('fst_abc123...');
328
+ ```
329
+
330
+ ### Token format
331
+
332
+ Tokens follow the pattern: `fst_` prefix + 40 hexadecimal characters (total 44 characters).
333
+
334
+ ## ProtocolContext
335
+
336
+ Passed to your handler methods. Provides access to FirstStep Studio platform features.
337
+
338
+ When running standalone (via createServer), context methods are no-op stubs. When called from the FirstStep platform, they connect to real services.
339
+
340
+ ```typescript
341
+ interface ProtocolContext {
342
+ session: SessionContext; // session state, history, form data
343
+ knowledge: KnowledgeContext; // knowledge base queries
344
+ integrations: IntegrationContext; // third-party integrations
345
+ analytics: AnalyticsContext; // event tracking
346
+ logger: LoggerContext; // structured logging
347
+ deployment: DeploymentInfo; // deployment metadata
348
+ chatbot?: ChatbotInfo; // chatbot config (GuidedForm only)
349
+ }
350
+ ```
351
+
352
+ ### SessionContext
353
+
354
+ ```typescript
355
+ interface SessionContext {
356
+ sessionId: string;
357
+ getState(): Promise<SessionState>;
358
+ updateState(updates: Partial<SessionState>): Promise<void>;
359
+ getHistory(): Promise<ChatMessage[]>;
360
+ saveMessage(message: ChatMessage): Promise<void>;
361
+ complete(): Promise<void>;
362
+ getFormData(): Promise<FormData>;
363
+ updateFormField(fieldId: string, value: FormFieldValue): Promise<void>;
364
+ updateFormData(fields: Record<string, FormFieldValue>): Promise<void>;
365
+ }
366
+ ```
367
+
368
+ ### KnowledgeContext
369
+
370
+ ```typescript
371
+ interface KnowledgeContext {
372
+ queryDatabase(knowledgeId: string, query: string): Promise<KnowledgeResult>;
373
+ searchPages(knowledgeId: string, query: string): Promise<KnowledgeResult>;
374
+ }
375
+
376
+ interface KnowledgeResult {
377
+ success: boolean;
378
+ records?: Record<string, unknown>[];
379
+ error?: string;
380
+ }
381
+ ```
382
+
383
+ ### AnalyticsContext
384
+
385
+ ```typescript
386
+ interface AnalyticsContext {
387
+ logActionExecuted(type: 'knowledge' | 'helpline' | 'integration', actionId: string, metadata?: Record<string, unknown>): void;
388
+ logInteraction(event: InteractionEvent): void;
389
+ logCustomEvent(eventName: string, data?: Record<string, unknown>): void;
390
+ }
391
+
392
+ type InteractionEventType =
393
+ | 'helpline_sms_click'
394
+ | 'helpline_call_click'
395
+ | 'helpline_website_click'
396
+ | 'knowledge_link_click'
397
+ | 'announcement_cta_click'
398
+ | 'card_click'
399
+ | 'form_submit'
400
+ | 'custom';
401
+ ```
402
+
403
+ ### LoggerContext
404
+
405
+ ```typescript
406
+ interface LoggerContext {
407
+ debug(message: string, data?: Record<string, unknown>): void;
408
+ info(message: string, data?: Record<string, unknown>): void;
409
+ warn(message: string, data?: Record<string, unknown>): void;
410
+ error(message: string, data?: Record<string, unknown>): void;
411
+ logRouting(decision: RoutingDecision): void;
412
+ logToolUse(tool: string, params: Record<string, unknown>, result: unknown): void;
413
+ }
414
+ ```
415
+
416
+ ## UCP (Universal Classification Protocol)
417
+
418
+ Optional module for integrating with classifier services.
419
+
420
+ ```typescript
421
+ import { createUCPClient, classifyWithUCP, UCPClient } from '@firststep-studio/sdk';
422
+
423
+ // One-shot classification
424
+ const result = await classifyWithUCP(
425
+ 'https://api.example.com/ucp/v1/classifiers/abc123',
426
+ chatMessages,
427
+ { apiKey: 'optional-key' }
428
+ );
429
+ // result: { classifierId, category, level, confidence, reasoning, raw }
430
+
431
+ // Reusable client
432
+ const client = createUCPClient('https://api.example.com/ucp/v1/classifiers/abc123');
433
+ const result = await client.classify(ucpMessages);
434
+ const info = await client.getInfo();
435
+ const healthy = await client.healthCheck();
436
+ ```
437
+
438
+ ### UCP Types
439
+
440
+ ```typescript
441
+ interface UCPMessage {
442
+ id: string;
443
+ role: 'user' | 'assistant';
444
+ content: string;
445
+ timestamp: number; // Unix ms
446
+ metadata?: Record<string, unknown>;
447
+ }
448
+
449
+ interface UCPClassifyResponse {
450
+ category: string;
451
+ level: string;
452
+ score: number; // 0-100
453
+ rationale: string;
454
+ metadata?: { executionTime?: number; timestamp?: number };
455
+ allCategoryResults?: Array<{ category: string; level: string; score: number }>;
456
+ }
457
+
458
+ interface ClassificationResult {
459
+ classifierId: string;
460
+ category: string;
461
+ level: string;
462
+ confidence: number; // 0-1 (normalized from score)
463
+ reasoning?: string;
464
+ raw?: UCPClassifyResponse;
465
+ }
466
+ ```
467
+
468
+ ## Complete Example: Echo Handler
469
+
470
+ ```typescript
471
+ import { createServer } from '@firststep-studio/sdk/server';
472
+ import type {
473
+ ProtocolHandler,
474
+ ProtocolRequest,
475
+ ProtocolResponse,
476
+ ProtocolContext,
477
+ ProtocolCapabilities,
478
+ ProtocolStreamChunk,
479
+ HandlerInfo,
480
+ } from '@firststep-studio/sdk';
481
+
482
+ const handler: ProtocolHandler = {
483
+ async handleMessage(request, context): Promise<ProtocolResponse> {
484
+ const sessionId = request.sessionId || `session-${Date.now()}`;
485
+ const userMessage = request.message?.trim();
486
+
487
+ // Session init
488
+ if (!userMessage) {
489
+ return {
490
+ message: 'Hello! Send me a message.',
491
+ sessionId,
492
+ agentId: 'main',
493
+ sessionStatus: 'active',
494
+ };
495
+ }
496
+
497
+ // Echo back
498
+ return {
499
+ message: `Echo: ${userMessage}`,
500
+ sessionId,
501
+ agentId: 'main',
502
+ sessionStatus: 'active',
503
+ };
504
+ },
505
+
506
+ async *handleStream(request, context): AsyncGenerator<ProtocolStreamChunk> {
507
+ const response = await handler.handleMessage(request, context);
508
+ const words = response.message.split(' ');
509
+
510
+ for (let i = 0; i < words.length; i++) {
511
+ yield { type: 'text', content: (i === 0 ? '' : ' ') + words[i] };
512
+ await new Promise(r => setTimeout(r, 30));
513
+ }
514
+
515
+ yield { type: 'status', content: response.sessionStatus };
516
+ },
517
+
518
+ getCapabilities(): ProtocolCapabilities {
519
+ return {
520
+ streaming: true,
521
+ formQuestions: false,
522
+ knowledgeActions: false,
523
+ integrations: false,
524
+ };
525
+ },
526
+
527
+ getHandlerInfo(): HandlerInfo {
528
+ return { name: 'Echo Handler' };
529
+ },
530
+ };
531
+
532
+ const token = process.env.FIRSTSTEP_TOKEN || '';
533
+ const port = parseInt(process.env.PORT || '4001', 10);
534
+
535
+ const server = createServer(handler, {
536
+ token,
537
+ port,
538
+ skipSignatureVerification: !token,
539
+ });
540
+
541
+ server.start();
542
+ ```
543
+
544
+ ## Environment Variables
545
+
546
+ - `FIRSTSTEP_TOKEN` - API token (format: `fst_` + 40 hex chars). Required for production. Without it, signature verification is disabled.
547
+ - `PORT` - Server port. Default: 3001.
548
+
549
+ ## Imports Summary
550
+
551
+ ```typescript
552
+ // Server (separate entry point)
553
+ import { createServer } from '@firststep-studio/sdk/server';
554
+
555
+ // Types (main entry point)
556
+ import type {
557
+ // Core
558
+ ProtocolHandler, ProtocolRequest, ProtocolResponse, SessionStatus,
559
+ ProtocolCapabilities, HandlerInfo,
560
+
561
+ // Streaming
562
+ ProtocolStreamChunk, ProtocolError,
563
+
564
+ // Forms
565
+ ProtocolForm, ProtocolFormField, ProtocolFormOption, ProtocolFieldValidation,
566
+
567
+ // Context
568
+ ProtocolContext, SessionContext, SessionState, ChatMessage,
569
+ KnowledgeContext, KnowledgeResult,
570
+ IntegrationContext, IntegrationResult,
571
+ AnalyticsContext, InteractionEvent, InteractionEventType,
572
+ LoggerContext, RoutingDecision,
573
+ DeploymentInfo, ChatbotInfo,
574
+
575
+ // Platform data types
576
+ FormData, FormFieldValue, RoutingLog, SessionMetadata,
577
+ FormSchema, FormFieldDefinition, FormFieldType,
578
+ ProtocolRegistration,
579
+
580
+ // UCP
581
+ UCPMessage, UCPClassifyRequest, UCPClassifyResponse,
582
+ UCPClientConfig, ClassificationResult,
583
+ } from '@firststep-studio/sdk';
584
+
585
+ // Auth utilities (main entry point)
586
+ import {
587
+ verifyRequestSignature,
588
+ createRequestSignature,
589
+ createAuthHeader,
590
+ isValidToken,
591
+ } from '@firststep-studio/sdk';
592
+
593
+ // UCP client (main entry point)
594
+ import {
595
+ UCPClient, UCPError, createUCPClient, classifyWithUCP,
596
+ } from '@firststep-studio/sdk';
597
+ ```
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@firststep-studio/sdk",
3
+ "version": "0.1.0",
4
+ "description": "SDK for building protocol handlers that integrate with FirstStep Studio",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./server": {
15
+ "types": "./dist/server.d.ts",
16
+ "import": "./dist/server.mjs",
17
+ "require": "./dist/server.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "llms.txt"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "dev": "tsup --watch",
27
+ "typecheck": "tsc --noEmit",
28
+ "clean": "rm -rf dist"
29
+ },
30
+ "keywords": [
31
+ "firststep",
32
+ "protocol",
33
+ "sdk",
34
+ "chatbot"
35
+ ],
36
+ "author": "FirstStep",
37
+ "license": "MIT",
38
+ "devDependencies": {
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.0.0"
41
+ },
42
+ "peerDependencies": {
43
+ "typescript": ">=4.7.0"
44
+ }
45
+ }