@tanstack/ai 0.43.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/messages.js +21 -8
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/embed/adapter.d.ts +69 -0
- package/dist/esm/activities/embed/adapter.js +23 -0
- package/dist/esm/activities/embed/adapter.js.map +1 -0
- package/dist/esm/activities/embed/index.d.ts +117 -0
- package/dist/esm/activities/embed/index.js +166 -0
- package/dist/esm/activities/embed/index.js.map +1 -0
- package/dist/esm/activities/error-payload.d.ts +8 -0
- package/dist/esm/activities/error-payload.js +29 -17
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +12 -0
- package/dist/esm/activities/generateAudio/index.js +19 -6
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +12 -0
- package/dist/esm/activities/generateImage/index.js +21 -7
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +17 -1
- package/dist/esm/activities/generateSpeech/index.js +19 -6
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +17 -1
- package/dist/esm/activities/generateTranscription/index.js +19 -6
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +18 -0
- package/dist/esm/activities/generateVideo/index.js +54 -15
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +8 -2
- package/dist/esm/activities/index.js +11 -7
- package/dist/esm/activities/middleware/types.d.ts +1 -1
- package/dist/esm/activities/rerank/adapter.d.ts +63 -0
- package/dist/esm/activities/rerank/adapter.js +23 -0
- package/dist/esm/activities/rerank/adapter.js.map +1 -0
- package/dist/esm/activities/rerank/index.d.ts +92 -0
- package/dist/esm/activities/rerank/index.js +163 -0
- package/dist/esm/activities/rerank/index.js.map +1 -0
- package/dist/esm/activities/summarize/index.d.ts +17 -1
- package/dist/esm/activities/summarize/index.js +19 -5
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/index.d.ts +7 -2
- package/dist/esm/index.js +5 -1
- package/dist/esm/middlewares/otel.d.ts +3 -1
- package/dist/esm/middlewares/otel.js +25 -6
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/types.d.ts +195 -0
- package/dist/esm/utilities/activity-abort.d.ts +53 -0
- package/dist/esm/utilities/activity-abort.js +150 -0
- package/dist/esm/utilities/activity-abort.js.map +1 -0
- package/dist/esm/utilities/embedding-input.d.ts +32 -0
- package/dist/esm/utilities/embedding-input.js +61 -0
- package/dist/esm/utilities/embedding-input.js.map +1 -0
- package/package.json +3 -3
- package/src/activities/chat/messages.ts +30 -1
- package/src/activities/embed/adapter.ts +112 -0
- package/src/activities/embed/index.ts +318 -0
- package/src/activities/error-payload.ts +41 -9
- package/src/activities/generateAudio/index.ts +47 -5
- package/src/activities/generateImage/index.ts +48 -5
- package/src/activities/generateSpeech/index.ts +52 -9
- package/src/activities/generateTranscription/index.ts +52 -9
- package/src/activities/generateVideo/index.ts +131 -33
- package/src/activities/index.ts +44 -0
- package/src/activities/middleware/types.ts +2 -0
- package/src/activities/rerank/adapter.ts +90 -0
- package/src/activities/rerank/index.ts +302 -0
- package/src/activities/summarize/index.ts +59 -19
- package/src/index.ts +19 -0
- package/src/middlewares/otel.ts +60 -8
- package/src/types.ts +219 -0
- package/src/utilities/activity-abort.ts +197 -0
- package/src/utilities/embedding-input.ts +83 -0
package/src/types.ts
CHANGED
|
@@ -376,6 +376,11 @@ export interface ModelMessage<
|
|
|
376
376
|
* resume the SAME message bubble in place (see `@tanstack/ai-persistence`).
|
|
377
377
|
*/
|
|
378
378
|
id?: string
|
|
379
|
+
/**
|
|
380
|
+
* Optional message creation timestamp. When present, message converters
|
|
381
|
+
* preserve it across persist → hydrate round-trips.
|
|
382
|
+
*/
|
|
383
|
+
createdAt?: Date
|
|
379
384
|
}
|
|
380
385
|
|
|
381
386
|
/**
|
|
@@ -1992,6 +1997,12 @@ export interface SummarizationOptions<
|
|
|
1992
1997
|
* call logger.request() before the SDK call and logger.errors() in catch blocks.
|
|
1993
1998
|
*/
|
|
1994
1999
|
logger: InternalLogger
|
|
2000
|
+
/**
|
|
2001
|
+
* Effective abort signal composed by the activity from caller `abortSignal`
|
|
2002
|
+
* and/or `timeout`. Adapters should forward this to the provider SDK when
|
|
2003
|
+
* supported. Request-specific — never store on a global client config.
|
|
2004
|
+
*/
|
|
2005
|
+
abortSignal?: AbortSignal
|
|
1995
2006
|
}
|
|
1996
2007
|
|
|
1997
2008
|
export interface SummarizationResult {
|
|
@@ -2001,6 +2012,71 @@ export interface SummarizationResult {
|
|
|
2001
2012
|
usage: TokenUsage
|
|
2002
2013
|
}
|
|
2003
2014
|
|
|
2015
|
+
// ============================================================================
|
|
2016
|
+
// Rerank Types
|
|
2017
|
+
// ============================================================================
|
|
2018
|
+
|
|
2019
|
+
/**
|
|
2020
|
+
* Options passed to a {@link RerankAdapter}. Documents reach the adapter
|
|
2021
|
+
* already serialized to strings — the `rerank()` activity stringifies object
|
|
2022
|
+
* documents and maps results back to the original elements, so adapters never
|
|
2023
|
+
* deal with the caller's document type.
|
|
2024
|
+
*/
|
|
2025
|
+
export interface RerankOptions<
|
|
2026
|
+
TProviderOptions extends object = Record<string, unknown>,
|
|
2027
|
+
> {
|
|
2028
|
+
model: string
|
|
2029
|
+
/** The search query documents are scored against. */
|
|
2030
|
+
query: string
|
|
2031
|
+
/** Documents to rerank, pre-serialized to strings by the activity. */
|
|
2032
|
+
documents: Array<string>
|
|
2033
|
+
/** Return only the top N results. Passed through to the provider. */
|
|
2034
|
+
topN?: number
|
|
2035
|
+
/** Provider-specific options forwarded by the rerank() activity. */
|
|
2036
|
+
modelOptions?: TProviderOptions
|
|
2037
|
+
/** Forwarded to the provider request for cancellation. */
|
|
2038
|
+
abortSignal?: AbortSignal
|
|
2039
|
+
/**
|
|
2040
|
+
* Internal logger threaded from the rerank() entry point. Adapters must call
|
|
2041
|
+
* logger.request() before the provider call and logger.errors() in catch
|
|
2042
|
+
* blocks.
|
|
2043
|
+
*/
|
|
2044
|
+
logger: InternalLogger
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
/**
|
|
2048
|
+
* Provider-level rerank result. Adapters return scored indices into the
|
|
2049
|
+
* (serialized) `documents` array plus usage — never the documents themselves.
|
|
2050
|
+
* The activity attaches the original documents.
|
|
2051
|
+
*/
|
|
2052
|
+
export interface RerankAdapterResult {
|
|
2053
|
+
id: string
|
|
2054
|
+
/** Scored results, highest relevance first, as indices into `documents`. */
|
|
2055
|
+
ranking: Array<{ index: number; score: number }>
|
|
2056
|
+
usage: TokenUsage
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
/**
|
|
2060
|
+
* Public result of the `rerank()` activity, generic over the caller's document
|
|
2061
|
+
* element type so `document` / `rerankedDocuments` carry the original values
|
|
2062
|
+
* (strings or objects), not their serialized form.
|
|
2063
|
+
*/
|
|
2064
|
+
export interface RerankResult<TDocument = string> {
|
|
2065
|
+
id: string
|
|
2066
|
+
model: string
|
|
2067
|
+
/** Scored results, highest relevance first. */
|
|
2068
|
+
ranking: Array<{ index: number; score: number; document: TDocument }>
|
|
2069
|
+
/** The documents reordered by relevance — `ranking.map(r => r.document)`. */
|
|
2070
|
+
rerankedDocuments: Array<TDocument>
|
|
2071
|
+
/**
|
|
2072
|
+
* Usage for the request. Rerank typically bills in provider-defined "search
|
|
2073
|
+
* units" (`usage.unitsBilled`) rather than tokens. Some providers (e.g.
|
|
2074
|
+
* OpenRouter) may also report `totalTokens` and `cost`; Cohere reports only
|
|
2075
|
+
* search units and leaves the token counts at 0.
|
|
2076
|
+
*/
|
|
2077
|
+
usage: TokenUsage
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2004
2080
|
// ============================================================================
|
|
2005
2081
|
// Image Generation Types
|
|
2006
2082
|
// ============================================================================
|
|
@@ -2129,6 +2205,12 @@ export interface ImageGenerationOptions<
|
|
|
2129
2205
|
* call logger.request() before the SDK call and logger.errors() in catch blocks.
|
|
2130
2206
|
*/
|
|
2131
2207
|
logger: InternalLogger
|
|
2208
|
+
/**
|
|
2209
|
+
* Effective abort signal composed by the activity from caller `abortSignal`
|
|
2210
|
+
* and/or `timeout`. Adapters should forward this to the provider SDK when
|
|
2211
|
+
* supported. Request-specific — never store on a global client config.
|
|
2212
|
+
*/
|
|
2213
|
+
abortSignal?: AbortSignal
|
|
2132
2214
|
}
|
|
2133
2215
|
|
|
2134
2216
|
/**
|
|
@@ -2242,6 +2324,12 @@ export interface AudioGenerationOptions<
|
|
|
2242
2324
|
* catch blocks.
|
|
2243
2325
|
*/
|
|
2244
2326
|
logger: InternalLogger
|
|
2327
|
+
/**
|
|
2328
|
+
* Effective abort signal composed by the activity from caller `abortSignal`
|
|
2329
|
+
* and/or `timeout`. Adapters should forward this to the provider SDK when
|
|
2330
|
+
* supported. Request-specific — never store on a global client config.
|
|
2331
|
+
*/
|
|
2332
|
+
abortSignal?: AbortSignal
|
|
2245
2333
|
}
|
|
2246
2334
|
|
|
2247
2335
|
/**
|
|
@@ -2311,6 +2399,12 @@ export interface VideoGenerationOptions<
|
|
|
2311
2399
|
* call logger.request() before the SDK call and logger.errors() in catch blocks.
|
|
2312
2400
|
*/
|
|
2313
2401
|
logger: InternalLogger
|
|
2402
|
+
/**
|
|
2403
|
+
* Effective abort signal composed by the activity from caller `abortSignal`
|
|
2404
|
+
* and/or `timeout`. Adapters should forward this to the provider SDK when
|
|
2405
|
+
* supported. Request-specific — never store on a global client config.
|
|
2406
|
+
*/
|
|
2407
|
+
abortSignal?: AbortSignal
|
|
2314
2408
|
}
|
|
2315
2409
|
|
|
2316
2410
|
/**
|
|
@@ -2396,6 +2490,12 @@ export interface TTSOptions<TProviderOptions extends object = object> {
|
|
|
2396
2490
|
* catch blocks.
|
|
2397
2491
|
*/
|
|
2398
2492
|
logger: InternalLogger
|
|
2493
|
+
/**
|
|
2494
|
+
* Effective abort signal composed by the activity from caller `abortSignal`
|
|
2495
|
+
* and/or `timeout`. Adapters should forward this to the provider SDK when
|
|
2496
|
+
* supported. Request-specific — never store on a global client config.
|
|
2497
|
+
*/
|
|
2498
|
+
abortSignal?: AbortSignal
|
|
2399
2499
|
}
|
|
2400
2500
|
|
|
2401
2501
|
/**
|
|
@@ -2456,6 +2556,12 @@ export interface TranscriptionOptions<
|
|
|
2456
2556
|
* in catch blocks.
|
|
2457
2557
|
*/
|
|
2458
2558
|
logger: InternalLogger
|
|
2559
|
+
/**
|
|
2560
|
+
* Effective abort signal composed by the activity from caller `abortSignal`
|
|
2561
|
+
* and/or `timeout`. Adapters should forward this to the provider SDK when
|
|
2562
|
+
* supported. Request-specific — never store on a global client config.
|
|
2563
|
+
*/
|
|
2564
|
+
abortSignal?: AbortSignal
|
|
2459
2565
|
}
|
|
2460
2566
|
|
|
2461
2567
|
/**
|
|
@@ -2512,6 +2618,119 @@ export interface TranscriptionResult {
|
|
|
2512
2618
|
artifacts?: Array<PersistedArtifactRef>
|
|
2513
2619
|
}
|
|
2514
2620
|
|
|
2621
|
+
// ============================================================================
|
|
2622
|
+
// Embedding Types
|
|
2623
|
+
// ============================================================================
|
|
2624
|
+
|
|
2625
|
+
/**
|
|
2626
|
+
* Input modalities an embedding model can accept. Unlike
|
|
2627
|
+
* {@link MediaPromptModality}, `'text'` is listed explicitly because
|
|
2628
|
+
* text-only embedding models are the common case and the modality list
|
|
2629
|
+
* drives compile-time narrowing of {@link EmbeddingInputItem}.
|
|
2630
|
+
*/
|
|
2631
|
+
export type EmbeddingModality = 'text' | 'image'
|
|
2632
|
+
|
|
2633
|
+
/**
|
|
2634
|
+
* Per-model map from model name to the input modalities it accepts, used as
|
|
2635
|
+
* an adapter type parameter (`TModelInputModalitiesByName`). Models absent
|
|
2636
|
+
* from the map fall back to the unconstrained {@link EmbeddingInputItem}.
|
|
2637
|
+
*/
|
|
2638
|
+
export type EmbeddingModelInputModalitiesByName = Record<
|
|
2639
|
+
string,
|
|
2640
|
+
ReadonlyArray<EmbeddingModality>
|
|
2641
|
+
>
|
|
2642
|
+
|
|
2643
|
+
/**
|
|
2644
|
+
* A fused multi-part embedding item: all parts are embedded together into a
|
|
2645
|
+
* single vector (e.g. a product photo plus its caption). Written as a nested
|
|
2646
|
+
* array of content parts — the same `Array<ContentPart>` convention chat
|
|
2647
|
+
* messages use — so a fused item is visually distinct from the top-level
|
|
2648
|
+
* `input` list, where each element produces its own vector. Supported by
|
|
2649
|
+
* multimodal embedding models such as Cohere embed-v4 and Amazon Titan
|
|
2650
|
+
* Multimodal.
|
|
2651
|
+
*/
|
|
2652
|
+
export type EmbeddingContentParts = Array<TextPart | ImagePart>
|
|
2653
|
+
|
|
2654
|
+
/**
|
|
2655
|
+
* One embeddable item, producing exactly one vector. A bare string is
|
|
2656
|
+
* shorthand for a text part; a nested {@link EmbeddingContentParts} array
|
|
2657
|
+
* fuses its parts into a single vector. Note that a bare array at the top
|
|
2658
|
+
* level of `input` is the *list of items* (one vector each) — fuse by
|
|
2659
|
+
* nesting, e.g. `input: [[textPart, imagePart]]`.
|
|
2660
|
+
*/
|
|
2661
|
+
export type EmbeddingInputItem =
|
|
2662
|
+
| string
|
|
2663
|
+
| TextPart
|
|
2664
|
+
| ImagePart
|
|
2665
|
+
| EmbeddingContentParts
|
|
2666
|
+
|
|
2667
|
+
/** Maps an embedding modality to the item types it admits. @internal */
|
|
2668
|
+
interface EmbeddingItemByModality {
|
|
2669
|
+
text: TextPart
|
|
2670
|
+
image: ImagePart | EmbeddingContentParts
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
/**
|
|
2674
|
+
* Embedding item type narrowed to the modalities a specific model supports.
|
|
2675
|
+
* `EmbeddingInputItemFor<'text'>` (a text-only model) is `string | TextPart`;
|
|
2676
|
+
* `'text' | 'image'` additionally admits image parts and fused
|
|
2677
|
+
* {@link EmbeddingContentParts} arrays. Used by the activity option types
|
|
2678
|
+
* together with the adapter's per-model modality map so unsupported inputs
|
|
2679
|
+
* fail at compile time.
|
|
2680
|
+
*/
|
|
2681
|
+
export type EmbeddingInputItemFor<
|
|
2682
|
+
TModalities extends EmbeddingModality = EmbeddingModality,
|
|
2683
|
+
> = string | TextPart | EmbeddingItemByModality[TModalities]
|
|
2684
|
+
|
|
2685
|
+
/**
|
|
2686
|
+
* Options for embedding generation, as received by adapters. The `embed()`
|
|
2687
|
+
* entry point normalizes a single input item to an array before calling the
|
|
2688
|
+
* adapter, so `input` is always an array here.
|
|
2689
|
+
*/
|
|
2690
|
+
export interface EmbeddingOptions<TProviderOptions extends object = object> {
|
|
2691
|
+
/** The model to use for embedding generation */
|
|
2692
|
+
model: string
|
|
2693
|
+
/** The items to embed — one vector per item */
|
|
2694
|
+
input: Array<EmbeddingInputItem>
|
|
2695
|
+
/**
|
|
2696
|
+
* Requested output dimensionality. Adapters for models with fixed
|
|
2697
|
+
* dimensions throw a clear runtime error when this is set.
|
|
2698
|
+
*/
|
|
2699
|
+
dimensions?: number
|
|
2700
|
+
/** Model-specific options for embedding generation */
|
|
2701
|
+
modelOptions?: TProviderOptions
|
|
2702
|
+
/**
|
|
2703
|
+
* Internal logger threaded from the embed() entry point. Adapters must
|
|
2704
|
+
* call logger.request() before the SDK call and logger.errors() in catch
|
|
2705
|
+
* blocks.
|
|
2706
|
+
*/
|
|
2707
|
+
logger: InternalLogger
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
/**
|
|
2711
|
+
* A single embedding vector.
|
|
2712
|
+
*/
|
|
2713
|
+
export interface Embedding {
|
|
2714
|
+
/** The embedding vector */
|
|
2715
|
+
vector: Array<number>
|
|
2716
|
+
/** Position of the source item in the (normalized) input array */
|
|
2717
|
+
index: number
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
/**
|
|
2721
|
+
* Result of embedding generation.
|
|
2722
|
+
*/
|
|
2723
|
+
export interface EmbeddingResult {
|
|
2724
|
+
/** Unique identifier for the generation */
|
|
2725
|
+
id: string
|
|
2726
|
+
/** Model used for generation */
|
|
2727
|
+
model: string
|
|
2728
|
+
/** One embedding per input item, in input order */
|
|
2729
|
+
embeddings: Array<Embedding>
|
|
2730
|
+
/** Token usage information (if provided by the adapter) */
|
|
2731
|
+
usage?: TokenUsage
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2515
2734
|
/**
|
|
2516
2735
|
* Default metadata type for adapters that don't define custom metadata.
|
|
2517
2736
|
* Uses unknown for all modalities.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared abort/timeout composition for media (and summarize) activities.
|
|
3
|
+
*
|
|
4
|
+
* Callers pass optional `timeout` and/or `abortSignal` on activity options.
|
|
5
|
+
* Core composes them into one effective signal, races the adapter call so a
|
|
6
|
+
* hung provider still rejects, clears timeout resources on settle, and
|
|
7
|
+
* classifies aborts so lifecycle middleware gets `onAbort` rather than
|
|
8
|
+
* `onError`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const ABORT_ERROR_NAMES = new Set([
|
|
12
|
+
'AbortError',
|
|
13
|
+
'TimeoutError',
|
|
14
|
+
'APIUserAbortError',
|
|
15
|
+
'RequestAbortedError',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Combine two optional AbortSignals into one that aborts when either does.
|
|
20
|
+
* Returns the other signal directly when one is absent or already aborted.
|
|
21
|
+
* First abort wins and preserves its reason.
|
|
22
|
+
*
|
|
23
|
+
* Manual implementation — `AbortSignal.any` requires Node >= 20.3.
|
|
24
|
+
*/
|
|
25
|
+
export function combineAbortSignals(
|
|
26
|
+
a: AbortSignal | undefined,
|
|
27
|
+
b: AbortSignal | undefined,
|
|
28
|
+
): AbortSignal | undefined {
|
|
29
|
+
if (!a) return b
|
|
30
|
+
if (!b) return a
|
|
31
|
+
if (a.aborted) return a
|
|
32
|
+
if (b.aborted) return b
|
|
33
|
+
const controller = new AbortController()
|
|
34
|
+
const onAbort = (source: AbortSignal) => () => {
|
|
35
|
+
controller.abort(source.reason)
|
|
36
|
+
}
|
|
37
|
+
a.addEventListener('abort', onAbort(a), { once: true })
|
|
38
|
+
b.addEventListener('abort', onAbort(b), { once: true })
|
|
39
|
+
return controller.signal
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createTimeoutReason(ms: number): Error {
|
|
43
|
+
if (typeof DOMException !== 'undefined') {
|
|
44
|
+
return new DOMException(`Activity timed out after ${ms}ms`, 'TimeoutError')
|
|
45
|
+
}
|
|
46
|
+
const err = new Error(`Activity timed out after ${ms}ms`)
|
|
47
|
+
err.name = 'TimeoutError'
|
|
48
|
+
return err
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Normalize an abort reason into an Error the activity can reject with. */
|
|
52
|
+
export function toAbortError(reason: unknown): Error {
|
|
53
|
+
if (reason instanceof Error) return reason
|
|
54
|
+
if (typeof reason === 'string' && reason.length > 0) {
|
|
55
|
+
const err = new Error(reason)
|
|
56
|
+
err.name = 'AbortError'
|
|
57
|
+
return err
|
|
58
|
+
}
|
|
59
|
+
const err = new Error('The operation was aborted')
|
|
60
|
+
err.name = 'AbortError'
|
|
61
|
+
return err
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ActivityAbortControls {
|
|
65
|
+
/** Effective signal, or `undefined` when neither timeout nor caller signal. */
|
|
66
|
+
signal: AbortSignal | undefined
|
|
67
|
+
/** Clear the timeout timer if one was set. Idempotent. */
|
|
68
|
+
clear: () => void
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compose an activity-level timeout with a caller AbortSignal.
|
|
73
|
+
*
|
|
74
|
+
* - No SDK-wide default timeout; omit both for unlimited wait.
|
|
75
|
+
* - First of caller cancellation or timeout wins and keeps its reason.
|
|
76
|
+
* - Call `clear()` when the activity settles (success or failure) so timers
|
|
77
|
+
* do not leak.
|
|
78
|
+
*/
|
|
79
|
+
export function createActivityAbortControls(options: {
|
|
80
|
+
abortSignal?: AbortSignal
|
|
81
|
+
timeout?: number
|
|
82
|
+
}): ActivityAbortControls {
|
|
83
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
84
|
+
let timeoutSignal: AbortSignal | undefined
|
|
85
|
+
|
|
86
|
+
if (options.timeout !== undefined) {
|
|
87
|
+
if (!Number.isFinite(options.timeout) || options.timeout < 0) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Invalid activity timeout: expected a non-negative finite number, got ${String(options.timeout)}`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
const controller = new AbortController()
|
|
93
|
+
timeoutSignal = controller.signal
|
|
94
|
+
const ms = options.timeout
|
|
95
|
+
timeoutId = setTimeout(() => {
|
|
96
|
+
controller.abort(createTimeoutReason(ms))
|
|
97
|
+
}, ms)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const signal = combineAbortSignals(options.abortSignal, timeoutSignal)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
signal,
|
|
104
|
+
clear: () => {
|
|
105
|
+
if (timeoutId !== undefined) {
|
|
106
|
+
clearTimeout(timeoutId)
|
|
107
|
+
timeoutId = undefined
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reject when `signal` aborts, even if the underlying promise ignores it.
|
|
115
|
+
* Ensures activity-level timeouts work for adapters that do not yet forward
|
|
116
|
+
* the signal to the provider SDK.
|
|
117
|
+
*
|
|
118
|
+
* When the signal wins, the adapter promise is observed with an empty handler
|
|
119
|
+
* so a later settle cannot surface as an unhandled rejection.
|
|
120
|
+
*/
|
|
121
|
+
export function raceWithAbort<T>(
|
|
122
|
+
promise: Promise<T>,
|
|
123
|
+
signal: AbortSignal | undefined,
|
|
124
|
+
): Promise<T> {
|
|
125
|
+
if (!signal) return promise
|
|
126
|
+
|
|
127
|
+
const swallow = () => {
|
|
128
|
+
// Observe the adapter promise without acting on its outcome so a late
|
|
129
|
+
// reject after we already aborted cannot become an unhandled rejection.
|
|
130
|
+
promise.then(
|
|
131
|
+
() => undefined,
|
|
132
|
+
() => undefined,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (signal.aborted) {
|
|
137
|
+
swallow()
|
|
138
|
+
return Promise.reject(toAbortError(signal.reason))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return new Promise<T>((resolve, reject) => {
|
|
142
|
+
let settled = false
|
|
143
|
+
const onAbort = () => {
|
|
144
|
+
if (settled) return
|
|
145
|
+
settled = true
|
|
146
|
+
cleanup()
|
|
147
|
+
swallow()
|
|
148
|
+
reject(toAbortError(signal.reason))
|
|
149
|
+
}
|
|
150
|
+
const cleanup = () => {
|
|
151
|
+
signal.removeEventListener('abort', onAbort)
|
|
152
|
+
}
|
|
153
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
154
|
+
promise.then(
|
|
155
|
+
(value) => {
|
|
156
|
+
if (settled) return
|
|
157
|
+
settled = true
|
|
158
|
+
cleanup()
|
|
159
|
+
resolve(value)
|
|
160
|
+
},
|
|
161
|
+
(error: unknown) => {
|
|
162
|
+
if (settled) return
|
|
163
|
+
settled = true
|
|
164
|
+
cleanup()
|
|
165
|
+
reject(error)
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Whether a thrown value (and optional effective signal) should route to
|
|
173
|
+
* middleware `onAbort` instead of `onError`.
|
|
174
|
+
*/
|
|
175
|
+
export function isActivityAbortError(
|
|
176
|
+
error: unknown,
|
|
177
|
+
signal?: AbortSignal,
|
|
178
|
+
): boolean {
|
|
179
|
+
if (signal?.aborted) return true
|
|
180
|
+
if (!error || typeof error !== 'object') return false
|
|
181
|
+
const name = (error as { name?: unknown }).name
|
|
182
|
+
return typeof name === 'string' && ABORT_ERROR_NAMES.has(name)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Best-effort string reason for {@link GenerationAbortInfo}. */
|
|
186
|
+
export function abortReasonMessage(
|
|
187
|
+
error: unknown,
|
|
188
|
+
signal?: AbortSignal,
|
|
189
|
+
): string | undefined {
|
|
190
|
+
if (signal?.reason !== undefined) {
|
|
191
|
+
if (typeof signal.reason === 'string') return signal.reason
|
|
192
|
+
if (signal.reason instanceof Error) return signal.reason.message
|
|
193
|
+
}
|
|
194
|
+
if (error instanceof Error) return error.message
|
|
195
|
+
if (typeof error === 'string') return error
|
|
196
|
+
return undefined
|
|
197
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { EmbeddingInputItem, ImagePart } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One embedding input item resolved into its text and image constituents.
|
|
5
|
+
* Produced by {@link resolveEmbeddingInput}; adapters map each entry onto
|
|
6
|
+
* one provider-native input (one vector per entry).
|
|
7
|
+
*/
|
|
8
|
+
export interface ResolvedEmbeddingItem {
|
|
9
|
+
/** Text contents of the item, in order (empty for image-only items) */
|
|
10
|
+
texts: Array<string>
|
|
11
|
+
/** Image parts of the item, in order (empty for text-only items) */
|
|
12
|
+
images: Array<ImagePart>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveItem(item: EmbeddingInputItem): ResolvedEmbeddingItem {
|
|
16
|
+
if (typeof item === 'string') {
|
|
17
|
+
return { texts: [item], images: [] }
|
|
18
|
+
}
|
|
19
|
+
// A nested array is a fused item: its parts embed together into one vector.
|
|
20
|
+
if (Array.isArray(item)) {
|
|
21
|
+
const resolved: ResolvedEmbeddingItem = { texts: [], images: [] }
|
|
22
|
+
for (const part of item) {
|
|
23
|
+
if (part.type === 'text') {
|
|
24
|
+
resolved.texts.push(part.content)
|
|
25
|
+
} else {
|
|
26
|
+
resolved.images.push(part)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return resolved
|
|
30
|
+
}
|
|
31
|
+
if (item.type === 'text') {
|
|
32
|
+
return { texts: [item.content], images: [] }
|
|
33
|
+
}
|
|
34
|
+
return { texts: [], images: [item] }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve each embedding input item into its text and image constituents,
|
|
39
|
+
* preserving input order (result[i] corresponds to input[i] and to the
|
|
40
|
+
* vector at index i).
|
|
41
|
+
*/
|
|
42
|
+
export function resolveEmbeddingInput(
|
|
43
|
+
input: Array<EmbeddingInputItem>,
|
|
44
|
+
): Array<ResolvedEmbeddingItem> {
|
|
45
|
+
return input.map(resolveItem)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract plain text inputs for a text-only embedding model, throwing a
|
|
50
|
+
* uniform error if any item carries an image. The per-model modality typing
|
|
51
|
+
* rejects these at compile time; this guard covers untyped/dynamic callers.
|
|
52
|
+
*/
|
|
53
|
+
export function requireTextOnlyEmbeddingInput(
|
|
54
|
+
input: Array<EmbeddingInputItem>,
|
|
55
|
+
provider: string,
|
|
56
|
+
model: string,
|
|
57
|
+
): Array<string> {
|
|
58
|
+
return resolveEmbeddingInput(input).map((item, index) => {
|
|
59
|
+
if (item.images.length > 0) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`${provider} model "${model}" only supports text embedding inputs; ` +
|
|
62
|
+
`input item at index ${index} contains an image part`,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
return item.texts.join('\n')
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Count text-only and image-carrying items for observability events. Never
|
|
71
|
+
* exposes input content.
|
|
72
|
+
*/
|
|
73
|
+
export function countEmbeddingInputModalities(
|
|
74
|
+
input: Array<EmbeddingInputItem>,
|
|
75
|
+
): { textInputCount: number; imageInputCount: number } {
|
|
76
|
+
let textInputCount = 0
|
|
77
|
+
let imageInputCount = 0
|
|
78
|
+
for (const item of resolveEmbeddingInput(input)) {
|
|
79
|
+
if (item.images.length > 0) imageInputCount++
|
|
80
|
+
else textInputCount++
|
|
81
|
+
}
|
|
82
|
+
return { textInputCount, imageInputCount }
|
|
83
|
+
}
|