@anuma/sdk 10.9.0 → 10.10.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.
@@ -1894,6 +1894,127 @@ declare function buildAttachedFilesText(fileContext: string): string;
1894
1894
  */
1895
1895
  declare function attachFileContextToLastUserMessage(messages: LlmapiMessage[], fileContext: string): LlmapiMessage[];
1896
1896
 
1897
+ type LlmTokenUsage = {
1898
+ inputTokens?: number;
1899
+ outputTokens?: number;
1900
+ };
1901
+ type ModelCallStartEvent = {
1902
+ runId: string;
1903
+ stepIndex: number;
1904
+ model: string;
1905
+ messages: Array<LlmapiMessage>;
1906
+ tools: Array<LlmapiChatCompletionTool>;
1907
+ requestBody: Record<string, unknown>;
1908
+ };
1909
+ type ModelCallEndEvent = {
1910
+ runId: string;
1911
+ stepIndex: number;
1912
+ content: string;
1913
+ toolCalls: Array<{
1914
+ id: string;
1915
+ name: string;
1916
+ arguments: string;
1917
+ }>;
1918
+ usage?: LlmTokenUsage;
1919
+ finishReason?: string;
1920
+ error?: string;
1921
+ };
1922
+ type ToolUseStartEvent = {
1923
+ runId: string;
1924
+ stepIndex: number;
1925
+ toolCallId: string;
1926
+ name: string;
1927
+ rawArguments: string;
1928
+ parsedArguments?: Record<string, unknown>;
1929
+ };
1930
+ type ToolUseEndEvent = {
1931
+ runId: string;
1932
+ stepIndex: number;
1933
+ toolCallId: string;
1934
+ name: string;
1935
+ result?: unknown;
1936
+ error?: string;
1937
+ errorType?: "parse" | "timeout" | "execution" | "cancelled";
1938
+ };
1939
+ type RunStartEvent = {
1940
+ runId: string;
1941
+ model: string;
1942
+ messages: Array<LlmapiMessage>;
1943
+ tools: Array<LlmapiChatCompletionTool>;
1944
+ };
1945
+ type RunEndEvent = {
1946
+ runId: string;
1947
+ finalContent: string;
1948
+ /** Number of LLM rounds executed. */
1949
+ totalSteps: number;
1950
+ };
1951
+ type RunErrorEvent = {
1952
+ runId: string;
1953
+ error: string;
1954
+ /**
1955
+ * Only `"model"` today. Tool failures surface on {@link RunHooks.afterToolUse}
1956
+ * with `errorType`, not on `onRunError`, so a tool stage isn't needed yet.
1957
+ * Left as a union to leave room for `"validation"` or `"tool"` later
1958
+ * without a breaking change.
1959
+ */
1960
+ stage: "model";
1961
+ /**
1962
+ * The underlying `Error` when one is available. `RunErrorEvent` predates
1963
+ * this field, so `error` (a string message) is kept for backwards
1964
+ * compatibility; new consumers should prefer `errorObject` when present
1965
+ * for stack traces and `cause`.
1966
+ */
1967
+ errorObject?: Error;
1968
+ };
1969
+ type RunHooks = {
1970
+ /**
1971
+ * Fires once per `runToolLoop` invocation, before any pre-processor or
1972
+ * model call. The `messages` payload is the raw caller-supplied
1973
+ * conversation — pre-processors that enrich it (e.g. web search context)
1974
+ * run afterwards, so `beforeModelCall.messages` reflects the
1975
+ * post-enrichment state.
1976
+ */
1977
+ onRunStart?: (e: RunStartEvent) => Promise<void> | void;
1978
+ /**
1979
+ * Fires exactly once per run on clean completion. Mutually exclusive
1980
+ * with `onRunError` — a run that completes fires `onRunEnd`; a run that
1981
+ * errors or is aborted fires `onRunError`. Consumers can rely on
1982
+ * exactly one terminal hook per `onRunStart`.
1983
+ */
1984
+ onRunEnd?: (e: RunEndEvent) => Promise<void> | void;
1985
+ /**
1986
+ * Fires exactly once per run when the loop errors or is aborted.
1987
+ * `stage` is `"model"` for stream/provider errors and for aborts;
1988
+ * tool execution failures surface as structured `{ error, errorType }`
1989
+ * results on `afterToolUse` rather than firing `onRunError`.
1990
+ * Mutually exclusive with `onRunEnd`.
1991
+ */
1992
+ onRunError?: (e: RunErrorEvent) => Promise<void> | void;
1993
+ beforeModelCall?: (e: ModelCallStartEvent) => Promise<void> | void;
1994
+ afterModelCall?: (e: ModelCallEndEvent) => Promise<void> | void;
1995
+ /**
1996
+ * Fires before each tool the model invoked — including server-side
1997
+ * tools that have no client executor. Today this is observe-only.
1998
+ * TODO(follow-up PR): allow returning { args?, abort?: { reason } } to
1999
+ * mutate arguments or short-circuit the tool call.
2000
+ *
2001
+ * Pairing with {@link afterToolUse} is asymmetric: server-side tools
2002
+ * routed via `onToolCall` get `beforeToolUse` but no `afterToolUse`,
2003
+ * because they don't execute in-process. Tools blocked by a failed
2004
+ * dependency or a dependency cycle DO get `afterToolUse` with
2005
+ * `errorType: "execution"` — consumers tallying matched pairs should
2006
+ * filter on executor-backed tools to avoid spurious imbalances.
2007
+ */
2008
+ beforeToolUse?: (e: ToolUseStartEvent) => Promise<void> | void;
2009
+ /**
2010
+ * Fires after each executor-backed tool finishes (success or failure)
2011
+ * and after each dependency-blocked tool is skipped (`errorType:
2012
+ * "execution"`). Does NOT fire for server-side tools routed via
2013
+ * `onToolCall`. See {@link beforeToolUse} for the pairing contract.
2014
+ */
2015
+ afterToolUse?: (e: ToolUseEndEvent) => Promise<void> | void;
2016
+ };
2017
+
1897
2018
  /**
1898
2019
  * PII detection patterns for structured data types.
1899
2020
  *
@@ -2208,127 +2329,6 @@ declare class PiiRedactor {
2208
2329
  dispose(): void;
2209
2330
  }
2210
2331
 
2211
- type LlmTokenUsage = {
2212
- inputTokens?: number;
2213
- outputTokens?: number;
2214
- };
2215
- type ModelCallStartEvent = {
2216
- runId: string;
2217
- stepIndex: number;
2218
- model: string;
2219
- messages: Array<LlmapiMessage>;
2220
- tools: Array<LlmapiChatCompletionTool>;
2221
- requestBody: Record<string, unknown>;
2222
- };
2223
- type ModelCallEndEvent = {
2224
- runId: string;
2225
- stepIndex: number;
2226
- content: string;
2227
- toolCalls: Array<{
2228
- id: string;
2229
- name: string;
2230
- arguments: string;
2231
- }>;
2232
- usage?: LlmTokenUsage;
2233
- finishReason?: string;
2234
- error?: string;
2235
- };
2236
- type ToolUseStartEvent = {
2237
- runId: string;
2238
- stepIndex: number;
2239
- toolCallId: string;
2240
- name: string;
2241
- rawArguments: string;
2242
- parsedArguments?: Record<string, unknown>;
2243
- };
2244
- type ToolUseEndEvent = {
2245
- runId: string;
2246
- stepIndex: number;
2247
- toolCallId: string;
2248
- name: string;
2249
- result?: unknown;
2250
- error?: string;
2251
- errorType?: "parse" | "timeout" | "execution" | "cancelled";
2252
- };
2253
- type RunStartEvent = {
2254
- runId: string;
2255
- model: string;
2256
- messages: Array<LlmapiMessage>;
2257
- tools: Array<LlmapiChatCompletionTool>;
2258
- };
2259
- type RunEndEvent = {
2260
- runId: string;
2261
- finalContent: string;
2262
- /** Number of LLM rounds executed. */
2263
- totalSteps: number;
2264
- };
2265
- type RunErrorEvent = {
2266
- runId: string;
2267
- error: string;
2268
- /**
2269
- * Only `"model"` today. Tool failures surface on {@link RunHooks.afterToolUse}
2270
- * with `errorType`, not on `onRunError`, so a tool stage isn't needed yet.
2271
- * Left as a union to leave room for `"validation"` or `"tool"` later
2272
- * without a breaking change.
2273
- */
2274
- stage: "model";
2275
- /**
2276
- * The underlying `Error` when one is available. `RunErrorEvent` predates
2277
- * this field, so `error` (a string message) is kept for backwards
2278
- * compatibility; new consumers should prefer `errorObject` when present
2279
- * for stack traces and `cause`.
2280
- */
2281
- errorObject?: Error;
2282
- };
2283
- type RunHooks = {
2284
- /**
2285
- * Fires once per `runToolLoop` invocation, before any pre-processor or
2286
- * model call. The `messages` payload is the raw caller-supplied
2287
- * conversation — pre-processors that enrich it (e.g. web search context)
2288
- * run afterwards, so `beforeModelCall.messages` reflects the
2289
- * post-enrichment state.
2290
- */
2291
- onRunStart?: (e: RunStartEvent) => Promise<void> | void;
2292
- /**
2293
- * Fires exactly once per run on clean completion. Mutually exclusive
2294
- * with `onRunError` — a run that completes fires `onRunEnd`; a run that
2295
- * errors or is aborted fires `onRunError`. Consumers can rely on
2296
- * exactly one terminal hook per `onRunStart`.
2297
- */
2298
- onRunEnd?: (e: RunEndEvent) => Promise<void> | void;
2299
- /**
2300
- * Fires exactly once per run when the loop errors or is aborted.
2301
- * `stage` is `"model"` for stream/provider errors and for aborts;
2302
- * tool execution failures surface as structured `{ error, errorType }`
2303
- * results on `afterToolUse` rather than firing `onRunError`.
2304
- * Mutually exclusive with `onRunEnd`.
2305
- */
2306
- onRunError?: (e: RunErrorEvent) => Promise<void> | void;
2307
- beforeModelCall?: (e: ModelCallStartEvent) => Promise<void> | void;
2308
- afterModelCall?: (e: ModelCallEndEvent) => Promise<void> | void;
2309
- /**
2310
- * Fires before each tool the model invoked — including server-side
2311
- * tools that have no client executor. Today this is observe-only.
2312
- * TODO(follow-up PR): allow returning { args?, abort?: { reason } } to
2313
- * mutate arguments or short-circuit the tool call.
2314
- *
2315
- * Pairing with {@link afterToolUse} is asymmetric: server-side tools
2316
- * routed via `onToolCall` get `beforeToolUse` but no `afterToolUse`,
2317
- * because they don't execute in-process. Tools blocked by a failed
2318
- * dependency or a dependency cycle DO get `afterToolUse` with
2319
- * `errorType: "execution"` — consumers tallying matched pairs should
2320
- * filter on executor-backed tools to avoid spurious imbalances.
2321
- */
2322
- beforeToolUse?: (e: ToolUseStartEvent) => Promise<void> | void;
2323
- /**
2324
- * Fires after each executor-backed tool finishes (success or failure)
2325
- * and after each dependency-blocked tool is skipped (`errorType:
2326
- * "execution"`). Does NOT fire for server-side tools routed via
2327
- * `onToolCall`. See {@link beforeToolUse} for the pairing contract.
2328
- */
2329
- afterToolUse?: (e: ToolUseEndEvent) => Promise<void> | void;
2330
- };
2331
-
2332
2332
  /**
2333
2333
  * Configuration for stream output smoothing.
2334
2334
  *
@@ -2367,8 +2367,6 @@ type ToolCallArgumentsDeltaEvent = {
2367
2367
  argumentsDelta: string;
2368
2368
  accumulatedArguments: string;
2369
2369
  };
2370
- type ToolExecutionErrorType = "parse" | "timeout" | "execution" | "cancelled";
2371
-
2372
2370
  /**
2373
2371
  * Error thrown when an upstream provider emits an in-stream error event.
2374
2372
  * Carries the provider's code (e.g. `"timeout"`) so callers can match
@@ -2379,6 +2377,7 @@ declare class ProviderStreamError extends Error {
2379
2377
  readonly code: string | undefined;
2380
2378
  constructor(message: string, code?: string);
2381
2379
  }
2380
+ type ToolExecutionErrorType = "parse" | "timeout" | "execution" | "cancelled";
2382
2381
 
2383
2382
  /** A tool result from an auto-executed tool. */
2384
2383
  type AutoExecutedToolResult = {
@@ -2434,6 +2433,8 @@ declare const INFERENCE_ID_HEADER = "X-Inference-ID";
2434
2433
  */
2435
2434
  type StreamResumeHandle = {
2436
2435
  inferenceId: string;
2436
+ /** The reply id the storage hook writes this turn's answer under, when it chose one. */
2437
+ assistantMessageId?: string;
2437
2438
  /** The RESOLVED api type (never "auto") — resolveApiType() already ran inside runToolLoop. */
2438
2439
  apiType: Exclude<ApiType, "auto">;
2439
2440
  model?: string;
@@ -2787,481 +2788,6 @@ type ApiType = "auto" | "responses" | "completions";
2787
2788
  */
2788
2789
  type ApiResponse = LlmapiResponseResponse | LlmapiChatCompletionResponse;
2789
2790
 
2790
- /**
2791
- * A finished background answer, as portal sealed it into the vault inbox.
2792
- * @public
2793
- */
2794
- type InboxPlaintext = {
2795
- v: 1;
2796
- inference_id: string;
2797
- conversation_id: string;
2798
- parent_message_id: string | null;
2799
- assistant_message_id?: string | null;
2800
- api_type: "responses" | "completions";
2801
- model: string;
2802
- status: "completed" | "tool_request";
2803
- pending_tool: string | null;
2804
- response: ApiResponse;
2805
- finished_at: string;
2806
- };
2807
- /**
2808
- * One sealed item in the vault inbox.
2809
- * @public
2810
- */
2811
- type InboxItem = {
2812
- id: string;
2813
- sealed: string;
2814
- created_at: string;
2815
- };
2816
- /**
2817
- * The account's vault inbox and the public key portal seals answers to.
2818
- * @public
2819
- */
2820
- interface VaultInbox {
2821
- putPublicKey(publicKey: string): Promise<void>;
2822
- list(): Promise<InboxItem[]>;
2823
- remove(id: string): Promise<void>;
2824
- }
2825
- /**
2826
- * The vault inbox client, under the same `/api/private-chats` routes as the chat store.
2827
- * @public
2828
- */
2829
- declare function createVaultInbox(options: {
2830
- baseUrl: string;
2831
- getToken: () => Promise<string | null>;
2832
- fetch?: typeof globalThis.fetch;
2833
- timeoutMs?: number;
2834
- }): VaultInbox;
2835
- /** The 65-byte uncompressed P-256 point, base64url, as vault stores it. */
2836
- declare function exportInboxPublicKey(publicKey: CryptoKey): Promise<string>;
2837
- /** Decrypt a sealed inbox item with the account's ECDH key pair. */
2838
- declare function openInboxItem(item: Pick<InboxItem, "id" | "sealed">, keyPair: CryptoKeyPair): Promise<InboxPlaintext>;
2839
-
2840
- /**
2841
- * What became of a restaurant booking or cancellation in one turn, read from
2842
- * the server's own tool result rather than from the model's reply.
2843
- */
2844
- type ReservationReceipt = {
2845
- kind: "booking" | "cancel";
2846
- /**
2847
- * `made`: the provider confirmed it. `not_made`: it did not happen.
2848
- * `already_done`: an earlier call in the conversation had already done it.
2849
- * `unknown`: the call reached the provider and its outcome is not known.
2850
- */
2851
- status: "made" | "not_made" | "already_done" | "unknown";
2852
- venueName: string;
2853
- /** As the call sent it, e.g. `2026-10-23`. */
2854
- day: string;
2855
- /** As the call sent it, e.g. `9:00 PM`. */
2856
- time: string;
2857
- partySize?: number;
2858
- reservationId?: string;
2859
- /** Resy page where the user can see the reservation. */
2860
- resyUrl?: string;
2861
- /** The restaurant's own cancellation terms, when the booking result carries them. */
2862
- feeSummary?: string;
2863
- /** The provider's stated reason, when it did not happen. */
2864
- reason?: string;
2865
- };
2866
- /** One receipt per kind for the turn's booking and cancel calls, holding the last attempt's outcome unless an earlier one was `made` or `unknown`. */
2867
- declare function extractReservationReceipts(toolCallEvents?: LlmapiToolCallEvent[]): ReservationReceipt[];
2868
-
2869
- /**
2870
- * Build the replay path for a detached stream. The portal serves the buffered
2871
- * stream from seq 0 on a GET to this path — no `starting_after`, no `id:`
2872
- * cursor, no request body. Replay always starts from the first buffered byte
2873
- * and the client rebuilds its own view with a fresh accumulator; partial
2874
- * client state is never sent back to the server.
2875
- *
2876
- * Exported so client repos can mock the endpoint in E2E without string drift.
2877
- */
2878
- declare function streamReplayPath(inferenceId: string): string;
2879
- /**
2880
- * Build the cancel path for a detached stream. A POST here tells the portal to
2881
- * stop generating into the buffer and release it — the billing-safe teardown
2882
- * for "the user pressed stop after we'd already detached".
2883
- *
2884
- * Exported so client repos can mock the endpoint in E2E without string drift.
2885
- */
2886
- declare function streamCancelPath(inferenceId: string): string;
2887
- /**
2888
- * A generation still running on the portal for the caller.
2889
- * @public
2890
- */
2891
- type ActiveStream = {
2892
- inferenceId: string;
2893
- assistantMessageId: string | null;
2894
- conversationId: string | null;
2895
- parentMessageId: string | null;
2896
- apiType: "responses" | "completions";
2897
- model?: string;
2898
- startedAt: string;
2899
- };
2900
- /** List the caller's generations still running on the portal. */
2901
- declare function listActiveStreams(options: {
2902
- token: string;
2903
- baseUrl?: string;
2904
- conversationId?: string;
2905
- fetch?: typeof globalThis.fetch;
2906
- }): Promise<ActiveStream[]>;
2907
- /**
2908
- * Thrown when the portal answers `410 Gone` to a replay GET: the buffered
2909
- * stream is gone — expired (10-min sliding TTL), absent, not owned by the
2910
- * caller, or already cancelled. There is nothing to replay; the answer is
2911
- * permanently lost and the caller should fall back to a fresh send rather than
2912
- * retry. A `stop()`-then-resume race lands here and finalizes as stopped, which
2913
- * is correct.
2914
- *
2915
- * `resumeStream` throws this (it does not return it) and fires neither
2916
- * `onFinish` nor `onError` — there is no body to deliver.
2917
- * @public
2918
- */
2919
- declare class StreamExpiredError extends Error {
2920
- readonly name = "StreamExpiredError";
2921
- readonly inferenceId: string;
2922
- constructor(inferenceId: string, message?: string);
2923
- }
2924
- /**
2925
- * Options for {@link resumeStream}.
2926
- * @public
2927
- */
2928
- interface ResumeStreamOptions {
2929
- /** The handle captured from a detached `runToolLoop` result. */
2930
- handle: StreamResumeHandle;
2931
- /**
2932
- * Fresh bearer token. The CALLER fetches it at resume time — a multi-minute
2933
- * background gap expires the bearer, so a token captured when the original
2934
- * stream started must never be reused (see `useChat.resumeStream`).
2935
- */
2936
- token: string;
2937
- /** Base URL for the portal. @default the SDK's configured BASE_URL. */
2938
- baseUrl?: string;
2939
- /**
2940
- * Streaming transport. Defaults to the GET-capable fetch transport; Expo
2941
- * passes `xhrTransport` (RN can't stream `fetch` bodies).
2942
- */
2943
- transport?: StreamingTransport;
2944
- /** Aborts the replay; the partial is returned as an interrupted result. */
2945
- signal?: AbortSignal;
2946
- /** Adaptive output smoothing for the replayed content. @default true */
2947
- smoothing?: StreamSmoothingConfig | boolean;
2948
- /**
2949
- * Client-side watchdog for a dead live-tail. @default 120_000ms.
2950
- *
2951
- * Deliberately NOT ~30s: the portal tolerates >100s reasoning silences (no
2952
- * `data:` frames on the replay connection during them) and its own liveness
2953
- * rule (heartbeat stale >90s => SSE error + `[DONE]`) is the authoritative
2954
- * guard. This client timer is a backstop for a half-open TCP connection and
2955
- * must sit ABOVE the server's 90s rule so the server error always wins when
2956
- * the portal is reachable. Set to 0 / Infinity to disable.
2957
- */
2958
- idleTimeoutMs?: number;
2959
- /** Content text deltas as they replay (always from seq 0 — reset accumulated text first). */
2960
- onData?: (chunk: string) => void;
2961
- /** Thinking/reasoning deltas as they replay. */
2962
- onThinking?: (chunk: string) => void;
2963
- /** Restores the placeholders this redactor assigned when the original request was sent. */
2964
- piiRedactor?: PiiRedactor;
2965
- /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
2966
- onFinish?: (response: ApiResponse) => void;
2967
- /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
2968
- onError?: (error: Error) => void;
2969
- }
2970
- /**
2971
- * Result of {@link resumeStream}. A `410 Gone` is the only outcome that throws
2972
- * ({@link StreamExpiredError}); every other terminal is returned.
2973
- *
2974
- * - clean completion → `{ data, error: null, interrupted: false, empty }`
2975
- * - in-stream error / tool-request terminal / idle timeout → `{ data, error,
2976
- * interrupted: true }` (finalize as a stopped/partial message)
2977
- * - transient transport/HTTP failure (401, 5xx, network) → `{ data, error,
2978
- * interrupted: false, statusCode? }` (retryable — keep the handle)
2979
- *
2980
- * `empty` is true when the clean terminal delivered ZERO content/thinking
2981
- * deltas — a `[DONE]`-only replay. That is never a real completion (a
2982
- * completed generation always buffered at least one content frame); it means
2983
- * the buffered frames were lost server-side (evicted/trimmed) while the
2984
- * terminal survived. Consumers must NOT persist `data` (an empty response)
2985
- * over content they already hold — keep the detached partial instead. On the
2986
- * empty terminal `onFinish` is NOT fired (there is no completion to deliver);
2987
- * the flag is optional so existing constructors of the clean arm stay valid.
2988
- * @public
2989
- */
2990
- type ResumeStreamResult = {
2991
- data: ApiResponse;
2992
- error: null;
2993
- interrupted: false;
2994
- empty?: boolean;
2995
- } | {
2996
- data: ApiResponse | null;
2997
- error: string;
2998
- interrupted: boolean;
2999
- statusCode?: number;
3000
- };
3001
- /**
3002
- * Replay a detached stream from the portal's buffer.
3003
- *
3004
- * This is the reconnect primitive: after `runToolLoop` returns the detached
3005
- * variant with a {@link StreamResumeHandle}, `resumeStream` issues a GET to the
3006
- * portal's replay endpoint and rebuilds the response from seq 0 using a FRESH
3007
- * accumulator and FRESH smoothers. Nothing from the original (detached) run is
3008
- * reused — the completions strategy's reasoning-tag parser is stateful, so
3009
- * replaying into reused state would double-count; replay-from-0 into fresh
3010
- * state is the correctness mechanism, not just a simplification.
3011
- *
3012
- * Contract highlights:
3013
- * - **No `starting_after`, no `id:` cursor, no body.** The GET carries the
3014
- * inference id in the path only; replay is always whole-stream. (The server
3015
- * reserves `starting_after` but does NOT honor it — sending it would silently
3016
- * duplicate content.)
3017
- * - **410 → throws {@link StreamExpiredError}.** No `onFinish`/`onError`.
3018
- * - **In-stream error / tool-request terminal → `interrupted: true`.** Flushes
3019
- * both smoothers and returns the partial; no throw, no `onError`, no `onFinish`.
3020
- * - **token resolved by the caller at resume time** so a refresh during the
3021
- * detach window is honored.
3022
- *
3023
- * @public
3024
- */
3025
- declare function resumeStream(options: ResumeStreamOptions): Promise<ResumeStreamResult>;
3026
-
3027
- interface StockPriceClassification {
3028
- /** Whether the prompt likely asks for stock/ETF/FX quote data. */
3029
- needsStockPrice: boolean;
3030
- /** Cosine similarity to the "needs stock price" centroid. */
3031
- stockPriceScore: number;
3032
- /** Cosine similarity to the "no stock price" centroid. */
3033
- noStockPriceScore: number;
3034
- }
3035
- interface StockPriceClassifierOptions extends EmbeddingOptions {
3036
- /**
3037
- * Score margin: the stock-price score must exceed the no-stock-price
3038
- * score by at least this amount to classify as "needs stock price data".
3039
- * @default 0.02
3040
- */
3041
- margin?: number;
3042
- }
3043
- /**
3044
- * Classify whether a prompt needs stock/ETF/index/FX price data.
3045
- *
3046
- * Domain: traditional financial instruments (equities, indices, ETFs, FX).
3047
- * Crypto is handled by `classifyCryptoPrice`. Ambiguous prompts
3048
- * (e.g. *"gold price"*) may fire both — that's expected; the caller can
3049
- * inject both sets of results.
3050
- */
3051
- declare function classifyStockPrice(prompt: string, options: StockPriceClassifierOptions): Promise<StockPriceClassification>;
3052
- /**
3053
- * Batch-classify multiple prompts. Embeds all prompts in one batch
3054
- * call for efficiency.
3055
- */
3056
- declare function classifyStockPriceBatch(prompts: string[], options: StockPriceClassifierOptions): Promise<StockPriceClassification[]>;
3057
- interface StockPricePreProcessorOptions {
3058
- /**
3059
- * Called with the caller's stock-quote provider when the classifier
3060
- * decides the prompt is asking for stock/ETF/FX data. Return either a
3061
- * plain string (wrapped by the SDK in a default user message) or a
3062
- * fully-formed `LlmapiMessage[]` (full control over role/shape). Omit
3063
- * to run in observer mode — classification still fires but nothing is
3064
- * injected.
3065
- *
3066
- * The `signal` argument is forwarded from the tool loop so long-running
3067
- * provider requests can be aborted when the caller aborts.
3068
- */
3069
- fetchStockPriceData?: (prompt: string, options: {
3070
- signal?: AbortSignal;
3071
- }) => Promise<string | LlmapiMessage[]>;
3072
- /**
3073
- * Score margin: `stockPriceScore` must exceed `noStockPriceScore` by at
3074
- * least this amount to classify as "needs stock price data".
3075
- * @default 0.02
3076
- */
3077
- margin?: number;
3078
- /** Observe the classification without injecting anything. */
3079
- onClassification?: (result: StockPriceClassification) => void;
3080
- }
3081
- /**
3082
- * Build a pre-processor that runs stock-price classification on the shared
3083
- * embedding provided by `runToolLoop`, and — if the classifier decides the
3084
- * prompt is asking for quotes — invokes the caller-supplied
3085
- * `fetchStockPriceData` and injects the result into the conversation.
3086
- *
3087
- * Domain: stocks, ETFs, indices, FX. For crypto use
3088
- * `createCryptoPricePreProcessor`. The SDK does not run the lookup itself;
3089
- * the caller wires up whichever provider they want (Twelve Data, Alpha
3090
- * Vantage, Finnhub, etc.).
3091
- */
3092
- declare function createStockPricePreProcessor(options?: StockPricePreProcessorOptions): PromptPreProcessor;
3093
-
3094
- /**
3095
- * Value of the `origin` column on a row the SDK synthesised for a turn's tool results (v44, #866).
3096
- *
3097
- * `satisfies MessageOrigin` so this constant and the column's union cannot drift apart silently.
3098
- */
3099
- declare const TOOL_RESULT_ORIGIN = "tool_result";
3100
-
3101
- interface WeatherClassification {
3102
- /** Whether the prompt likely asks for weather data. */
3103
- needsWeather: boolean;
3104
- /** Cosine similarity to the "needs weather" centroid. */
3105
- weatherScore: number;
3106
- /** Cosine similarity to the "no weather" centroid. */
3107
- noWeatherScore: number;
3108
- }
3109
- interface WeatherClassifierOptions extends EmbeddingOptions {
3110
- /**
3111
- * Score margin: the weather score must exceed the no-weather score by at
3112
- * least this amount to classify as "needs weather data".
3113
- * @default 0.02
3114
- */
3115
- margin?: number;
3116
- }
3117
- /**
3118
- * Classify whether a prompt needs weather data.
3119
- *
3120
- * Covers forecasts, temperature, precipitation, wind, humidity, UV, AQI,
3121
- * marine, flood, climate — anything OpenMeteo-shaped.
3122
- */
3123
- declare function classifyWeather(prompt: string, options: WeatherClassifierOptions): Promise<WeatherClassification>;
3124
- /**
3125
- * Batch-classify multiple prompts. Embeds all prompts in one batch
3126
- * call for efficiency.
3127
- */
3128
- declare function classifyWeatherBatch(prompts: string[], options: WeatherClassifierOptions): Promise<WeatherClassification[]>;
3129
- interface WeatherPreProcessorOptions {
3130
- /**
3131
- * Called with the caller's weather provider when the classifier decides
3132
- * the prompt is asking for weather. Return either a plain string
3133
- * (wrapped by the SDK in a default user message) or a fully-formed
3134
- * `LlmapiMessage[]` (full control over role/shape). Omit to run in
3135
- * observer mode — classification still fires but nothing is injected.
3136
- *
3137
- * The `signal` argument is forwarded from the tool loop so long-running
3138
- * provider requests can be aborted when the caller aborts.
3139
- */
3140
- fetchWeatherData?: (prompt: string, options: {
3141
- signal?: AbortSignal;
3142
- }) => Promise<string | LlmapiMessage[]>;
3143
- /**
3144
- * Score margin: `weatherScore` must exceed `noWeatherScore` by at least
3145
- * this amount to classify as "needs weather data".
3146
- * @default 0.02
3147
- */
3148
- margin?: number;
3149
- /** Observe the classification without injecting anything. */
3150
- onClassification?: (result: WeatherClassification) => void;
3151
- }
3152
- /**
3153
- * Build a pre-processor that runs weather classification on the shared
3154
- * embedding provided by `runToolLoop`, and — if the classifier decides
3155
- * the prompt is asking for weather — invokes the caller-supplied
3156
- * `fetchWeatherData` and injects the result into the conversation.
3157
- *
3158
- * The SDK does not run the weather lookup itself; the caller wires up
3159
- * whichever provider they want (OpenMeteo, AccuWeather, weather.gov, etc.).
3160
- */
3161
- declare function createWeatherPreProcessor(options?: WeatherPreProcessorOptions): PromptPreProcessor;
3162
-
3163
- interface WebSearchClassification {
3164
- /** Whether the prompt likely needs a web search. */
3165
- needsWebSearch: boolean;
3166
- /** Cosine similarity to the "needs search" centroid. */
3167
- searchScore: number;
3168
- /** Cosine similarity to the "no search" centroid. */
3169
- noSearchScore: number;
3170
- }
3171
- interface WebSearchClassifierOptions extends EmbeddingOptions {
3172
- /**
3173
- * Score margin: the search score must exceed the no-search score
3174
- * by at least this amount to classify as "needs web search".
3175
- * @default 0.02
3176
- */
3177
- margin?: number;
3178
- }
3179
- /**
3180
- * Classify whether a prompt needs a web search.
3181
- *
3182
- * Embeds the prompt and compares it against the pre-computed
3183
- * search and no-search centroid vectors.
3184
- */
3185
- declare function classifyWebSearch(prompt: string, options: WebSearchClassifierOptions): Promise<WebSearchClassification>;
3186
- /**
3187
- * Batch-classify multiple prompts. Embeds all prompts in one batch
3188
- * call for efficiency.
3189
- */
3190
- declare function classifyWebSearchBatch(prompts: string[], options: WebSearchClassifierOptions): Promise<WebSearchClassification[]>;
3191
- interface WebSearchPreProcessorOptions {
3192
- /**
3193
- * Called with the caller's search provider when the classifier decides
3194
- * a web search is needed. Return either a plain string (the SDK will
3195
- * wrap it in a default user message) or a fully-formed message array
3196
- * (full control over role/shape). Omit to run in observer mode —
3197
- * classification still fires but no messages are injected.
3198
- *
3199
- * The `signal` argument is forwarded from the tool loop so long-running
3200
- * search requests can be aborted when the caller aborts.
3201
- */
3202
- fetchSearchResults?: (prompt: string, options: {
3203
- signal?: AbortSignal;
3204
- }) => Promise<string | LlmapiMessage[]>;
3205
- /**
3206
- * Score margin: `searchScore` must exceed `noSearchScore` by at least
3207
- * this amount to classify as "needs web search".
3208
- * @default 0.02
3209
- */
3210
- margin?: number;
3211
- /** Observe the classification without injecting anything. */
3212
- onClassification?: (result: WebSearchClassification) => void;
3213
- }
3214
- /**
3215
- * Build a pre-processor that runs web-search classification on the
3216
- * shared embedding provided by `runToolLoop`, and — if the classifier
3217
- * decides a search is warranted — invokes the caller-supplied
3218
- * `fetchSearchResults` and injects the result into the conversation.
3219
- *
3220
- * @example Basic usage with a search provider
3221
- * ```ts
3222
- * import { runToolLoop, createWebSearchPreProcessor } from "@anuma/sdk/server";
3223
- *
3224
- * const webSearch = createWebSearchPreProcessor({
3225
- * fetchSearchResults: async (prompt, { signal }) => {
3226
- * const res = await mySearchProvider.query(prompt, { signal });
3227
- * return res.results.map((r) => `- ${r.title}: ${r.snippet}`).join("\n");
3228
- * },
3229
- * });
3230
- *
3231
- * await runToolLoop({
3232
- * messages,
3233
- * model,
3234
- * token,
3235
- * preProcessors: [webSearch],
3236
- * });
3237
- * ```
3238
- *
3239
- * @example Observer mode — only log classification, inject nothing
3240
- * ```ts
3241
- * const observer = createWebSearchPreProcessor({
3242
- * onClassification: ({ needsWebSearch, searchScore, noSearchScore }) => {
3243
- * metrics.record({ needsWebSearch, searchScore, noSearchScore });
3244
- * },
3245
- * });
3246
- * ```
3247
- *
3248
- * @example Full control — return a custom message shape
3249
- * ```ts
3250
- * const webSearch = createWebSearchPreProcessor({
3251
- * fetchSearchResults: async (prompt, { signal }) => {
3252
- * const results = await mySearchProvider.query(prompt, { signal });
3253
- * return [
3254
- * {
3255
- * role: "system",
3256
- * content: [{ type: "text", text: `Search results for "${prompt}":\n${formatResults(results)}` }],
3257
- * },
3258
- * ];
3259
- * },
3260
- * });
3261
- * ```
3262
- */
3263
- declare function createWebSearchPreProcessor(options?: WebSearchPreProcessorOptions): PromptPreProcessor;
3264
-
3265
2791
  type ChatRecordKind = "conversation" | "message" | "summary" | "project" | "memory_links" | "setting";
3266
2792
  type ChatJson = null | boolean | number | string | ChatJson[] | {
3267
2793
  [key: string]: ChatJson;
@@ -5073,7 +4599,7 @@ interface ChatStorageAdapter {
5073
4599
  createMessage(options: CreateMessageOptions): Promise<StoredMessage>;
5074
4600
  updateMessageEmbedding(uniqueId: string, vector: number[], embeddingModel: string): Promise<StoredMessage | null>;
5075
4601
  updateMessageChunks(uniqueId: string, chunks: MessageChunk[], embeddingModel: string): Promise<StoredMessage | null>;
5076
- updateMessageError(uniqueId: string, error: string): Promise<StoredMessage | null>;
4602
+ updateMessageError(uniqueId: string, error: string | null): Promise<StoredMessage | null>;
5077
4603
  updateMessageFeedback(uniqueId: string, feedback: MessageFeedback): Promise<StoredMessage | null>;
5078
4604
  /**
5079
4605
  * Replace a message's attached media ids (`fileIds`). Used to attach a
@@ -9293,132 +8819,618 @@ interface BaseSendMessageWithStorageArgs {
9293
8819
  */
9294
8820
  thoughtProcess?: ActivityPhase[];
9295
8821
  /**
9296
- * Callback to get activity phases AFTER streaming completes.
9297
- * Use this instead of `thoughtProcess` when phases are added dynamically during streaming
9298
- * (e.g., via server tool call events like "Searching...", "Generating image...").
9299
- *
9300
- * If both `thoughtProcess` and `getThoughtProcess` are provided, `getThoughtProcess` takes precedence.
8822
+ * Callback to get activity phases AFTER streaming completes.
8823
+ * Use this instead of `thoughtProcess` when phases are added dynamically during streaming
8824
+ * (e.g., via server tool call events like "Searching...", "Generating image...").
8825
+ *
8826
+ * If both `thoughtProcess` and `getThoughtProcess` are provided, `getThoughtProcess` takes precedence.
8827
+ */
8828
+ getThoughtProcess?: () => ActivityPhase[];
8829
+ /**
8830
+ * Controls randomness in the response (0.0 to 2.0).
8831
+ * Lower values make output more deterministic, higher values more creative.
8832
+ */
8833
+ temperature?: number;
8834
+ /**
8835
+ * Maximum number of tokens to generate in the response.
8836
+ * Use this to limit response length and control costs.
8837
+ */
8838
+ maxOutputTokens?: number;
8839
+ /**
8840
+ * Client-side tools with optional executors.
8841
+ * These tools run in the browser/app and can have JavaScript executor functions.
8842
+ */
8843
+ clientTools?: LlmapiChatCompletionTool[];
8844
+ /**
8845
+ * Server-side tools to include from /api/v1/tools.
8846
+ * - undefined: Include all server-side tools (default)
8847
+ * - string[]: Include only tools with these names
8848
+ * - []: Include no server-side tools
8849
+ * - function: Dynamic filter that receives prompt embedding(s) and all tools,
8850
+ * returns tool names to include. Useful for semantic tool matching.
8851
+ *
8852
+ * @example
8853
+ * // Include only specific server tools
8854
+ * serverTools: ["anuma_create_image", "perplexity_search"]
8855
+ *
8856
+ * // Disable server tools for this request
8857
+ * serverTools: []
8858
+ *
8859
+ * // Semantic tool matching based on prompt
8860
+ * serverTools: (embeddings, tools) => {
8861
+ * const matches = findMatchingTools(embeddings, tools, { limit: 5 });
8862
+ * return matches.map(m => m.tool.name);
8863
+ * }
8864
+ */
8865
+ serverTools?: ServerToolsFilter;
8866
+ /**
8867
+ * Dynamic filter for client-side tools based on prompt embeddings.
8868
+ * Receives the prompt embedding(s) (or null for short messages) and all client tools,
8869
+ * returns tool names to include. Tools not in the returned list are excluded from the request.
8870
+ *
8871
+ * @example
8872
+ * clientToolsFilter: (embeddings, tools) => {
8873
+ * if (!embeddings) return []; // Short message — no client tools
8874
+ * const matches = findMatchingTools(embeddings, pseudoServerTools);
8875
+ * return matches.map(m => m.tool.name);
8876
+ * }
8877
+ */
8878
+ clientToolsFilter?: ClientToolsFilterFn;
8879
+ /**
8880
+ * Controls which tool the model should use:
8881
+ * - "auto": Model decides whether to use a tool (default)
8882
+ * - "any": Model must use one of the provided tools
8883
+ * - "none": Model cannot use any tools
8884
+ * - "required": Model must use a tool
8885
+ * - Specific tool name: Model must use that specific tool
8886
+ */
8887
+ toolChoice?: string;
8888
+ /**
8889
+ * Maximum number of tool execution rounds before forcing the model to respond with text.
8890
+ * After this many rounds, `toolChoice` is set to `"none"` on the next continuation,
8891
+ * so the model produces a text answer using whatever tool results it has gathered.
8892
+ * @default 3
8893
+ */
8894
+ maxToolRounds?: number;
8895
+ /**
8896
+ * Reasoning configuration for o-series and other reasoning models.
8897
+ * Controls reasoning effort level and whether to include reasoning summary.
8898
+ */
8899
+ reasoning?: LlmapiResponseReasoning;
8900
+ /**
8901
+ * Extended thinking configuration for Anthropic models (Claude).
8902
+ * Enables the model to think through complex problems step by step
8903
+ * before generating the final response.
8904
+ */
8905
+ thinking?: LlmapiThinkingOptions;
8906
+ /** User-selected image generation model for server-side enforcement. */
8907
+ imageModel?: string;
8908
+ /**
8909
+ * Per-request callback for thinking/reasoning chunks.
8910
+ * Called with delta chunks as the model "thinks" through a problem.
8911
+ * Use this to display thinking progress in the UI.
8912
+ */
8913
+ onThinking?: (chunk: string) => void;
8914
+ /** Parent message ID for branching (edit/regenerate). Sets on the user message. */
8915
+ parentMessageId?: string;
8916
+ /**
8917
+ * Pre-generated unique ID for the assistant response message.
8918
+ * When provided, the persisted assistant message will use this ID instead of
8919
+ * an auto-generated one. This lets the consumer show an in-flight streaming
8920
+ * placeholder under the same React key, avoiding an unmount/remount flash
8921
+ * when streaming completes and the message is loaded from the database.
8922
+ */
8923
+ assistantUniqueId?: string;
8924
+ }
8925
+ interface BaseUseChatStorageResult {
8926
+ isLoading: boolean;
8927
+ stop: () => void;
8928
+ conversationId: string | null;
8929
+ setConversationId: (id: string | null) => void;
8930
+ createConversation: (options?: CreateConversationOptions) => Promise<StoredConversation>;
8931
+ getConversation: (id: string) => Promise<StoredConversation | null>;
8932
+ getConversations: () => Promise<StoredConversation[]>;
8933
+ updateConversationTitle: (id: string, title: string) => Promise<boolean>;
8934
+ updateConversationPinned: (id: string, pinned: boolean) => Promise<boolean>;
8935
+ deleteConversation: (id: string) => Promise<boolean>;
8936
+ getMessages: (conversationId: string) => Promise<StoredMessage[]>;
8937
+ /**
8938
+ * Paginated display read: the newest `limit` messages (optionally below
8939
+ * `beforeMessageId`), ascending, with embedding columns skipped.
8940
+ */
8941
+ getMessagesPage: (conversationId: string, options: GetMessagesPageOptions) => Promise<StoredMessage[]>;
8942
+ /** Whole-thread branch-tree skeleton — no field decryption. */
8943
+ getMessageSkeletons: (conversationId: string) => Promise<MessageSkeleton[]>;
8944
+ /** Total message count for a conversation. */
8945
+ getMessageCount: (conversationId: string) => Promise<number>;
8946
+ }
8947
+ declare function generateConversationId(): string;
8948
+
8949
+ /**
8950
+ * A finished background answer, as portal sealed it into the vault inbox.
8951
+ * @public
8952
+ */
8953
+ type InboxPlaintext = {
8954
+ v: 1;
8955
+ inference_id: string;
8956
+ conversation_id: string;
8957
+ parent_message_id: string | null;
8958
+ assistant_message_id?: string | null;
8959
+ api_type: "responses" | "completions";
8960
+ model: string;
8961
+ status: "completed" | "tool_request";
8962
+ pending_tool: string | null;
8963
+ response: ApiResponse;
8964
+ finished_at: string;
8965
+ };
8966
+ /**
8967
+ * One sealed item in the vault inbox.
8968
+ * @public
8969
+ */
8970
+ type InboxItem = {
8971
+ id: string;
8972
+ sealed: string;
8973
+ created_at: string;
8974
+ };
8975
+ /**
8976
+ * The account's vault inbox and the public key portal seals answers to.
8977
+ * @public
8978
+ */
8979
+ interface VaultInbox {
8980
+ putPublicKey(publicKey: string): Promise<void>;
8981
+ list(): Promise<InboxItem[]>;
8982
+ remove(id: string): Promise<void>;
8983
+ }
8984
+ /**
8985
+ * The vault inbox client, under the same `/api/private-chats` routes as the chat store.
8986
+ * @public
8987
+ */
8988
+ declare function createVaultInbox(options: {
8989
+ baseUrl: string;
8990
+ getToken: () => Promise<string | null>;
8991
+ fetch?: typeof globalThis.fetch;
8992
+ timeoutMs?: number;
8993
+ }): VaultInbox;
8994
+ /** The 65-byte uncompressed P-256 point, base64url, as vault stores it. */
8995
+ declare function exportInboxPublicKey(publicKey: CryptoKey): Promise<string>;
8996
+ /** Decrypt a sealed inbox item with the account's ECDH key pair. */
8997
+ declare function openInboxItem(item: Pick<InboxItem, "id" | "sealed">, keyPair: CryptoKeyPair): Promise<InboxPlaintext>;
8998
+ /**
8999
+ * Outcome of delivering one vault inbox item.
9000
+ * @public
9001
+ */
9002
+ type InboxDeliveryResult = {
9003
+ id: string;
9004
+ conversationId: string | null;
9005
+ status: "stored" | "delivered" | "elsewhere" | "unreadable" | "retry";
9006
+ assistantMessage: StoredMessage | null;
9007
+ error?: string;
9008
+ };
9009
+
9010
+ /**
9011
+ * What became of a restaurant booking or cancellation in one turn, read from
9012
+ * the server's own tool result rather than from the model's reply.
9013
+ */
9014
+ type ReservationReceipt = {
9015
+ kind: "booking" | "cancel";
9016
+ /**
9017
+ * `made`: the provider confirmed it. `not_made`: it did not happen.
9018
+ * `already_done`: an earlier call in the conversation had already done it.
9019
+ * `unknown`: the call reached the provider and its outcome is not known.
9301
9020
  */
9302
- getThoughtProcess?: () => ActivityPhase[];
9021
+ status: "made" | "not_made" | "already_done" | "unknown";
9022
+ venueName: string;
9023
+ /** As the call sent it, e.g. `2026-10-23`. */
9024
+ day: string;
9025
+ /** As the call sent it, e.g. `9:00 PM`. */
9026
+ time: string;
9027
+ partySize?: number;
9028
+ reservationId?: string;
9029
+ /** Resy page where the user can see the reservation. */
9030
+ resyUrl?: string;
9031
+ /** The restaurant's own cancellation terms, when the booking result carries them. */
9032
+ feeSummary?: string;
9033
+ /** The provider's stated reason, when it did not happen. */
9034
+ reason?: string;
9035
+ };
9036
+ /** One receipt per kind for the turn's booking and cancel calls, holding the last attempt's outcome unless an earlier one was `made` or `unknown`. */
9037
+ declare function extractReservationReceipts(toolCallEvents?: LlmapiToolCallEvent[]): ReservationReceipt[];
9038
+
9039
+ /**
9040
+ * Build the replay path for a detached stream. The portal serves the buffered
9041
+ * stream from seq 0 on a GET to this path — no `starting_after`, no `id:`
9042
+ * cursor, no request body. Replay always starts from the first buffered byte
9043
+ * and the client rebuilds its own view with a fresh accumulator; partial
9044
+ * client state is never sent back to the server.
9045
+ *
9046
+ * Exported so client repos can mock the endpoint in E2E without string drift.
9047
+ */
9048
+ declare function streamReplayPath(inferenceId: string): string;
9049
+ /**
9050
+ * Build the cancel path for a detached stream. A POST here tells the portal to
9051
+ * stop generating into the buffer and release it — the billing-safe teardown
9052
+ * for "the user pressed stop after we'd already detached".
9053
+ *
9054
+ * Exported so client repos can mock the endpoint in E2E without string drift.
9055
+ */
9056
+ declare function streamCancelPath(inferenceId: string): string;
9057
+ /**
9058
+ * A generation still running on the portal for the caller.
9059
+ * @public
9060
+ */
9061
+ type ActiveStream = {
9062
+ inferenceId: string;
9063
+ assistantMessageId: string | null;
9064
+ conversationId: string | null;
9065
+ parentMessageId: string | null;
9066
+ apiType: "responses" | "completions";
9067
+ model?: string;
9068
+ startedAt: string;
9069
+ };
9070
+ /** List the caller's generations still running on the portal. */
9071
+ declare function listActiveStreams(options: {
9072
+ token: string;
9073
+ baseUrl?: string;
9074
+ conversationId?: string;
9075
+ fetch?: typeof globalThis.fetch;
9076
+ }): Promise<ActiveStream[]>;
9077
+ /**
9078
+ * Thrown when the portal answers `410 Gone` to a replay GET: the buffered
9079
+ * stream is gone — expired (10-min sliding TTL), absent, not owned by the
9080
+ * caller, or already cancelled. There is nothing to replay; the answer is
9081
+ * permanently lost and the caller should fall back to a fresh send rather than
9082
+ * retry. A `stop()`-then-resume race lands here and finalizes as stopped, which
9083
+ * is correct.
9084
+ *
9085
+ * `resumeStream` throws this (it does not return it) and fires neither
9086
+ * `onFinish` nor `onError` — there is no body to deliver.
9087
+ * @public
9088
+ */
9089
+ declare class StreamExpiredError extends Error {
9090
+ readonly name = "StreamExpiredError";
9091
+ readonly inferenceId: string;
9092
+ constructor(inferenceId: string, message?: string);
9093
+ }
9094
+ /**
9095
+ * Options for {@link resumeStream}.
9096
+ * @public
9097
+ */
9098
+ interface ResumeStreamOptions {
9099
+ /** The handle captured from a detached `runToolLoop` result. */
9100
+ handle: StreamResumeHandle;
9303
9101
  /**
9304
- * Controls randomness in the response (0.0 to 2.0).
9305
- * Lower values make output more deterministic, higher values more creative.
9102
+ * Fresh bearer token. The CALLER fetches it at resume time — a multi-minute
9103
+ * background gap expires the bearer, so a token captured when the original
9104
+ * stream started must never be reused (see `useChat.resumeStream`).
9306
9105
  */
9307
- temperature?: number;
9106
+ token: string;
9107
+ /** Base URL for the portal. @default the SDK's configured BASE_URL. */
9108
+ baseUrl?: string;
9308
9109
  /**
9309
- * Maximum number of tokens to generate in the response.
9310
- * Use this to limit response length and control costs.
9110
+ * Streaming transport. Defaults to the GET-capable fetch transport; Expo
9111
+ * passes `xhrTransport` (RN can't stream `fetch` bodies).
9311
9112
  */
9312
- maxOutputTokens?: number;
9113
+ transport?: StreamingTransport;
9114
+ /** Aborts the replay; the partial is returned as an interrupted result. */
9115
+ signal?: AbortSignal;
9116
+ /** Adaptive output smoothing for the replayed content. @default true */
9117
+ smoothing?: StreamSmoothingConfig | boolean;
9313
9118
  /**
9314
- * Client-side tools with optional executors.
9315
- * These tools run in the browser/app and can have JavaScript executor functions.
9119
+ * Client-side watchdog for a dead live-tail. @default 120_000ms.
9120
+ *
9121
+ * Deliberately NOT ~30s: the portal tolerates >100s reasoning silences (no
9122
+ * `data:` frames on the replay connection during them) and its own liveness
9123
+ * rule (heartbeat stale >90s => SSE error + `[DONE]`) is the authoritative
9124
+ * guard. This client timer is a backstop for a half-open TCP connection and
9125
+ * must sit ABOVE the server's 90s rule so the server error always wins when
9126
+ * the portal is reachable. Set to 0 / Infinity to disable.
9316
9127
  */
9317
- clientTools?: LlmapiChatCompletionTool[];
9128
+ idleTimeoutMs?: number;
9129
+ /** Content text deltas as they replay (always from seq 0 — reset accumulated text first). */
9130
+ onData?: (chunk: string) => void;
9131
+ /** Thinking/reasoning deltas as they replay. */
9132
+ onThinking?: (chunk: string) => void;
9133
+ /** Restores the placeholders this redactor assigned when the original request was sent. */
9134
+ piiRedactor?: PiiRedactor;
9135
+ /** Called once on a clean completion. Never called for 410 nor interrupted terminals. */
9136
+ onFinish?: (response: ApiResponse) => void;
9137
+ /** Called on a transient/unexpected failure. Never called for 410 (throws) nor interrupted terminals. */
9138
+ onError?: (error: Error) => void;
9139
+ }
9140
+ /**
9141
+ * Result of {@link resumeStream}. A `410 Gone` is the only outcome that throws
9142
+ * ({@link StreamExpiredError}); every other terminal is returned.
9143
+ *
9144
+ * - clean completion → `{ data, error: null, interrupted: false, empty }`
9145
+ * - in-stream error / tool-request terminal / idle timeout → `{ data, error,
9146
+ * interrupted: true }` (finalize as a stopped/partial message)
9147
+ * - transient transport/HTTP failure (401, 5xx, network) → `{ data, error,
9148
+ * interrupted: false, statusCode? }` (retryable — keep the handle)
9149
+ *
9150
+ * `empty` is true when the clean terminal delivered ZERO content/thinking
9151
+ * deltas — a `[DONE]`-only replay. That is never a real completion (a
9152
+ * completed generation always buffered at least one content frame); it means
9153
+ * the buffered frames were lost server-side (evicted/trimmed) while the
9154
+ * terminal survived. Consumers must NOT persist `data` (an empty response)
9155
+ * over content they already hold — keep the detached partial instead. On the
9156
+ * empty terminal `onFinish` is NOT fired (there is no completion to deliver);
9157
+ * the flag is optional so existing constructors of the clean arm stay valid.
9158
+ * @public
9159
+ */
9160
+ type ResumeStreamResult = {
9161
+ data: ApiResponse;
9162
+ error: null;
9163
+ interrupted: false;
9164
+ empty?: boolean;
9165
+ } | {
9166
+ data: ApiResponse | null;
9167
+ error: string;
9168
+ interrupted: boolean;
9169
+ statusCode?: number;
9170
+ };
9171
+ /**
9172
+ * Replay a detached stream from the portal's buffer.
9173
+ *
9174
+ * This is the reconnect primitive: after `runToolLoop` returns the detached
9175
+ * variant with a {@link StreamResumeHandle}, `resumeStream` issues a GET to the
9176
+ * portal's replay endpoint and rebuilds the response from seq 0 using a FRESH
9177
+ * accumulator and FRESH smoothers. Nothing from the original (detached) run is
9178
+ * reused — the completions strategy's reasoning-tag parser is stateful, so
9179
+ * replaying into reused state would double-count; replay-from-0 into fresh
9180
+ * state is the correctness mechanism, not just a simplification.
9181
+ *
9182
+ * Contract highlights:
9183
+ * - **No `starting_after`, no `id:` cursor, no body.** The GET carries the
9184
+ * inference id in the path only; replay is always whole-stream. (The server
9185
+ * reserves `starting_after` but does NOT honor it — sending it would silently
9186
+ * duplicate content.)
9187
+ * - **410 → throws {@link StreamExpiredError}.** No `onFinish`/`onError`.
9188
+ * - **In-stream error / tool-request terminal → `interrupted: true`.** Flushes
9189
+ * both smoothers and returns the partial; no throw, no `onError`, no `onFinish`.
9190
+ * - **token resolved by the caller at resume time** so a refresh during the
9191
+ * detach window is honored.
9192
+ *
9193
+ * @public
9194
+ */
9195
+ declare function resumeStream(options: ResumeStreamOptions): Promise<ResumeStreamResult>;
9196
+
9197
+ interface StockPriceClassification {
9198
+ /** Whether the prompt likely asks for stock/ETF/FX quote data. */
9199
+ needsStockPrice: boolean;
9200
+ /** Cosine similarity to the "needs stock price" centroid. */
9201
+ stockPriceScore: number;
9202
+ /** Cosine similarity to the "no stock price" centroid. */
9203
+ noStockPriceScore: number;
9204
+ }
9205
+ interface StockPriceClassifierOptions extends EmbeddingOptions {
9318
9206
  /**
9319
- * Server-side tools to include from /api/v1/tools.
9320
- * - undefined: Include all server-side tools (default)
9321
- * - string[]: Include only tools with these names
9322
- * - []: Include no server-side tools
9323
- * - function: Dynamic filter that receives prompt embedding(s) and all tools,
9324
- * returns tool names to include. Useful for semantic tool matching.
9325
- *
9326
- * @example
9327
- * // Include only specific server tools
9328
- * serverTools: ["anuma_create_image", "perplexity_search"]
9329
- *
9330
- * // Disable server tools for this request
9331
- * serverTools: []
9332
- *
9333
- * // Semantic tool matching based on prompt
9334
- * serverTools: (embeddings, tools) => {
9335
- * const matches = findMatchingTools(embeddings, tools, { limit: 5 });
9336
- * return matches.map(m => m.tool.name);
9337
- * }
9207
+ * Score margin: the stock-price score must exceed the no-stock-price
9208
+ * score by at least this amount to classify as "needs stock price data".
9209
+ * @default 0.02
9338
9210
  */
9339
- serverTools?: ServerToolsFilter;
9211
+ margin?: number;
9212
+ }
9213
+ /**
9214
+ * Classify whether a prompt needs stock/ETF/index/FX price data.
9215
+ *
9216
+ * Domain: traditional financial instruments (equities, indices, ETFs, FX).
9217
+ * Crypto is handled by `classifyCryptoPrice`. Ambiguous prompts
9218
+ * (e.g. *"gold price"*) may fire both — that's expected; the caller can
9219
+ * inject both sets of results.
9220
+ */
9221
+ declare function classifyStockPrice(prompt: string, options: StockPriceClassifierOptions): Promise<StockPriceClassification>;
9222
+ /**
9223
+ * Batch-classify multiple prompts. Embeds all prompts in one batch
9224
+ * call for efficiency.
9225
+ */
9226
+ declare function classifyStockPriceBatch(prompts: string[], options: StockPriceClassifierOptions): Promise<StockPriceClassification[]>;
9227
+ interface StockPricePreProcessorOptions {
9340
9228
  /**
9341
- * Dynamic filter for client-side tools based on prompt embeddings.
9342
- * Receives the prompt embedding(s) (or null for short messages) and all client tools,
9343
- * returns tool names to include. Tools not in the returned list are excluded from the request.
9229
+ * Called with the caller's stock-quote provider when the classifier
9230
+ * decides the prompt is asking for stock/ETF/FX data. Return either a
9231
+ * plain string (wrapped by the SDK in a default user message) or a
9232
+ * fully-formed `LlmapiMessage[]` (full control over role/shape). Omit
9233
+ * to run in observer mode — classification still fires but nothing is
9234
+ * injected.
9344
9235
  *
9345
- * @example
9346
- * clientToolsFilter: (embeddings, tools) => {
9347
- * if (!embeddings) return []; // Short message — no client tools
9348
- * const matches = findMatchingTools(embeddings, pseudoServerTools);
9349
- * return matches.map(m => m.tool.name);
9350
- * }
9236
+ * The `signal` argument is forwarded from the tool loop so long-running
9237
+ * provider requests can be aborted when the caller aborts.
9351
9238
  */
9352
- clientToolsFilter?: ClientToolsFilterFn;
9239
+ fetchStockPriceData?: (prompt: string, options: {
9240
+ signal?: AbortSignal;
9241
+ }) => Promise<string | LlmapiMessage[]>;
9353
9242
  /**
9354
- * Controls which tool the model should use:
9355
- * - "auto": Model decides whether to use a tool (default)
9356
- * - "any": Model must use one of the provided tools
9357
- * - "none": Model cannot use any tools
9358
- * - "required": Model must use a tool
9359
- * - Specific tool name: Model must use that specific tool
9243
+ * Score margin: `stockPriceScore` must exceed `noStockPriceScore` by at
9244
+ * least this amount to classify as "needs stock price data".
9245
+ * @default 0.02
9360
9246
  */
9361
- toolChoice?: string;
9247
+ margin?: number;
9248
+ /** Observe the classification without injecting anything. */
9249
+ onClassification?: (result: StockPriceClassification) => void;
9250
+ }
9251
+ /**
9252
+ * Build a pre-processor that runs stock-price classification on the shared
9253
+ * embedding provided by `runToolLoop`, and — if the classifier decides the
9254
+ * prompt is asking for quotes — invokes the caller-supplied
9255
+ * `fetchStockPriceData` and injects the result into the conversation.
9256
+ *
9257
+ * Domain: stocks, ETFs, indices, FX. For crypto use
9258
+ * `createCryptoPricePreProcessor`. The SDK does not run the lookup itself;
9259
+ * the caller wires up whichever provider they want (Twelve Data, Alpha
9260
+ * Vantage, Finnhub, etc.).
9261
+ */
9262
+ declare function createStockPricePreProcessor(options?: StockPricePreProcessorOptions): PromptPreProcessor;
9263
+
9264
+ /**
9265
+ * Value of the `origin` column on a row the SDK synthesised for a turn's tool results (v44, #866).
9266
+ *
9267
+ * `satisfies MessageOrigin` so this constant and the column's union cannot drift apart silently.
9268
+ */
9269
+ declare const TOOL_RESULT_ORIGIN = "tool_result";
9270
+
9271
+ interface WeatherClassification {
9272
+ /** Whether the prompt likely asks for weather data. */
9273
+ needsWeather: boolean;
9274
+ /** Cosine similarity to the "needs weather" centroid. */
9275
+ weatherScore: number;
9276
+ /** Cosine similarity to the "no weather" centroid. */
9277
+ noWeatherScore: number;
9278
+ }
9279
+ interface WeatherClassifierOptions extends EmbeddingOptions {
9362
9280
  /**
9363
- * Maximum number of tool execution rounds before forcing the model to respond with text.
9364
- * After this many rounds, `toolChoice` is set to `"none"` on the next continuation,
9365
- * so the model produces a text answer using whatever tool results it has gathered.
9366
- * @default 3
9281
+ * Score margin: the weather score must exceed the no-weather score by at
9282
+ * least this amount to classify as "needs weather data".
9283
+ * @default 0.02
9367
9284
  */
9368
- maxToolRounds?: number;
9285
+ margin?: number;
9286
+ }
9287
+ /**
9288
+ * Classify whether a prompt needs weather data.
9289
+ *
9290
+ * Covers forecasts, temperature, precipitation, wind, humidity, UV, AQI,
9291
+ * marine, flood, climate — anything OpenMeteo-shaped.
9292
+ */
9293
+ declare function classifyWeather(prompt: string, options: WeatherClassifierOptions): Promise<WeatherClassification>;
9294
+ /**
9295
+ * Batch-classify multiple prompts. Embeds all prompts in one batch
9296
+ * call for efficiency.
9297
+ */
9298
+ declare function classifyWeatherBatch(prompts: string[], options: WeatherClassifierOptions): Promise<WeatherClassification[]>;
9299
+ interface WeatherPreProcessorOptions {
9369
9300
  /**
9370
- * Reasoning configuration for o-series and other reasoning models.
9371
- * Controls reasoning effort level and whether to include reasoning summary.
9301
+ * Called with the caller's weather provider when the classifier decides
9302
+ * the prompt is asking for weather. Return either a plain string
9303
+ * (wrapped by the SDK in a default user message) or a fully-formed
9304
+ * `LlmapiMessage[]` (full control over role/shape). Omit to run in
9305
+ * observer mode — classification still fires but nothing is injected.
9306
+ *
9307
+ * The `signal` argument is forwarded from the tool loop so long-running
9308
+ * provider requests can be aborted when the caller aborts.
9372
9309
  */
9373
- reasoning?: LlmapiResponseReasoning;
9310
+ fetchWeatherData?: (prompt: string, options: {
9311
+ signal?: AbortSignal;
9312
+ }) => Promise<string | LlmapiMessage[]>;
9374
9313
  /**
9375
- * Extended thinking configuration for Anthropic models (Claude).
9376
- * Enables the model to think through complex problems step by step
9377
- * before generating the final response.
9314
+ * Score margin: `weatherScore` must exceed `noWeatherScore` by at least
9315
+ * this amount to classify as "needs weather data".
9316
+ * @default 0.02
9378
9317
  */
9379
- thinking?: LlmapiThinkingOptions;
9380
- /** User-selected image generation model for server-side enforcement. */
9381
- imageModel?: string;
9318
+ margin?: number;
9319
+ /** Observe the classification without injecting anything. */
9320
+ onClassification?: (result: WeatherClassification) => void;
9321
+ }
9322
+ /**
9323
+ * Build a pre-processor that runs weather classification on the shared
9324
+ * embedding provided by `runToolLoop`, and — if the classifier decides
9325
+ * the prompt is asking for weather — invokes the caller-supplied
9326
+ * `fetchWeatherData` and injects the result into the conversation.
9327
+ *
9328
+ * The SDK does not run the weather lookup itself; the caller wires up
9329
+ * whichever provider they want (OpenMeteo, AccuWeather, weather.gov, etc.).
9330
+ */
9331
+ declare function createWeatherPreProcessor(options?: WeatherPreProcessorOptions): PromptPreProcessor;
9332
+
9333
+ interface WebSearchClassification {
9334
+ /** Whether the prompt likely needs a web search. */
9335
+ needsWebSearch: boolean;
9336
+ /** Cosine similarity to the "needs search" centroid. */
9337
+ searchScore: number;
9338
+ /** Cosine similarity to the "no search" centroid. */
9339
+ noSearchScore: number;
9340
+ }
9341
+ interface WebSearchClassifierOptions extends EmbeddingOptions {
9382
9342
  /**
9383
- * Per-request callback for thinking/reasoning chunks.
9384
- * Called with delta chunks as the model "thinks" through a problem.
9385
- * Use this to display thinking progress in the UI.
9343
+ * Score margin: the search score must exceed the no-search score
9344
+ * by at least this amount to classify as "needs web search".
9345
+ * @default 0.02
9386
9346
  */
9387
- onThinking?: (chunk: string) => void;
9388
- /** Parent message ID for branching (edit/regenerate). Sets on the user message. */
9389
- parentMessageId?: string;
9347
+ margin?: number;
9348
+ }
9349
+ /**
9350
+ * Classify whether a prompt needs a web search.
9351
+ *
9352
+ * Embeds the prompt and compares it against the pre-computed
9353
+ * search and no-search centroid vectors.
9354
+ */
9355
+ declare function classifyWebSearch(prompt: string, options: WebSearchClassifierOptions): Promise<WebSearchClassification>;
9356
+ /**
9357
+ * Batch-classify multiple prompts. Embeds all prompts in one batch
9358
+ * call for efficiency.
9359
+ */
9360
+ declare function classifyWebSearchBatch(prompts: string[], options: WebSearchClassifierOptions): Promise<WebSearchClassification[]>;
9361
+ interface WebSearchPreProcessorOptions {
9390
9362
  /**
9391
- * Pre-generated unique ID for the assistant response message.
9392
- * When provided, the persisted assistant message will use this ID instead of
9393
- * an auto-generated one. This lets the consumer show an in-flight streaming
9394
- * placeholder under the same React key, avoiding an unmount/remount flash
9395
- * when streaming completes and the message is loaded from the database.
9363
+ * Called with the caller's search provider when the classifier decides
9364
+ * a web search is needed. Return either a plain string (the SDK will
9365
+ * wrap it in a default user message) or a fully-formed message array
9366
+ * (full control over role/shape). Omit to run in observer mode —
9367
+ * classification still fires but no messages are injected.
9368
+ *
9369
+ * The `signal` argument is forwarded from the tool loop so long-running
9370
+ * search requests can be aborted when the caller aborts.
9396
9371
  */
9397
- assistantUniqueId?: string;
9398
- }
9399
- interface BaseUseChatStorageResult {
9400
- isLoading: boolean;
9401
- stop: () => void;
9402
- conversationId: string | null;
9403
- setConversationId: (id: string | null) => void;
9404
- createConversation: (options?: CreateConversationOptions) => Promise<StoredConversation>;
9405
- getConversation: (id: string) => Promise<StoredConversation | null>;
9406
- getConversations: () => Promise<StoredConversation[]>;
9407
- updateConversationTitle: (id: string, title: string) => Promise<boolean>;
9408
- updateConversationPinned: (id: string, pinned: boolean) => Promise<boolean>;
9409
- deleteConversation: (id: string) => Promise<boolean>;
9410
- getMessages: (conversationId: string) => Promise<StoredMessage[]>;
9372
+ fetchSearchResults?: (prompt: string, options: {
9373
+ signal?: AbortSignal;
9374
+ }) => Promise<string | LlmapiMessage[]>;
9411
9375
  /**
9412
- * Paginated display read: the newest `limit` messages (optionally below
9413
- * `beforeMessageId`), ascending, with embedding columns skipped.
9376
+ * Score margin: `searchScore` must exceed `noSearchScore` by at least
9377
+ * this amount to classify as "needs web search".
9378
+ * @default 0.02
9414
9379
  */
9415
- getMessagesPage: (conversationId: string, options: GetMessagesPageOptions) => Promise<StoredMessage[]>;
9416
- /** Whole-thread branch-tree skeleton — no field decryption. */
9417
- getMessageSkeletons: (conversationId: string) => Promise<MessageSkeleton[]>;
9418
- /** Total message count for a conversation. */
9419
- getMessageCount: (conversationId: string) => Promise<number>;
9380
+ margin?: number;
9381
+ /** Observe the classification without injecting anything. */
9382
+ onClassification?: (result: WebSearchClassification) => void;
9420
9383
  }
9421
- declare function generateConversationId(): string;
9384
+ /**
9385
+ * Build a pre-processor that runs web-search classification on the
9386
+ * shared embedding provided by `runToolLoop`, and — if the classifier
9387
+ * decides a search is warranted — invokes the caller-supplied
9388
+ * `fetchSearchResults` and injects the result into the conversation.
9389
+ *
9390
+ * @example Basic usage with a search provider
9391
+ * ```ts
9392
+ * import { runToolLoop, createWebSearchPreProcessor } from "@anuma/sdk/server";
9393
+ *
9394
+ * const webSearch = createWebSearchPreProcessor({
9395
+ * fetchSearchResults: async (prompt, { signal }) => {
9396
+ * const res = await mySearchProvider.query(prompt, { signal });
9397
+ * return res.results.map((r) => `- ${r.title}: ${r.snippet}`).join("\n");
9398
+ * },
9399
+ * });
9400
+ *
9401
+ * await runToolLoop({
9402
+ * messages,
9403
+ * model,
9404
+ * token,
9405
+ * preProcessors: [webSearch],
9406
+ * });
9407
+ * ```
9408
+ *
9409
+ * @example Observer mode — only log classification, inject nothing
9410
+ * ```ts
9411
+ * const observer = createWebSearchPreProcessor({
9412
+ * onClassification: ({ needsWebSearch, searchScore, noSearchScore }) => {
9413
+ * metrics.record({ needsWebSearch, searchScore, noSearchScore });
9414
+ * },
9415
+ * });
9416
+ * ```
9417
+ *
9418
+ * @example Full control — return a custom message shape
9419
+ * ```ts
9420
+ * const webSearch = createWebSearchPreProcessor({
9421
+ * fetchSearchResults: async (prompt, { signal }) => {
9422
+ * const results = await mySearchProvider.query(prompt, { signal });
9423
+ * return [
9424
+ * {
9425
+ * role: "system",
9426
+ * content: [{ type: "text", text: `Search results for "${prompt}":\n${formatResults(results)}` }],
9427
+ * },
9428
+ * ];
9429
+ * },
9430
+ * });
9431
+ * ```
9432
+ */
9433
+ declare function createWebSearchPreProcessor(options?: WebSearchPreProcessorOptions): PromptPreProcessor;
9422
9434
 
9423
9435
  /** Returns `value` as an {@link AnumaTurn} when it is one, otherwise `undefined`. */
9424
9436
  declare function readAnumaTurn(value: unknown): AnumaTurn | undefined;
@@ -13819,7 +13831,7 @@ declare class WatermelonChatStorageAdapter implements ChatBackend {
13819
13831
  createMessage(options: CreateMessageOptions): Promise<StoredMessage>;
13820
13832
  updateMessageEmbedding(uniqueId: string, vector: number[], embeddingModel: string): Promise<StoredMessage | null>;
13821
13833
  updateMessageChunks(uniqueId: string, chunks: MessageChunk[], embeddingModel: string): Promise<StoredMessage | null>;
13822
- updateMessageError(uniqueId: string, error: string): Promise<StoredMessage | null>;
13834
+ updateMessageError(uniqueId: string, error: string | null): Promise<StoredMessage | null>;
13823
13835
  updateMessageFeedback(uniqueId: string, feedback: MessageFeedback): Promise<StoredMessage | null>;
13824
13836
  updateMessageFileIds(uniqueId: string, fileIds: string[]): Promise<StoredMessage | null>;
13825
13837
  clearMessages(conversationId: string): Promise<void>;
@@ -15093,17 +15105,6 @@ type SendMessageWithStorageResult = {
15093
15105
  userMessage: StoredMessage;
15094
15106
  assistantMessage?: undefined;
15095
15107
  };
15096
- /**
15097
- * Outcome of delivering one vault inbox item with `deliverInbox`.
15098
- * @public
15099
- */
15100
- type InboxDeliveryResult = {
15101
- id: string;
15102
- conversationId: string | null;
15103
- status: "stored" | "delivered" | "elsewhere" | "unreadable" | "retry";
15104
- assistantMessage: StoredMessage | null;
15105
- error?: string;
15106
- };
15107
15108
  /**
15108
15109
  * Options for searching messages
15109
15110
  */