@guideai/react-native 0.0.0-stage → 0.2.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.
@@ -0,0 +1,1091 @@
1
+ import React from 'react';
2
+
3
+ interface ToolCallResponse {
4
+ tool: string;
5
+ method: string;
6
+ path: string;
7
+ params?: Record<string, unknown>;
8
+ body?: Record<string, unknown>;
9
+ headers?: Record<string, string>;
10
+ requires_confirm: boolean;
11
+ /**
12
+ * Human-readable phrase for the UI ("Retrieving site information"), supplied
13
+ * by the model. `describeToolCall()` derives one when it is missing — the
14
+ * chat UI never shows the raw tool/endpoint name.
15
+ */
16
+ label?: string | null;
17
+ }
18
+ interface NavigationAction {
19
+ url: string;
20
+ description?: string;
21
+ }
22
+ interface ToolExecutionResult {
23
+ success: boolean;
24
+ data?: unknown;
25
+ error?: string;
26
+ error_type?: string;
27
+ status?: number;
28
+ }
29
+ interface CopilotToolDef {
30
+ name: string;
31
+ description: string;
32
+ method: string;
33
+ path: string;
34
+ params?: {
35
+ name: string;
36
+ in: string;
37
+ type: string;
38
+ required: boolean;
39
+ description?: string;
40
+ }[];
41
+ body_schema?: Record<string, unknown>;
42
+ risk_level: 'read' | 'write' | 'destructive';
43
+ }
44
+ /** A host/browser tool advertised to the planner but executed only by the SDK. */
45
+ interface ClientToolDefinition {
46
+ name: string;
47
+ description: string;
48
+ parameters: Record<string, unknown>;
49
+ consequential?: boolean;
50
+ }
51
+ interface CopilotConfig {
52
+ siteId: string;
53
+ /** Runtime label from the SDK script; this is the assistant's visible name. */
54
+ assistantLabel?: string;
55
+ /** Host-owned rule describing which requests another assistant should handle. */
56
+ delegationInstruction?: string;
57
+ /**
58
+ * The analytics session this chat belongs to. Sent with each question so a
59
+ * repeated "how do I …" can be correlated with whether the user then got
60
+ * there — the question alone says something is hard, the pairing says
61
+ * whether anything fixed it. Carries no message content.
62
+ */
63
+ analyticsSessionId?: string;
64
+ /**
65
+ * The product-analytics bundle's own ids, read at send time because that
66
+ * bundle can load after the chat and rotates its session when idle. Its
67
+ * session is the one the visitor's events are recorded under, so it wins
68
+ * over analyticsSessionId when present. Ids only, never content.
69
+ */
70
+ analyticsIds?: () => {
71
+ session_id?: string;
72
+ distinct_id?: string;
73
+ };
74
+ /**
75
+ * What this runtime can render beyond plain answers and walkthroughs. The
76
+ * server only sends a richer reply (a coach run for "coach") to a client
77
+ * that lists it, so a runtime that cannot play one keeps getting what it can.
78
+ */
79
+ clientCapabilities?: string[];
80
+ memoryTtlMs?: number;
81
+ maxMemoryMessages?: number;
82
+ }
83
+ /** A published interactive demo the assistant suggests playing inline (Storylane-style). */
84
+ interface DemoSuggestion {
85
+ share_token: string;
86
+ title: string;
87
+ description?: string | null;
88
+ /** Player URL with embed=1&autostart=1 — for in-chat iframe playback. */
89
+ embed_url: string;
90
+ /** Plain player URL — for "open in new tab". */
91
+ share_url: string;
92
+ thumbnail_url?: string | null;
93
+ step_count?: number;
94
+ }
95
+ /**
96
+ * One element on the visitor's page the reply rings, with a caption. Only
97
+ * from a turn whose skill grants `can_point`; the server checked the page
98
+ * snapshot listed it.
99
+ */
100
+ interface PagePoint {
101
+ selector: string;
102
+ text?: string;
103
+ caption: string;
104
+ }
105
+ /** A button to a page the site has. Only from a skill with `cta_buttons`. */
106
+ interface ChatAction {
107
+ label: string;
108
+ url: string;
109
+ }
110
+ /**
111
+ * A checkout offer from the site's payment connector (agent flow). Name, price
112
+ * and currency come from the site's settings, never from the model; the
113
+ * checkout itself is created only when the visitor presses Pay.
114
+ */
115
+ interface ChatPayment {
116
+ item_id: string;
117
+ name: string;
118
+ /** Smallest currency unit (cents, kobo). */
119
+ amount_minor: number;
120
+ currency: string;
121
+ provider: 'stripe' | 'paystack' | 'flutterwave' | string;
122
+ }
123
+ /** One stop on a coach run: reach a screen ("go"), or work through it ("do"). */
124
+ interface CoachMilestone {
125
+ title: string;
126
+ /**
127
+ * A route from the site's scanned map: the screen this stop happens on.
128
+ * Plans from servers before Oct 2026 may leave it null.
129
+ */
130
+ route: string | null;
131
+ /** The page's name in the scan ("Onboarding"): what a link to it says. */
132
+ page?: string | null;
133
+ kind: 'go' | 'do';
134
+ /** The task can succeed without it; the visitor may skip it. */
135
+ optional?: boolean;
136
+ /** The link the site's skill and KB agree leads to this stop's page. */
137
+ way?: CoachTarget | null;
138
+ /** This stop's controls in the skill's order, each found in the KB. */
139
+ targets?: CoachTarget[];
140
+ /** Inputs the task cannot finish without (labels), held before Done. */
141
+ needs?: string[];
142
+ }
143
+ /**
144
+ * One step of a site skill's flow, checked against the scanned app: the
145
+ * skill says it is needed, the KB says where it is.
146
+ */
147
+ interface CoachTarget {
148
+ label: string;
149
+ kind: 'link' | 'control' | 'field' | 'toggle' | 'section';
150
+ route: string;
151
+ /** What the visitor does with it. */
152
+ action: 'go' | 'open' | 'fill' | 'choose' | 'switch';
153
+ /** The section or menu it sits in. */
154
+ under?: string;
155
+ /** The folded group to open first when it is hidden. */
156
+ reveal?: string;
157
+ /** For a link: the page it opens. */
158
+ opens?: string;
159
+ required?: boolean;
160
+ /** The scanned fingerprint, when the KB has one. */
161
+ fingerprint?: Record<string, unknown>;
162
+ }
163
+ /**
164
+ * A coach run: "how do I…" answered by taking the visitor to the screen and
165
+ * talking them through it, rather than only pointing.
166
+ */
167
+ interface CoachPlan {
168
+ goal: string;
169
+ intro: string;
170
+ /** Asked first when the task has more than one path; options are chips. */
171
+ question: {
172
+ text: string;
173
+ options: string[];
174
+ } | null;
175
+ milestones: CoachMilestone[];
176
+ /** The page the task itself is done on — the run is never coached elsewhere. */
177
+ task_route?: string | null;
178
+ /**
179
+ * "site", "workspace"…: the plan wanted the visitor to switch it. They are
180
+ * asked once to check they are in the right one instead of switching.
181
+ */
182
+ confirm_context?: string | null;
183
+ /**
184
+ * Set when this plan is new: sent back to /chat/coach/confirm once the run
185
+ * finishes on the task's page, and only then shared with the next visitor.
186
+ */
187
+ proof_key?: string | null;
188
+ done_when: {
189
+ route: string | null;
190
+ text: string | null;
191
+ };
192
+ settings?: {
193
+ voice: 'off' | 'browser' | 'neural';
194
+ explain_options: boolean;
195
+ offer_help: boolean;
196
+ vision_per_run: number;
197
+ };
198
+ cached?: boolean;
199
+ }
200
+ /** Escalation signal from the AI assistant. */
201
+ interface EscalationSignal {
202
+ offered: boolean;
203
+ reason?: 'low_confidence' | 'cannot_resolve' | 'user_requested';
204
+ confidence?: number;
205
+ }
206
+ /** Callback for reporting agent loop progress to the UI. */
207
+ type AgentStatusCallback = (status: string) => void;
208
+ /** One step of the plan the assistant stated before it started working. */
209
+ interface AssistantPlanStep {
210
+ title: string;
211
+ /** Endpoint this step uses; absent when the assistant does it itself. */
212
+ tool?: string | null;
213
+ /** Set when nothing available can do this step. */
214
+ blocked_reason?: string | null;
215
+ }
216
+ interface AssistantPlan {
217
+ steps: AssistantPlanStep[];
218
+ note?: string | null;
219
+ }
220
+ type PlanCallback = (plan: AssistantPlan) => void;
221
+ interface HarnessResponse {
222
+ mode: 'answer' | 'guide' | 'execute' | 'coach';
223
+ answer: string;
224
+ /** What the assistant said it would do, shown while it works. */
225
+ plan?: AssistantPlan;
226
+ intent?: 'delegate';
227
+ intentReason?: string;
228
+ guideSteps?: Record<string, unknown>[];
229
+ guideTitle?: string;
230
+ navigation?: NavigationAction;
231
+ toolResult?: ToolExecutionResult;
232
+ confirmationNeeded?: boolean;
233
+ pendingToolCall?: ToolCallResponse;
234
+ /** Full concurrent batch when the model requested more than one tool. */
235
+ pendingToolCalls?: ToolCallResponse[];
236
+ /** When true, the ChatManager should auto-execute the pendingToolCall (read-only action). */
237
+ autoExecuteToolCall?: boolean;
238
+ autoExecute: boolean;
239
+ /** Escalation signal from the AI. */
240
+ escalation?: EscalationSignal;
241
+ /** Interactive demo suggestion the chat UI can play inline. */
242
+ demo?: DemoSuggestion;
243
+ point?: PagePoint;
244
+ actions?: ChatAction[];
245
+ payment?: ChatPayment;
246
+ coach?: CoachPlan;
247
+ /** One-tap replies; a tap sends the label as the next message. */
248
+ quickReplies?: string[];
249
+ }
250
+ interface ConversationMessage {
251
+ id?: string;
252
+ role: 'user' | 'assistant';
253
+ content: string;
254
+ toolResult?: ToolExecutionResult;
255
+ toolCalls?: ToolCallRecord[];
256
+ timestamp: number;
257
+ /** Guide steps generated by the assistant (for walkthrough buttons). */
258
+ guideSteps?: Record<string, unknown>[];
259
+ /** Title for the guide walkthrough. */
260
+ guideTitle?: string;
261
+ /** Navigation action returned by the assistant. */
262
+ navigation?: NavigationAction;
263
+ /** Response mode (answer, guide, execute). */
264
+ mode?: 'answer' | 'guide' | 'execute' | 'coach';
265
+ /** Whether this message has a pending tool call needing confirmation. */
266
+ confirmationNeeded?: boolean;
267
+ /** The pending tool call awaiting user confirmation. */
268
+ pendingToolCall?: ToolCallResponse;
269
+ /** Escalation signal from the AI. */
270
+ escalation?: EscalationSignal;
271
+ /** Interactive demo suggestion attached to this assistant message. */
272
+ demo?: DemoSuggestion;
273
+ }
274
+ /** Record of a tool call for conversation history context. */
275
+ interface ToolCallRecord {
276
+ tool: string;
277
+ description?: string;
278
+ success: boolean;
279
+ summary?: string;
280
+ /** Present when the record came from a client-executed call rather than SSE. */
281
+ method?: string;
282
+ path?: string;
283
+ }
284
+ /** Callback for streaming text deltas to the UI. */
285
+ type TextDeltaCallback = (delta: string) => void;
286
+ /** Callback for tool call lifecycle events. */
287
+ type ToolCallEventCallback = (event: ToolCallDisplayEvent) => void;
288
+ /** Event describing tool call progress for UI display. */
289
+ interface ToolCallDisplayEvent {
290
+ type: 'start' | 'done';
291
+ tool: string;
292
+ description?: string;
293
+ success?: boolean;
294
+ summary?: string;
295
+ }
296
+
297
+ /** Shared onboarding contract. UI targets are interpreted by each platform. */
298
+ /** One highlighted action "Show me" plays, in order, on the step's page. */
299
+ interface OnboardingAction {
300
+ id: string;
301
+ title: string;
302
+ instruction: string;
303
+ action: 'click' | 'fill' | 'select' | 'check';
304
+ /** Words on the control; the page is searched for them when no fingerprint matches. */
305
+ target_label: string;
306
+ target_fingerprint: Record<string, unknown>;
307
+ kind: string;
308
+ context: string;
309
+ }
310
+ interface OnboardingStepState {
311
+ id: string;
312
+ title: string;
313
+ instruction: string;
314
+ required: boolean;
315
+ state: 'complete' | 'skipped' | 'available' | 'locked';
316
+ /** Complete on the person's own tick (a manual step), not on evidence. */
317
+ self_reported?: boolean;
318
+ location: {
319
+ surface: 'web' | 'mobile' | 'desktop' | 'other';
320
+ screen: string;
321
+ target_label: string;
322
+ target_fingerprint: Record<string, unknown>;
323
+ /** A help hint on `screen` that "Show me" plays instead of highlighting a control. */
324
+ hint_id?: string | null;
325
+ };
326
+ completion: {
327
+ type: 'event' | 'screen' | 'text_visible' | 'manual';
328
+ value: string;
329
+ };
330
+ actions?: OnboardingAction[];
331
+ }
332
+ /** The button that opens the checklist; every field falls back to the card-matching default. */
333
+ interface OnboardingLauncher {
334
+ label?: string;
335
+ invite_label?: string;
336
+ complete_label?: string;
337
+ show_progress?: boolean;
338
+ shape?: 'rounded' | 'pill';
339
+ background_color?: string | null;
340
+ text_color?: string | null;
341
+ }
342
+ interface OnboardingAppearance {
343
+ template: 'classic' | 'minimal' | 'bold';
344
+ accent_color: string;
345
+ /** Optional checklist surface and text colours; missing values use the SDK theme. */
346
+ background_color?: string | null;
347
+ text_color?: string | null;
348
+ /** Missing means the default card; position missing means left. */
349
+ layout?: 'card' | 'modal' | 'side';
350
+ position?: 'left' | 'right';
351
+ launcher?: OnboardingLauncher;
352
+ }
353
+ interface OnboardingState {
354
+ id: string;
355
+ title: string;
356
+ goal: string;
357
+ start_mode: 'automatic' | 'ask' | 'checklist';
358
+ chat_policy: 'unrestricted' | 'setup_only';
359
+ allow_dismissal: boolean;
360
+ appearance?: OnboardingAppearance;
361
+ completed: boolean;
362
+ dismissed: boolean;
363
+ completed_required: number;
364
+ total_required: number;
365
+ /** Every step, optional ones included; a skipped optional step counts as done. Missing from older servers. */
366
+ completed_steps?: number;
367
+ total_steps?: number;
368
+ steps: OnboardingStepState[];
369
+ }
370
+
371
+ /** A picture drawn for an answer: a PNG to show, and the SVG it was drawn as. */
372
+ interface HeadlessImage {
373
+ url: string;
374
+ svg: string | null;
375
+ alt: string;
376
+ width: number;
377
+ height: number;
378
+ }
379
+
380
+ interface PageElement {
381
+ tag: string;
382
+ text: string;
383
+ selector: string;
384
+ aria_label?: string;
385
+ name?: string;
386
+ role?: string;
387
+ type?: string;
388
+ placeholder?: string;
389
+ /** href attribute for anchor elements */
390
+ href?: string;
391
+ /** Current value of input/textarea/select elements */
392
+ value?: string;
393
+ /** Associated label text (from <label> or aria-labelledby) */
394
+ label?: string;
395
+ /** More descriptive form label when available. */
396
+ form_label?: string;
397
+ /** True when element exists in DOM but is not currently visible */
398
+ hidden?: boolean;
399
+ /** Describes the container: "hamburger menu", "dropdown", "sidebar", etc. */
400
+ container?: string;
401
+ /** State of the container: "collapsed", "closed", etc. */
402
+ containerState?: string;
403
+ /** Nearby descriptive text from the element's parent container. */
404
+ parent_text?: string;
405
+ /** Title of the nearest visible section heading. */
406
+ section_title?: string;
407
+ /** Source URL when the element is inside an iframe */
408
+ iframe_src?: string;
409
+ }
410
+ interface PageSectionLink {
411
+ text: string;
412
+ href: string;
413
+ selector: string;
414
+ }
415
+ interface PageSection {
416
+ title: string;
417
+ selector: string;
418
+ text?: string;
419
+ links: PageSectionLink[];
420
+ elements: PageElement[];
421
+ }
422
+ interface PageSnapshot {
423
+ url: string;
424
+ title: string;
425
+ page_text?: string;
426
+ sections?: PageSection[];
427
+ elements: PageElement[];
428
+ }
429
+
430
+ interface ClientToolRegistry {
431
+ /** Tools this host can run, in the shape the backend expects. */
432
+ definitions(): ClientToolDefinition[];
433
+ /** The same tools shaped for the copilot planner and the validator. */
434
+ copilotDefinitions(): CopilotToolDef[];
435
+ /** Whether a tool name belongs to this registry. */
436
+ canExecute(name: string): boolean;
437
+ /** Whether a specific planned call is one this registry should run. */
438
+ canExecuteCall(toolCall: ToolCallResponse): boolean;
439
+ /** Run one call. Never throws — failures come back as a result. */
440
+ execute(toolCall: ToolCallResponse): Promise<ToolExecutionResult>;
441
+ /**
442
+ * Run several calls, reporting each as it lands.
443
+ *
444
+ * `onComplete` fires per call so a slow one cannot hold back the others'
445
+ * progress in the UI.
446
+ */
447
+ executeBatch(toolCalls: ToolCallResponse[], onComplete?: (toolCall: ToolCallResponse, result: ToolExecutionResult) => void): Promise<ToolExecutionResult[]>;
448
+ /**
449
+ * Release anything the registry is holding (timers, highlights).
450
+ *
451
+ * Optional: the harness has no teardown path today, so requiring it
452
+ * would force every registry to implement code nothing calls.
453
+ */
454
+ destroy?(): void;
455
+ }
456
+
457
+ /**
458
+ * A synchronous key/value store.
459
+ *
460
+ * Synchronous on purpose: the conversation memory reads storage inside
461
+ * `getMessages()`, which the agent loop calls from ~20 sync places.
462
+ * Making reads async would push `await` through the whole loop and change
463
+ * its public signatures for no functional gain.
464
+ *
465
+ * Web: `localStorage` already *is* this shape.
466
+ * React Native: MMKV or `expo-sqlite/kv-store` (both sync). A host whose
467
+ * only store is AsyncStorage must hydrate a snapshot at boot and write
468
+ * through — the honest cost of not rewriting the loop.
469
+ */
470
+ interface KVStore {
471
+ getItem(key: string): string | null;
472
+ setItem(key: string, value: string): void;
473
+ removeItem(key: string): void;
474
+ }
475
+ /**
476
+ * Where a host keeps credentials the copilot may read, named by the
477
+ * source a site configured (`localStorage`, `sessionStorage`, `cookie`),
478
+ * or `app` for values the host app hands over itself (declared headers).
479
+ *
480
+ * Returns null for a source this platform does not have. A mobile app has
481
+ * no cookie jar, and saying so plainly is better than returning an empty
482
+ * string that reads as "present but blank".
483
+ */
484
+ interface CredentialSource {
485
+ read(source: string, key: string): string | null;
486
+ /**
487
+ * The signed-in user's bearer token, when the host hands it over itself.
488
+ *
489
+ * A native app keeps its token where it chooses (SecureStore,
490
+ * AsyncStorage), not in a store the site's dashboard can name. When this
491
+ * returns a token it wins over the configured storage lookup for a
492
+ * `bearer` toolset; null falls back to that lookup.
493
+ */
494
+ authToken?(): string | null;
495
+ }
496
+
497
+ /**
498
+ * Decoded text chunks arriving as the response streams.
499
+ *
500
+ * Text, not bytes, on purpose. Decoding UTF-8 incrementally is the one
501
+ * part of stream handling that is easy to get subtly wrong: a multi-byte
502
+ * character split across a chunk boundary is corrupted unless the decoder
503
+ * carries state between chunks. Browsers have a spec-correct
504
+ * `TextDecoder`; React Native's polyfills vary, and one that ignores
505
+ * incremental mode mangles non-ASCII text silently, at runtime, past
506
+ * every typecheck and test.
507
+ *
508
+ * So each platform decodes where it knows the answer, and core consumes
509
+ * strings. **Implementations MUST handle characters split across chunk
510
+ * boundaries** — that is a contract, not a hint.
511
+ */
512
+ type TextStream = AsyncIterable<string>;
513
+ /** A request, already serialized. */
514
+ interface HttpRequest {
515
+ url: string;
516
+ /**
517
+ * Customer toolsets declare their own verbs — a `destructive` tool is
518
+ * typically a DELETE — so this cannot be narrowed to GET/POST without
519
+ * silently rewriting those calls.
520
+ */
521
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';
522
+ headers: Record<string, string>;
523
+ /** Pre-serialized body. Multipart stays platform-side; see HttpTransport. */
524
+ body?: string;
525
+ signal?: AbortSignal;
526
+ /**
527
+ * Best-effort delivery while the app is tearing down. Platforms without
528
+ * an equivalent ignore it — the semantics are already "may be dropped".
529
+ */
530
+ keepalive?: boolean;
531
+ }
532
+ /**
533
+ * What the agent loop actually reads off a response.
534
+ *
535
+ * Deliberately narrower than `Response`. `ok` is absent because nothing
536
+ * reads it and its presence invites `if (!res.ok)` to drift between
537
+ * platforms; `blob`/`arrayBuffer`/`redirected` are absent because they
538
+ * have no callers.
539
+ */
540
+ interface HttpResponse {
541
+ readonly status: number;
542
+ /**
543
+ * Header lookup. Shaped as `{ get() }` rather than a flat record to
544
+ * match `Headers`, so a browser transport can forward it directly.
545
+ */
546
+ readonly headers: {
547
+ get(name: string): string | null;
548
+ };
549
+ json<T = unknown>(): Promise<T>;
550
+ text(): Promise<string>;
551
+ /**
552
+ * Present only when this response carries a readable stream. Absent is
553
+ * normal and handled: the caller falls back to `json()`.
554
+ */
555
+ stream?: TextStream;
556
+ }
557
+ interface HttpTransport {
558
+ request(req: HttpRequest): Promise<HttpResponse>;
559
+ /**
560
+ * Whether this platform can deliver progressive bytes at all.
561
+ *
562
+ * Read by the agent loop to decide what to *ask the backend for*. It is
563
+ * a property of the transport rather than a setting, so a host cannot
564
+ * request streaming its platform physically cannot read — that would
565
+ * produce an empty answer with no error.
566
+ *
567
+ * Note both browsers and React Native can stream (fetch bodies and
568
+ * progressive XHR respectively), so this is true on both. It exists for
569
+ * the runtime that genuinely cannot.
570
+ */
571
+ readonly supportsStreaming: boolean;
572
+ }
573
+
574
+ interface CopilotApi {
575
+ /** JSON GET with the platform's default time budget. */
576
+ get<T>(path: string, timeoutMs?: number): Promise<T>;
577
+ /** JSON POST. Encrypts the body when the client has crypto configured. */
578
+ post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
579
+ /**
580
+ * POST returning the response unparsed, so the caller can stream it or
581
+ * branch on content-type. Not time-budgeted: a stream is long-lived by
582
+ * design and the caller owns cancellation through `signal`.
583
+ */
584
+ postRaw(path: string, body?: unknown, signal?: AbortSignal): Promise<HttpResponse>;
585
+ /** Whether this platform can read a response progressively. */
586
+ readonly supportsStreaming: boolean;
587
+ }
588
+
589
+ /**
590
+ * Copilot Agent Harness — client-side orchestrator.
591
+ *
592
+ * Architecture:
593
+ * - Backend plans the next step and formats final answers
594
+ * - Backend streams progress via SSE events during the loop
595
+ * - SDK can execute safe read tools in the browser and continue the chain
596
+ * - SDK reads the stream, shows status updates, and displays the final answer
597
+ * - For confirmed tool calls, SDK executes the customer API request in the browser
598
+ *
599
+ * Memory persists through an injected KVStore with TTL (localStorage on
600
+ * the web, MMKV on mobile). Multi-tenant safe via site_id scoping.
601
+ */
602
+
603
+ declare class CopilotHarness {
604
+ private api;
605
+ private config;
606
+ private memory;
607
+ private validator;
608
+ private toolExecutor;
609
+ private tools;
610
+ private readonly credentials;
611
+ private readonly transport;
612
+ private activeTools;
613
+ private initialized;
614
+ /** Told about the assistant's plan as soon as the server states one. */
615
+ private onPlan?;
616
+ private lastPlanFingerprint;
617
+ /** The plan in force for the message being answered. */
618
+ private currentPlan?;
619
+ /** Tool inputs the host page supplies — whatever the user has open. */
620
+ private toolContext?;
621
+ /**
622
+ * @param tools How this platform actually touches the screen. Defaults to
623
+ * the browser registry so every existing caller is unaffected; a mobile
624
+ * runtime passes one that dispatches native effects instead. The loop
625
+ * below never learns which it is holding.
626
+ */
627
+ constructor(api: CopilotApi, config: CopilotConfig, tools: ClientToolRegistry, storage: KVStore, transport: HttpTransport, credentials: CredentialSource);
628
+ /** Hear every turn the conversation records (typed, spoken, replies). */
629
+ onMessageRecorded(listener: ((message: ConversationMessage) => void) | null): void;
630
+ /** Initialize the harness — fetch active tools for the site. */
631
+ initialize(): Promise<boolean>;
632
+ /**
633
+ * Register the listener that renders the assistant's plan.
634
+ *
635
+ * Set on the harness rather than passed per message: `processMessage` is
636
+ * already called positionally from several places, and a plan belongs to
637
+ * the UI for the whole session, not to one call.
638
+ */
639
+ setPlanCallback(callback: PlanCallback | undefined): void;
640
+ /**
641
+ * Values the host page supplies for tool inputs.
642
+ *
643
+ * Sent with every turn, including chain rounds — a later round builds calls
644
+ * too, and dropping it there is how a chain that started on the right
645
+ * record finishes on the default one.
646
+ */
647
+ /** Bind to the SDK's analytics session. Carries no message content. */
648
+ setAnalyticsSessionId(sessionId: string): void;
649
+ /** Read the analytics bundle's ids on every question. Ids only. */
650
+ setAnalyticsIds(read: () => {
651
+ session_id?: string;
652
+ distinct_id?: string;
653
+ }): void;
654
+ setToolContext(values: Record<string, string> | undefined): void;
655
+ /**
656
+ * The app's tool inputs as the API names them. A value declared in Copilot
657
+ * Tools as filling other inputs (app key `course` fills `content_id`) is
658
+ * sent under those names too; one the app set by that name already wins.
659
+ */
660
+ private contextForTools;
661
+ /** Whether copilot action tools are available for this site. */
662
+ isEnabled(): boolean;
663
+ /**
664
+ * Process a user message through the harness.
665
+ *
666
+ * 1. Sends question + history + page context to backend /ask
667
+ * 2. If backend returns SSE stream (agent loop running), reads events in real-time
668
+ * 3. Calls onStatus callback with progress updates during the loop
669
+ * 4. Returns the final response when the stream completes
670
+ * 5. For non-tool queries, returns JSON response directly
671
+ */
672
+ processMessage(question: string, pageUrl?: string, pageSnapshot?: PageSnapshot, chatMode?: string, onStatus?: AgentStatusCallback, onTextDelta?: TextDeltaCallback, onToolCallEvent?: ToolCallEventCallback, abortSignal?: AbortSignal, userContext?: Record<string, unknown>, onboardingUserId?: string): Promise<HarnessResponse>;
673
+ /**
674
+ * Execute a confirmed tool call after user approval.
675
+ * Called when user clicks "Yes, do it" for write/destructive actions.
676
+ * Executes in the browser when possible, then formats the result with LLM.
677
+ */
678
+ executeToolCall(toolCall: ToolCallResponse, originalQuestion: string, onStatus?: AgentStatusCallback, onTextDelta?: TextDeltaCallback): Promise<HarnessResponse>;
679
+ /** Alias for executeToolCall. */
680
+ confirmAndExecute(toolCall: ToolCallResponse, originalQuestion: string): Promise<HarnessResponse>;
681
+ /** Get conversation history for display in chat. */
682
+ getConversationHistory(): ConversationMessage[];
683
+ /** Recent redacted context for semantic conversational follow-ups. */
684
+ getRecentConversationContext(count?: number): {
685
+ role: string;
686
+ content: string;
687
+ }[];
688
+ /** Persist a completed Realtime turn so voice and text share one history. */
689
+ recordRealtimeTurn(role: 'user' | 'assistant', content: string): void;
690
+ /** Check if there is existing conversation history. */
691
+ hasHistory(): boolean;
692
+ /** Clear conversation history. */
693
+ clearHistory(): void;
694
+ /**
695
+ * Read SSE events from the backend agent loop stream.
696
+ * Supports incremental text streaming, tool call display events, and cancellation.
697
+ * Updates status via callback and resolves with the final response.
698
+ */
699
+ private _readSSEStream;
700
+ /**
701
+ * Parse the ChatAskResponse from a streamed "done" event into HarnessResponse.
702
+ */
703
+ /**
704
+ * Strip tool_call artifacts from streamed text so raw JSON is never shown.
705
+ *
706
+ * Handles:
707
+ * - Fenced code blocks: ```tool_call\n{...}```
708
+ * - Bare JSON objects with a "tool" key: {"tool":"list_guides","params":{}}
709
+ * - Standalone mode: lines (mode: execute)
710
+ */
711
+ private _stripToolCallArtifacts;
712
+ private _parseStreamedResponse;
713
+ /**
714
+ * Handle a regular JSON response (non-streaming).
715
+ */
716
+ /**
717
+ * Surface a plan to the UI once per chain.
718
+ *
719
+ * The chain revisits the server on every round, and a mid-chain response
720
+ * may restate the plan. Re-announcing an identical plan would redraw it and
721
+ * make the assistant look like it had started over, so only a changed plan
722
+ * is emitted.
723
+ */
724
+ private _announcePlan;
725
+ private _handleJsonResponse;
726
+ private _continueToolChain;
727
+ /**
728
+ * Remove calls that have already been made in this chain.
729
+ *
730
+ * The model is told not to repeat itself, and mostly does not, but a long
731
+ * chain occasionally re-requests an endpoint it already has — which costs a
732
+ * request and a model round trip to learn nothing new.
733
+ */
734
+ private _dropRepeats;
735
+ /**
736
+ * Run a client-executed tool chain.
737
+ *
738
+ * Each round the model may request either one tool or a batch of
739
+ * independent tools. A batch is executed concurrently, so the wall-clock
740
+ * cost of gathering four unrelated resources is one request plus one model
741
+ * round trip, rather than four of each. Dependent calls still arrive one at
742
+ * a time — the model only batches what it can run without waiting.
743
+ */
744
+ private _runClientToolChain;
745
+ private hasAnyLocalTools;
746
+ private canExecuteTool;
747
+ private executeLocalTool;
748
+ /**
749
+ * An MCP tool, after the user approved it: we cannot call it — it is
750
+ * reached from GuideAI's network with a secret the client never holds —
751
+ * so the backend runs it, told who the user is by their token.
752
+ */
753
+ private executeOnServer;
754
+ private executeLocalToolBatch;
755
+ private _formatToolResult;
756
+ private _readFormatToolResultStream;
757
+ private _executePendingToolCall;
758
+ /**
759
+ * Emit a fully-buffered answer to the UI.
760
+ *
761
+ * This used to fake a typing effect by releasing one word every 12ms. The
762
+ * response has already arrived in full by this point, so that delay was pure
763
+ * added latency — a 250-word answer sat behind three seconds of animation
764
+ * after the data was on the client. Real streaming still arrives token by
765
+ * token over SSE; a buffered answer is shown as soon as we have it.
766
+ */
767
+ private _emitAnswerText;
768
+ private _errorResponse;
769
+ }
770
+
771
+ /**
772
+ * The slice of MMKV this needs. Declared structurally so `new MMKV()`
773
+ * satisfies it without this package depending on react-native-mmkv.
774
+ */
775
+ interface SyncNativeStore {
776
+ getString(key: string): string | undefined;
777
+ set(key: string, value: string): void;
778
+ delete(key: string): void;
779
+ }
780
+
781
+ /** Returns the signed-in user's token, or null when signed out. May be async. */
782
+ type AuthTokenGetter = () => string | null | undefined | Promise<string | null | undefined>;
783
+ /**
784
+ * Values for the headers the site's toolset declares as supplied by the app
785
+ * (Copilot Tools → "Supplied by your app"). May be async. Only declared
786
+ * names are ever sent.
787
+ */
788
+ type RequestHeadersGetter = () => Record<string, string> | null | undefined | Promise<Record<string, string> | null | undefined>;
789
+
790
+ /** What a host says about one control on screen. */
791
+ interface DeclaredElement {
792
+ /**
793
+ * Stable id, unique within the screen. Becomes the snapshot's
794
+ * `selector` — legitimate because the backend treats `selector` as an
795
+ * opaque round-trip token: it renders elements to the model as numbered
796
+ * [ID]s and only checks the string is specific (not the word "button").
797
+ */
798
+ id: string;
799
+ /** Visible text. What the model matches the user's words against. */
800
+ label: string;
801
+ /** Coarse kind, mapped to the `tag` the backend's prompt expects. */
802
+ kind?: 'button' | 'link' | 'input' | 'text' | 'toggle' | 'image';
803
+ /** Current value, for inputs. */
804
+ value?: string;
805
+ /** Accessibility label when it says more than the visible text. */
806
+ accessibilityLabel?: string;
807
+ /** Placeholder text for an empty input. */
808
+ placeholder?: string;
809
+ /** True when mounted but off-screen or collapsed. */
810
+ hidden?: boolean;
811
+ /** Heading of the group this sits in, e.g. "Sharing options". */
812
+ sectionTitle?: string;
813
+ }
814
+ /** Actions a declared element can perform, supplied by the host at registration. */
815
+ interface ElementHandlers {
816
+ /** Invoked by guideai_click_element. */
817
+ press?: () => void | Promise<void>;
818
+ /** Invoked by guideai_fill_field / guideai_fill_form. */
819
+ setValue?: (value: string) => void | Promise<void>;
820
+ /** Invoked by guideai_scroll_to_element. */
821
+ scrollTo?: () => void | Promise<void>;
822
+ }
823
+
824
+ /** The slice of React Navigation's ref this needs. */
825
+ interface NavigationRefLike {
826
+ isReady?(): boolean;
827
+ navigate(screen: string, params?: object): void;
828
+ goBack(): void;
829
+ canGoBack?(): boolean;
830
+ }
831
+
832
+ interface GuideAIConfig {
833
+ /**
834
+ * The site's public token, from the dashboard. The only required
835
+ * field: it identifies the site, and everything else about that site
836
+ * — its tools, knowledge base, model and prompts — is resolved
837
+ * server-side from it.
838
+ */
839
+ apiKey: string;
840
+ /** The application's stable user ID after login, if already known. */
841
+ visitorId?: string;
842
+ /**
843
+ * A domain registered on this site (its own domain, or one of its
844
+ * `domain_aliases`).
845
+ *
846
+ * Required in practice, and it cannot be fetched from the dashboard:
847
+ * the backend uses it to AUTHORIZE the request, so it must accompany
848
+ * the very first call. A native app sends no `Origin` header, so this
849
+ * travels as `x-guideai-request-origin`. Set it once; a mismatch is
850
+ * answered with 403.
851
+ */
852
+ origin?: string;
853
+ /**
854
+ * Where the GuideAI backend lives. Defaults to production; set it to test
855
+ * against a local or staging GuideAI (`http://localhost:8000`).
856
+ */
857
+ baseUrl?: string;
858
+ /**
859
+ * How the assistant navigates between screens — a React Navigation
860
+ * ref, or a getter for one. Omitted, the assistant simply does not
861
+ * offer navigation rather than offering it and failing.
862
+ */
863
+ navigation?: NavigationRefLike | (() => NavigationRefLike | null | undefined);
864
+ /**
865
+ * A synchronous key/value store (MMKV, or `expo-sqlite/kv-store`) for
866
+ * conversation history across launches. Omitted, history is kept in
867
+ * memory and resets on relaunch.
868
+ */
869
+ store?: SyncNativeStore;
870
+ /** The assistant's visible name in its own replies. */
871
+ assistantName?: string;
872
+ /**
873
+ * Which of the site's agents (Agent Blueprints) answers, by its key —
874
+ * e.g. 'tutor'. Omitted: the site's main agent.
875
+ */
876
+ agent?: string;
877
+ /**
878
+ * The signed-in user's token for the site's own API. Read before every
879
+ * turn; may be async (AsyncStorage, SecureStore). Return null when
880
+ * signed out. The assistant then calls your endpoints as that user,
881
+ * from the device.
882
+ */
883
+ getAuthToken?: AuthTokenGetter;
884
+ /**
885
+ * Called once when your API answers 401 to a tool call. Refresh the
886
+ * session and return the new token; the call is repeated with it.
887
+ * Omitted: getAuthToken() is asked again.
888
+ */
889
+ onAuthExpired?: AuthTokenGetter;
890
+ /**
891
+ * Values for the headers your toolset declares in Copilot Tools as
892
+ * "Supplied by your app" — a device id, a tenant: `async () => ({
893
+ * 'device-id': id })`. Read before every turn; sent only to your API,
894
+ * never to GuideAI, and only for declared names.
895
+ */
896
+ getRequestHeaders?: RequestHeadersGetter;
897
+ /**
898
+ * Values the app knows right now — the course open on screen: `{ course:
899
+ * '42' }`. Each fills the API inputs of the same name, or those the
900
+ * toolset declares it fills (Copilot Tools → "Supplied by your app").
901
+ * Change it with GuideAI.setToolContext().
902
+ */
903
+ toolContext?: Record<string, string>;
904
+ /**
905
+ * Facts about the signed-in user for the agent to pitch answers to —
906
+ * `{ age: 12, class: 'JSS1', country: 'Nigeria', language: 'en' }`.
907
+ * Sent with every turn; change it with GuideAI.setUserProfile().
908
+ */
909
+ userProfile?: GuideAIUserProfile;
910
+ /**
911
+ * Log the full conversation and every request. Development only —
912
+ * it prints message content to the console.
913
+ */
914
+ debug?: boolean;
915
+ }
916
+ /** Short facts about the user: strings, numbers, booleans or short lists of them. */
917
+ type GuideAIUserProfile = Record<string, string | number | boolean | Array<string | number>>;
918
+ /**
919
+ * Configure the assistant. Call once, at app startup.
920
+ *
921
+ * Deliberately a function rather than a provider or a module constant:
922
+ * `apiKey` often arrives from a remote config or an env fetch, and a
923
+ * constructor running at import time cannot wait for it.
924
+ */
925
+ declare function configure(config: GuideAIConfig): void;
926
+ /** Call after login so chat and the checklist use the same person. */
927
+ declare function identify(userId: string | null): void;
928
+ /** Replace the tool inputs the app knows now (e.g. when another course opens). */
929
+ declare function setToolContext(values: Record<string, string> | null): void;
930
+ /** Replace what the agent knows about the user (after login, or a profile edit). */
931
+ declare function setUserProfile(profile: GuideAIUserProfile | null): void;
932
+ /** Whether configure() has run. Lets a host render its own fallback. */
933
+ declare function isConfigured(): boolean;
934
+ /** Test seam, and for hosts that switch sites at runtime. */
935
+ declare function reset(): void;
936
+ declare const GuideAI: {
937
+ configure: typeof configure;
938
+ identify: typeof identify;
939
+ setUserProfile: typeof setUserProfile;
940
+ setToolContext: typeof setToolContext;
941
+ isConfigured: typeof isConfigured;
942
+ reset: typeof reset;
943
+ };
944
+
945
+ interface GuideAIScreenProps {
946
+ /** React Navigation screen name, e.g. "ShareForm". Used by navigate(). */
947
+ name: string;
948
+ /** Scanner route path, e.g. "/share". Used for grounding. */
949
+ path: string;
950
+ /** Human title shown to the model. Defaults to `name`. */
951
+ title?: string;
952
+ /**
953
+ * Whether this screen is the one currently on top.
954
+ *
955
+ * Defaults to true. React Navigation keeps previous screens MOUNTED in
956
+ * the stack, so without this a backgrounded screen would keep claiming
957
+ * to be active and the model would be told about controls the user
958
+ * cannot see. Hosts using React Navigation should pass
959
+ * `useIsFocused()`.
960
+ */
961
+ active?: boolean;
962
+ children: React.ReactNode;
963
+ }
964
+ declare function GuideAIScreen({ name, path, title, active, children, }: GuideAIScreenProps): React.ReactElement;
965
+
966
+ type UseGuideAIElementOptions = Omit<DeclaredElement, 'id'> & ElementHandlers;
967
+ /**
968
+ * Declare one element to the assistant.
969
+ *
970
+ * Registration is an effect, so it runs after mount and unwinds on
971
+ * unmount. Handlers are kept in a ref and read through a stable wrapper:
972
+ * without that, an inline `press={() => …}` would be a new function on
973
+ * every render and re-register the element on every render with it.
974
+ */
975
+ declare function useGuideAIElement(id: string, options: UseGuideAIElementOptions): void;
976
+
977
+ interface GuideAIMessage {
978
+ id: string;
979
+ role: 'user' | 'assistant';
980
+ content: string;
981
+ /** True while tokens are still arriving for this message. */
982
+ streaming?: boolean;
983
+ error?: boolean;
984
+ /**
985
+ * Follow-up suggestions to draw as chips. A tap is just
986
+ * `send(suggestion)`.
987
+ */
988
+ suggestions?: string[];
989
+ /**
990
+ * Pictures the agent drew. They are also in `content` as Markdown
991
+ * images where they belong; use this list to place them yourself.
992
+ */
993
+ images?: HeadlessImage[];
994
+ /**
995
+ * A change to the user's data waiting for their yes ("Create the
996
+ * course"). Draw Approve / Cancel and call `confirm(id)` or `cancel(id)`.
997
+ */
998
+ pendingAction?: {
999
+ label: string;
1000
+ tool: string;
1001
+ };
1002
+ }
1003
+ /** A recording the host made (expo-audio or any recorder) and saved to a file. */
1004
+ interface GuideAIVoiceRecording {
1005
+ /** `file://…` URI of the finished recording. */
1006
+ uri: string;
1007
+ /** e.g. 'audio/m4a'. Omitted: guessed from the file extension. */
1008
+ mimeType?: string;
1009
+ /** BCP-47 language the user spoke, e.g. 'en-NG'. Defaults to 'en-US'. */
1010
+ languageCode?: string;
1011
+ }
1012
+ interface GuideAIVoiceResult {
1013
+ /** What was heard, already sent as the next message; null when nothing was. */
1014
+ text: string | null;
1015
+ /** Why nothing was sent, in words to show the user. */
1016
+ error?: string;
1017
+ }
1018
+ /**
1019
+ * Everything a custom chat UI needs, and nothing about how to draw it.
1020
+ *
1021
+ * A host owns two things — their design, and the text the user typed.
1022
+ * This hook owns the rest: the conversation, the streaming, the tool
1023
+ * calls, the transport. That split is what lets the agent be upgraded
1024
+ * underneath a host's UI without them changing a line.
1025
+ */
1026
+ interface UseGuideAIResult {
1027
+ /** The conversation so far, oldest first. */
1028
+ messages: GuideAIMessage[];
1029
+ /**
1030
+ * The current input text, and its setter.
1031
+ *
1032
+ * Managed here so a host binds it straight to their TextInput rather
1033
+ * than reimplementing "clear on send, block while busy" — the two
1034
+ * rules every chat UI needs and each one gets subtly wrong.
1035
+ */
1036
+ input: string;
1037
+ setInput(text: string): void;
1038
+ /**
1039
+ * Send `input`, or an explicit string.
1040
+ *
1041
+ * Ignores empty text and calls made while a turn is in flight, so a
1042
+ * host does not have to guard their send button.
1043
+ */
1044
+ send(question?: string): Promise<void>;
1045
+ /** True from send() until the turn settles. Drives a disabled state. */
1046
+ busy: boolean;
1047
+ /** Whether `send()` would do anything right now. */
1048
+ canSend: boolean;
1049
+ /** Status text from the agent loop ("Thinking", "Reading cart"). */
1050
+ status: string | null;
1051
+ /** Re-send the last user message. Useful after a failed turn. */
1052
+ retry(): Promise<void>;
1053
+ /** Drop the conversation and start over. */
1054
+ clear(): void;
1055
+ /** Approve the message's `pendingAction`; the agent carries it out. */
1056
+ confirm(messageId: string): Promise<void>;
1057
+ /** Decline the message's `pendingAction`. Nothing is changed. */
1058
+ cancel(messageId: string): void;
1059
+ /**
1060
+ * Push-to-talk: transcribe a recording and send it as the next message.
1061
+ * Recording is the host's (no native module here); this uploads it.
1062
+ */
1063
+ sendVoice(recording: GuideAIVoiceRecording): Promise<GuideAIVoiceResult>;
1064
+ /** True while a recording is being transcribed. */
1065
+ transcribing: boolean;
1066
+ }
1067
+ interface UseGuideAIOptions {
1068
+ /**
1069
+ * Log the full conversation and every request. Defaults to whatever
1070
+ * GuideAI.configure({ debug }) was given, so a host sets it once.
1071
+ */
1072
+ trace?: boolean;
1073
+ /**
1074
+ * The harness, for tests and for a host running two assistants at
1075
+ * once. Omitted — the normal case — it comes from GuideAI.configure(),
1076
+ * so a host never holds a reference to it.
1077
+ */
1078
+ harness?: CopilotHarness;
1079
+ }
1080
+ declare function useGuideAI(options?: UseGuideAIOptions): UseGuideAIResult;
1081
+
1082
+ /** Reads the same durable checklist as the browser adapter. */
1083
+ declare function useOnboarding(): {
1084
+ checklist: OnboardingState | null;
1085
+ loading: boolean;
1086
+ refresh: () => Promise<void>;
1087
+ dismiss: () => Promise<void>;
1088
+ };
1089
+
1090
+ export { GuideAI, GuideAIScreen, useGuideAI, useGuideAIElement, useOnboarding };
1091
+ export type { AuthTokenGetter, DeclaredElement, ElementHandlers, GuideAIConfig, HeadlessImage as GuideAIImage, GuideAIMessage, GuideAIScreenProps, GuideAIUserProfile, GuideAIVoiceRecording, GuideAIVoiceResult, NavigationRefLike, OnboardingState, OnboardingStepState, RequestHeadersGetter, SyncNativeStore, UseGuideAIElementOptions, UseGuideAIResult };