@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/README.md +40 -0
- package/dist/index.d.mts +299 -0
- package/dist/index.d.ts +299 -0
- package/dist/index.js +238 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +204 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +77 -0
- package/dist/server.d.ts +77 -0
- package/dist/server.js +312 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +287 -0
- package/dist/server.mjs.map +1 -0
- package/dist/types-Bm98aHcd.d.mts +438 -0
- package/dist/types-Bm98aHcd.d.ts +438 -0
- package/llms.txt +597 -0
- package/package.json +45 -0
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
|
+
}
|