@firststep-studio/sdk 0.16.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.ts CHANGED
@@ -285,693 +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
- }>;
459
+ knowledgeId: string;
460
+ sheetId: string;
461
+ data: Record<string, unknown>;
462
+ createdAt?: number | string;
463
+ updatedAt?: number | string;
429
464
  }
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;
465
+ interface KnowledgeSearchOptions {
466
+ /** Max records to return (server-side clamped to 1..50, default 10). */
467
+ limit?: number;
468
+ /**
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.
472
+ */
473
+ sheetId?: string;
442
474
  }
443
475
  /**
444
- * Render marker builder functions.
445
- *
446
- * Each function takes a typed payload and returns a render marker string
447
- * that can be yielded as a `text` chunk in handleStream().
448
- *
449
- * @example
450
- * ```typescript
451
- * // Emit helpline cards
452
- * yield { type: 'text', content: renderMarkers.helplineCard({ helplines: [...] }) };
453
- *
454
- * // Emit emergency number
455
- * yield { type: 'text', content: renderMarkers.emergency({ number: '911', countryName: 'United States' }) };
456
- * ```
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).
457
479
  */
458
- declare const renderMarkers: {
459
- /** Build a helpline card carousel marker */
460
- helplineCard: (payload: HelplineCardPayload) => string;
461
- /** Build an emergency number alert marker */
462
- emergency: (payload: EmergencyPayload) => string;
463
- /** Build a resource card carousel marker */
464
- resourceCard: (payload: ResourceCardPayload) => string;
465
- /** Build a provider card carousel marker */
466
- providerCard: (payload: ProviderCardPayload) => string;
467
- /** Build a safety plan artifact marker */
468
- safetyPlan: (payload: SafetyPlanPayload) => string;
469
- /** Build a report draft card marker */
470
- reportCard: (payload: ReportCardPayload) => string;
471
- };
472
-
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
+ }
473
489
  /**
474
- * Stream Metadata Builders
475
- *
476
- * Utilities for building typed metadata stream chunks that FirstStep Studio's
477
- * proxy persists to MongoDB for Dashboard features (Form Insights, Session
478
- * History, Routing Logs, Agent Transitions).
479
- *
480
- * Each function returns a ready-to-yield ProtocolStreamChunk. No need to
481
- * construct the `{ type, content }` wrapper manually.
482
- *
483
- * @example
484
- * ```typescript
485
- * import { streamMetadata } from '@firststep-studio/sdk';
486
- *
487
- * // In your handleStream():
488
- *
489
- * // 1. Welcome path: declare your schema once
490
- * yield streamMetadata.declareSchema({
491
- * agents: [{ id: 'intake', title: 'Intake', order: 0 }],
492
- * questions: [{ id: 'name', agentId: 'intake', title: 'Name', type: 'text' }],
493
- * });
494
- *
495
- * // 2. After each turn: send collected field values
496
- * yield streamMetadata.formDataUpdate({ name: 'Alice', age: '28' });
497
- *
498
- * // 3. On stage change: signal an agent transition
499
- * yield streamMetadata.agentTransition({ id: 'support', title: 'Support' });
500
- *
501
- * // 4. On classification: send routing result
502
- * yield streamMetadata.routingResult({
503
- * decision: 'classified',
504
- * reason: 'User mentioned self-harm',
505
- * category: 'crisis',
506
- * level: 'high',
507
- * score: 92,
508
- * });
509
- * ```
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`.
510
494
  */
511
-
512
- declare const streamMetadata: {
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;
513
558
  /**
514
- * Declare the form schema (agents + questions) for Dashboard Form Insights.
515
- *
516
- * Yield this once during session initialization (welcome message).
517
- * The Studio proxy stores it in `chatbot.externalSchema` so that
518
- * Dashboard Form Insights can display question-level analytics.
519
- *
520
- * @param schema - Agents and questions your handler uses
521
- * @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.
522
561
  */
523
- declareSchema(schema: SchemaDeclarationPayload): ProtocolStreamChunk;
562
+ getMe(): Promise<PlatformIdentity>;
524
563
  /**
525
- * Send collected form field values for Dashboard persistence.
526
- *
527
- * Call this after each turn when new fields are captured.
528
- * Values are incrementally merged into `ChatSession.formData`.
529
- *
530
- * @param fields - Key-value pairs of field IDs to their collected values
531
- * @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.
532
566
  */
533
- formDataUpdate(fields: Record<string, string | number>): ProtocolStreamChunk;
567
+ listChatbots(): Promise<PlatformChatbot[]>;
534
568
  /**
535
- * Signal an agent/stage transition for routing logs.
536
- *
537
- * The Studio proxy records this in `ChatSession.routingLogs` so
538
- * the Dashboard can show the conversation's agent flow.
539
- *
540
- * @param agent - The agent/stage being transitioned to
541
- * @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.
542
572
  */
543
- agentTransition(agent: AgentTransitionPayload): ProtocolStreamChunk;
573
+ registerChatbot(input: PlatformChatbotRegisterInput): Promise<PlatformChatbotRegistration>;
544
574
  /**
545
- * Send a routing/classification result for routing logs.
546
- *
547
- * The Studio proxy records this in `ChatSession.routingLogs` as a
548
- * classification event with category, level, and confidence score.
549
- *
550
- * @param result - Classification/routing decision details
551
- * @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).
552
577
  */
553
- routingResult(result: RoutingClassificationPayload): ProtocolStreamChunk;
578
+ unregisterChatbot(externalId: string): Promise<{
579
+ ok: boolean;
580
+ modified: number;
581
+ }>;
554
582
  /**
555
- * Request a handoff to another chatbot.
583
+ * Knowledge / resource surface.
556
584
  *
557
- * The Studio proxy pushes a handoff stack entry and routes subsequent
558
- * user messages to the target handler. If `options.consent` is set,
559
- * Studio stores the request as a pending offer instead.
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.
560
587
  *
561
- * @param payload - Handoff target, reason, context, and options
562
- * @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.
563
593
  */
564
- handoffRequest(payload: HandoffRequestPayload): 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
+ };
565
685
  /**
566
- * Return from a handoff back to the parent handler.
567
- *
568
- * The Studio proxy pops the handoff stack and delivers the return
569
- * result to the parent handler on the next user message.
686
+ * Throughline (find-a-helpline) surface.
570
687
  *
571
- * @param payload - Summary and artifacts from the completed handoff
572
- * @returns A metadata stream chunk ready to yield or queue.push
573
- */
574
- handoffReturn(payload: HandoffReturnPayload): ProtocolStreamChunk;
575
- /**
576
- * Offer a handoff with user consent required.
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.
577
694
  *
578
- * Studio stores this as a pending offer, sends an SSE event to the
579
- * frontend, and waits for the user to accept or decline before
580
- * executing the handoff.
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.
581
698
  *
582
- * @param payload - Offer display info and the pending handoff request
583
- * @returns A metadata stream chunk ready to yield or queue.push
699
+ * Caching (24h on countries + topics, none on helplines) is server-
700
+ * side; the SDK is stateless.
584
701
  */
585
- handoffOffer(payload: HandoffOfferPayload): ProtocolStreamChunk;
586
- };
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;
587
732
 
588
733
  /**
589
- * Platform Client
734
+ * Render Markers
590
735
  *
591
- * SDK client for handlers to call back to FirstStep Studio.
592
- * Enables handlers to query platform data (chatbot list, etc.)
593
- * 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
594
742
  *
595
743
  * @example
596
744
  * ```typescript
597
- * import { createPlatformClient } from '@firststep-studio/sdk';
598
- *
599
- * const client = createPlatformClient({
600
- * studioUrl: 'https://studio-api.example.com',
601
- * token: process.env.FIRSTSTEP_TOKEN,
602
- * });
745
+ * import { renderMarkers } from '@firststep-studio/sdk';
603
746
  *
604
- * const chatbots = await client.listChatbots();
605
- * // [{ 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
+ * };
606
754
  * ```
607
755
  */
608
- interface PlatformClientConfig {
609
- /** Studio backend API URL (e.g. https://studio-api.example.com) */
610
- studioUrl: string;
611
- /** API token (same as FIRSTSTEP_TOKEN used for signature verification) */
612
- token: string;
613
- /** Request timeout in ms (default: 10000) */
614
- timeout?: number;
615
- }
616
- interface PlatformOrganization {
617
- /** Studio organization ID */
618
- id: string;
619
- /** Display name (null if the organization record has been deleted) */
620
- name: string | null;
621
- /** URL-safe slug */
622
- slug: string | null;
623
- }
624
- interface PlatformTokenInfo {
625
- /** API token ID */
626
- id: string;
627
- /** Human-friendly token label */
628
- name: string;
629
- /** Authorized scopes */
630
- scopes: string[];
631
- /** Project IDs this token is scoped to (null = full org access) */
632
- projectIds: string[] | null;
633
- }
634
- interface PlatformIdentity {
635
- organization: PlatformOrganization;
636
- token: PlatformTokenInfo;
637
- }
638
- interface PlatformChatbotRegisterInput {
639
- /** Stable handler-side identifier used to upsert (e.g. handler config ID). */
640
- externalId: string;
641
- /** Display name shown in Studio's chatbot list. */
642
- name: string;
643
- /** Optional human description. */
644
- description?: string;
645
- /** Public URL where Studio should send protocol requests. */
646
- handlerUrl: string;
647
- /** Slug for multi-config routing on the handler side. */
648
- configSlug?: string;
649
- /** Optional capability flags advertised to Studio. */
650
- capabilities?: {
651
- streaming?: boolean;
652
- formQuestions?: boolean;
653
- knowledgeActions?: boolean;
654
- integrations?: boolean;
655
- [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[];
656
780
  };
657
781
  }
658
- interface PlatformChatbotRegistration {
659
- /** Studio chatbot MongoDB ID (immutable across re-registrations). */
660
- id: string;
661
- /** Echoes back the externalId used as upsert key. */
662
- externalId: string;
663
- /** 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;
664
789
  name: string;
665
- 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;
666
805
  }
667
- interface PlatformChatbot {
668
- /** Studio chatbot MongoDB ID */
669
- id: string;
670
- /** Chatbot display name */
671
- name: string;
672
- /** Handler URL (if external handler configured) */
673
- handlerUrl?: string;
674
- /** Config slug for multi-config routing */
675
- configSlug?: string;
676
- /** Handler type */
677
- handlerType?: 'builtin' | 'external';
806
+
807
+ /** Raw Throughline v2 card contract plus handler presentation metadata. */
808
+ interface HelplineCardV2Payload {
809
+ helplines: HelplineCardV2Item[];
810
+ type?: 'throughline' | 'stage';
678
811
  }
679
- interface PlatformKnowledgeSummary {
680
- /** Studio knowledge MongoDB ID */
681
- id: string;
682
- /** 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 {
683
833
  name: string;
684
- /** Optional description */
834
+ url?: string;
685
835
  description?: string;
686
- /** Knowledge type (e.g. 'text', 'database') */
836
+ video_url?: string;
687
837
  type?: string;
688
- /** Owning organization ID. Null for org-less system documents. */
689
- organizationId: string | null;
690
- /**
691
- * True for handler-managed documents created through the protocol
692
- * write API. System-owned knowledge is hidden from every Studio UI
693
- * surface and only appears in `list` when `includeSystemOwned` is set.
694
- */
695
- isSystemOwned?: boolean;
696
- /** Whether semantic search (record embedding) is enabled. */
697
- ragEnabled?: boolean;
698
- /** Timestamps */
699
- createdAt?: number | string;
700
- updatedAt?: number | string;
838
+ tags?: string[];
839
+ highlightedTag?: string;
701
840
  }
702
841
  /**
703
- * Full knowledge document (shape mirrors the underlying mongoose doc).
704
- * Kept loose because the schema is large and varies by knowledge `type`;
705
- * 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.
706
844
  */
707
- type PlatformKnowledge = PlatformKnowledgeSummary & Record<string, unknown>;
708
- interface ListKnowledgeOptions {
709
- /**
710
- * Target organization to list knowledge from. If omitted, defaults to
711
- * the org associated with the SDK token. Use this to read from a
712
- * different org (e.g. a platform-operated shared library marked
713
- * `publicReadable` in Studio).
714
- */
715
- orgId?: string;
716
- /**
717
- * Include system-owned (handler-managed) knowledge in the listing.
718
- * Only honored when listing the token's own org; other orgs never
719
- * expose their system-owned documents.
720
- */
721
- includeSystemOwned?: boolean;
722
- /**
723
- * 'system' lists ORG-LESS platform documents instead of org
724
- * knowledge. orgId and includeSystemOwned are ignored in that mode.
725
- */
726
- scope?: 'system';
845
+ interface ProviderCardPayload {
846
+ providers: ProviderCardItem[];
727
847
  }
728
- /** Sheet definition for `knowledge.create`. */
729
- interface CreateKnowledgeSheet {
730
- /** Stable sheet id, referenced later by record writes and search. */
848
+ interface ProviderCardItem {
731
849
  id: string;
732
850
  name: string;
851
+ type?: string;
852
+ specialty: string[];
853
+ language: string[];
733
854
  description?: string;
734
- /**
735
- * Column definitions ({ id, name, type, ... }). Loose-typed; the
736
- * Studio schema validates `type` against its column-type enum.
737
- */
738
- 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;
739
861
  }
740
- interface CreateKnowledgeOptions {
741
- description?: string;
742
- /**
743
- * Enable semantic search at creation time. Defaults to false; can be
744
- * flipped later with `setRagEnabled` (which also backfills embeddings
745
- * for records inserted while the flag was off).
746
- */
747
- ragEnabled?: boolean;
748
- /**
749
- * Create the knowledge as an ORG-LESS platform document: no
750
- * organizationId at all, structurally invisible to every org-scoped
751
- * Studio surface. This is the home for handler-managed data. Without
752
- * it the document is created under the token's org (hidden from the
753
- * UI by isSystemOwned, but org-bound).
754
- */
755
- 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
+ };
756
877
  }
757
- /** A raw record as returned by the protocol record-write endpoints. */
758
- interface PlatformKnowledgeRecord {
878
+ interface SafetyPlanSection {
759
879
  id: string;
760
- knowledgeId: string;
761
- sheetId: string;
762
- data: Record<string, unknown>;
763
- createdAt?: number | string;
764
- updatedAt?: number | string;
880
+ title: string;
881
+ items: Array<string | {
882
+ name: string;
883
+ phone?: string;
884
+ description?: string;
885
+ }>;
765
886
  }
766
- interface KnowledgeSearchOptions {
767
- /** Max records to return (server-side clamped to 1..50, default 10). */
768
- 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;
769
917
  /**
770
- * Optional sheet filter. Pass a sheet id (from the knowledge document)
771
- * to restrict the search to that sheet. Omit to search across all
772
- * 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.
773
921
  */
774
- 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;
775
930
  }
776
931
  /**
777
- * A single hit from `client.knowledge.search`. `score` is the Atlas
778
- * vector similarity score (cosine in our setup, so higher is closer).
779
- * `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`.
780
937
  */
781
- interface PlatformKnowledgeSearchHit {
782
- id: string;
783
- knowledgeId: string;
784
- sheetId: string;
785
- data: Record<string, unknown>;
786
- score: number;
787
- createdAt?: number | string;
788
- updatedAt?: number | string;
789
- }
938
+ declare function parseMarkerPayload<T = unknown>(content: string, type: string): T | null;
790
939
  /**
791
- * Helplines query params — passed verbatim to Throughline. Loose-typed
792
- * so the SDK stays compatible with new Throughline query keys without
793
- * a release. Required by Throughline today: `country_code`. Common
794
- * 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
+ * ```
795
1019
  */
796
- type ThroughlineHelplinesQuery = Record<string, string | number | boolean | undefined>;
797
- declare class PlatformError extends Error {
798
- status: number;
799
- constructor(message: string, status: number);
800
- }
801
- declare class PlatformClient {
802
- private studioUrl;
803
- private token;
804
- private timeout;
805
- constructor(config: PlatformClientConfig);
806
- private request;
1020
+
1021
+ declare const streamMetadata: {
807
1022
  /**
808
- * Resolve the organization and token metadata for the configured token.
809
- * 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
810
1031
  */
811
- getMe(): Promise<PlatformIdentity>;
1032
+ declareSchema(schema: SchemaDeclarationPayload): ProtocolStreamChunk;
812
1033
  /**
813
- * List chatbots accessible to this handler's organization.
814
- * 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
815
1041
  */
816
- listChatbots(): Promise<PlatformChatbot[]>;
1042
+ formDataUpdate(fields: Record<string, string | number>): ProtocolStreamChunk;
817
1043
  /**
818
- * Register or update an external-handler chatbot under the token's
819
- * organization. Idempotent: identified by `externalId`, so handlers
820
- * 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
821
1051
  */
822
- registerChatbot(input: PlatformChatbotRegisterInput): Promise<PlatformChatbotRegistration>;
1052
+ agentTransition(agent: AgentTransitionPayload): ProtocolStreamChunk;
823
1053
  /**
824
- * Mark the chatbot identified by `externalId` as disconnected.
825
- * 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
826
1061
  */
827
- unregisterChatbot(externalId: string): Promise<{
828
- ok: boolean;
829
- modified: number;
830
- }>;
1062
+ routingResult(result: RoutingClassificationPayload): ProtocolStreamChunk;
831
1063
  /**
832
- * Knowledge / resource surface.
1064
+ * Request a handoff to another chatbot.
833
1065
  *
834
- * Reads are allowed when the SDK token's org matches the target org,
835
- * 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.
836
1069
  *
837
- * Writes target system-owned (handler-managed) knowledge only: the
838
- * protocol API stamps everything created here `isSystemOwned: true`,
839
- * which keeps it out of every Studio UI surface — Studio acts as a
840
- * plain storage + RAG layer for it. UI-managed knowledge cannot be
841
- * 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
842
1072
  */
843
- knowledge: {
844
- /**
845
- * List knowledge items for an organization.
846
- * Defaults to the SDK token's own org if `orgId` is omitted.
847
- */
848
- list: (options?: ListKnowledgeOptions) => Promise<PlatformKnowledgeSummary[]>;
849
- /**
850
- * Fetch a single knowledge item by its Studio ID. The caller must
851
- * have read access to the knowledge's owning org (same-org or
852
- * publicReadable).
853
- */
854
- get: (id: string) => Promise<PlatformKnowledge>;
855
- /**
856
- * Semantic search over a knowledge's records. Studio embeds the
857
- * query server-side (Gemini gemini-embedding-001) and runs Atlas
858
- * Vector Search against the `vector_index` on `knowledge_records`.
859
- *
860
- * Same read access rules as `get` — same-org or publicReadable.
861
- *
862
- * Returns an empty array (not an error) when the Atlas index is
863
- * absent or when nothing matches, so handler code can treat
864
- * "no hits" and "RAG not configured yet" identically.
865
- */
866
- search: (id: string, query: string, options?: KnowledgeSearchOptions) => Promise<PlatformKnowledgeSearchHit[]>;
867
- /**
868
- * Turn semantic search on or off for a knowledge.
869
- *
870
- * Writes are strictly same-org — `publicReadable` does NOT confer
871
- * write access. Flipping `false -> true` schedules a background
872
- * backfill of existing records on the Studio side; this call
873
- * returns as soon as the flag is persisted, not when the backfill
874
- * finishes.
875
- *
876
- * Idempotent: setting the flag to its current value is a no-op
877
- * (`backfillTriggered` will be `false`).
878
- */
879
- setRagEnabled: (id: string, enabled: boolean) => Promise<{
880
- id: string;
881
- ragEnabled: boolean;
882
- backfillTriggered: boolean;
883
- }>;
884
- /**
885
- * Create a system-owned database knowledge under the token's org.
886
- *
887
- * 409 (surfaced as an error) means a knowledge of the same name
888
- * already exists in the org. Idempotent callers should recover by
889
- * listing with `includeSystemOwned: true` and matching on name.
890
- */
891
- create: (name: string, sheets: CreateKnowledgeSheet[], options?: CreateKnowledgeOptions) => Promise<PlatformKnowledge>;
892
- /**
893
- * Delete a system-owned knowledge and all of its records.
894
- * Refused (403) for UI-managed knowledge.
895
- */
896
- delete: (id: string) => Promise<{
897
- deletedRecords: number;
898
- }>;
899
- /**
900
- * List a sheet's records (newest first). Full-list surfaces (admin
901
- * tables, exact name matching) where semantic search is the wrong
902
- * tool. `limit` defaults to 500 (server max 2000).
903
- */
904
- listRecords: (id: string, sheetId: string, options?: {
905
- limit?: number;
906
- offset?: number;
907
- }) => Promise<{
908
- records: PlatformKnowledgeRecord[];
909
- total: number;
910
- }>;
911
- /**
912
- * Append one record to a sheet. Embeds async when ragEnabled.
913
- */
914
- addRecord: (id: string, sheetId: string, data: Record<string, unknown>) => Promise<PlatformKnowledgeRecord>;
915
- /**
916
- * Replace a record's data payload. Re-embeds async when ragEnabled.
917
- */
918
- updateRecord: (id: string, recordId: string, data: Record<string, unknown>) => Promise<PlatformKnowledgeRecord>;
919
- /** Delete one record. */
920
- deleteRecord: (id: string, recordId: string) => Promise<void>;
921
- /**
922
- * Bulk-insert records into one sheet (each array entry becomes one
923
- * record's `data`). With `replace: true` the sheet's existing
924
- * records are deleted first — the full-resync primitive backfills
925
- * use. Server caps a single call at 5000 records.
926
- */
927
- bulkRecords: (id: string, sheetId: string, records: Array<Record<string, unknown>>, options?: {
928
- replace?: boolean;
929
- }) => Promise<{
930
- insertedCount: number;
931
- deletedCount: number;
932
- }>;
933
- };
1073
+ handoffRequest(payload: HandoffRequestPayload): ProtocolStreamChunk;
934
1074
  /**
935
- * Throughline (find-a-helpline) surface.
1075
+ * Return from a handoff back to the parent handler.
936
1076
  *
937
- * Handlers used to call api.throughlinecare.com directly, which meant
938
- * each handler had to hold OAuth client credentials and re-implement
939
- * token caching. Studio holds the per-org credentials on
940
- * `Organization.throughline.{clientId,clientSecret}` and proxies the
941
- * Throughline endpoints; the SDK token identifies the org server-side
942
- * 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.
943
1079
  *
944
- * Responses are returned as parsed JSON. Studio does not reshape the
945
- * Throughline body — these surfaces stay compatible with future
946
- * 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.
947
1086
  *
948
- * Caching (24h on countries + topics, none on helplines) is server-
949
- * 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
950
1093
  */
951
- throughline: {
952
- /**
953
- * GET /v1/countries — Throughline's country catalog (codes,
954
- * names, emergency numbers). Cached 24h server-side.
955
- */
956
- getCountries: () => Promise<unknown>;
957
- /**
958
- * GET /v1/topics — Throughline's topic taxonomy used to filter
959
- * helpline searches. Cached 24h server-side.
960
- */
961
- getTopics: () => Promise<unknown>;
962
- /**
963
- * GET /v1/helplines?country_code=...&topic=...&... — live search,
964
- * not cached. The query object is forwarded verbatim to Throughline,
965
- * so any new key Throughline adds will work without an SDK change.
966
- * Required by Throughline today: `country_code`.
967
- */
968
- getHelplines: (query: ThroughlineHelplinesQuery) => Promise<unknown>;
969
- };
970
- }
971
- /**
972
- * Create a PlatformClient for calling back to FirstStep Studio.
973
- */
974
- declare function createPlatformClient(config: PlatformClientConfig): PlatformClient;
1094
+ handoffOffer(payload: HandoffOfferPayload): ProtocolStreamChunk;
1095
+ };
975
1096
 
976
1097
  /**
977
1098
  * Verify an HMAC-SHA256 request signature.
@@ -1240,4 +1361,4 @@ declare class LLMClient {
1240
1361
  */
1241
1362
  declare function createLLMClient(config: LLMClientConfig): LLMClient;
1242
1363
 
1243
- 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 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, isValidToken, 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 };