@firststep-studio/sdk 0.14.0 → 0.16.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/resources/storageClient.ts","../src/resources/sessionClient.ts","../src/auth.ts"],"sourcesContent":["/**\n * FirstStep SDK Server\n *\n * Standalone HTTP server for protocol handlers.\n * Zero external dependencies, uses Node.js built-in `http` module.\n *\n * @example\n * ```typescript\n * import { createServer } from '@firststep-studio/sdk/server';\n * import type { ProtocolHandler } from '@firststep-studio/sdk';\n *\n * const handler: ProtocolHandler = {\n * async handleMessage(request, context) {\n * return {\n * message: 'Hello from my handler!',\n * sessionId: request.sessionId || 'new',\n * agentId: 'main',\n * sessionStatus: 'active',\n * };\n * },\n * getCapabilities() {\n * return { streaming: false, formQuestions: false, knowledgeActions: false, integrations: false };\n * },\n * };\n *\n * const server = createServer(handler, {\n * token: process.env.FIRSTSTEP_TOKEN!,\n * port: 3001,\n * });\n *\n * server.start();\n * ```\n */\n\nimport { createServer as createHttpServer, IncomingMessage, ServerResponse } from 'http';\nimport type {\n ProtocolHandler,\n ProtocolRequest,\n ProtocolContext,\n ProtocolCapabilities,\n ProtocolStreamChunk,\n StorageContext,\n} from './types';\nimport { StorageClient } from './resources/storageClient.js';\nimport { SessionClient } from './resources/sessionClient.js';\nimport { verifyRequestSignature } from './auth';\n\n// ============================================\n// Configuration\n// ============================================\n\nexport interface ServerConfig {\n /** API token (FIRSTSTEP_TOKEN). Used for request signature verification. */\n token: string;\n\n /** Port to listen on. Defaults to 3001, or the PORT env variable. */\n port?: number;\n\n /** Host to bind to. Defaults to '0.0.0.0'. */\n host?: string;\n\n /** Skip signature verification (for local development only). */\n skipSignatureVerification?: boolean;\n\n /**\n * URL prefix for all endpoints.\n * Example: '/ucp/v1' makes endpoints available at /ucp/v1/handshake, /ucp/v1/message, etc.\n */\n prefix?: string;\n}\n\n// ============================================\n// Server Instance\n// ============================================\n\nexport interface FirstStepServer {\n /** Start the server */\n start(): Promise<void>;\n\n /** Stop the server gracefully */\n stop(): Promise<void>;\n\n /**\n * Get the request handler function.\n * Use this to integrate with Express, Fastify, or other frameworks.\n *\n * @example\n * ```typescript\n * // Express\n * const server = createServer(handler, { token: '...' });\n * app.post('/handshake', server.getRequestHandler());\n * app.post('/message', server.getRequestHandler());\n * ```\n */\n getRequestHandler(): (req: IncomingMessage, res: ServerResponse) => void;\n}\n\n// ============================================\n// Internal Helpers\n// ============================================\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n req.on('error', reject);\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json),\n 'Access-Control-Allow-Origin': '*',\n });\n res.end(json);\n}\n\nfunction parsePath(url: string | undefined): string {\n if (!url) return '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.substring(0, idx) : url;\n}\n\n/**\n * Build a stub ProtocolContext for external handlers.\n *\n * External handlers receive context data as plain JSON from the platform.\n * The stub provides no-op implementations for methods like session.getState()\n * since those will be handled by callback APIs to the FirstStep backend.\n *\n * When the platform sends a /message request, the request body includes\n * a `context` field with serialized context data. This function merges\n * that data with no-op method stubs.\n */\nfunction buildStubContext(data?: any): ProtocolContext {\n const noopAsync = async () => ({} as any);\n const noop = () => {};\n\n // Detect _resources injected by Studio proxy for HTTP-backed resource clients\n const resources = data?._resources as { baseUrl: string; token: string; capabilities?: Record<string, boolean> } | undefined;\n let storage: StorageContext | undefined;\n\n if (resources?.baseUrl && resources?.token && resources.capabilities?.storage !== false) {\n storage = new StorageClient(resources.baseUrl, resources.token);\n }\n\n // HTTP-backed session (Redis hot store) when the proxy advertises it.\n // Falls back to the no-op stub so handlers that don't use ctx.session, or\n // hosts that don't expose it, behave exactly as before.\n const sessionId: string = data?.session?.sessionId ?? '';\n const sessionStub = {\n sessionId,\n getState: noopAsync,\n updateState: noopAsync,\n getHistory: async () => [],\n saveMessage: noopAsync,\n complete: noopAsync,\n getFormData: async () => ({}),\n updateFormField: noopAsync,\n updateFormData: noopAsync,\n ...data?.session,\n };\n const session =\n resources?.baseUrl && resources?.token && resources.capabilities?.session\n ? new SessionClient(resources.baseUrl, resources.token, sessionId)\n : sessionStub;\n\n return {\n session,\n knowledge: {\n queryDatabase: noopAsync,\n searchPages: noopAsync,\n ...data?.knowledge,\n },\n integrations: {\n execute: noopAsync,\n ...data?.integrations,\n },\n storage,\n analytics: {\n logActionExecuted: noop,\n logInteraction: noop,\n logCustomEvent: noop,\n ...data?.analytics,\n },\n logger: {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n logRouting: noop,\n logToolUse: noop,\n ...data?.logger,\n },\n deployment: {\n id: '',\n slug: '',\n name: '',\n protocolType: 'external',\n ...data?.deployment,\n },\n chatbot: data?.chatbot,\n };\n}\n\n// ============================================\n// createServer\n// ============================================\n\n/**\n * Create a standalone HTTP server for a protocol handler.\n *\n * The server exposes three endpoints:\n * - `GET /health` - Health check (always 200)\n * - `POST /handshake` - Returns handler capabilities (signature verified)\n * - `POST /message` - Handles a chat message (signature verified)\n */\nexport function createServer(\n handler: ProtocolHandler,\n config: ServerConfig\n): FirstStepServer {\n const {\n token,\n port = parseInt(process.env.PORT || '3001', 10),\n host = '0.0.0.0',\n skipSignatureVerification = false,\n prefix: rawPrefix = '',\n } = config;\n\n // Normalize prefix: ensure leading slash, no trailing slash\n const prefix = rawPrefix ? ('/' + rawPrefix.replace(/^\\/|\\/$/g, '')) : '';\n\n // Validate config\n if (!token && !skipSignatureVerification) {\n throw new Error(\n 'FIRSTSTEP_TOKEN is required. Pass it via config.token or set skipSignatureVerification for local dev.'\n );\n }\n\n /**\n * Core request router\n */\n async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const path = parsePath(req.url);\n const method = (req.method || 'GET').toUpperCase();\n\n // CORS preflight\n if (method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-FirstStep-Signature',\n 'Access-Control-Max-Age': '86400',\n });\n res.end();\n return;\n }\n\n // Strip prefix to get the remaining path\n const afterPrefix = prefix && path.startsWith(prefix) ? path.slice(prefix.length) || '/' : path;\n\n // Extract optional config slug from path: /{slug}/message -> slug = \"slug\", route = \"/message\"\n // If no slug segment, route is the full remaining path (backward compatible)\n let configSlug: string | undefined;\n let route: string;\n const slugMatch = afterPrefix.match(/^\\/([^/]+)(\\/(?:health|handshake|message(?:\\/stream)?))?$/);\n if (slugMatch && slugMatch[2]) {\n // Path has a slug segment: /{slug}/{endpoint}\n configSlug = slugMatch[1];\n route = slugMatch[2];\n } else {\n // No slug: /{endpoint} (backward compatible)\n route = afterPrefix;\n }\n\n // Health check\n if (route === '/health' && method === 'GET') {\n sendJson(res, 200, { status: 'ok', timestamp: Date.now() });\n return;\n }\n\n // Handshake\n // Signature is verified when present, but not required.\n // This allows the frontend to probe handler capabilities before\n // a project (and its API token binding) exists.\n if (route === '/handshake' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n const capabilities: ProtocolCapabilities = handler.getCapabilities();\n const handlerInfo = handler.getHandlerInfo?.();\n sendJson(res, 200, { capabilities, handler: handlerInfo || undefined });\n return;\n }\n\n // Message\n if (route === '/message' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n const context = buildStubContext(parsed.context);\n\n try {\n const response = await handler.handleMessage(parsed.request, context);\n sendJson(res, 200, response);\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, {\n error: 'Handler error',\n message: err?.message || 'Unknown error',\n });\n }\n return;\n }\n\n // Message (streaming via SSE)\n if (route === '/message/stream' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n if (!handler.handleStream) {\n // Fallback: use non-streaming handleMessage, send as single SSE burst\n const context = buildStubContext(parsed.context);\n try {\n const response = await handler.handleMessage(parsed.request, context);\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n sendSSE('connected', { sessionId: response.sessionId });\n sendSSE('text', { type: 'text', content: response.message, sessionId: response.sessionId });\n if (response.metadata) {\n sendSSE('metadata', { type: 'metadata', content: response.metadata, sessionId: response.sessionId });\n }\n if (response.form) {\n sendSSE('form', { type: 'form', content: response.form, sessionId: response.sessionId });\n }\n sendSSE('status', { type: 'status', content: response.sessionStatus, sessionId: response.sessionId });\n sendSSE('done', { sessionId: response.sessionId });\n res.end();\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, { error: 'Handler error', message: err?.message || 'Unknown error' });\n }\n return;\n }\n\n const context = buildStubContext(parsed.context);\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n\n const sessionId = parsed.request.sessionId || `ext-${Date.now()}`;\n sendSSE('connected', { sessionId });\n\n try {\n for await (const chunk of handler.handleStream(parsed.request, context)) {\n sendSSE(chunk.type, {\n type: chunk.type,\n content: chunk.content,\n sessionId,\n });\n }\n sendSSE('done', { sessionId });\n } catch (err: any) {\n console.error('[firststep] Stream error:', err);\n sendSSE('error', { code: 'STREAM_ERROR', message: err?.message || 'Unknown error' });\n }\n\n res.end();\n return;\n }\n\n // 404\n sendJson(res, 404, { error: 'Not found' });\n }\n\n // Wrap in error boundary\n function requestListener(req: IncomingMessage, res: ServerResponse): void {\n handleRequest(req, res).catch((err) => {\n console.error('[firststep] Unexpected error:', err);\n if (!res.headersSent) {\n sendJson(res, 500, { error: 'Internal server error' });\n }\n });\n }\n\n const httpServer = createHttpServer(requestListener);\n\n return {\n start() {\n return new Promise<void>((resolve) => {\n httpServer.listen(port, host, () => {\n console.log(`[firststep] Handler server listening on ${host}:${port}`);\n console.log(`[firststep] Endpoints:`);\n console.log(` GET ${prefix}/health - Health check`);\n console.log(` POST ${prefix}/handshake - Capability exchange`);\n console.log(` POST ${prefix}/message - Handle chat message`);\n console.log(` POST ${prefix}/message/stream - Handle chat message (SSE stream)`);\n resolve();\n });\n });\n },\n\n stop() {\n return new Promise<void>((resolve, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n getRequestHandler() {\n return requestListener;\n },\n };\n}\n","import type { StorageContext, StorageSetOptions } from '../types.js';\n\n/**\n * HTTP-backed implementation of StorageContext.\n * All operations call back to Studio's protocol-resources API.\n * Data is scoped to the deployment via the JWT token.\n */\nexport class StorageClient implements StorageContext {\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const res = await this._post('/storage/get', { key });\n if (!res.success) return null;\n return (res.value ?? null) as T;\n }\n\n async set(key: string, value: unknown, options?: StorageSetOptions): Promise<void> {\n await this._post('/storage/set', { key, value, ttl: options?.ttl });\n }\n\n async delete(key: string): Promise<void> {\n await this._post('/storage/delete', { key });\n }\n\n async has(key: string): Promise<boolean> {\n const res = await this._post('/storage/has', { key });\n return res.exists === true;\n }\n\n async keys(prefix?: string): Promise<string[]> {\n const res = await this._post('/storage/keys', { prefix });\n return res.keys ?? [];\n }\n\n async merge(key: string, partial: Record<string, unknown>): Promise<void> {\n await this._post('/storage/merge', { key, partial });\n }\n\n async append(key: string, item: unknown): Promise<void> {\n await this._post('/storage/append', { key, item });\n }\n\n private async _post(path: string, body: unknown): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[StorageClient] ${path} failed: ${res.status} ${text}`);\n return { success: false, error: `HTTP ${res.status}` };\n }\n return res.json();\n }\n}\n","import type {\n SessionContext,\n SessionState,\n ChatMessage,\n FormData,\n FormFieldValue,\n} from '../types.js';\n\n/**\n * HTTP-backed implementation of SessionContext.\n *\n * All operations call back to Studio's protocol-resources `/session/*` API,\n * which is backed by the Redis hot-session store (protocolSession.service).\n * Session is scoped to the deployment + sessionId encoded in the JWT token, so\n * the request bodies never carry the session id themselves.\n *\n * This is the external-handler counterpart to the in-process SessionContext\n * the built-in GuidedForm engine gets via handlers/context.ts — same\n * interface, same Redis backend, reached over HTTP instead of in-process.\n */\nexport class SessionClient implements SessionContext {\n public sessionId: string;\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string, sessionId: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n this.sessionId = sessionId;\n }\n\n async getState(): Promise<SessionState> {\n const res = await this._post('/session/get-state', {});\n return reviveState(res.state);\n }\n\n async updateState(updates: Partial<SessionState>): Promise<void> {\n await this._post('/session/update-state', { updates });\n }\n\n async getHistory(): Promise<ChatMessage[]> {\n const res = await this._post('/session/get-history', {});\n return (res.messages ?? []).map(reviveMessage);\n }\n\n async saveMessage(message: ChatMessage): Promise<void> {\n await this._post('/session/save-message', { message });\n }\n\n async complete(): Promise<void> {\n await this._post('/session/complete', {});\n }\n\n async getFormData(): Promise<FormData> {\n const res = await this._post('/session/get-form-data', {});\n return (res.formData ?? {}) as FormData;\n }\n\n async updateFormField(fieldId: string, value: FormFieldValue): Promise<void> {\n await this._post('/session/update-form-field', { fieldId, value });\n }\n\n async updateFormData(fields: Record<string, FormFieldValue>): Promise<void> {\n await this._post('/session/update-form-data', { fields });\n }\n\n private async _post(path: string, body: unknown): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[SessionClient] ${path} failed: ${res.status} ${text}`);\n throw new Error(`SessionClient ${path} failed: HTTP ${res.status}`);\n }\n return res.json();\n }\n}\n\n/** JSON transport stringifies Dates; revive the envelope timestamps. */\nfunction reviveState(state: any): SessionState {\n if (!state) return state;\n if (state.createdAt) state.createdAt = new Date(state.createdAt);\n if (state.updatedAt) state.updatedAt = new Date(state.updatedAt);\n return state as SessionState;\n}\n\nfunction reviveMessage(msg: any): ChatMessage {\n if (msg?.timestamp) msg.timestamp = new Date(msg.timestamp);\n return msg as ChatMessage;\n}\n","import { createHash, createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Hash a token to derive the shared HMAC key.\n * The backend only stores SHA-256(token) and uses that hash as the HMAC key.\n * The SDK must hash the plaintext token the same way to verify signatures.\n */\nfunction deriveSigningKey(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\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 * The HMAC key is SHA-256(token), matching the backend which only stores\n * the token hash and uses it directly as the HMAC key.\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 signingKey = deriveSigningKey(token);\n const expected = createHmac('sha256', signingKey)\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 const signingKey = deriveSigningKey(token);\n return createHmac('sha256', signingKey).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":["context","sendSSE","createHttpServer"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/resources/storageClient.ts","../src/resources/sessionClient.ts","../src/auth.ts"],"sourcesContent":["/**\n * FirstStep SDK Server\n *\n * Standalone HTTP server for protocol handlers.\n * Zero external dependencies, uses Node.js built-in `http` module.\n *\n * @example\n * ```typescript\n * import { createServer } from '@firststep-studio/sdk/server';\n * import type { ProtocolHandler } from '@firststep-studio/sdk';\n *\n * const handler: ProtocolHandler = {\n * async handleMessage(request, context) {\n * return {\n * message: 'Hello from my handler!',\n * sessionId: request.sessionId || 'new',\n * agentId: 'main',\n * sessionStatus: 'active',\n * };\n * },\n * getCapabilities() {\n * return { streaming: false, formQuestions: false, knowledgeActions: false, integrations: false };\n * },\n * };\n *\n * const server = createServer(handler, {\n * token: process.env.FIRSTSTEP_TOKEN!,\n * port: 3001,\n * });\n *\n * server.start();\n * ```\n */\n\nimport { createServer as createHttpServer, IncomingMessage, ServerResponse } from 'http';\nimport type {\n ProtocolHandler,\n ProtocolRequest,\n ProtocolContext,\n ProtocolCapabilities,\n ProtocolStreamChunk,\n StorageContext,\n} from './types';\nimport { StorageClient } from './resources/storageClient.js';\nimport { SessionClient } from './resources/sessionClient.js';\nimport { verifyRequestSignature } from './auth';\n\n// ============================================\n// Configuration\n// ============================================\n\nexport interface ServerConfig {\n /** API token (FIRSTSTEP_TOKEN). Used for request signature verification. */\n token: string;\n\n /** Port to listen on. Defaults to 3001, or the PORT env variable. */\n port?: number;\n\n /** Host to bind to. Defaults to '0.0.0.0'. */\n host?: string;\n\n /** Skip signature verification (for local development only). */\n skipSignatureVerification?: boolean;\n\n /**\n * URL prefix for all endpoints.\n * Example: '/ucp/v1' makes endpoints available at /ucp/v1/handshake, /ucp/v1/message, etc.\n */\n prefix?: string;\n}\n\n// ============================================\n// Server Instance\n// ============================================\n\nexport interface FirstStepServer {\n /** Start the server */\n start(): Promise<void>;\n\n /** Stop the server gracefully */\n stop(): Promise<void>;\n\n /**\n * Get the request handler function.\n * Use this to integrate with Express, Fastify, or other frameworks.\n *\n * @example\n * ```typescript\n * // Express\n * const server = createServer(handler, { token: '...' });\n * app.post('/handshake', server.getRequestHandler());\n * app.post('/message', server.getRequestHandler());\n * ```\n */\n getRequestHandler(): (req: IncomingMessage, res: ServerResponse) => void;\n}\n\n// ============================================\n// Internal Helpers\n// ============================================\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n req.on('error', reject);\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json),\n 'Access-Control-Allow-Origin': '*',\n });\n res.end(json);\n}\n\nfunction parsePath(url: string | undefined): string {\n if (!url) return '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.substring(0, idx) : url;\n}\n\n/**\n * Build a stub ProtocolContext for external handlers.\n *\n * External handlers receive context data as plain JSON from the platform.\n * The stub provides no-op implementations for methods like session.getState()\n * since those will be handled by callback APIs to the FirstStep backend.\n *\n * When the platform sends a /message request, the request body includes\n * a `context` field with serialized context data. This function merges\n * that data with no-op method stubs.\n */\nfunction buildStubContext(data?: any, requestSessionId?: string): ProtocolContext {\n const noopAsync = async () => ({} as any);\n const noop = () => {};\n\n // Detect _resources injected by Studio proxy for HTTP-backed resource clients\n const resources = data?._resources as { baseUrl: string; token: string; capabilities?: Record<string, boolean> } | undefined;\n let storage: StorageContext | undefined;\n\n if (resources?.baseUrl && resources?.token && resources.capabilities?.storage !== false) {\n storage = new StorageClient(resources.baseUrl, resources.token);\n }\n\n // ctx.session over HTTP: real (Redis-backed, ephemeral) session state for\n // external handlers, replacing the noop stub. Requires the Studio proxy to\n // opt in via capabilities.session and the request to carry a sessionId.\n let session: ProtocolContext['session'] | undefined;\n if (resources?.baseUrl && resources?.token && resources.capabilities?.session === true && requestSessionId) {\n session = new SessionClient(resources.baseUrl, resources.token, requestSessionId);\n }\n\n return {\n session: session ?? {\n sessionId: requestSessionId || '',\n getState: noopAsync,\n updateState: noopAsync,\n getHistory: async () => [],\n saveMessage: noopAsync,\n complete: noopAsync,\n getFormData: async () => ({}),\n updateFormField: noopAsync,\n updateFormData: noopAsync,\n ...data?.session,\n },\n knowledge: {\n queryDatabase: noopAsync,\n searchPages: noopAsync,\n ...data?.knowledge,\n },\n integrations: {\n execute: noopAsync,\n ...data?.integrations,\n },\n storage,\n analytics: {\n logActionExecuted: noop,\n logInteraction: noop,\n logCustomEvent: noop,\n ...data?.analytics,\n },\n logger: {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n logRouting: noop,\n logToolUse: noop,\n ...data?.logger,\n },\n deployment: {\n id: '',\n slug: '',\n name: '',\n protocolType: 'external',\n ...data?.deployment,\n },\n chatbot: data?.chatbot,\n };\n}\n\n// ============================================\n// createServer\n// ============================================\n\n/**\n * Create a standalone HTTP server for a protocol handler.\n *\n * The server exposes three endpoints:\n * - `GET /health` - Health check (always 200)\n * - `POST /handshake` - Returns handler capabilities (signature verified)\n * - `POST /message` - Handles a chat message (signature verified)\n */\nexport function createServer(\n handler: ProtocolHandler,\n config: ServerConfig\n): FirstStepServer {\n const {\n token,\n port = parseInt(process.env.PORT || '3001', 10),\n host = '0.0.0.0',\n skipSignatureVerification = false,\n prefix: rawPrefix = '',\n } = config;\n\n // Normalize prefix: ensure leading slash, no trailing slash\n const prefix = rawPrefix ? ('/' + rawPrefix.replace(/^\\/|\\/$/g, '')) : '';\n\n // Validate config\n if (!token && !skipSignatureVerification) {\n throw new Error(\n 'FIRSTSTEP_TOKEN is required. Pass it via config.token or set skipSignatureVerification for local dev.'\n );\n }\n\n /**\n * Core request router\n */\n async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const path = parsePath(req.url);\n const method = (req.method || 'GET').toUpperCase();\n\n // CORS preflight\n if (method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-FirstStep-Signature',\n 'Access-Control-Max-Age': '86400',\n });\n res.end();\n return;\n }\n\n // Strip prefix to get the remaining path\n const afterPrefix = prefix && path.startsWith(prefix) ? path.slice(prefix.length) || '/' : path;\n\n // Extract optional config slug from path: /{slug}/message -> slug = \"slug\", route = \"/message\"\n // If no slug segment, route is the full remaining path (backward compatible)\n let configSlug: string | undefined;\n let route: string;\n const slugMatch = afterPrefix.match(/^\\/([^/]+)(\\/(?:health|handshake|message(?:\\/stream)?))?$/);\n if (slugMatch && slugMatch[2]) {\n // Path has a slug segment: /{slug}/{endpoint}\n configSlug = slugMatch[1];\n route = slugMatch[2];\n } else {\n // No slug: /{endpoint} (backward compatible)\n route = afterPrefix;\n }\n\n // Health check\n if (route === '/health' && method === 'GET') {\n sendJson(res, 200, { status: 'ok', timestamp: Date.now() });\n return;\n }\n\n // Handshake\n // Signature is verified when present, but not required.\n // This allows the frontend to probe handler capabilities before\n // a project (and its API token binding) exists.\n if (route === '/handshake' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n const capabilities: ProtocolCapabilities = handler.getCapabilities();\n const handlerInfo = handler.getHandlerInfo?.();\n sendJson(res, 200, { capabilities, handler: handlerInfo || undefined });\n return;\n }\n\n // Message\n if (route === '/message' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n const context = buildStubContext(parsed.context, parsed.request?.sessionId);\n\n try {\n const response = await handler.handleMessage(parsed.request, context);\n sendJson(res, 200, response);\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, {\n error: 'Handler error',\n message: err?.message || 'Unknown error',\n });\n }\n return;\n }\n\n // Message (streaming via SSE)\n if (route === '/message/stream' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n if (!handler.handleStream) {\n // Fallback: use non-streaming handleMessage, send as single SSE burst\n const context = buildStubContext(parsed.context, parsed.request?.sessionId);\n try {\n const response = await handler.handleMessage(parsed.request, context);\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n sendSSE('connected', { sessionId: response.sessionId });\n sendSSE('text', { type: 'text', content: response.message, sessionId: response.sessionId });\n if (response.metadata) {\n sendSSE('metadata', { type: 'metadata', content: response.metadata, sessionId: response.sessionId });\n }\n if (response.form) {\n sendSSE('form', { type: 'form', content: response.form, sessionId: response.sessionId });\n }\n sendSSE('status', { type: 'status', content: response.sessionStatus, sessionId: response.sessionId });\n sendSSE('done', { sessionId: response.sessionId });\n res.end();\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, { error: 'Handler error', message: err?.message || 'Unknown error' });\n }\n return;\n }\n\n const context = buildStubContext(parsed.context, parsed.request?.sessionId);\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n\n const sessionId = parsed.request.sessionId || `ext-${Date.now()}`;\n sendSSE('connected', { sessionId });\n\n try {\n for await (const chunk of handler.handleStream(parsed.request, context)) {\n sendSSE(chunk.type, {\n type: chunk.type,\n content: chunk.content,\n sessionId,\n });\n }\n sendSSE('done', { sessionId });\n } catch (err: any) {\n console.error('[firststep] Stream error:', err);\n sendSSE('error', { code: 'STREAM_ERROR', message: err?.message || 'Unknown error' });\n }\n\n res.end();\n return;\n }\n\n // 404\n sendJson(res, 404, { error: 'Not found' });\n }\n\n // Wrap in error boundary\n function requestListener(req: IncomingMessage, res: ServerResponse): void {\n handleRequest(req, res).catch((err) => {\n console.error('[firststep] Unexpected error:', err);\n if (!res.headersSent) {\n sendJson(res, 500, { error: 'Internal server error' });\n }\n });\n }\n\n const httpServer = createHttpServer(requestListener);\n\n return {\n start() {\n return new Promise<void>((resolve) => {\n httpServer.listen(port, host, () => {\n console.log(`[firststep] Handler server listening on ${host}:${port}`);\n console.log(`[firststep] Endpoints:`);\n console.log(` GET ${prefix}/health - Health check`);\n console.log(` POST ${prefix}/handshake - Capability exchange`);\n console.log(` POST ${prefix}/message - Handle chat message`);\n console.log(` POST ${prefix}/message/stream - Handle chat message (SSE stream)`);\n resolve();\n });\n });\n },\n\n stop() {\n return new Promise<void>((resolve, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n getRequestHandler() {\n return requestListener;\n },\n };\n}\n","import type { StorageContext, StorageSetOptions } from '../types.js';\n\n/**\n * HTTP-backed implementation of StorageContext.\n * All operations call back to Studio's protocol-resources API.\n * Data is scoped to the deployment via the JWT token.\n */\nexport class StorageClient implements StorageContext {\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const res = await this._post('/storage/get', { key });\n if (!res.success) return null;\n return (res.value ?? null) as T;\n }\n\n async set(key: string, value: unknown, options?: StorageSetOptions): Promise<void> {\n await this._post('/storage/set', { key, value, ttl: options?.ttl });\n }\n\n async delete(key: string): Promise<void> {\n await this._post('/storage/delete', { key });\n }\n\n async has(key: string): Promise<boolean> {\n const res = await this._post('/storage/has', { key });\n return res.exists === true;\n }\n\n async keys(prefix?: string): Promise<string[]> {\n const res = await this._post('/storage/keys', { prefix });\n return res.keys ?? [];\n }\n\n async merge(key: string, partial: Record<string, unknown>): Promise<void> {\n await this._post('/storage/merge', { key, partial });\n }\n\n async append(key: string, item: unknown): Promise<void> {\n await this._post('/storage/append', { key, item });\n }\n\n private async _post(path: string, body: unknown): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[StorageClient] ${path} failed: ${res.status} ${text}`);\n return { success: false, error: `HTTP ${res.status}` };\n }\n return res.json();\n }\n}\n","import type {\n SessionContext,\n SessionState,\n ChatMessage,\n FormData,\n FormFieldValue,\n} from '../types.js';\n\n/**\n * HTTP-backed implementation of SessionContext.\n *\n * External handlers (running out-of-process, reached over /message) get a\n * noop session stub by default; this client makes ctx.session real for them\n * by calling back to Studio's protocol-resources API, which fronts the same\n * Redis-backed ProtocolSessionService the in-process handlers use. Session\n * state is ephemeral by design (sliding TTL in Redis, never durably stored);\n * durable data belongs in ctx.storage.\n */\nexport class SessionClient implements SessionContext {\n readonly sessionId: string;\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string, sessionId: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n this.sessionId = sessionId;\n }\n\n async getState(): Promise<SessionState> {\n const res = await this._post('/session/get-state', {});\n return this._reviveState(res.state);\n }\n\n async updateState(updates: Partial<SessionState>): Promise<void> {\n await this._post('/session/update-state', { updates });\n }\n\n async getHistory(): Promise<ChatMessage[]> {\n const res = await this._post('/session/get-history', {});\n const messages = Array.isArray(res.messages) ? res.messages : [];\n return messages.map((m: any) => ({\n ...m,\n timestamp: m?.timestamp ? new Date(m.timestamp) : undefined,\n }));\n }\n\n async saveMessage(message: ChatMessage): Promise<void> {\n await this._post('/session/save-message', { message });\n }\n\n async complete(): Promise<void> {\n await this._post('/session/complete', {});\n }\n\n async getFormData(): Promise<FormData> {\n const res = await this._post('/session/get-form-data', {});\n return res.formData ?? {};\n }\n\n async updateFormField(fieldId: string, value: FormFieldValue): Promise<void> {\n await this._post('/session/update-form-field', { fieldId, value });\n }\n\n async updateFormData(fields: Record<string, FormFieldValue>): Promise<void> {\n await this._post('/session/update-form-data', { fields });\n }\n\n private _reviveState(state: any): SessionState {\n const now = new Date();\n if (!state || typeof state !== 'object') {\n return {\n sessionId: this.sessionId,\n deploymentId: '',\n formData: {},\n metadata: {},\n createdAt: now,\n updatedAt: now,\n } as SessionState;\n }\n return {\n ...state,\n createdAt: state.createdAt ? new Date(state.createdAt) : now,\n updatedAt: state.updatedAt ? new Date(state.updatedAt) : now,\n } as SessionState;\n }\n\n private async _post(path: string, body: Record<string, unknown>): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify({ sessionId: this.sessionId, ...body }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[SessionClient] ${path} failed: ${res.status} ${text}`);\n return { success: false, error: `HTTP ${res.status}` };\n }\n return res.json();\n }\n}\n","import { createHash, createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Hash a token to derive the shared HMAC key.\n * The backend only stores SHA-256(token) and uses that hash as the HMAC key.\n * The SDK must hash the plaintext token the same way to verify signatures.\n */\nfunction deriveSigningKey(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\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 * The HMAC key is SHA-256(token), matching the backend which only stores\n * the token hash and uses it directly as the HMAC key.\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 signingKey = deriveSigningKey(token);\n const expected = createHmac('sha256', signingKey)\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 const signingKey = deriveSigningKey(token);\n return createHmac('sha256', signingKey).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":["context","sendSSE","createHttpServer"]}
package/dist/server.mjs CHANGED
@@ -59,14 +59,18 @@ var SessionClient = class {
59
59
  }
60
60
  async getState() {
61
61
  const res = await this._post("/session/get-state", {});
62
- return reviveState(res.state);
62
+ return this._reviveState(res.state);
63
63
  }
64
64
  async updateState(updates) {
65
65
  await this._post("/session/update-state", { updates });
66
66
  }
67
67
  async getHistory() {
68
68
  const res = await this._post("/session/get-history", {});
69
- return (res.messages ?? []).map(reviveMessage);
69
+ const messages = Array.isArray(res.messages) ? res.messages : [];
70
+ return messages.map((m) => ({
71
+ ...m,
72
+ timestamp: m?.timestamp ? new Date(m.timestamp) : void 0
73
+ }));
70
74
  }
71
75
  async saveMessage(message) {
72
76
  await this._post("/session/save-message", { message });
@@ -84,6 +88,24 @@ var SessionClient = class {
84
88
  async updateFormData(fields) {
85
89
  await this._post("/session/update-form-data", { fields });
86
90
  }
91
+ _reviveState(state) {
92
+ const now = /* @__PURE__ */ new Date();
93
+ if (!state || typeof state !== "object") {
94
+ return {
95
+ sessionId: this.sessionId,
96
+ deploymentId: "",
97
+ formData: {},
98
+ metadata: {},
99
+ createdAt: now,
100
+ updatedAt: now
101
+ };
102
+ }
103
+ return {
104
+ ...state,
105
+ createdAt: state.createdAt ? new Date(state.createdAt) : now,
106
+ updatedAt: state.updatedAt ? new Date(state.updatedAt) : now
107
+ };
108
+ }
87
109
  async _post(path, body) {
88
110
  const res = await fetch(`${this.baseUrl}${path}`, {
89
111
  method: "POST",
@@ -91,26 +113,16 @@ var SessionClient = class {
91
113
  "Content-Type": "application/json",
92
114
  Authorization: `Bearer ${this.token}`
93
115
  },
94
- body: JSON.stringify(body)
116
+ body: JSON.stringify({ sessionId: this.sessionId, ...body })
95
117
  });
96
118
  if (!res.ok) {
97
119
  const text = await res.text().catch(() => "");
98
120
  console.error(`[SessionClient] ${path} failed: ${res.status} ${text}`);
99
- throw new Error(`SessionClient ${path} failed: HTTP ${res.status}`);
121
+ return { success: false, error: `HTTP ${res.status}` };
100
122
  }
101
123
  return res.json();
102
124
  }
103
125
  };
104
- function reviveState(state) {
105
- if (!state) return state;
106
- if (state.createdAt) state.createdAt = new Date(state.createdAt);
107
- if (state.updatedAt) state.updatedAt = new Date(state.updatedAt);
108
- return state;
109
- }
110
- function reviveMessage(msg) {
111
- if (msg?.timestamp) msg.timestamp = new Date(msg.timestamp);
112
- return msg;
113
- }
114
126
 
115
127
  // src/auth.ts
116
128
  import { createHash, createHmac, timingSafeEqual } from "crypto";
@@ -155,7 +167,7 @@ function parsePath(url) {
155
167
  const idx = url.indexOf("?");
156
168
  return idx >= 0 ? url.substring(0, idx) : url;
157
169
  }
158
- function buildStubContext(data) {
170
+ function buildStubContext(data, requestSessionId) {
159
171
  const noopAsync = async () => ({});
160
172
  const noop = () => {
161
173
  };
@@ -164,22 +176,23 @@ function buildStubContext(data) {
164
176
  if (resources?.baseUrl && resources?.token && resources.capabilities?.storage !== false) {
165
177
  storage = new StorageClient(resources.baseUrl, resources.token);
166
178
  }
167
- const sessionId = data?.session?.sessionId ?? "";
168
- const sessionStub = {
169
- sessionId,
170
- getState: noopAsync,
171
- updateState: noopAsync,
172
- getHistory: async () => [],
173
- saveMessage: noopAsync,
174
- complete: noopAsync,
175
- getFormData: async () => ({}),
176
- updateFormField: noopAsync,
177
- updateFormData: noopAsync,
178
- ...data?.session
179
- };
180
- const session = resources?.baseUrl && resources?.token && resources.capabilities?.session ? new SessionClient(resources.baseUrl, resources.token, sessionId) : sessionStub;
179
+ let session;
180
+ if (resources?.baseUrl && resources?.token && resources.capabilities?.session === true && requestSessionId) {
181
+ session = new SessionClient(resources.baseUrl, resources.token, requestSessionId);
182
+ }
181
183
  return {
182
- session,
184
+ session: session ?? {
185
+ sessionId: requestSessionId || "",
186
+ getState: noopAsync,
187
+ updateState: noopAsync,
188
+ getHistory: async () => [],
189
+ saveMessage: noopAsync,
190
+ complete: noopAsync,
191
+ getFormData: async () => ({}),
192
+ updateFormField: noopAsync,
193
+ updateFormData: noopAsync,
194
+ ...data?.session
195
+ },
183
196
  knowledge: {
184
197
  queryDatabase: noopAsync,
185
198
  searchPages: noopAsync,
@@ -289,7 +302,7 @@ function createServer(handler, config) {
289
302
  if (configSlug) {
290
303
  parsed.request.configSlug = configSlug;
291
304
  }
292
- const context = buildStubContext(parsed.context);
305
+ const context = buildStubContext(parsed.context, parsed.request?.sessionId);
293
306
  try {
294
307
  const response = await handler.handleMessage(parsed.request, context);
295
308
  sendJson(res, 200, response);
@@ -322,7 +335,7 @@ function createServer(handler, config) {
322
335
  parsed.request.configSlug = configSlug;
323
336
  }
324
337
  if (!handler.handleStream) {
325
- const context2 = buildStubContext(parsed.context);
338
+ const context2 = buildStubContext(parsed.context, parsed.request?.sessionId);
326
339
  try {
327
340
  const response = await handler.handleMessage(parsed.request, context2);
328
341
  res.writeHead(200, {
@@ -354,7 +367,7 @@ data: ${JSON.stringify(data)}
354
367
  }
355
368
  return;
356
369
  }
357
- const context = buildStubContext(parsed.context);
370
+ const context = buildStubContext(parsed.context, parsed.request?.sessionId);
358
371
  res.writeHead(200, {
359
372
  "Content-Type": "text/event-stream",
360
373
  "Cache-Control": "no-cache",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/resources/storageClient.ts","../src/resources/sessionClient.ts","../src/auth.ts"],"sourcesContent":["/**\n * FirstStep SDK Server\n *\n * Standalone HTTP server for protocol handlers.\n * Zero external dependencies, uses Node.js built-in `http` module.\n *\n * @example\n * ```typescript\n * import { createServer } from '@firststep-studio/sdk/server';\n * import type { ProtocolHandler } from '@firststep-studio/sdk';\n *\n * const handler: ProtocolHandler = {\n * async handleMessage(request, context) {\n * return {\n * message: 'Hello from my handler!',\n * sessionId: request.sessionId || 'new',\n * agentId: 'main',\n * sessionStatus: 'active',\n * };\n * },\n * getCapabilities() {\n * return { streaming: false, formQuestions: false, knowledgeActions: false, integrations: false };\n * },\n * };\n *\n * const server = createServer(handler, {\n * token: process.env.FIRSTSTEP_TOKEN!,\n * port: 3001,\n * });\n *\n * server.start();\n * ```\n */\n\nimport { createServer as createHttpServer, IncomingMessage, ServerResponse } from 'http';\nimport type {\n ProtocolHandler,\n ProtocolRequest,\n ProtocolContext,\n ProtocolCapabilities,\n ProtocolStreamChunk,\n StorageContext,\n} from './types';\nimport { StorageClient } from './resources/storageClient.js';\nimport { SessionClient } from './resources/sessionClient.js';\nimport { verifyRequestSignature } from './auth';\n\n// ============================================\n// Configuration\n// ============================================\n\nexport interface ServerConfig {\n /** API token (FIRSTSTEP_TOKEN). Used for request signature verification. */\n token: string;\n\n /** Port to listen on. Defaults to 3001, or the PORT env variable. */\n port?: number;\n\n /** Host to bind to. Defaults to '0.0.0.0'. */\n host?: string;\n\n /** Skip signature verification (for local development only). */\n skipSignatureVerification?: boolean;\n\n /**\n * URL prefix for all endpoints.\n * Example: '/ucp/v1' makes endpoints available at /ucp/v1/handshake, /ucp/v1/message, etc.\n */\n prefix?: string;\n}\n\n// ============================================\n// Server Instance\n// ============================================\n\nexport interface FirstStepServer {\n /** Start the server */\n start(): Promise<void>;\n\n /** Stop the server gracefully */\n stop(): Promise<void>;\n\n /**\n * Get the request handler function.\n * Use this to integrate with Express, Fastify, or other frameworks.\n *\n * @example\n * ```typescript\n * // Express\n * const server = createServer(handler, { token: '...' });\n * app.post('/handshake', server.getRequestHandler());\n * app.post('/message', server.getRequestHandler());\n * ```\n */\n getRequestHandler(): (req: IncomingMessage, res: ServerResponse) => void;\n}\n\n// ============================================\n// Internal Helpers\n// ============================================\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n req.on('error', reject);\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json),\n 'Access-Control-Allow-Origin': '*',\n });\n res.end(json);\n}\n\nfunction parsePath(url: string | undefined): string {\n if (!url) return '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.substring(0, idx) : url;\n}\n\n/**\n * Build a stub ProtocolContext for external handlers.\n *\n * External handlers receive context data as plain JSON from the platform.\n * The stub provides no-op implementations for methods like session.getState()\n * since those will be handled by callback APIs to the FirstStep backend.\n *\n * When the platform sends a /message request, the request body includes\n * a `context` field with serialized context data. This function merges\n * that data with no-op method stubs.\n */\nfunction buildStubContext(data?: any): ProtocolContext {\n const noopAsync = async () => ({} as any);\n const noop = () => {};\n\n // Detect _resources injected by Studio proxy for HTTP-backed resource clients\n const resources = data?._resources as { baseUrl: string; token: string; capabilities?: Record<string, boolean> } | undefined;\n let storage: StorageContext | undefined;\n\n if (resources?.baseUrl && resources?.token && resources.capabilities?.storage !== false) {\n storage = new StorageClient(resources.baseUrl, resources.token);\n }\n\n // HTTP-backed session (Redis hot store) when the proxy advertises it.\n // Falls back to the no-op stub so handlers that don't use ctx.session, or\n // hosts that don't expose it, behave exactly as before.\n const sessionId: string = data?.session?.sessionId ?? '';\n const sessionStub = {\n sessionId,\n getState: noopAsync,\n updateState: noopAsync,\n getHistory: async () => [],\n saveMessage: noopAsync,\n complete: noopAsync,\n getFormData: async () => ({}),\n updateFormField: noopAsync,\n updateFormData: noopAsync,\n ...data?.session,\n };\n const session =\n resources?.baseUrl && resources?.token && resources.capabilities?.session\n ? new SessionClient(resources.baseUrl, resources.token, sessionId)\n : sessionStub;\n\n return {\n session,\n knowledge: {\n queryDatabase: noopAsync,\n searchPages: noopAsync,\n ...data?.knowledge,\n },\n integrations: {\n execute: noopAsync,\n ...data?.integrations,\n },\n storage,\n analytics: {\n logActionExecuted: noop,\n logInteraction: noop,\n logCustomEvent: noop,\n ...data?.analytics,\n },\n logger: {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n logRouting: noop,\n logToolUse: noop,\n ...data?.logger,\n },\n deployment: {\n id: '',\n slug: '',\n name: '',\n protocolType: 'external',\n ...data?.deployment,\n },\n chatbot: data?.chatbot,\n };\n}\n\n// ============================================\n// createServer\n// ============================================\n\n/**\n * Create a standalone HTTP server for a protocol handler.\n *\n * The server exposes three endpoints:\n * - `GET /health` - Health check (always 200)\n * - `POST /handshake` - Returns handler capabilities (signature verified)\n * - `POST /message` - Handles a chat message (signature verified)\n */\nexport function createServer(\n handler: ProtocolHandler,\n config: ServerConfig\n): FirstStepServer {\n const {\n token,\n port = parseInt(process.env.PORT || '3001', 10),\n host = '0.0.0.0',\n skipSignatureVerification = false,\n prefix: rawPrefix = '',\n } = config;\n\n // Normalize prefix: ensure leading slash, no trailing slash\n const prefix = rawPrefix ? ('/' + rawPrefix.replace(/^\\/|\\/$/g, '')) : '';\n\n // Validate config\n if (!token && !skipSignatureVerification) {\n throw new Error(\n 'FIRSTSTEP_TOKEN is required. Pass it via config.token or set skipSignatureVerification for local dev.'\n );\n }\n\n /**\n * Core request router\n */\n async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const path = parsePath(req.url);\n const method = (req.method || 'GET').toUpperCase();\n\n // CORS preflight\n if (method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-FirstStep-Signature',\n 'Access-Control-Max-Age': '86400',\n });\n res.end();\n return;\n }\n\n // Strip prefix to get the remaining path\n const afterPrefix = prefix && path.startsWith(prefix) ? path.slice(prefix.length) || '/' : path;\n\n // Extract optional config slug from path: /{slug}/message -> slug = \"slug\", route = \"/message\"\n // If no slug segment, route is the full remaining path (backward compatible)\n let configSlug: string | undefined;\n let route: string;\n const slugMatch = afterPrefix.match(/^\\/([^/]+)(\\/(?:health|handshake|message(?:\\/stream)?))?$/);\n if (slugMatch && slugMatch[2]) {\n // Path has a slug segment: /{slug}/{endpoint}\n configSlug = slugMatch[1];\n route = slugMatch[2];\n } else {\n // No slug: /{endpoint} (backward compatible)\n route = afterPrefix;\n }\n\n // Health check\n if (route === '/health' && method === 'GET') {\n sendJson(res, 200, { status: 'ok', timestamp: Date.now() });\n return;\n }\n\n // Handshake\n // Signature is verified when present, but not required.\n // This allows the frontend to probe handler capabilities before\n // a project (and its API token binding) exists.\n if (route === '/handshake' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n const capabilities: ProtocolCapabilities = handler.getCapabilities();\n const handlerInfo = handler.getHandlerInfo?.();\n sendJson(res, 200, { capabilities, handler: handlerInfo || undefined });\n return;\n }\n\n // Message\n if (route === '/message' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n const context = buildStubContext(parsed.context);\n\n try {\n const response = await handler.handleMessage(parsed.request, context);\n sendJson(res, 200, response);\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, {\n error: 'Handler error',\n message: err?.message || 'Unknown error',\n });\n }\n return;\n }\n\n // Message (streaming via SSE)\n if (route === '/message/stream' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n if (!handler.handleStream) {\n // Fallback: use non-streaming handleMessage, send as single SSE burst\n const context = buildStubContext(parsed.context);\n try {\n const response = await handler.handleMessage(parsed.request, context);\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n sendSSE('connected', { sessionId: response.sessionId });\n sendSSE('text', { type: 'text', content: response.message, sessionId: response.sessionId });\n if (response.metadata) {\n sendSSE('metadata', { type: 'metadata', content: response.metadata, sessionId: response.sessionId });\n }\n if (response.form) {\n sendSSE('form', { type: 'form', content: response.form, sessionId: response.sessionId });\n }\n sendSSE('status', { type: 'status', content: response.sessionStatus, sessionId: response.sessionId });\n sendSSE('done', { sessionId: response.sessionId });\n res.end();\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, { error: 'Handler error', message: err?.message || 'Unknown error' });\n }\n return;\n }\n\n const context = buildStubContext(parsed.context);\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n\n const sessionId = parsed.request.sessionId || `ext-${Date.now()}`;\n sendSSE('connected', { sessionId });\n\n try {\n for await (const chunk of handler.handleStream(parsed.request, context)) {\n sendSSE(chunk.type, {\n type: chunk.type,\n content: chunk.content,\n sessionId,\n });\n }\n sendSSE('done', { sessionId });\n } catch (err: any) {\n console.error('[firststep] Stream error:', err);\n sendSSE('error', { code: 'STREAM_ERROR', message: err?.message || 'Unknown error' });\n }\n\n res.end();\n return;\n }\n\n // 404\n sendJson(res, 404, { error: 'Not found' });\n }\n\n // Wrap in error boundary\n function requestListener(req: IncomingMessage, res: ServerResponse): void {\n handleRequest(req, res).catch((err) => {\n console.error('[firststep] Unexpected error:', err);\n if (!res.headersSent) {\n sendJson(res, 500, { error: 'Internal server error' });\n }\n });\n }\n\n const httpServer = createHttpServer(requestListener);\n\n return {\n start() {\n return new Promise<void>((resolve) => {\n httpServer.listen(port, host, () => {\n console.log(`[firststep] Handler server listening on ${host}:${port}`);\n console.log(`[firststep] Endpoints:`);\n console.log(` GET ${prefix}/health - Health check`);\n console.log(` POST ${prefix}/handshake - Capability exchange`);\n console.log(` POST ${prefix}/message - Handle chat message`);\n console.log(` POST ${prefix}/message/stream - Handle chat message (SSE stream)`);\n resolve();\n });\n });\n },\n\n stop() {\n return new Promise<void>((resolve, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n getRequestHandler() {\n return requestListener;\n },\n };\n}\n","import type { StorageContext, StorageSetOptions } from '../types.js';\n\n/**\n * HTTP-backed implementation of StorageContext.\n * All operations call back to Studio's protocol-resources API.\n * Data is scoped to the deployment via the JWT token.\n */\nexport class StorageClient implements StorageContext {\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const res = await this._post('/storage/get', { key });\n if (!res.success) return null;\n return (res.value ?? null) as T;\n }\n\n async set(key: string, value: unknown, options?: StorageSetOptions): Promise<void> {\n await this._post('/storage/set', { key, value, ttl: options?.ttl });\n }\n\n async delete(key: string): Promise<void> {\n await this._post('/storage/delete', { key });\n }\n\n async has(key: string): Promise<boolean> {\n const res = await this._post('/storage/has', { key });\n return res.exists === true;\n }\n\n async keys(prefix?: string): Promise<string[]> {\n const res = await this._post('/storage/keys', { prefix });\n return res.keys ?? [];\n }\n\n async merge(key: string, partial: Record<string, unknown>): Promise<void> {\n await this._post('/storage/merge', { key, partial });\n }\n\n async append(key: string, item: unknown): Promise<void> {\n await this._post('/storage/append', { key, item });\n }\n\n private async _post(path: string, body: unknown): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[StorageClient] ${path} failed: ${res.status} ${text}`);\n return { success: false, error: `HTTP ${res.status}` };\n }\n return res.json();\n }\n}\n","import type {\n SessionContext,\n SessionState,\n ChatMessage,\n FormData,\n FormFieldValue,\n} from '../types.js';\n\n/**\n * HTTP-backed implementation of SessionContext.\n *\n * All operations call back to Studio's protocol-resources `/session/*` API,\n * which is backed by the Redis hot-session store (protocolSession.service).\n * Session is scoped to the deployment + sessionId encoded in the JWT token, so\n * the request bodies never carry the session id themselves.\n *\n * This is the external-handler counterpart to the in-process SessionContext\n * the built-in GuidedForm engine gets via handlers/context.ts — same\n * interface, same Redis backend, reached over HTTP instead of in-process.\n */\nexport class SessionClient implements SessionContext {\n public sessionId: string;\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string, sessionId: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n this.sessionId = sessionId;\n }\n\n async getState(): Promise<SessionState> {\n const res = await this._post('/session/get-state', {});\n return reviveState(res.state);\n }\n\n async updateState(updates: Partial<SessionState>): Promise<void> {\n await this._post('/session/update-state', { updates });\n }\n\n async getHistory(): Promise<ChatMessage[]> {\n const res = await this._post('/session/get-history', {});\n return (res.messages ?? []).map(reviveMessage);\n }\n\n async saveMessage(message: ChatMessage): Promise<void> {\n await this._post('/session/save-message', { message });\n }\n\n async complete(): Promise<void> {\n await this._post('/session/complete', {});\n }\n\n async getFormData(): Promise<FormData> {\n const res = await this._post('/session/get-form-data', {});\n return (res.formData ?? {}) as FormData;\n }\n\n async updateFormField(fieldId: string, value: FormFieldValue): Promise<void> {\n await this._post('/session/update-form-field', { fieldId, value });\n }\n\n async updateFormData(fields: Record<string, FormFieldValue>): Promise<void> {\n await this._post('/session/update-form-data', { fields });\n }\n\n private async _post(path: string, body: unknown): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[SessionClient] ${path} failed: ${res.status} ${text}`);\n throw new Error(`SessionClient ${path} failed: HTTP ${res.status}`);\n }\n return res.json();\n }\n}\n\n/** JSON transport stringifies Dates; revive the envelope timestamps. */\nfunction reviveState(state: any): SessionState {\n if (!state) return state;\n if (state.createdAt) state.createdAt = new Date(state.createdAt);\n if (state.updatedAt) state.updatedAt = new Date(state.updatedAt);\n return state as SessionState;\n}\n\nfunction reviveMessage(msg: any): ChatMessage {\n if (msg?.timestamp) msg.timestamp = new Date(msg.timestamp);\n return msg as ChatMessage;\n}\n","import { createHash, createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Hash a token to derive the shared HMAC key.\n * The backend only stores SHA-256(token) and uses that hash as the HMAC key.\n * The SDK must hash the plaintext token the same way to verify signatures.\n */\nfunction deriveSigningKey(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\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 * The HMAC key is SHA-256(token), matching the backend which only stores\n * the token hash and uses it directly as the HMAC key.\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 signingKey = deriveSigningKey(token);\n const expected = createHmac('sha256', signingKey)\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 const signingKey = deriveSigningKey(token);\n return createHmac('sha256', signingKey).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":["context","sendSSE"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/resources/storageClient.ts","../src/resources/sessionClient.ts","../src/auth.ts"],"sourcesContent":["/**\n * FirstStep SDK Server\n *\n * Standalone HTTP server for protocol handlers.\n * Zero external dependencies, uses Node.js built-in `http` module.\n *\n * @example\n * ```typescript\n * import { createServer } from '@firststep-studio/sdk/server';\n * import type { ProtocolHandler } from '@firststep-studio/sdk';\n *\n * const handler: ProtocolHandler = {\n * async handleMessage(request, context) {\n * return {\n * message: 'Hello from my handler!',\n * sessionId: request.sessionId || 'new',\n * agentId: 'main',\n * sessionStatus: 'active',\n * };\n * },\n * getCapabilities() {\n * return { streaming: false, formQuestions: false, knowledgeActions: false, integrations: false };\n * },\n * };\n *\n * const server = createServer(handler, {\n * token: process.env.FIRSTSTEP_TOKEN!,\n * port: 3001,\n * });\n *\n * server.start();\n * ```\n */\n\nimport { createServer as createHttpServer, IncomingMessage, ServerResponse } from 'http';\nimport type {\n ProtocolHandler,\n ProtocolRequest,\n ProtocolContext,\n ProtocolCapabilities,\n ProtocolStreamChunk,\n StorageContext,\n} from './types';\nimport { StorageClient } from './resources/storageClient.js';\nimport { SessionClient } from './resources/sessionClient.js';\nimport { verifyRequestSignature } from './auth';\n\n// ============================================\n// Configuration\n// ============================================\n\nexport interface ServerConfig {\n /** API token (FIRSTSTEP_TOKEN). Used for request signature verification. */\n token: string;\n\n /** Port to listen on. Defaults to 3001, or the PORT env variable. */\n port?: number;\n\n /** Host to bind to. Defaults to '0.0.0.0'. */\n host?: string;\n\n /** Skip signature verification (for local development only). */\n skipSignatureVerification?: boolean;\n\n /**\n * URL prefix for all endpoints.\n * Example: '/ucp/v1' makes endpoints available at /ucp/v1/handshake, /ucp/v1/message, etc.\n */\n prefix?: string;\n}\n\n// ============================================\n// Server Instance\n// ============================================\n\nexport interface FirstStepServer {\n /** Start the server */\n start(): Promise<void>;\n\n /** Stop the server gracefully */\n stop(): Promise<void>;\n\n /**\n * Get the request handler function.\n * Use this to integrate with Express, Fastify, or other frameworks.\n *\n * @example\n * ```typescript\n * // Express\n * const server = createServer(handler, { token: '...' });\n * app.post('/handshake', server.getRequestHandler());\n * app.post('/message', server.getRequestHandler());\n * ```\n */\n getRequestHandler(): (req: IncomingMessage, res: ServerResponse) => void;\n}\n\n// ============================================\n// Internal Helpers\n// ============================================\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n req.on('error', reject);\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json),\n 'Access-Control-Allow-Origin': '*',\n });\n res.end(json);\n}\n\nfunction parsePath(url: string | undefined): string {\n if (!url) return '/';\n const idx = url.indexOf('?');\n return idx >= 0 ? url.substring(0, idx) : url;\n}\n\n/**\n * Build a stub ProtocolContext for external handlers.\n *\n * External handlers receive context data as plain JSON from the platform.\n * The stub provides no-op implementations for methods like session.getState()\n * since those will be handled by callback APIs to the FirstStep backend.\n *\n * When the platform sends a /message request, the request body includes\n * a `context` field with serialized context data. This function merges\n * that data with no-op method stubs.\n */\nfunction buildStubContext(data?: any, requestSessionId?: string): ProtocolContext {\n const noopAsync = async () => ({} as any);\n const noop = () => {};\n\n // Detect _resources injected by Studio proxy for HTTP-backed resource clients\n const resources = data?._resources as { baseUrl: string; token: string; capabilities?: Record<string, boolean> } | undefined;\n let storage: StorageContext | undefined;\n\n if (resources?.baseUrl && resources?.token && resources.capabilities?.storage !== false) {\n storage = new StorageClient(resources.baseUrl, resources.token);\n }\n\n // ctx.session over HTTP: real (Redis-backed, ephemeral) session state for\n // external handlers, replacing the noop stub. Requires the Studio proxy to\n // opt in via capabilities.session and the request to carry a sessionId.\n let session: ProtocolContext['session'] | undefined;\n if (resources?.baseUrl && resources?.token && resources.capabilities?.session === true && requestSessionId) {\n session = new SessionClient(resources.baseUrl, resources.token, requestSessionId);\n }\n\n return {\n session: session ?? {\n sessionId: requestSessionId || '',\n getState: noopAsync,\n updateState: noopAsync,\n getHistory: async () => [],\n saveMessage: noopAsync,\n complete: noopAsync,\n getFormData: async () => ({}),\n updateFormField: noopAsync,\n updateFormData: noopAsync,\n ...data?.session,\n },\n knowledge: {\n queryDatabase: noopAsync,\n searchPages: noopAsync,\n ...data?.knowledge,\n },\n integrations: {\n execute: noopAsync,\n ...data?.integrations,\n },\n storage,\n analytics: {\n logActionExecuted: noop,\n logInteraction: noop,\n logCustomEvent: noop,\n ...data?.analytics,\n },\n logger: {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n logRouting: noop,\n logToolUse: noop,\n ...data?.logger,\n },\n deployment: {\n id: '',\n slug: '',\n name: '',\n protocolType: 'external',\n ...data?.deployment,\n },\n chatbot: data?.chatbot,\n };\n}\n\n// ============================================\n// createServer\n// ============================================\n\n/**\n * Create a standalone HTTP server for a protocol handler.\n *\n * The server exposes three endpoints:\n * - `GET /health` - Health check (always 200)\n * - `POST /handshake` - Returns handler capabilities (signature verified)\n * - `POST /message` - Handles a chat message (signature verified)\n */\nexport function createServer(\n handler: ProtocolHandler,\n config: ServerConfig\n): FirstStepServer {\n const {\n token,\n port = parseInt(process.env.PORT || '3001', 10),\n host = '0.0.0.0',\n skipSignatureVerification = false,\n prefix: rawPrefix = '',\n } = config;\n\n // Normalize prefix: ensure leading slash, no trailing slash\n const prefix = rawPrefix ? ('/' + rawPrefix.replace(/^\\/|\\/$/g, '')) : '';\n\n // Validate config\n if (!token && !skipSignatureVerification) {\n throw new Error(\n 'FIRSTSTEP_TOKEN is required. Pass it via config.token or set skipSignatureVerification for local dev.'\n );\n }\n\n /**\n * Core request router\n */\n async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const path = parsePath(req.url);\n const method = (req.method || 'GET').toUpperCase();\n\n // CORS preflight\n if (method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-FirstStep-Signature',\n 'Access-Control-Max-Age': '86400',\n });\n res.end();\n return;\n }\n\n // Strip prefix to get the remaining path\n const afterPrefix = prefix && path.startsWith(prefix) ? path.slice(prefix.length) || '/' : path;\n\n // Extract optional config slug from path: /{slug}/message -> slug = \"slug\", route = \"/message\"\n // If no slug segment, route is the full remaining path (backward compatible)\n let configSlug: string | undefined;\n let route: string;\n const slugMatch = afterPrefix.match(/^\\/([^/]+)(\\/(?:health|handshake|message(?:\\/stream)?))?$/);\n if (slugMatch && slugMatch[2]) {\n // Path has a slug segment: /{slug}/{endpoint}\n configSlug = slugMatch[1];\n route = slugMatch[2];\n } else {\n // No slug: /{endpoint} (backward compatible)\n route = afterPrefix;\n }\n\n // Health check\n if (route === '/health' && method === 'GET') {\n sendJson(res, 200, { status: 'ok', timestamp: Date.now() });\n return;\n }\n\n // Handshake\n // Signature is verified when present, but not required.\n // This allows the frontend to probe handler capabilities before\n // a project (and its API token binding) exists.\n if (route === '/handshake' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n const capabilities: ProtocolCapabilities = handler.getCapabilities();\n const handlerInfo = handler.getHandlerInfo?.();\n sendJson(res, 200, { capabilities, handler: handlerInfo || undefined });\n return;\n }\n\n // Message\n if (route === '/message' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n const context = buildStubContext(parsed.context, parsed.request?.sessionId);\n\n try {\n const response = await handler.handleMessage(parsed.request, context);\n sendJson(res, 200, response);\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, {\n error: 'Handler error',\n message: err?.message || 'Unknown error',\n });\n }\n return;\n }\n\n // Message (streaming via SSE)\n if (route === '/message/stream' && method === 'POST') {\n const body = await readBody(req);\n\n if (!skipSignatureVerification) {\n const signature = req.headers['x-firststep-signature'] as string;\n if (signature && !verifyRequestSignature(token, body, signature)) {\n sendJson(res, 401, { error: 'Invalid signature' });\n return;\n }\n }\n\n let parsed: { request: ProtocolRequest; context?: Partial<ProtocolContext> };\n try {\n parsed = JSON.parse(body);\n } catch {\n sendJson(res, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n // Inject config slug from URL path into request\n if (configSlug) {\n parsed.request.configSlug = configSlug;\n }\n\n if (!handler.handleStream) {\n // Fallback: use non-streaming handleMessage, send as single SSE burst\n const context = buildStubContext(parsed.context, parsed.request?.sessionId);\n try {\n const response = await handler.handleMessage(parsed.request, context);\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n sendSSE('connected', { sessionId: response.sessionId });\n sendSSE('text', { type: 'text', content: response.message, sessionId: response.sessionId });\n if (response.metadata) {\n sendSSE('metadata', { type: 'metadata', content: response.metadata, sessionId: response.sessionId });\n }\n if (response.form) {\n sendSSE('form', { type: 'form', content: response.form, sessionId: response.sessionId });\n }\n sendSSE('status', { type: 'status', content: response.sessionStatus, sessionId: response.sessionId });\n sendSSE('done', { sessionId: response.sessionId });\n res.end();\n } catch (err: any) {\n console.error('[firststep] Handler error:', err);\n sendJson(res, 500, { error: 'Handler error', message: err?.message || 'Unknown error' });\n }\n return;\n }\n\n const context = buildStubContext(parsed.context, parsed.request?.sessionId);\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n\n const sendSSE = (event: string, data: unknown) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n\n const sessionId = parsed.request.sessionId || `ext-${Date.now()}`;\n sendSSE('connected', { sessionId });\n\n try {\n for await (const chunk of handler.handleStream(parsed.request, context)) {\n sendSSE(chunk.type, {\n type: chunk.type,\n content: chunk.content,\n sessionId,\n });\n }\n sendSSE('done', { sessionId });\n } catch (err: any) {\n console.error('[firststep] Stream error:', err);\n sendSSE('error', { code: 'STREAM_ERROR', message: err?.message || 'Unknown error' });\n }\n\n res.end();\n return;\n }\n\n // 404\n sendJson(res, 404, { error: 'Not found' });\n }\n\n // Wrap in error boundary\n function requestListener(req: IncomingMessage, res: ServerResponse): void {\n handleRequest(req, res).catch((err) => {\n console.error('[firststep] Unexpected error:', err);\n if (!res.headersSent) {\n sendJson(res, 500, { error: 'Internal server error' });\n }\n });\n }\n\n const httpServer = createHttpServer(requestListener);\n\n return {\n start() {\n return new Promise<void>((resolve) => {\n httpServer.listen(port, host, () => {\n console.log(`[firststep] Handler server listening on ${host}:${port}`);\n console.log(`[firststep] Endpoints:`);\n console.log(` GET ${prefix}/health - Health check`);\n console.log(` POST ${prefix}/handshake - Capability exchange`);\n console.log(` POST ${prefix}/message - Handle chat message`);\n console.log(` POST ${prefix}/message/stream - Handle chat message (SSE stream)`);\n resolve();\n });\n });\n },\n\n stop() {\n return new Promise<void>((resolve, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n getRequestHandler() {\n return requestListener;\n },\n };\n}\n","import type { StorageContext, StorageSetOptions } from '../types.js';\n\n/**\n * HTTP-backed implementation of StorageContext.\n * All operations call back to Studio's protocol-resources API.\n * Data is scoped to the deployment via the JWT token.\n */\nexport class StorageClient implements StorageContext {\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const res = await this._post('/storage/get', { key });\n if (!res.success) return null;\n return (res.value ?? null) as T;\n }\n\n async set(key: string, value: unknown, options?: StorageSetOptions): Promise<void> {\n await this._post('/storage/set', { key, value, ttl: options?.ttl });\n }\n\n async delete(key: string): Promise<void> {\n await this._post('/storage/delete', { key });\n }\n\n async has(key: string): Promise<boolean> {\n const res = await this._post('/storage/has', { key });\n return res.exists === true;\n }\n\n async keys(prefix?: string): Promise<string[]> {\n const res = await this._post('/storage/keys', { prefix });\n return res.keys ?? [];\n }\n\n async merge(key: string, partial: Record<string, unknown>): Promise<void> {\n await this._post('/storage/merge', { key, partial });\n }\n\n async append(key: string, item: unknown): Promise<void> {\n await this._post('/storage/append', { key, item });\n }\n\n private async _post(path: string, body: unknown): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[StorageClient] ${path} failed: ${res.status} ${text}`);\n return { success: false, error: `HTTP ${res.status}` };\n }\n return res.json();\n }\n}\n","import type {\n SessionContext,\n SessionState,\n ChatMessage,\n FormData,\n FormFieldValue,\n} from '../types.js';\n\n/**\n * HTTP-backed implementation of SessionContext.\n *\n * External handlers (running out-of-process, reached over /message) get a\n * noop session stub by default; this client makes ctx.session real for them\n * by calling back to Studio's protocol-resources API, which fronts the same\n * Redis-backed ProtocolSessionService the in-process handlers use. Session\n * state is ephemeral by design (sliding TTL in Redis, never durably stored);\n * durable data belongs in ctx.storage.\n */\nexport class SessionClient implements SessionContext {\n readonly sessionId: string;\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string, sessionId: string) {\n this.baseUrl = baseUrl;\n this.token = token;\n this.sessionId = sessionId;\n }\n\n async getState(): Promise<SessionState> {\n const res = await this._post('/session/get-state', {});\n return this._reviveState(res.state);\n }\n\n async updateState(updates: Partial<SessionState>): Promise<void> {\n await this._post('/session/update-state', { updates });\n }\n\n async getHistory(): Promise<ChatMessage[]> {\n const res = await this._post('/session/get-history', {});\n const messages = Array.isArray(res.messages) ? res.messages : [];\n return messages.map((m: any) => ({\n ...m,\n timestamp: m?.timestamp ? new Date(m.timestamp) : undefined,\n }));\n }\n\n async saveMessage(message: ChatMessage): Promise<void> {\n await this._post('/session/save-message', { message });\n }\n\n async complete(): Promise<void> {\n await this._post('/session/complete', {});\n }\n\n async getFormData(): Promise<FormData> {\n const res = await this._post('/session/get-form-data', {});\n return res.formData ?? {};\n }\n\n async updateFormField(fieldId: string, value: FormFieldValue): Promise<void> {\n await this._post('/session/update-form-field', { fieldId, value });\n }\n\n async updateFormData(fields: Record<string, FormFieldValue>): Promise<void> {\n await this._post('/session/update-form-data', { fields });\n }\n\n private _reviveState(state: any): SessionState {\n const now = new Date();\n if (!state || typeof state !== 'object') {\n return {\n sessionId: this.sessionId,\n deploymentId: '',\n formData: {},\n metadata: {},\n createdAt: now,\n updatedAt: now,\n } as SessionState;\n }\n return {\n ...state,\n createdAt: state.createdAt ? new Date(state.createdAt) : now,\n updatedAt: state.updatedAt ? new Date(state.updatedAt) : now,\n } as SessionState;\n }\n\n private async _post(path: string, body: Record<string, unknown>): Promise<any> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify({ sessionId: this.sessionId, ...body }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n console.error(`[SessionClient] ${path} failed: ${res.status} ${text}`);\n return { success: false, error: `HTTP ${res.status}` };\n }\n return res.json();\n }\n}\n","import { createHash, createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Hash a token to derive the shared HMAC key.\n * The backend only stores SHA-256(token) and uses that hash as the HMAC key.\n * The SDK must hash the plaintext token the same way to verify signatures.\n */\nfunction deriveSigningKey(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\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 * The HMAC key is SHA-256(token), matching the backend which only stores\n * the token hash and uses it directly as the HMAC key.\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 signingKey = deriveSigningKey(token);\n const expected = createHmac('sha256', signingKey)\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 const signingKey = deriveSigningKey(token);\n return createHmac('sha256', signingKey).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":["context","sendSSE"]}
@@ -195,24 +195,14 @@ interface SessionContext {
195
195
  interface SessionState {
196
196
  sessionId: string;
197
197
  deploymentId: string;
198
- metadata?: SessionMetadata;
199
- createdAt: Date;
200
- updatedAt: Date;
201
- /**
202
- * Generic per-handler state bag. Any protocol handler stores its own
203
- * turn-to-turn state here (conversation context, tracked fields, turn
204
- * counter, handoff state, risk history, etc.) without coupling the shared
205
- * SessionState shape to one handler's vocabulary. The host persists it
206
- * verbatim (Redis), so keep it JSON-serializable. Handlers should namespace
207
- * their own keys and not assume any particular structure from others.
208
- */
209
- handlerState?: Record<string, unknown>;
210
- /** GuidedForm: id of the agent/stage currently driving the turn. */
211
198
  currentAgentId?: string;
212
- /** GuidedForm: current question being asked (for proper answer tracking). */
199
+ /** Current question being asked (for proper answer tracking) */
213
200
  currentQuestionId?: string;
214
- /** GuidedForm: internal form data storage (handler-specific format). */
201
+ /** Internal form data storage (handler-specific format) */
215
202
  formData?: Record<string, unknown>;
203
+ metadata?: SessionMetadata;
204
+ createdAt: Date;
205
+ updatedAt: Date;
216
206
  }
217
207
  interface ChatMessage {
218
208
  role: 'user' | 'assistant' | 'system';
@@ -783,4 +773,4 @@ interface HandoffInboundContext {
783
773
  returnResult?: HandoffReturnPayload;
784
774
  }
785
775
 
786
- export type { SchemaQuestion as $, AgentTransitionPayload as A, Helpline as B, ChatMessage as C, ThroughlineTopicFallback as D, RoutingDecision as E, FormData as F, DeploymentInfo as G, HandoffRequestPayload as H, IntegrationContext as I, ChatbotInfo as J, KnowledgeContext as K, LoggerContext as L, ClassifierConfig as M, RoutingLog as N, SessionMetadata as O, ProtocolStreamChunk as P, AnalyticsContext as Q, RoutingClassificationPayload as R, SchemaDeclarationPayload as S, ThroughlineReference as T, InteractionEvent as U, InteractionEventType as V, FormSchema as W, FormFieldDefinition as X, FormFieldType as Y, ProtocolRegistration as Z, SchemaAgent as _, HandoffReturnPayload as a, HandoffContext as a0, HandoffOptions as a1, HandoffOfferPayload as b, StorageContext as c, StorageSetOptions as d, SessionContext as e, SessionState as f, FormFieldValue as g, ProtocolRequest as h, ProtocolResponse as i, SessionStatus as j, HandoffInboundContext as k, ProtocolForm as l, ProtocolFormField as m, ProtocolFormOption as n, ProtocolFieldValidation as o, ProtocolError as p, ProtocolHandler as q, ProtocolCapabilities as r, HandlerInfo as s, ProtocolContext as t, KnowledgeResult as u, KnowledgeMetadata as v, KnowledgeColumnMetadata as w, IntegrationResult as x, HelplineSearchOptions as y, HelplineResult as z };
776
+ export type { SchemaQuestion as $, AgentTransitionPayload as A, StorageSetOptions as B, ChatMessage as C, RoutingDecision as D, DeploymentInfo as E, ChatbotInfo as F, ClassifierConfig as G, HandoffRequestPayload as H, IntegrationContext as I, FormData as J, KnowledgeContext as K, LoggerContext as L, FormFieldValue as M, RoutingLog as N, SessionMetadata as O, ProtocolStreamChunk as P, AnalyticsContext as Q, RoutingClassificationPayload as R, SchemaDeclarationPayload as S, ThroughlineReference as T, InteractionEvent as U, InteractionEventType as V, FormSchema as W, FormFieldDefinition as X, FormFieldType as Y, ProtocolRegistration as Z, SchemaAgent as _, HandoffReturnPayload as a, HandoffContext as a0, HandoffOptions as a1, HandoffOfferPayload as b, ProtocolRequest as c, ProtocolResponse as d, SessionStatus as e, HandoffInboundContext as f, ProtocolForm as g, ProtocolFormField as h, ProtocolFormOption as i, ProtocolFieldValidation as j, ProtocolError as k, ProtocolHandler as l, ProtocolCapabilities as m, HandlerInfo as n, ProtocolContext as o, SessionContext as p, SessionState as q, KnowledgeResult as r, KnowledgeMetadata as s, KnowledgeColumnMetadata as t, IntegrationResult as u, HelplineSearchOptions as v, HelplineResult as w, Helpline as x, ThroughlineTopicFallback as y, StorageContext as z };
@@ -195,24 +195,14 @@ interface SessionContext {
195
195
  interface SessionState {
196
196
  sessionId: string;
197
197
  deploymentId: string;
198
- metadata?: SessionMetadata;
199
- createdAt: Date;
200
- updatedAt: Date;
201
- /**
202
- * Generic per-handler state bag. Any protocol handler stores its own
203
- * turn-to-turn state here (conversation context, tracked fields, turn
204
- * counter, handoff state, risk history, etc.) without coupling the shared
205
- * SessionState shape to one handler's vocabulary. The host persists it
206
- * verbatim (Redis), so keep it JSON-serializable. Handlers should namespace
207
- * their own keys and not assume any particular structure from others.
208
- */
209
- handlerState?: Record<string, unknown>;
210
- /** GuidedForm: id of the agent/stage currently driving the turn. */
211
198
  currentAgentId?: string;
212
- /** GuidedForm: current question being asked (for proper answer tracking). */
199
+ /** Current question being asked (for proper answer tracking) */
213
200
  currentQuestionId?: string;
214
- /** GuidedForm: internal form data storage (handler-specific format). */
201
+ /** Internal form data storage (handler-specific format) */
215
202
  formData?: Record<string, unknown>;
203
+ metadata?: SessionMetadata;
204
+ createdAt: Date;
205
+ updatedAt: Date;
216
206
  }
217
207
  interface ChatMessage {
218
208
  role: 'user' | 'assistant' | 'system';
@@ -783,4 +773,4 @@ interface HandoffInboundContext {
783
773
  returnResult?: HandoffReturnPayload;
784
774
  }
785
775
 
786
- export type { SchemaQuestion as $, AgentTransitionPayload as A, Helpline as B, ChatMessage as C, ThroughlineTopicFallback as D, RoutingDecision as E, FormData as F, DeploymentInfo as G, HandoffRequestPayload as H, IntegrationContext as I, ChatbotInfo as J, KnowledgeContext as K, LoggerContext as L, ClassifierConfig as M, RoutingLog as N, SessionMetadata as O, ProtocolStreamChunk as P, AnalyticsContext as Q, RoutingClassificationPayload as R, SchemaDeclarationPayload as S, ThroughlineReference as T, InteractionEvent as U, InteractionEventType as V, FormSchema as W, FormFieldDefinition as X, FormFieldType as Y, ProtocolRegistration as Z, SchemaAgent as _, HandoffReturnPayload as a, HandoffContext as a0, HandoffOptions as a1, HandoffOfferPayload as b, StorageContext as c, StorageSetOptions as d, SessionContext as e, SessionState as f, FormFieldValue as g, ProtocolRequest as h, ProtocolResponse as i, SessionStatus as j, HandoffInboundContext as k, ProtocolForm as l, ProtocolFormField as m, ProtocolFormOption as n, ProtocolFieldValidation as o, ProtocolError as p, ProtocolHandler as q, ProtocolCapabilities as r, HandlerInfo as s, ProtocolContext as t, KnowledgeResult as u, KnowledgeMetadata as v, KnowledgeColumnMetadata as w, IntegrationResult as x, HelplineSearchOptions as y, HelplineResult as z };
776
+ export type { SchemaQuestion as $, AgentTransitionPayload as A, StorageSetOptions as B, ChatMessage as C, RoutingDecision as D, DeploymentInfo as E, ChatbotInfo as F, ClassifierConfig as G, HandoffRequestPayload as H, IntegrationContext as I, FormData as J, KnowledgeContext as K, LoggerContext as L, FormFieldValue as M, RoutingLog as N, SessionMetadata as O, ProtocolStreamChunk as P, AnalyticsContext as Q, RoutingClassificationPayload as R, SchemaDeclarationPayload as S, ThroughlineReference as T, InteractionEvent as U, InteractionEventType as V, FormSchema as W, FormFieldDefinition as X, FormFieldType as Y, ProtocolRegistration as Z, SchemaAgent as _, HandoffReturnPayload as a, HandoffContext as a0, HandoffOptions as a1, HandoffOfferPayload as b, ProtocolRequest as c, ProtocolResponse as d, SessionStatus as e, HandoffInboundContext as f, ProtocolForm as g, ProtocolFormField as h, ProtocolFormOption as i, ProtocolFieldValidation as j, ProtocolError as k, ProtocolHandler as l, ProtocolCapabilities as m, HandlerInfo as n, ProtocolContext as o, SessionContext as p, SessionState as q, KnowledgeResult as r, KnowledgeMetadata as s, KnowledgeColumnMetadata as t, IntegrationResult as u, HelplineSearchOptions as v, HelplineResult as w, Helpline as x, ThroughlineTopicFallback as y, StorageContext as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firststep-studio/sdk",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "SDK for building protocol handlers that integrate with FirstStep Studio",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",