@firststep-studio/sdk 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -285,743 +285,814 @@ declare function classifyWithUCP(endpoint: string, messages: ChatMessage[], opti
285
285
  }): Promise<ClassificationResult>;
286
286
 
287
287
  /**
288
- * Render Markers
289
- *
290
- * Utilities for building render markers that FirstStep Studio's frontend
291
- * can parse and display as rich UI components (cards, alerts, forms, etc.).
288
+ * Platform Client
292
289
  *
293
- * This module is the single source of truth for render marker specs.
294
- * - Handlers (producers) use the builder functions to emit markers
295
- * - Studio frontend (consumer) uses the types and MARKER_TYPES constants to parse them
290
+ * SDK client for handlers to call back to FirstStep Studio.
291
+ * Enables handlers to query platform data (chatbot list, etc.)
292
+ * using the same API token used for request signature verification.
296
293
  *
297
294
  * @example
298
295
  * ```typescript
299
- * import { renderMarkers } from '@firststep-studio/sdk';
296
+ * import { createPlatformClient } from '@firststep-studio/sdk';
300
297
  *
301
- * // In your handleStream():
302
- * yield {
303
- * type: 'text',
304
- * content: renderMarkers.helplineCard({
305
- * helplines: [{ name: '988 Lifeline', phoneNumber: '988', categories: ['crisis'], status: 'open', statusLabel: 'Available' }],
306
- * }),
307
- * };
298
+ * const client = createPlatformClient({
299
+ * studioUrl: 'https://studio-api.example.com',
300
+ * token: process.env.FIRSTSTEP_TOKEN,
301
+ * });
302
+ *
303
+ * const chatbots = await client.listChatbots();
304
+ * // [{ id: '...', name: 'Safety Navigator', configSlug: 'safety-nav' }, ...]
308
305
  * ```
309
306
  */
310
- declare const MARKER_TYPES: {
311
- readonly HELPLINE_CARD: "RENDER_HELPLINE_CARD";
312
- readonly EMERGENCY: "RENDER_EMERGENCY";
313
- readonly RESOURCE_CARD: "RENDER_RESOURCE_CARD";
314
- readonly PROVIDER_CARD: "RENDER_PROVIDER_CARD";
315
- readonly SAFETY_PLAN: "RENDER_SAFETY_PLAN";
316
- readonly REPORT_CARD: "RENDER_REPORT_CARD";
317
- };
318
- type MarkerType = typeof MARKER_TYPES[keyof typeof MARKER_TYPES];
319
- /**
320
- * Helpline card payload.
321
- * Renders a carousel of helpline cards with contact buttons (call, text, chat, WhatsApp).
322
- */
323
- interface HelplineCardPayload {
324
- helplines: HelplineCardItem[];
325
- type?: 'throughline' | 'throughline_fallback' | 'stage';
326
- fallback?: {
327
- message: string;
328
- linkText?: string;
329
- linkUrl?: string;
330
- icon?: string;
331
- topic?: string;
332
- topics?: string[];
333
- };
307
+ interface PlatformClientConfig {
308
+ /** Studio backend API URL (e.g. https://studio-api.example.com) */
309
+ studioUrl: string;
310
+ /** API token (same as FIRSTSTEP_TOKEN used for signature verification) */
311
+ token: string;
312
+ /** Request timeout in ms (default: 10000) */
313
+ timeout?: number;
334
314
  }
335
- interface HelplineCardItem {
336
- /**
337
- * Stable helpline id from the throughline API. Lets consumers count
338
- * distinct helplines across display/click events; name-keyed tracking
339
- * cannot. Optional: older producers omit it.
340
- */
341
- id?: string;
315
+ interface PlatformOrganization {
316
+ /** Studio organization ID */
317
+ id: string;
318
+ /** Display name (null if the organization record has been deleted) */
319
+ name: string | null;
320
+ /** URL-safe slug */
321
+ slug: string | null;
322
+ }
323
+ interface PlatformTokenInfo {
324
+ /** API token ID */
325
+ id: string;
326
+ /** Human-friendly token label */
327
+ name: string;
328
+ /** Authorized scopes */
329
+ scopes: string[];
330
+ /** Project IDs this token is scoped to (null = full org access) */
331
+ projectIds: string[] | null;
332
+ }
333
+ interface PlatformIdentity {
334
+ organization: PlatformOrganization;
335
+ token: PlatformTokenInfo;
336
+ }
337
+ interface PlatformChatbotRegisterInput {
338
+ /** Stable handler-side identifier used to upsert (e.g. handler config ID). */
339
+ externalId: string;
340
+ /** Display name shown in Studio's chatbot list. */
342
341
  name: string;
342
+ /** Optional human description. */
343
343
  description?: string;
344
- categories: string[];
345
- status: 'open' | 'closed';
346
- statusLabel: string;
347
- statusBadge?: string;
348
- hoursText?: string;
349
- supportTypes?: string;
350
- smsNumber?: string;
351
- phoneNumber?: string;
352
- website?: string;
353
- webchat?: string;
354
- whatsapp?: string;
355
- specialties?: string[];
356
- highlightedTag?: string;
357
- verified?: boolean;
344
+ /** Public URL where Studio should send protocol requests. */
345
+ handlerUrl: string;
346
+ /** Slug for multi-config routing on the handler side. */
347
+ configSlug?: string;
348
+ /** Optional capability flags advertised to Studio. */
349
+ capabilities?: {
350
+ streaming?: boolean;
351
+ formQuestions?: boolean;
352
+ knowledgeActions?: boolean;
353
+ integrations?: boolean;
354
+ [key: string]: unknown;
355
+ };
358
356
  }
359
- /**
360
- * Emergency number payload.
361
- * Renders a prominent alert card with a large call button.
362
- */
363
- interface EmergencyPayload {
364
- number: string;
365
- countryName?: string;
366
- countryCode?: string;
357
+ interface PlatformChatbotRegistration {
358
+ /** Studio chatbot MongoDB ID (immutable across re-registrations). */
359
+ id: string;
360
+ /** Echoes back the externalId used as upsert key. */
361
+ externalId: string;
362
+ /** Resolved name as stored in Studio. */
363
+ name: string;
364
+ protocolConfig: Record<string, unknown>;
367
365
  }
368
- /**
369
- * Resource card payload.
370
- * Renders a carousel of resource cards with video thumbnails, tags, and visit buttons.
371
- */
372
- interface ResourceCardPayload {
373
- resources: ResourceCardItem[];
366
+ interface PlatformChatbot {
367
+ /** Studio chatbot MongoDB ID */
368
+ id: string;
369
+ /** Chatbot display name */
370
+ name: string;
371
+ /** Handler URL (if external handler configured) */
372
+ handlerUrl?: string;
373
+ /** Config slug for multi-config routing */
374
+ configSlug?: string;
375
+ /** Handler type */
376
+ handlerType?: 'builtin' | 'external';
374
377
  }
375
- interface ResourceCardItem {
378
+ interface PlatformKnowledgeSummary {
379
+ /** Studio knowledge MongoDB ID */
380
+ id: string;
381
+ /** Knowledge name */
376
382
  name: string;
377
- url?: string;
383
+ /** Optional description */
378
384
  description?: string;
379
- video_url?: string;
385
+ /** Knowledge type (e.g. 'text', 'database') */
380
386
  type?: string;
381
- tags?: string[];
382
- highlightedTag?: string;
387
+ /** Owning organization ID. Null for org-less system documents. */
388
+ organizationId: string | null;
389
+ /**
390
+ * True for handler-managed documents created through the protocol
391
+ * write API. System-owned knowledge is hidden from every Studio UI
392
+ * surface and only appears in `list` when `includeSystemOwned` is set.
393
+ */
394
+ isSystemOwned?: boolean;
395
+ /** Whether semantic search (record embedding) is enabled. */
396
+ ragEnabled?: boolean;
397
+ /** Timestamps */
398
+ createdAt?: number | string;
399
+ updatedAt?: number | string;
383
400
  }
384
401
  /**
385
- * Provider card payload.
386
- * Renders a carousel of provider directory cards with specialty/language tags and contact info.
402
+ * Full knowledge document (shape mirrors the underlying mongoose doc).
403
+ * Kept loose because the schema is large and varies by knowledge `type`;
404
+ * narrow types can be added incrementally as callers need them.
387
405
  */
388
- interface ProviderCardPayload {
389
- providers: ProviderCardItem[];
406
+ type PlatformKnowledge = PlatformKnowledgeSummary & Record<string, unknown>;
407
+ interface ListKnowledgeOptions {
408
+ /**
409
+ * Target organization to list knowledge from. If omitted, defaults to
410
+ * the org associated with the SDK token. Use this to read from a
411
+ * different org (e.g. a platform-operated shared library marked
412
+ * `publicReadable` in Studio).
413
+ */
414
+ orgId?: string;
415
+ /**
416
+ * Include system-owned (handler-managed) knowledge in the listing.
417
+ * Only honored when listing the token's own org; other orgs never
418
+ * expose their system-owned documents.
419
+ */
420
+ includeSystemOwned?: boolean;
421
+ /**
422
+ * 'system' lists ORG-LESS platform documents instead of org
423
+ * knowledge. orgId and includeSystemOwned are ignored in that mode.
424
+ */
425
+ scope?: 'system';
390
426
  }
391
- interface ProviderCardItem {
427
+ /** Sheet definition for `knowledge.create`. */
428
+ interface CreateKnowledgeSheet {
429
+ /** Stable sheet id, referenced later by record writes and search. */
392
430
  id: string;
393
431
  name: string;
394
- type?: string;
395
- specialty: string[];
396
- language: string[];
397
432
  description?: string;
398
- location?: string;
399
- open_hours?: string;
400
- contact_phone?: string;
401
- contact_email?: string;
402
- address?: string;
403
- website?: string;
433
+ /**
434
+ * Column definitions ({ id, name, type, ... }). Loose-typed; the
435
+ * Studio schema validates `type` against its column-type enum.
436
+ */
437
+ columns?: Array<Record<string, unknown>>;
404
438
  }
405
- /**
406
- * Safety plan payload.
407
- * Renders a multi-section table with expandable sections and save/export actions.
408
- */
409
- interface SafetyPlanPayload {
410
- sections: SafetyPlanSection[];
411
- helplines?: Array<{
412
- name: string;
413
- phone?: string;
414
- }>;
415
- actions?: {
416
- savePng?: boolean;
417
- saveTxt?: boolean;
418
- copy?: boolean;
419
- };
439
+ interface CreateKnowledgeOptions {
440
+ description?: string;
441
+ /**
442
+ * Enable semantic search at creation time. Defaults to false; can be
443
+ * flipped later with `setRagEnabled` (which also backfills embeddings
444
+ * for records inserted while the flag was off).
445
+ */
446
+ ragEnabled?: boolean;
447
+ /**
448
+ * Create the knowledge as an ORG-LESS platform document: no
449
+ * organizationId at all, structurally invisible to every org-scoped
450
+ * Studio surface. This is the home for handler-managed data. Without
451
+ * it the document is created under the token's org (hidden from the
452
+ * UI by isSystemOwned, but org-bound).
453
+ */
454
+ system?: boolean;
420
455
  }
421
- interface SafetyPlanSection {
456
+ /** A raw record as returned by the protocol record-write endpoints. */
457
+ interface PlatformKnowledgeRecord {
422
458
  id: string;
423
- title: string;
424
- items: Array<string | {
425
- name: string;
426
- phone?: string;
427
- description?: string;
428
- }>;
429
- }
430
- /**
431
- * Report card payload.
432
- * Renders a summary card of collected report data with a submit button.
433
- */
434
- interface ReportCardPayload {
435
- topic: string;
436
- location?: string;
437
- description?: string;
438
- perpetrator_known?: boolean;
439
- contact_mode?: string;
440
- contact_value?: string;
441
- submitEndpoint?: string;
459
+ knowledgeId: string;
460
+ sheetId: string;
461
+ data: Record<string, unknown>;
462
+ createdAt?: number | string;
463
+ updatedAt?: number | string;
442
464
  }
443
- /**
444
- * The marker families that constitute a *recommendation*: the handler put
445
- * something in front of the user and expects them to act on it. Safety plans
446
- * and report cards are excluded: they summarise what the user already said
447
- * rather than recommending anything, so rating their "satisfaction" would
448
- * measure a different thing and drag the average with it.
449
- */
450
- type RecommendationKind = 'helpline' | 'resource' | 'provider' | 'emergency';
451
- /**
452
- * One recommended thing, flattened out of a card carousel.
453
- *
454
- * A single marker usually carries several of these (a helpline carousel is
455
- * one marker, five helplines), and each one is separately good or bad for the
456
- * user in front of it, so the unit here is the item, not the card.
457
- */
458
- interface RecommendedItem {
459
- kind: RecommendationKind;
460
- /**
461
- * Stable producer-side id when there is one. Lets a consumer tally the same
462
- * helpline or provider across sessions; label-keyed tallies cannot, because
463
- * two helplines can share a display name across countries.
464
- */
465
- id?: string;
466
- /** The card's headline, as the user reads it. */
467
- label: string;
465
+ interface KnowledgeSearchOptions {
466
+ /** Max records to return (server-side clamped to 1..50, default 10). */
467
+ limit?: number;
468
468
  /**
469
- * One line of gist for a consumer that never sees the rendered card: an
470
- * evaluator, a log, a transcript export.
469
+ * Optional sheet filter. Pass a sheet id (from the knowledge document)
470
+ * to restrict the search to that sheet. Omit to search across all
471
+ * sheets in the knowledge.
471
472
  */
472
- detail?: string;
473
+ sheetId?: string;
473
474
  }
474
475
  /**
475
- * Pull one marker's JSON payload out of a message.
476
- *
477
- * Tolerates whitespace and newlines between the tags and the payload, because
478
- * producers differ on that: the SDK builders emit no padding, the Studio
479
- * frontend's own helpline blocks emit `\n`.
480
- */
481
- declare function parseMarkerPayload<T = unknown>(content: string, type: string): T | null;
482
- /**
483
- * Normalise every recommendation marker in an assistant message into a flat
484
- * list of recommended items.
485
- *
486
- * This is the read side of the builders above, and it lives here for the same
487
- * reason they do: the marker spec should have one owner. Consumers that want
488
- * to know "what did this turn recommend" (evaluators, analytics, transcript
489
- * exporters) otherwise each grow their own regex, and they drift. One reads
490
- * `payload.title` off a resource card that has never had one.
491
- */
492
- declare function extractRecommendedItems(content: string): RecommendedItem[];
493
- /**
494
- * Render marker builder functions.
495
- *
496
- * Each function takes a typed payload and returns a render marker string
497
- * that can be yielded as a `text` chunk in handleStream().
498
- *
499
- * @example
500
- * ```typescript
501
- * // Emit helpline cards
502
- * yield { type: 'text', content: renderMarkers.helplineCard({ helplines: [...] }) };
503
- *
504
- * // Emit emergency number
505
- * yield { type: 'text', content: renderMarkers.emergency({ number: '911', countryName: 'United States' }) };
506
- * ```
476
+ * A single hit from `client.knowledge.search`. `score` is the Atlas
477
+ * vector similarity score (cosine in our setup, so higher is closer).
478
+ * `data` mirrors the underlying record fields (column id -> value).
507
479
  */
508
- declare const renderMarkers: {
509
- /** Build a helpline card carousel marker */
510
- helplineCard: (payload: HelplineCardPayload) => string;
511
- /** Build an emergency number alert marker */
512
- emergency: (payload: EmergencyPayload) => string;
513
- /** Build a resource card carousel marker */
514
- resourceCard: (payload: ResourceCardPayload) => string;
515
- /** Build a provider card carousel marker */
516
- providerCard: (payload: ProviderCardPayload) => string;
517
- /** Build a safety plan artifact marker */
518
- safetyPlan: (payload: SafetyPlanPayload) => string;
519
- /** Build a report draft card marker */
520
- reportCard: (payload: ReportCardPayload) => string;
521
- };
522
-
480
+ interface PlatformKnowledgeSearchHit {
481
+ id: string;
482
+ knowledgeId: string;
483
+ sheetId: string;
484
+ data: Record<string, unknown>;
485
+ score: number;
486
+ createdAt?: number | string;
487
+ updatedAt?: number | string;
488
+ }
523
489
  /**
524
- * Stream Metadata Builders
525
- *
526
- * Utilities for building typed metadata stream chunks that FirstStep Studio's
527
- * proxy persists to MongoDB for Dashboard features (Form Insights, Session
528
- * History, Routing Logs, Agent Transitions).
529
- *
530
- * Each function returns a ready-to-yield ProtocolStreamChunk. No need to
531
- * construct the `{ type, content }` wrapper manually.
532
- *
533
- * @example
534
- * ```typescript
535
- * import { streamMetadata } from '@firststep-studio/sdk';
536
- *
537
- * // In your handleStream():
538
- *
539
- * // 1. Welcome path: declare your schema once
540
- * yield streamMetadata.declareSchema({
541
- * agents: [{ id: 'intake', title: 'Intake', order: 0 }],
542
- * questions: [{ id: 'name', agentId: 'intake', title: 'Name', type: 'text' }],
543
- * });
544
- *
545
- * // 2. After each turn: send collected field values
546
- * yield streamMetadata.formDataUpdate({ name: 'Alice', age: '28' });
547
- *
548
- * // 3. On stage change: signal an agent transition
549
- * yield streamMetadata.agentTransition({ id: 'support', title: 'Support' });
550
- *
551
- * // 4. On classification: send routing result
552
- * yield streamMetadata.routingResult({
553
- * decision: 'classified',
554
- * reason: 'User mentioned self-harm',
555
- * category: 'crisis',
556
- * level: 'high',
557
- * score: 92,
558
- * });
559
- * ```
490
+ * Helplines query params — passed verbatim to Throughline. Loose-typed
491
+ * so the SDK stays compatible with new Throughline query keys without
492
+ * a release. Required by Throughline today: `country_code`. Common
493
+ * optional keys: `topic`, `contact_method`, `limit`.
560
494
  */
561
-
562
- declare const streamMetadata: {
563
- /**
564
- * Declare the form schema (agents + questions) for Dashboard Form Insights.
565
- *
566
- * Yield this once during session initialization (welcome message).
567
- * The Studio proxy stores it in `chatbot.externalSchema` so that
568
- * Dashboard Form Insights can display question-level analytics.
569
- *
570
- * @param schema - Agents and questions your handler uses
571
- * @returns A metadata stream chunk ready to yield or queue.push
572
- */
573
- declareSchema(schema: SchemaDeclarationPayload): ProtocolStreamChunk;
495
+ type ThroughlineHelplinesQuery = Record<string, string | number | boolean | undefined>;
496
+ /** Query contract for Throughline GET /v2/helplines. */
497
+ interface ThroughlineV2HelplinesQuery {
498
+ countryCode: string;
499
+ topic?: string;
500
+ specialty?: string;
501
+ contactMethod?: 'phone' | 'sms' | 'onlineChat' | 'whatsapp' | 'tty' | 'reportingForm';
502
+ limit?: number;
503
+ priorityOnly?: boolean;
504
+ }
505
+ interface ThroughlineV2Description {
506
+ language: string;
507
+ fullDescription: string;
508
+ shortDescription?: string | null;
509
+ }
510
+ interface ThroughlineV2Country {
511
+ name: string;
512
+ code: string;
513
+ emergencyNumber?: string;
514
+ }
515
+ interface ThroughlineV2OpeningSpan {
516
+ localDayOfWeek: 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
517
+ open: string;
518
+ close: string;
519
+ }
520
+ interface ThroughlineV2ContactMethod {
521
+ type: 'phone' | 'sms' | 'onlineChat' | 'whatsapp' | 'tty' | 'reportingForm';
522
+ value: string;
523
+ accessibilityNotes?: string | null;
524
+ alwaysOpen: boolean;
525
+ /** null when the application's API tier does not expose opening hours. */
526
+ openingHours: ThroughlineV2OpeningSpan[] | null;
527
+ }
528
+ interface ThroughlineV2Helpline {
529
+ id: string;
530
+ name: string;
531
+ descriptions: ThroughlineV2Description[];
532
+ website?: string;
533
+ country: ThroughlineV2Country;
534
+ timezone: string;
535
+ primaryTopics: string[];
536
+ secondaryTopics: string[];
537
+ supportedLanguages: string[];
538
+ specialties: string[];
539
+ /** Omitted on API tiers that do not expose support types. */
540
+ supportTypes?: string[];
541
+ interpretationServiceProvided: boolean;
542
+ membershipOrganizations: string[];
543
+ contactMethods: ThroughlineV2ContactMethod[];
544
+ }
545
+ interface ThroughlineV2HelplinesResponse {
546
+ helplines: ThroughlineV2Helpline[];
547
+ }
548
+ declare class PlatformError extends Error {
549
+ status: number;
550
+ constructor(message: string, status: number);
551
+ }
552
+ declare class PlatformClient {
553
+ private studioUrl;
554
+ private token;
555
+ private timeout;
556
+ constructor(config: PlatformClientConfig);
557
+ private request;
574
558
  /**
575
- * Send collected form field values for Dashboard persistence.
576
- *
577
- * Call this after each turn when new fields are captured.
578
- * Values are incrementally merged into `ChatSession.formData`.
579
- *
580
- * @param fields - Key-value pairs of field IDs to their collected values
581
- * @returns A metadata stream chunk ready to yield or queue.push
559
+ * Resolve the organization and token metadata for the configured token.
560
+ * Useful for connection state UI ("Connected to {orgName}") and audit.
582
561
  */
583
- formDataUpdate(fields: Record<string, string | number>): ProtocolStreamChunk;
562
+ getMe(): Promise<PlatformIdentity>;
584
563
  /**
585
- * Signal an agent/stage transition for routing logs.
586
- *
587
- * The Studio proxy records this in `ChatSession.routingLogs` so
588
- * the Dashboard can show the conversation's agent flow.
589
- *
590
- * @param agent - The agent/stage being transitioned to
591
- * @returns A metadata stream chunk ready to yield or queue.push
564
+ * List chatbots accessible to this handler's organization.
565
+ * Studio resolves the organization from the API token.
592
566
  */
593
- agentTransition(agent: AgentTransitionPayload): ProtocolStreamChunk;
567
+ listChatbots(): Promise<PlatformChatbot[]>;
594
568
  /**
595
- * Send a routing/classification result for routing logs.
596
- *
597
- * The Studio proxy records this in `ChatSession.routingLogs` as a
598
- * classification event with category, level, and confidence score.
599
- *
600
- * @param result - Classification/routing decision details
601
- * @returns A metadata stream chunk ready to yield or queue.push
569
+ * Register or update an external-handler chatbot under the token's
570
+ * organization. Idempotent: identified by `externalId`, so handlers
571
+ * can call this on every config save without creating duplicates.
602
572
  */
603
- routingResult(result: RoutingClassificationPayload): ProtocolStreamChunk;
573
+ registerChatbot(input: PlatformChatbotRegisterInput): Promise<PlatformChatbotRegistration>;
604
574
  /**
605
- * Request a handoff to another chatbot.
606
- *
607
- * The Studio proxy pushes a handoff stack entry and routes subsequent
608
- * user messages to the target handler. If `options.consent` is set,
609
- * Studio stores the request as a pending offer instead.
610
- *
611
- * @param payload - Handoff target, reason, context, and options
612
- * @returns A metadata stream chunk ready to yield or queue.push
575
+ * Mark the chatbot identified by `externalId` as disconnected.
576
+ * The Studio record is preserved (history, analytics survive).
613
577
  */
614
- handoffRequest(payload: HandoffRequestPayload): ProtocolStreamChunk;
578
+ unregisterChatbot(externalId: string): Promise<{
579
+ ok: boolean;
580
+ modified: number;
581
+ }>;
615
582
  /**
616
- * Return from a handoff back to the parent handler.
583
+ * Knowledge / resource surface.
617
584
  *
618
- * The Studio proxy pops the handoff stack and delivers the return
619
- * result to the parent handler on the next user message.
585
+ * Reads are allowed when the SDK token's org matches the target org,
586
+ * OR when the target org has `publicReadable: true` on the Studio side.
620
587
  *
621
- * @param payload - Summary and artifacts from the completed handoff
622
- * @returns A metadata stream chunk ready to yield or queue.push
588
+ * Writes target system-owned (handler-managed) knowledge only: the
589
+ * protocol API stamps everything created here `isSystemOwned: true`,
590
+ * which keeps it out of every Studio UI surface — Studio acts as a
591
+ * plain storage + RAG layer for it. UI-managed knowledge cannot be
592
+ * modified through this surface.
623
593
  */
624
- handoffReturn(payload: HandoffReturnPayload): ProtocolStreamChunk;
594
+ knowledge: {
595
+ /**
596
+ * List knowledge items for an organization.
597
+ * Defaults to the SDK token's own org if `orgId` is omitted.
598
+ */
599
+ list: (options?: ListKnowledgeOptions) => Promise<PlatformKnowledgeSummary[]>;
600
+ /**
601
+ * Fetch a single knowledge item by its Studio ID. The caller must
602
+ * have read access to the knowledge's owning org (same-org or
603
+ * publicReadable).
604
+ */
605
+ get: (id: string) => Promise<PlatformKnowledge>;
606
+ /**
607
+ * Semantic search over a knowledge's records. Studio embeds the
608
+ * query server-side (Gemini gemini-embedding-001) and runs Atlas
609
+ * Vector Search against the `vector_index` on `knowledge_records`.
610
+ *
611
+ * Same read access rules as `get` — same-org or publicReadable.
612
+ *
613
+ * Returns an empty array (not an error) when the Atlas index is
614
+ * absent or when nothing matches, so handler code can treat
615
+ * "no hits" and "RAG not configured yet" identically.
616
+ */
617
+ search: (id: string, query: string, options?: KnowledgeSearchOptions) => Promise<PlatformKnowledgeSearchHit[]>;
618
+ /**
619
+ * Turn semantic search on or off for a knowledge.
620
+ *
621
+ * Writes are strictly same-org — `publicReadable` does NOT confer
622
+ * write access. Flipping `false -> true` schedules a background
623
+ * backfill of existing records on the Studio side; this call
624
+ * returns as soon as the flag is persisted, not when the backfill
625
+ * finishes.
626
+ *
627
+ * Idempotent: setting the flag to its current value is a no-op
628
+ * (`backfillTriggered` will be `false`).
629
+ */
630
+ setRagEnabled: (id: string, enabled: boolean) => Promise<{
631
+ id: string;
632
+ ragEnabled: boolean;
633
+ backfillTriggered: boolean;
634
+ }>;
635
+ /**
636
+ * Create a system-owned database knowledge under the token's org.
637
+ *
638
+ * 409 (surfaced as an error) means a knowledge of the same name
639
+ * already exists in the org. Idempotent callers should recover by
640
+ * listing with `includeSystemOwned: true` and matching on name.
641
+ */
642
+ create: (name: string, sheets: CreateKnowledgeSheet[], options?: CreateKnowledgeOptions) => Promise<PlatformKnowledge>;
643
+ /**
644
+ * Delete a system-owned knowledge and all of its records.
645
+ * Refused (403) for UI-managed knowledge.
646
+ */
647
+ delete: (id: string) => Promise<{
648
+ deletedRecords: number;
649
+ }>;
650
+ /**
651
+ * List a sheet's records (newest first). Full-list surfaces (admin
652
+ * tables, exact name matching) where semantic search is the wrong
653
+ * tool. `limit` defaults to 500 (server max 2000).
654
+ */
655
+ listRecords: (id: string, sheetId: string, options?: {
656
+ limit?: number;
657
+ offset?: number;
658
+ }) => Promise<{
659
+ records: PlatformKnowledgeRecord[];
660
+ total: number;
661
+ }>;
662
+ /**
663
+ * Append one record to a sheet. Embeds async when ragEnabled.
664
+ */
665
+ addRecord: (id: string, sheetId: string, data: Record<string, unknown>) => Promise<PlatformKnowledgeRecord>;
666
+ /**
667
+ * Replace a record's data payload. Re-embeds async when ragEnabled.
668
+ */
669
+ updateRecord: (id: string, recordId: string, data: Record<string, unknown>) => Promise<PlatformKnowledgeRecord>;
670
+ /** Delete one record. */
671
+ deleteRecord: (id: string, recordId: string) => Promise<void>;
672
+ /**
673
+ * Bulk-insert records into one sheet (each array entry becomes one
674
+ * record's `data`). With `replace: true` the sheet's existing
675
+ * records are deleted first — the full-resync primitive backfills
676
+ * use. Server caps a single call at 5000 records.
677
+ */
678
+ bulkRecords: (id: string, sheetId: string, records: Array<Record<string, unknown>>, options?: {
679
+ replace?: boolean;
680
+ }) => Promise<{
681
+ insertedCount: number;
682
+ deletedCount: number;
683
+ }>;
684
+ };
625
685
  /**
626
- * Offer a handoff with user consent required.
686
+ * Throughline (find-a-helpline) surface.
627
687
  *
628
- * Studio stores this as a pending offer, sends an SSE event to the
629
- * frontend, and waits for the user to accept or decline before
630
- * executing the handoff.
688
+ * Handlers used to call api.throughlinecare.com directly, which meant
689
+ * each handler had to hold OAuth client credentials and re-implement
690
+ * token caching. Studio holds the per-org credentials on
691
+ * `Organization.throughline.{clientId,clientSecret}` and proxies the
692
+ * Throughline endpoints; the SDK token identifies the org server-side
693
+ * so callers never see the secret.
631
694
  *
632
- * @param payload - Offer display info and the pending handoff request
633
- * @returns A metadata stream chunk ready to yield or queue.push
695
+ * Responses are returned as parsed JSON. Studio does not reshape the
696
+ * Throughline body — these surfaces stay compatible with future
697
+ * Throughline API additions without a Studio or SDK release.
698
+ *
699
+ * Caching (24h on countries + topics, none on helplines) is server-
700
+ * side; the SDK is stateless.
634
701
  */
635
- handoffOffer(payload: HandoffOfferPayload): ProtocolStreamChunk;
636
- };
702
+ throughline: {
703
+ /**
704
+ * GET /v1/countries — Throughline's country catalog (codes,
705
+ * names, emergency numbers). Cached 24h server-side.
706
+ */
707
+ getCountries: () => Promise<unknown>;
708
+ /**
709
+ * GET /v1/topics — Throughline's topic taxonomy used to filter
710
+ * helpline searches. Cached 24h server-side.
711
+ */
712
+ getTopics: () => Promise<unknown>;
713
+ /**
714
+ * GET /v1/helplines?country_code=...&topic=...&... — live search,
715
+ * not cached. The query object is forwarded verbatim to Throughline,
716
+ * so any new key Throughline adds will work without an SDK change.
717
+ * Required by Throughline today: `country_code`.
718
+ */
719
+ getHelplines: (query: ThroughlineHelplinesQuery) => Promise<unknown>;
720
+ /**
721
+ * GET /v2/helplines through Studio's platform-held credential.
722
+ * This is opt-in and does not replace the organization-configured v1
723
+ * integration exposed by getHelplines().
724
+ */
725
+ getHelplinesV2: (query: ThroughlineV2HelplinesQuery) => Promise<ThroughlineV2HelplinesResponse>;
726
+ };
727
+ }
728
+ /**
729
+ * Create a PlatformClient for calling back to FirstStep Studio.
730
+ */
731
+ declare function createPlatformClient(config: PlatformClientConfig): PlatformClient;
637
732
 
638
733
  /**
639
- * Platform Client
734
+ * Render Markers
640
735
  *
641
- * SDK client for handlers to call back to FirstStep Studio.
642
- * Enables handlers to query platform data (chatbot list, etc.)
643
- * using the same API token used for request signature verification.
736
+ * Utilities for building render markers that FirstStep Studio's frontend
737
+ * can parse and display as rich UI components (cards, alerts, forms, etc.).
738
+ *
739
+ * This module is the single source of truth for render marker specs.
740
+ * - Handlers (producers) use the builder functions to emit markers
741
+ * - Studio frontend (consumer) uses the types and MARKER_TYPES constants to parse them
644
742
  *
645
743
  * @example
646
744
  * ```typescript
647
- * import { createPlatformClient } from '@firststep-studio/sdk';
648
- *
649
- * const client = createPlatformClient({
650
- * studioUrl: 'https://studio-api.example.com',
651
- * token: process.env.FIRSTSTEP_TOKEN,
652
- * });
745
+ * import { renderMarkers } from '@firststep-studio/sdk';
653
746
  *
654
- * const chatbots = await client.listChatbots();
655
- * // [{ id: '...', name: 'Safety Navigator', configSlug: 'safety-nav' }, ...]
747
+ * // In your handleStream():
748
+ * yield {
749
+ * type: 'text',
750
+ * content: renderMarkers.helplineCard({
751
+ * helplines: [{ name: '988 Lifeline', phoneNumber: '988', categories: ['crisis'], status: 'open', statusLabel: 'Available' }],
752
+ * }),
753
+ * };
656
754
  * ```
657
755
  */
658
- interface PlatformClientConfig {
659
- /** Studio backend API URL (e.g. https://studio-api.example.com) */
660
- studioUrl: string;
661
- /** API token (same as FIRSTSTEP_TOKEN used for signature verification) */
662
- token: string;
663
- /** Request timeout in ms (default: 10000) */
664
- timeout?: number;
665
- }
666
- interface PlatformOrganization {
667
- /** Studio organization ID */
668
- id: string;
669
- /** Display name (null if the organization record has been deleted) */
670
- name: string | null;
671
- /** URL-safe slug */
672
- slug: string | null;
673
- }
674
- interface PlatformTokenInfo {
675
- /** API token ID */
676
- id: string;
677
- /** Human-friendly token label */
678
- name: string;
679
- /** Authorized scopes */
680
- scopes: string[];
681
- /** Project IDs this token is scoped to (null = full org access) */
682
- projectIds: string[] | null;
683
- }
684
- interface PlatformIdentity {
685
- organization: PlatformOrganization;
686
- token: PlatformTokenInfo;
687
- }
688
- interface PlatformChatbotRegisterInput {
689
- /** Stable handler-side identifier used to upsert (e.g. handler config ID). */
690
- externalId: string;
691
- /** Display name shown in Studio's chatbot list. */
692
- name: string;
693
- /** Optional human description. */
694
- description?: string;
695
- /** Public URL where Studio should send protocol requests. */
696
- handlerUrl: string;
697
- /** Slug for multi-config routing on the handler side. */
698
- configSlug?: string;
699
- /** Optional capability flags advertised to Studio. */
700
- capabilities?: {
701
- streaming?: boolean;
702
- formQuestions?: boolean;
703
- knowledgeActions?: boolean;
704
- integrations?: boolean;
705
- [key: string]: unknown;
756
+ declare const MARKER_TYPES: {
757
+ readonly HELPLINE_CARD: "RENDER_HELPLINE_CARD";
758
+ readonly HELPLINE_CARD_V2: "RENDER_HELPLINE_CARD_V2";
759
+ readonly EMERGENCY: "RENDER_EMERGENCY";
760
+ readonly RESOURCE_CARD: "RENDER_RESOURCE_CARD";
761
+ readonly PROVIDER_CARD: "RENDER_PROVIDER_CARD";
762
+ readonly SAFETY_PLAN: "RENDER_SAFETY_PLAN";
763
+ readonly REPORT_CARD: "RENDER_REPORT_CARD";
764
+ };
765
+ type MarkerType = typeof MARKER_TYPES[keyof typeof MARKER_TYPES];
766
+ /**
767
+ * Helpline card payload.
768
+ * Renders a carousel of helpline cards with contact buttons (call, text, chat, WhatsApp).
769
+ */
770
+ interface HelplineCardPayload {
771
+ helplines: HelplineCardItem[];
772
+ type?: 'throughline' | 'throughline_fallback' | 'stage';
773
+ fallback?: {
774
+ message: string;
775
+ linkText?: string;
776
+ linkUrl?: string;
777
+ icon?: string;
778
+ topic?: string;
779
+ topics?: string[];
706
780
  };
707
781
  }
708
- interface PlatformChatbotRegistration {
709
- /** Studio chatbot MongoDB ID (immutable across re-registrations). */
710
- id: string;
711
- /** Echoes back the externalId used as upsert key. */
712
- externalId: string;
713
- /** Resolved name as stored in Studio. */
782
+ interface HelplineCardItem {
783
+ /**
784
+ * Stable helpline id from the throughline API. Lets consumers count
785
+ * distinct helplines across display/click events; name-keyed tracking
786
+ * cannot. Optional: older producers omit it.
787
+ */
788
+ id?: string;
714
789
  name: string;
715
- protocolConfig: Record<string, unknown>;
790
+ description?: string;
791
+ categories: string[];
792
+ status: 'open' | 'closed';
793
+ statusLabel: string;
794
+ statusBadge?: string;
795
+ hoursText?: string;
796
+ supportTypes?: string;
797
+ smsNumber?: string;
798
+ phoneNumber?: string;
799
+ website?: string;
800
+ webchat?: string;
801
+ whatsapp?: string;
802
+ specialties?: string[];
803
+ highlightedTag?: string;
804
+ verified?: boolean;
716
805
  }
717
- interface PlatformChatbot {
718
- /** Studio chatbot MongoDB ID */
719
- id: string;
720
- /** Chatbot display name */
721
- name: string;
722
- /** Handler URL (if external handler configured) */
723
- handlerUrl?: string;
724
- /** Config slug for multi-config routing */
725
- configSlug?: string;
726
- /** Handler type */
727
- handlerType?: 'builtin' | 'external';
806
+
807
+ /** Raw Throughline v2 card contract plus handler presentation metadata. */
808
+ interface HelplineCardV2Payload {
809
+ helplines: HelplineCardV2Item[];
810
+ type?: 'throughline' | 'stage';
728
811
  }
729
- interface PlatformKnowledgeSummary {
730
- /** Studio knowledge MongoDB ID */
731
- id: string;
732
- /** Knowledge name */
812
+ interface HelplineCardV2Item extends ThroughlineV2Helpline {
813
+ isTopMatch?: boolean;
814
+ matchReason?: string | null;
815
+ }
816
+ /**
817
+ * Emergency number payload.
818
+ * Renders a prominent alert card with a large call button.
819
+ */
820
+ interface EmergencyPayload {
821
+ number: string;
822
+ countryName?: string;
823
+ countryCode?: string;
824
+ }
825
+ /**
826
+ * Resource card payload.
827
+ * Renders a carousel of resource cards with video thumbnails, tags, and visit buttons.
828
+ */
829
+ interface ResourceCardPayload {
830
+ resources: ResourceCardItem[];
831
+ }
832
+ interface ResourceCardItem {
733
833
  name: string;
734
- /** Optional description */
834
+ url?: string;
735
835
  description?: string;
736
- /** Knowledge type (e.g. 'text', 'database') */
836
+ video_url?: string;
737
837
  type?: string;
738
- /** Owning organization ID. Null for org-less system documents. */
739
- organizationId: string | null;
740
- /**
741
- * True for handler-managed documents created through the protocol
742
- * write API. System-owned knowledge is hidden from every Studio UI
743
- * surface and only appears in `list` when `includeSystemOwned` is set.
744
- */
745
- isSystemOwned?: boolean;
746
- /** Whether semantic search (record embedding) is enabled. */
747
- ragEnabled?: boolean;
748
- /** Timestamps */
749
- createdAt?: number | string;
750
- updatedAt?: number | string;
838
+ tags?: string[];
839
+ highlightedTag?: string;
751
840
  }
752
841
  /**
753
- * Full knowledge document (shape mirrors the underlying mongoose doc).
754
- * Kept loose because the schema is large and varies by knowledge `type`;
755
- * narrow types can be added incrementally as callers need them.
842
+ * Provider card payload.
843
+ * Renders a carousel of provider directory cards with specialty/language tags and contact info.
756
844
  */
757
- type PlatformKnowledge = PlatformKnowledgeSummary & Record<string, unknown>;
758
- interface ListKnowledgeOptions {
759
- /**
760
- * Target organization to list knowledge from. If omitted, defaults to
761
- * the org associated with the SDK token. Use this to read from a
762
- * different org (e.g. a platform-operated shared library marked
763
- * `publicReadable` in Studio).
764
- */
765
- orgId?: string;
766
- /**
767
- * Include system-owned (handler-managed) knowledge in the listing.
768
- * Only honored when listing the token's own org; other orgs never
769
- * expose their system-owned documents.
770
- */
771
- includeSystemOwned?: boolean;
772
- /**
773
- * 'system' lists ORG-LESS platform documents instead of org
774
- * knowledge. orgId and includeSystemOwned are ignored in that mode.
775
- */
776
- scope?: 'system';
845
+ interface ProviderCardPayload {
846
+ providers: ProviderCardItem[];
777
847
  }
778
- /** Sheet definition for `knowledge.create`. */
779
- interface CreateKnowledgeSheet {
780
- /** Stable sheet id, referenced later by record writes and search. */
848
+ interface ProviderCardItem {
781
849
  id: string;
782
850
  name: string;
851
+ type?: string;
852
+ specialty: string[];
853
+ language: string[];
783
854
  description?: string;
784
- /**
785
- * Column definitions ({ id, name, type, ... }). Loose-typed; the
786
- * Studio schema validates `type` against its column-type enum.
787
- */
788
- columns?: Array<Record<string, unknown>>;
855
+ location?: string;
856
+ open_hours?: string;
857
+ contact_phone?: string;
858
+ contact_email?: string;
859
+ address?: string;
860
+ website?: string;
789
861
  }
790
- interface CreateKnowledgeOptions {
791
- description?: string;
792
- /**
793
- * Enable semantic search at creation time. Defaults to false; can be
794
- * flipped later with `setRagEnabled` (which also backfills embeddings
795
- * for records inserted while the flag was off).
796
- */
797
- ragEnabled?: boolean;
798
- /**
799
- * Create the knowledge as an ORG-LESS platform document: no
800
- * organizationId at all, structurally invisible to every org-scoped
801
- * Studio surface. This is the home for handler-managed data. Without
802
- * it the document is created under the token's org (hidden from the
803
- * UI by isSystemOwned, but org-bound).
804
- */
805
- system?: boolean;
862
+ /**
863
+ * Safety plan payload.
864
+ * Renders a multi-section table with expandable sections and save/export actions.
865
+ */
866
+ interface SafetyPlanPayload {
867
+ sections: SafetyPlanSection[];
868
+ helplines?: Array<{
869
+ name: string;
870
+ phone?: string;
871
+ }>;
872
+ actions?: {
873
+ savePng?: boolean;
874
+ saveTxt?: boolean;
875
+ copy?: boolean;
876
+ };
806
877
  }
807
- /** A raw record as returned by the protocol record-write endpoints. */
808
- interface PlatformKnowledgeRecord {
878
+ interface SafetyPlanSection {
809
879
  id: string;
810
- knowledgeId: string;
811
- sheetId: string;
812
- data: Record<string, unknown>;
813
- createdAt?: number | string;
814
- updatedAt?: number | string;
880
+ title: string;
881
+ items: Array<string | {
882
+ name: string;
883
+ phone?: string;
884
+ description?: string;
885
+ }>;
815
886
  }
816
- interface KnowledgeSearchOptions {
817
- /** Max records to return (server-side clamped to 1..50, default 10). */
818
- limit?: number;
887
+ /**
888
+ * Report card payload.
889
+ * Renders a summary card of collected report data with a submit button.
890
+ */
891
+ interface ReportCardPayload {
892
+ topic: string;
893
+ location?: string;
894
+ description?: string;
895
+ perpetrator_known?: boolean;
896
+ contact_mode?: string;
897
+ contact_value?: string;
898
+ submitEndpoint?: string;
899
+ }
900
+ /**
901
+ * The marker families that constitute a *recommendation*: the handler put
902
+ * something in front of the user and expects them to act on it. Safety plans
903
+ * and report cards are excluded: they summarise what the user already said
904
+ * rather than recommending anything, so rating their "satisfaction" would
905
+ * measure a different thing and drag the average with it.
906
+ */
907
+ type RecommendationKind = 'helpline' | 'resource' | 'provider' | 'emergency';
908
+ /**
909
+ * One recommended thing, flattened out of a card carousel.
910
+ *
911
+ * A single marker usually carries several of these (a helpline carousel is
912
+ * one marker, five helplines), and each one is separately good or bad for the
913
+ * user in front of it, so the unit here is the item, not the card.
914
+ */
915
+ interface RecommendedItem {
916
+ kind: RecommendationKind;
819
917
  /**
820
- * Optional sheet filter. Pass a sheet id (from the knowledge document)
821
- * to restrict the search to that sheet. Omit to search across all
822
- * sheets in the knowledge.
918
+ * Stable producer-side id when there is one. Lets a consumer tally the same
919
+ * helpline or provider across sessions; label-keyed tallies cannot, because
920
+ * two helplines can share a display name across countries.
823
921
  */
824
- sheetId?: string;
922
+ id?: string;
923
+ /** The card's headline, as the user reads it. */
924
+ label: string;
925
+ /**
926
+ * One line of gist for a consumer that never sees the rendered card: an
927
+ * evaluator, a log, a transcript export.
928
+ */
929
+ detail?: string;
825
930
  }
826
931
  /**
827
- * A single hit from `client.knowledge.search`. `score` is the Atlas
828
- * vector similarity score (cosine in our setup, so higher is closer).
829
- * `data` mirrors the underlying record fields (column id -> value).
932
+ * Pull one marker's JSON payload out of a message.
933
+ *
934
+ * Tolerates whitespace and newlines between the tags and the payload, because
935
+ * producers differ on that: the SDK builders emit no padding, the Studio
936
+ * frontend's own helpline blocks emit `\n`.
830
937
  */
831
- interface PlatformKnowledgeSearchHit {
832
- id: string;
833
- knowledgeId: string;
834
- sheetId: string;
835
- data: Record<string, unknown>;
836
- score: number;
837
- createdAt?: number | string;
838
- updatedAt?: number | string;
839
- }
938
+ declare function parseMarkerPayload<T = unknown>(content: string, type: string): T | null;
840
939
  /**
841
- * Helplines query params — passed verbatim to Throughline. Loose-typed
842
- * so the SDK stays compatible with new Throughline query keys without
843
- * a release. Required by Throughline today: `country_code`. Common
844
- * optional keys: `topic`, `contact_method`, `limit`.
940
+ * Normalise every recommendation marker in an assistant message into a flat
941
+ * list of recommended items.
942
+ *
943
+ * This is the read side of the builders above, and it lives here for the same
944
+ * reason they do: the marker spec should have one owner. Consumers that want
945
+ * to know "what did this turn recommend" (evaluators, analytics, transcript
946
+ * exporters) otherwise each grow their own regex, and they drift. One reads
947
+ * `payload.title` off a resource card that has never had one.
948
+ */
949
+ declare function extractRecommendedItems(content: string): RecommendedItem[];
950
+ /**
951
+ * Render marker builder functions.
952
+ *
953
+ * Each function takes a typed payload and returns a render marker string
954
+ * that can be yielded as a `text` chunk in handleStream().
955
+ *
956
+ * @example
957
+ * ```typescript
958
+ * // Emit helpline cards
959
+ * yield { type: 'text', content: renderMarkers.helplineCard({ helplines: [...] }) };
960
+ *
961
+ * // Emit emergency number
962
+ * yield { type: 'text', content: renderMarkers.emergency({ number: '911', countryName: 'United States' }) };
963
+ * ```
964
+ */
965
+ declare const renderMarkers: {
966
+ /** Build a helpline card carousel marker */
967
+ helplineCard: (payload: HelplineCardPayload) => string;
968
+ /** Build a Throughline v2 helpline card carousel marker. */
969
+ helplineCardV2: (payload: HelplineCardV2Payload) => string;
970
+ /** Build an emergency number alert marker */
971
+ emergency: (payload: EmergencyPayload) => string;
972
+ /** Build a resource card carousel marker */
973
+ resourceCard: (payload: ResourceCardPayload) => string;
974
+ /** Build a provider card carousel marker */
975
+ providerCard: (payload: ProviderCardPayload) => string;
976
+ /** Build a safety plan artifact marker */
977
+ safetyPlan: (payload: SafetyPlanPayload) => string;
978
+ /** Build a report draft card marker */
979
+ reportCard: (payload: ReportCardPayload) => string;
980
+ };
981
+
982
+ /**
983
+ * Stream Metadata Builders
984
+ *
985
+ * Utilities for building typed metadata stream chunks that FirstStep Studio's
986
+ * proxy persists to MongoDB for Dashboard features (Form Insights, Session
987
+ * History, Routing Logs, Agent Transitions).
988
+ *
989
+ * Each function returns a ready-to-yield ProtocolStreamChunk. No need to
990
+ * construct the `{ type, content }` wrapper manually.
991
+ *
992
+ * @example
993
+ * ```typescript
994
+ * import { streamMetadata } from '@firststep-studio/sdk';
995
+ *
996
+ * // In your handleStream():
997
+ *
998
+ * // 1. Welcome path: declare your schema once
999
+ * yield streamMetadata.declareSchema({
1000
+ * agents: [{ id: 'intake', title: 'Intake', order: 0 }],
1001
+ * questions: [{ id: 'name', agentId: 'intake', title: 'Name', type: 'text' }],
1002
+ * });
1003
+ *
1004
+ * // 2. After each turn: send collected field values
1005
+ * yield streamMetadata.formDataUpdate({ name: 'Alice', age: '28' });
1006
+ *
1007
+ * // 3. On stage change: signal an agent transition
1008
+ * yield streamMetadata.agentTransition({ id: 'support', title: 'Support' });
1009
+ *
1010
+ * // 4. On classification: send routing result
1011
+ * yield streamMetadata.routingResult({
1012
+ * decision: 'classified',
1013
+ * reason: 'User mentioned self-harm',
1014
+ * category: 'crisis',
1015
+ * level: 'high',
1016
+ * score: 92,
1017
+ * });
1018
+ * ```
845
1019
  */
846
- type ThroughlineHelplinesQuery = Record<string, string | number | boolean | undefined>;
847
- declare class PlatformError extends Error {
848
- status: number;
849
- constructor(message: string, status: number);
850
- }
851
- declare class PlatformClient {
852
- private studioUrl;
853
- private token;
854
- private timeout;
855
- constructor(config: PlatformClientConfig);
856
- private request;
1020
+
1021
+ declare const streamMetadata: {
857
1022
  /**
858
- * Resolve the organization and token metadata for the configured token.
859
- * Useful for connection state UI ("Connected to {orgName}") and audit.
1023
+ * Declare the form schema (agents + questions) for Dashboard Form Insights.
1024
+ *
1025
+ * Yield this once during session initialization (welcome message).
1026
+ * The Studio proxy stores it in `chatbot.externalSchema` so that
1027
+ * Dashboard Form Insights can display question-level analytics.
1028
+ *
1029
+ * @param schema - Agents and questions your handler uses
1030
+ * @returns A metadata stream chunk ready to yield or queue.push
860
1031
  */
861
- getMe(): Promise<PlatformIdentity>;
1032
+ declareSchema(schema: SchemaDeclarationPayload): ProtocolStreamChunk;
862
1033
  /**
863
- * List chatbots accessible to this handler's organization.
864
- * Studio resolves the organization from the API token.
1034
+ * Send collected form field values for Dashboard persistence.
1035
+ *
1036
+ * Call this after each turn when new fields are captured.
1037
+ * Values are incrementally merged into `ChatSession.formData`.
1038
+ *
1039
+ * @param fields - Key-value pairs of field IDs to their collected values
1040
+ * @returns A metadata stream chunk ready to yield or queue.push
865
1041
  */
866
- listChatbots(): Promise<PlatformChatbot[]>;
1042
+ formDataUpdate(fields: Record<string, string | number>): ProtocolStreamChunk;
867
1043
  /**
868
- * Register or update an external-handler chatbot under the token's
869
- * organization. Idempotent: identified by `externalId`, so handlers
870
- * can call this on every config save without creating duplicates.
1044
+ * Signal an agent/stage transition for routing logs.
1045
+ *
1046
+ * The Studio proxy records this in `ChatSession.routingLogs` so
1047
+ * the Dashboard can show the conversation's agent flow.
1048
+ *
1049
+ * @param agent - The agent/stage being transitioned to
1050
+ * @returns A metadata stream chunk ready to yield or queue.push
871
1051
  */
872
- registerChatbot(input: PlatformChatbotRegisterInput): Promise<PlatformChatbotRegistration>;
1052
+ agentTransition(agent: AgentTransitionPayload): ProtocolStreamChunk;
873
1053
  /**
874
- * Mark the chatbot identified by `externalId` as disconnected.
875
- * The Studio record is preserved (history, analytics survive).
1054
+ * Send a routing/classification result for routing logs.
1055
+ *
1056
+ * The Studio proxy records this in `ChatSession.routingLogs` as a
1057
+ * classification event with category, level, and confidence score.
1058
+ *
1059
+ * @param result - Classification/routing decision details
1060
+ * @returns A metadata stream chunk ready to yield or queue.push
876
1061
  */
877
- unregisterChatbot(externalId: string): Promise<{
878
- ok: boolean;
879
- modified: number;
880
- }>;
1062
+ routingResult(result: RoutingClassificationPayload): ProtocolStreamChunk;
881
1063
  /**
882
- * Knowledge / resource surface.
1064
+ * Request a handoff to another chatbot.
883
1065
  *
884
- * Reads are allowed when the SDK token's org matches the target org,
885
- * OR when the target org has `publicReadable: true` on the Studio side.
1066
+ * The Studio proxy pushes a handoff stack entry and routes subsequent
1067
+ * user messages to the target handler. If `options.consent` is set,
1068
+ * Studio stores the request as a pending offer instead.
886
1069
  *
887
- * Writes target system-owned (handler-managed) knowledge only: the
888
- * protocol API stamps everything created here `isSystemOwned: true`,
889
- * which keeps it out of every Studio UI surface — Studio acts as a
890
- * plain storage + RAG layer for it. UI-managed knowledge cannot be
891
- * modified through this surface.
1070
+ * @param payload - Handoff target, reason, context, and options
1071
+ * @returns A metadata stream chunk ready to yield or queue.push
892
1072
  */
893
- knowledge: {
894
- /**
895
- * List knowledge items for an organization.
896
- * Defaults to the SDK token's own org if `orgId` is omitted.
897
- */
898
- list: (options?: ListKnowledgeOptions) => Promise<PlatformKnowledgeSummary[]>;
899
- /**
900
- * Fetch a single knowledge item by its Studio ID. The caller must
901
- * have read access to the knowledge's owning org (same-org or
902
- * publicReadable).
903
- */
904
- get: (id: string) => Promise<PlatformKnowledge>;
905
- /**
906
- * Semantic search over a knowledge's records. Studio embeds the
907
- * query server-side (Gemini gemini-embedding-001) and runs Atlas
908
- * Vector Search against the `vector_index` on `knowledge_records`.
909
- *
910
- * Same read access rules as `get` — same-org or publicReadable.
911
- *
912
- * Returns an empty array (not an error) when the Atlas index is
913
- * absent or when nothing matches, so handler code can treat
914
- * "no hits" and "RAG not configured yet" identically.
915
- */
916
- search: (id: string, query: string, options?: KnowledgeSearchOptions) => Promise<PlatformKnowledgeSearchHit[]>;
917
- /**
918
- * Turn semantic search on or off for a knowledge.
919
- *
920
- * Writes are strictly same-org — `publicReadable` does NOT confer
921
- * write access. Flipping `false -> true` schedules a background
922
- * backfill of existing records on the Studio side; this call
923
- * returns as soon as the flag is persisted, not when the backfill
924
- * finishes.
925
- *
926
- * Idempotent: setting the flag to its current value is a no-op
927
- * (`backfillTriggered` will be `false`).
928
- */
929
- setRagEnabled: (id: string, enabled: boolean) => Promise<{
930
- id: string;
931
- ragEnabled: boolean;
932
- backfillTriggered: boolean;
933
- }>;
934
- /**
935
- * Create a system-owned database knowledge under the token's org.
936
- *
937
- * 409 (surfaced as an error) means a knowledge of the same name
938
- * already exists in the org. Idempotent callers should recover by
939
- * listing with `includeSystemOwned: true` and matching on name.
940
- */
941
- create: (name: string, sheets: CreateKnowledgeSheet[], options?: CreateKnowledgeOptions) => Promise<PlatformKnowledge>;
942
- /**
943
- * Delete a system-owned knowledge and all of its records.
944
- * Refused (403) for UI-managed knowledge.
945
- */
946
- delete: (id: string) => Promise<{
947
- deletedRecords: number;
948
- }>;
949
- /**
950
- * List a sheet's records (newest first). Full-list surfaces (admin
951
- * tables, exact name matching) where semantic search is the wrong
952
- * tool. `limit` defaults to 500 (server max 2000).
953
- */
954
- listRecords: (id: string, sheetId: string, options?: {
955
- limit?: number;
956
- offset?: number;
957
- }) => Promise<{
958
- records: PlatformKnowledgeRecord[];
959
- total: number;
960
- }>;
961
- /**
962
- * Append one record to a sheet. Embeds async when ragEnabled.
963
- */
964
- addRecord: (id: string, sheetId: string, data: Record<string, unknown>) => Promise<PlatformKnowledgeRecord>;
965
- /**
966
- * Replace a record's data payload. Re-embeds async when ragEnabled.
967
- */
968
- updateRecord: (id: string, recordId: string, data: Record<string, unknown>) => Promise<PlatformKnowledgeRecord>;
969
- /** Delete one record. */
970
- deleteRecord: (id: string, recordId: string) => Promise<void>;
971
- /**
972
- * Bulk-insert records into one sheet (each array entry becomes one
973
- * record's `data`). With `replace: true` the sheet's existing
974
- * records are deleted first — the full-resync primitive backfills
975
- * use. Server caps a single call at 5000 records.
976
- */
977
- bulkRecords: (id: string, sheetId: string, records: Array<Record<string, unknown>>, options?: {
978
- replace?: boolean;
979
- }) => Promise<{
980
- insertedCount: number;
981
- deletedCount: number;
982
- }>;
983
- };
1073
+ handoffRequest(payload: HandoffRequestPayload): ProtocolStreamChunk;
984
1074
  /**
985
- * Throughline (find-a-helpline) surface.
1075
+ * Return from a handoff back to the parent handler.
986
1076
  *
987
- * Handlers used to call api.throughlinecare.com directly, which meant
988
- * each handler had to hold OAuth client credentials and re-implement
989
- * token caching. Studio holds the per-org credentials on
990
- * `Organization.throughline.{clientId,clientSecret}` and proxies the
991
- * Throughline endpoints; the SDK token identifies the org server-side
992
- * so callers never see the secret.
1077
+ * The Studio proxy pops the handoff stack and delivers the return
1078
+ * result to the parent handler on the next user message.
993
1079
  *
994
- * Responses are returned as parsed JSON. Studio does not reshape the
995
- * Throughline body — these surfaces stay compatible with future
996
- * Throughline API additions without a Studio or SDK release.
1080
+ * @param payload - Summary and artifacts from the completed handoff
1081
+ * @returns A metadata stream chunk ready to yield or queue.push
1082
+ */
1083
+ handoffReturn(payload: HandoffReturnPayload): ProtocolStreamChunk;
1084
+ /**
1085
+ * Offer a handoff with user consent required.
997
1086
  *
998
- * Caching (24h on countries + topics, none on helplines) is server-
999
- * side; the SDK is stateless.
1087
+ * Studio stores this as a pending offer, sends an SSE event to the
1088
+ * frontend, and waits for the user to accept or decline before
1089
+ * executing the handoff.
1090
+ *
1091
+ * @param payload - Offer display info and the pending handoff request
1092
+ * @returns A metadata stream chunk ready to yield or queue.push
1000
1093
  */
1001
- throughline: {
1002
- /**
1003
- * GET /v1/countries — Throughline's country catalog (codes,
1004
- * names, emergency numbers). Cached 24h server-side.
1005
- */
1006
- getCountries: () => Promise<unknown>;
1007
- /**
1008
- * GET /v1/topics — Throughline's topic taxonomy used to filter
1009
- * helpline searches. Cached 24h server-side.
1010
- */
1011
- getTopics: () => Promise<unknown>;
1012
- /**
1013
- * GET /v1/helplines?country_code=...&topic=...&... — live search,
1014
- * not cached. The query object is forwarded verbatim to Throughline,
1015
- * so any new key Throughline adds will work without an SDK change.
1016
- * Required by Throughline today: `country_code`.
1017
- */
1018
- getHelplines: (query: ThroughlineHelplinesQuery) => Promise<unknown>;
1019
- };
1020
- }
1021
- /**
1022
- * Create a PlatformClient for calling back to FirstStep Studio.
1023
- */
1024
- declare function createPlatformClient(config: PlatformClientConfig): PlatformClient;
1094
+ handoffOffer(payload: HandoffOfferPayload): ProtocolStreamChunk;
1095
+ };
1025
1096
 
1026
1097
  /**
1027
1098
  * Verify an HMAC-SHA256 request signature.
@@ -1290,4 +1361,4 @@ declare class LLMClient {
1290
1361
  */
1291
1362
  declare function createLLMClient(config: LLMClientConfig): LLMClient;
1292
1363
 
1293
- export { AgentTransitionPayload, ChatMessage, type ClassificationResult, type CreateKnowledgeOptions, type CreateKnowledgeSheet, type EmergencyPayload, HandoffOfferPayload, HandoffRequestPayload, HandoffReturnPayload, type HelplineCardItem, type HelplineCardPayload, type KnowledgeSearchOptions, type LLMCacheCreateRequest, type LLMCachedContent, type LLMCandidate, LLMClient, type LLMClientConfig, type LLMContent, LLMError, type LLMGenerateRequest, type LLMGenerateResponse, type LLMGenerationConfig, type LLMPart, type LLMSafetySetting, type LLMUsageMetadata, type ListKnowledgeOptions, MARKER_TYPES, type MarkerType, type PlatformChatbot, PlatformClient, type PlatformClientConfig, PlatformError, type PlatformKnowledge, type PlatformKnowledgeRecord, type PlatformKnowledgeSearchHit, type PlatformKnowledgeSummary, ProtocolStreamChunk, type ProviderCardItem, type ProviderCardPayload, type RecommendationKind, type RecommendedItem, type ReportCardPayload, type ResourceCardItem, type ResourceCardPayload, RoutingClassificationPayload, type SafetyPlanPayload, type SafetyPlanSection, SchemaDeclarationPayload, type ThroughlineHelplinesQuery, type UCPClassifyRequest, type UCPClassifyResponse, UCPClient, type UCPClientConfig, type UCPEndpointConfig, UCPError, type UCPErrorResponse, type UCPInfoResponse, type UCPMessage, classifyWithUCP, createAuthHeader, createLLMClient, createPlatformClient, createRequestSignature, createUCPClient, extractRecommendedItems, isValidToken, parseMarkerPayload, renderMarkers, streamMetadata, verifyRequestSignature };
1364
+ export { AgentTransitionPayload, ChatMessage, type ClassificationResult, type CreateKnowledgeOptions, type CreateKnowledgeSheet, type EmergencyPayload, HandoffOfferPayload, HandoffRequestPayload, HandoffReturnPayload, type HelplineCardItem, type HelplineCardPayload, type HelplineCardV2Item, type HelplineCardV2Payload, type KnowledgeSearchOptions, type LLMCacheCreateRequest, type LLMCachedContent, type LLMCandidate, LLMClient, type LLMClientConfig, type LLMContent, LLMError, type LLMGenerateRequest, type LLMGenerateResponse, type LLMGenerationConfig, type LLMPart, type LLMSafetySetting, type LLMUsageMetadata, type ListKnowledgeOptions, MARKER_TYPES, type MarkerType, type PlatformChatbot, PlatformClient, type PlatformClientConfig, PlatformError, type PlatformKnowledge, type PlatformKnowledgeRecord, type PlatformKnowledgeSearchHit, type PlatformKnowledgeSummary, ProtocolStreamChunk, type ProviderCardItem, type ProviderCardPayload, type RecommendationKind, type RecommendedItem, type ReportCardPayload, type ResourceCardItem, type ResourceCardPayload, RoutingClassificationPayload, type SafetyPlanPayload, type SafetyPlanSection, SchemaDeclarationPayload, type ThroughlineHelplinesQuery, type ThroughlineV2ContactMethod, type ThroughlineV2Country, type ThroughlineV2Description, type ThroughlineV2Helpline, type ThroughlineV2HelplinesQuery, type ThroughlineV2HelplinesResponse, type ThroughlineV2OpeningSpan, type UCPClassifyRequest, type UCPClassifyResponse, UCPClient, type UCPClientConfig, type UCPEndpointConfig, UCPError, type UCPErrorResponse, type UCPInfoResponse, type UCPMessage, classifyWithUCP, createAuthHeader, createLLMClient, createPlatformClient, createRequestSignature, createUCPClient, extractRecommendedItems, isValidToken, parseMarkerPayload, renderMarkers, streamMetadata, verifyRequestSignature };