octaflow 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/index.d.ts +153 -3
- package/dist/ai/index.js +196 -10
- package/dist/ai/providers/index.d.ts +69 -0
- package/dist/ai/providers/index.js +106 -0
- package/dist/provider-registry-DhNXw_CC.js +224 -0
- package/dist/provider-registry-bXGOmyeh.d.ts +81 -0
- package/package.json +21 -1
package/dist/ai/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { E as RetryPolicy, g as TypedWorkflow, h as TypedStepContext, m as TypedStep, nt as Result, tt as FlowErrorShape } from "../index-BURAYZui.js";
|
|
2
2
|
import { n as Logger } from "../logger-BCus3AOa.js";
|
|
3
3
|
import { i as BuildStepContextArgs, o as WorkflowHooks } from "../hooks-BdxIcc92.js";
|
|
4
|
+
import { a as AiProviderMeta, c as DEFAULT_AI_PROVIDER, d as EMBEDDING_PROVIDER_IDS, f as EmbeddingProviderId, i as AiProviderId, l as DEFAULT_EMBEDDING_PROVIDER, m as embeddingDimensionsFor, n as AI_PROVIDER_IDS, o as AiProviderModelOption, p as EmbeddingProviderMeta, r as AiProcessingRegion, s as AiVendorResidency, t as AI_PROVIDERS, u as EMBEDDING_PROVIDERS } from "../provider-registry-bXGOmyeh.js";
|
|
4
5
|
import { z } from "zod";
|
|
5
|
-
import { EmbeddingModelV4, LanguageModelV4 } from "@ai-sdk/provider";
|
|
6
|
+
import { EmbeddingModelV4, LanguageModelV4, SharedV4ProviderMetadata } from "@ai-sdk/provider";
|
|
6
7
|
//#region src/ai/instrumented-model.d.ts
|
|
7
8
|
export interface AccumulatedUsage {
|
|
8
9
|
inputTokens: number;
|
|
@@ -71,7 +72,15 @@ export interface TokenUsage {
|
|
|
71
72
|
cacheReadTokens: number;
|
|
72
73
|
cacheWriteTokens: number;
|
|
73
74
|
}
|
|
74
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Default pricing, USD per million tokens — Anthropic as of 2026-09, Mistral and
|
|
77
|
+
* the OpenAI embeddings as of 2026-08. A best-effort estimate, not a billing
|
|
78
|
+
* record: a BYOK caller can name any model id, and an unknown id prices at the
|
|
79
|
+
* priciest row (safe overcounting — see {@link createCostEstimator}). Retired ids
|
|
80
|
+
* are kept rather than deleted: dropping one silently reprices every historical
|
|
81
|
+
* usage row at the fallback rate. Only the Mistral `-latest` aliases are listed;
|
|
82
|
+
* pinned ids (`mistral-medium-2508`) fall to the fallback.
|
|
83
|
+
*/
|
|
75
84
|
export declare const DEFAULT_MODEL_PRICING: Record<string, ModelPricing>;
|
|
76
85
|
export interface CostEstimatorOptions {
|
|
77
86
|
/** Pricing table keyed by model id. Defaults to `DEFAULT_MODEL_PRICING`. */
|
|
@@ -393,4 +402,145 @@ export interface AiUsageAggregationService {
|
|
|
393
402
|
}
|
|
394
403
|
/** Build a store-agnostic AI usage-aggregation service. */
|
|
395
404
|
export declare function createAiUsageAggregationService(deps: CreateAiUsageAggregationServiceDeps): AiUsageAggregationService;
|
|
396
|
-
//#endregion
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/ai/refusal.d.ts
|
|
407
|
+
/** The unified finish reason the AI SDK reports for a declined generation. */
|
|
408
|
+
export declare const REFUSAL_FINISH_REASON = "content-filter";
|
|
409
|
+
/**
|
|
410
|
+
* What the provider said about a refusal. Every field is optional except the
|
|
411
|
+
* model: providers other than Anthropic report the refusal without the
|
|
412
|
+
* `stop_details` breakdown, and Anthropic itself only sends `category` and
|
|
413
|
+
* `explanation` for some categories.
|
|
414
|
+
*/
|
|
415
|
+
export interface AiRefusalDetails {
|
|
416
|
+
/** The model that declined. */
|
|
417
|
+
modelId: string;
|
|
418
|
+
/** Provider's refusal category — Anthropic's `stop_details.category`. */
|
|
419
|
+
category?: string;
|
|
420
|
+
/** Provider's prose reason, when it sends one. */
|
|
421
|
+
explanation?: string;
|
|
422
|
+
/** The model the provider suggests retrying on (`stop_details.recommended_model`). */
|
|
423
|
+
recommendedModel?: string;
|
|
424
|
+
/** The provider's own stop reason (`refusal` on Anthropic), before unification. */
|
|
425
|
+
rawFinishReason?: string;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* A generation the model declined to produce.
|
|
429
|
+
*
|
|
430
|
+
* Distinct from a transport failure on purpose: it is deterministic, so
|
|
431
|
+
* retrying the same prompt on the same model produces the same refusal. Callers
|
|
432
|
+
* that classify retryability must treat it as terminal — `defineAiStep` does
|
|
433
|
+
* this for every AI workflow step.
|
|
434
|
+
*/
|
|
435
|
+
export declare class AiRefusalError extends Error {
|
|
436
|
+
readonly name = "AiRefusalError";
|
|
437
|
+
readonly details: AiRefusalDetails;
|
|
438
|
+
constructor(details: AiRefusalDetails);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* True when `error` is an {@link AiRefusalError}, or wraps one as its `cause`.
|
|
442
|
+
*
|
|
443
|
+
* Walks the chain because the SDK layer between the middleware and the caller
|
|
444
|
+
* may re-wrap: `generateObject`, for instance, reports parse failures as
|
|
445
|
+
* `NoObjectGeneratedError` with the original as `cause`. Depth-capped and
|
|
446
|
+
* cycle-safe.
|
|
447
|
+
*/
|
|
448
|
+
export declare function isAiRefusalError(error: unknown): error is AiRefusalError;
|
|
449
|
+
/**
|
|
450
|
+
* Pull the provider's refusal breakdown out of `providerMetadata`.
|
|
451
|
+
*
|
|
452
|
+
* Scans every provider namespace for a `stopDetails` record rather than reading
|
|
453
|
+
* `providerMetadata.anthropic` directly — the shape is Anthropic's today, and
|
|
454
|
+
* the surrounding module is deliberately provider-agnostic. A provider that
|
|
455
|
+
* reports a refusal with no breakdown simply yields no fields.
|
|
456
|
+
*/
|
|
457
|
+
export declare function readRefusalDetails(providerMetadata: SharedV4ProviderMetadata | undefined): Omit<AiRefusalDetails, 'modelId'>;
|
|
458
|
+
/**
|
|
459
|
+
* Wrap a model so a declined generation throws {@link AiRefusalError} instead
|
|
460
|
+
* of returning empty content.
|
|
461
|
+
*
|
|
462
|
+
* **`doGenerate` only, on purpose.** One-shot calls — every AI workflow step,
|
|
463
|
+
* every ad-hoc generation service, both classifiers — have no way to present a
|
|
464
|
+
* refusal to anyone: their contract is "return the value or fail", so a refusal
|
|
465
|
+
* that does not throw becomes an empty field or an unparseable object several
|
|
466
|
+
* frames away from its cause. The streaming path is the opposite case: the help
|
|
467
|
+
* chat is a conversation, where a decline is a legitimate turn the operator
|
|
468
|
+
* should *read*, and it already has a refusal surface (the `refusal` data part)
|
|
469
|
+
* for exactly that. `ChatAgentService` branches on the finish reason in
|
|
470
|
+
* `onFinish` instead, so the stream stays a stream.
|
|
471
|
+
*/
|
|
472
|
+
export declare function createRefusalAwareModel(model: LanguageModelV4): LanguageModelV4;
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/ai/capabilities.d.ts
|
|
475
|
+
/**
|
|
476
|
+
* Per-model API capability switches.
|
|
477
|
+
*
|
|
478
|
+
* Claude 4.6+ models (Opus 4.6/4.7/4.8/5, Sonnet 4.6/5, Fable/Mythos 5) use
|
|
479
|
+
* adaptive thinking; on 4.7+ the legacy `thinking: { type: 'enabled',
|
|
480
|
+
* budget_tokens }` shape and non-default sampling params (`temperature`,
|
|
481
|
+
* `top_p`, `top_k`) are rejected with a 400. Older models (Haiku 4.5,
|
|
482
|
+
* Sonnet 4.5, …) still require `enabled` + budgetTokens for extended
|
|
483
|
+
* thinking and accept `temperature`.
|
|
484
|
+
*
|
|
485
|
+
* A BYOK deployment lets a caller name any model id, so call sites must
|
|
486
|
+
* branch on the resolved id rather than assume one API shape — these
|
|
487
|
+
* functions are that branch, written once. `providerId` is a plain string:
|
|
488
|
+
* only `'anthropic'` has provider-specific shapes today.
|
|
489
|
+
*/
|
|
490
|
+
/** True for Claude 4.6+ / 5-family models: adaptive thinking, no budgetTokens, no sampling params. */
|
|
491
|
+
export declare function supportsAdaptiveThinking(modelId: string): boolean;
|
|
492
|
+
export interface AnthropicThinkingSettings {
|
|
493
|
+
temperature?: number;
|
|
494
|
+
headers?: Record<string, string>;
|
|
495
|
+
providerOptions: {
|
|
496
|
+
anthropic: {
|
|
497
|
+
thinking: {
|
|
498
|
+
type: 'adaptive';
|
|
499
|
+
display: 'summarized';
|
|
500
|
+
} | {
|
|
501
|
+
type: 'enabled';
|
|
502
|
+
budgetTokens: number;
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Spreadable `streamText`/`generateText` settings for a thinking-enabled
|
|
509
|
+
* agent loop, shaped for the target model's API generation.
|
|
510
|
+
*
|
|
511
|
+
* - Adaptive models: `thinking: { type: 'adaptive' }` with summarized display
|
|
512
|
+
* (the default `omitted` streams empty reasoning parts to the client).
|
|
513
|
+
* Interleaved thinking is implicit — no beta header, no temperature.
|
|
514
|
+
* - Legacy models: extended thinking with a token budget, the interleaved
|
|
515
|
+
* thinking beta header, and the caller's temperature.
|
|
516
|
+
*/
|
|
517
|
+
export declare function anthropicThinkingSettings(modelId: string, opts: {
|
|
518
|
+
temperature: number;
|
|
519
|
+
budgetTokens: number;
|
|
520
|
+
}): AnthropicThinkingSettings;
|
|
521
|
+
/**
|
|
522
|
+
* True when the provider+model pair supports Anthropic's tool search tool.
|
|
523
|
+
* Anthropic-only — like the thinking/beta-header shapes, `deferLoading`
|
|
524
|
+
* provider options and the search tool must never be emitted for another
|
|
525
|
+
* vendor. Unknown or pre-4.5 Anthropic model ids return false so callers
|
|
526
|
+
* fall back to shipping the full tool registry undeferred.
|
|
527
|
+
*/
|
|
528
|
+
export declare function supportsToolSearch(providerId: string, modelId: string): boolean;
|
|
529
|
+
export interface ThinkingSettings {
|
|
530
|
+
temperature?: number;
|
|
531
|
+
headers?: Record<string, string>;
|
|
532
|
+
providerOptions?: AnthropicThinkingSettings['providerOptions'];
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Provider-neutral wrapper around {@link anthropicThinkingSettings}. Other
|
|
536
|
+
* providers have no thinking/beta-header surface — they just get the caller's
|
|
537
|
+
* temperature. Never emit `providerOptions.anthropic` (or the beta header) for
|
|
538
|
+
* another vendor's model, even though it would be ignored on the wire — it
|
|
539
|
+
* pollutes request logs and breaks request-shape tests.
|
|
540
|
+
*/
|
|
541
|
+
export declare function thinkingSettingsFor(providerId: string, modelId: string, opts: {
|
|
542
|
+
temperature: number;
|
|
543
|
+
budgetTokens: number;
|
|
544
|
+
}): ThinkingSettings;
|
|
545
|
+
//#endregion
|
|
546
|
+
export { AI_PROVIDERS, AI_PROVIDER_IDS, AiProcessingRegion, AiProviderId, AiProviderMeta, AiProviderModelOption, AiVendorResidency, DEFAULT_AI_PROVIDER, DEFAULT_EMBEDDING_PROVIDER, EMBEDDING_PROVIDERS, EMBEDDING_PROVIDER_IDS, EmbeddingProviderId, EmbeddingProviderMeta, embeddingDimensionsFor };
|
package/dist/ai/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { S as ok, b as noopLogger, f as defineStep, l as buildWorkflow, x as err } from "../core-DNWPwFaE.js";
|
|
2
|
+
import { a as EMBEDDING_PROVIDERS, c as AiRefusalError, d as isAiRefusalError, f as readRefusalDetails, i as DEFAULT_EMBEDDING_PROVIDER, l as REFUSAL_FINISH_REASON, n as AI_PROVIDER_IDS, o as EMBEDDING_PROVIDER_IDS, r as DEFAULT_AI_PROVIDER, s as embeddingDimensionsFor, t as AI_PROVIDERS, u as createRefusalAwareModel } from "../provider-registry-DhNXw_CC.js";
|
|
2
3
|
import { wrapEmbeddingModel, wrapLanguageModel } from "ai";
|
|
3
4
|
//#region src/ai/instrumented-model.ts
|
|
4
5
|
function createUsageAccumulator() {
|
|
@@ -121,7 +122,15 @@ function createInstrumentedEmbeddingModel(model, accumulator) {
|
|
|
121
122
|
}
|
|
122
123
|
//#endregion
|
|
123
124
|
//#region src/ai/cost.ts
|
|
124
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* Default pricing, USD per million tokens — Anthropic as of 2026-09, Mistral and
|
|
127
|
+
* the OpenAI embeddings as of 2026-08. A best-effort estimate, not a billing
|
|
128
|
+
* record: a BYOK caller can name any model id, and an unknown id prices at the
|
|
129
|
+
* priciest row (safe overcounting — see {@link createCostEstimator}). Retired ids
|
|
130
|
+
* are kept rather than deleted: dropping one silently reprices every historical
|
|
131
|
+
* usage row at the fallback rate. Only the Mistral `-latest` aliases are listed;
|
|
132
|
+
* pinned ids (`mistral-medium-2508`) fall to the fallback.
|
|
133
|
+
*/
|
|
125
134
|
const DEFAULT_MODEL_PRICING = {
|
|
126
135
|
"text-embedding-3-small": {
|
|
127
136
|
inputPerMillion: .02,
|
|
@@ -135,6 +144,48 @@ const DEFAULT_MODEL_PRICING = {
|
|
|
135
144
|
cacheReadPerMillion: 0,
|
|
136
145
|
cacheWritePerMillion: 0
|
|
137
146
|
},
|
|
147
|
+
"claude-fable-5": {
|
|
148
|
+
inputPerMillion: 10,
|
|
149
|
+
outputPerMillion: 50,
|
|
150
|
+
cacheReadPerMillion: 1,
|
|
151
|
+
cacheWritePerMillion: 12.5
|
|
152
|
+
},
|
|
153
|
+
"claude-mythos-5": {
|
|
154
|
+
inputPerMillion: 10,
|
|
155
|
+
outputPerMillion: 50,
|
|
156
|
+
cacheReadPerMillion: 1,
|
|
157
|
+
cacheWritePerMillion: 12.5
|
|
158
|
+
},
|
|
159
|
+
"claude-fable-5-1": {
|
|
160
|
+
inputPerMillion: 10,
|
|
161
|
+
outputPerMillion: 50,
|
|
162
|
+
cacheReadPerMillion: .25,
|
|
163
|
+
cacheWritePerMillion: 12.5
|
|
164
|
+
},
|
|
165
|
+
"claude-mythos-5-1": {
|
|
166
|
+
inputPerMillion: 10,
|
|
167
|
+
outputPerMillion: 50,
|
|
168
|
+
cacheReadPerMillion: .25,
|
|
169
|
+
cacheWritePerMillion: 12.5
|
|
170
|
+
},
|
|
171
|
+
"claude-opus-5": {
|
|
172
|
+
inputPerMillion: 5,
|
|
173
|
+
outputPerMillion: 25,
|
|
174
|
+
cacheReadPerMillion: .5,
|
|
175
|
+
cacheWritePerMillion: 6.25
|
|
176
|
+
},
|
|
177
|
+
"claude-sonnet-5": {
|
|
178
|
+
inputPerMillion: 2,
|
|
179
|
+
outputPerMillion: 10,
|
|
180
|
+
cacheReadPerMillion: .2,
|
|
181
|
+
cacheWritePerMillion: 2.5
|
|
182
|
+
},
|
|
183
|
+
"claude-opus-4-8": {
|
|
184
|
+
inputPerMillion: 5,
|
|
185
|
+
outputPerMillion: 25,
|
|
186
|
+
cacheReadPerMillion: .5,
|
|
187
|
+
cacheWritePerMillion: 6.25
|
|
188
|
+
},
|
|
138
189
|
"claude-opus-4-7": {
|
|
139
190
|
inputPerMillion: 5,
|
|
140
191
|
outputPerMillion: 25,
|
|
@@ -142,10 +193,16 @@ const DEFAULT_MODEL_PRICING = {
|
|
|
142
193
|
cacheWritePerMillion: 6.25
|
|
143
194
|
},
|
|
144
195
|
"claude-opus-4-6": {
|
|
145
|
-
inputPerMillion:
|
|
146
|
-
outputPerMillion:
|
|
147
|
-
cacheReadPerMillion:
|
|
148
|
-
cacheWritePerMillion:
|
|
196
|
+
inputPerMillion: 5,
|
|
197
|
+
outputPerMillion: 25,
|
|
198
|
+
cacheReadPerMillion: .5,
|
|
199
|
+
cacheWritePerMillion: 6.25
|
|
200
|
+
},
|
|
201
|
+
"claude-opus-4-5": {
|
|
202
|
+
inputPerMillion: 5,
|
|
203
|
+
outputPerMillion: 25,
|
|
204
|
+
cacheReadPerMillion: .5,
|
|
205
|
+
cacheWritePerMillion: 6.25
|
|
149
206
|
},
|
|
150
207
|
"claude-sonnet-4-6": {
|
|
151
208
|
inputPerMillion: 3,
|
|
@@ -153,17 +210,59 @@ const DEFAULT_MODEL_PRICING = {
|
|
|
153
210
|
cacheReadPerMillion: .3,
|
|
154
211
|
cacheWritePerMillion: 3.75
|
|
155
212
|
},
|
|
213
|
+
"claude-sonnet-4-5": {
|
|
214
|
+
inputPerMillion: 3,
|
|
215
|
+
outputPerMillion: 15,
|
|
216
|
+
cacheReadPerMillion: .3,
|
|
217
|
+
cacheWritePerMillion: 3.75
|
|
218
|
+
},
|
|
156
219
|
"claude-sonnet-4-5-20250929": {
|
|
157
220
|
inputPerMillion: 3,
|
|
158
221
|
outputPerMillion: 15,
|
|
159
222
|
cacheReadPerMillion: .3,
|
|
160
223
|
cacheWritePerMillion: 3.75
|
|
161
224
|
},
|
|
225
|
+
"claude-haiku-4-5": {
|
|
226
|
+
inputPerMillion: 1,
|
|
227
|
+
outputPerMillion: 5,
|
|
228
|
+
cacheReadPerMillion: .1,
|
|
229
|
+
cacheWritePerMillion: 1.25
|
|
230
|
+
},
|
|
162
231
|
"claude-haiku-4-5-20251001": {
|
|
163
|
-
inputPerMillion:
|
|
164
|
-
outputPerMillion:
|
|
165
|
-
cacheReadPerMillion: .
|
|
166
|
-
cacheWritePerMillion: 1
|
|
232
|
+
inputPerMillion: 1,
|
|
233
|
+
outputPerMillion: 5,
|
|
234
|
+
cacheReadPerMillion: .1,
|
|
235
|
+
cacheWritePerMillion: 1.25
|
|
236
|
+
},
|
|
237
|
+
"mistral-medium-latest": {
|
|
238
|
+
inputPerMillion: 1.5,
|
|
239
|
+
outputPerMillion: 7.5,
|
|
240
|
+
cacheReadPerMillion: .15,
|
|
241
|
+
cacheWritePerMillion: 1.5
|
|
242
|
+
},
|
|
243
|
+
"mistral-large-latest": {
|
|
244
|
+
inputPerMillion: .5,
|
|
245
|
+
outputPerMillion: 1.5,
|
|
246
|
+
cacheReadPerMillion: .05,
|
|
247
|
+
cacheWritePerMillion: .5
|
|
248
|
+
},
|
|
249
|
+
"mistral-small-latest": {
|
|
250
|
+
inputPerMillion: .15,
|
|
251
|
+
outputPerMillion: .6,
|
|
252
|
+
cacheReadPerMillion: .015,
|
|
253
|
+
cacheWritePerMillion: .15
|
|
254
|
+
},
|
|
255
|
+
"magistral-medium-latest": {
|
|
256
|
+
inputPerMillion: 2,
|
|
257
|
+
outputPerMillion: 5,
|
|
258
|
+
cacheReadPerMillion: .2,
|
|
259
|
+
cacheWritePerMillion: 2
|
|
260
|
+
},
|
|
261
|
+
"mistral-embed": {
|
|
262
|
+
inputPerMillion: .1,
|
|
263
|
+
outputPerMillion: 0,
|
|
264
|
+
cacheReadPerMillion: 0,
|
|
265
|
+
cacheWritePerMillion: 0
|
|
167
266
|
}
|
|
168
267
|
};
|
|
169
268
|
function priciest(pricing) {
|
|
@@ -463,4 +562,91 @@ function createAiUsageAggregationService(deps) {
|
|
|
463
562
|
};
|
|
464
563
|
}
|
|
465
564
|
//#endregion
|
|
466
|
-
|
|
565
|
+
//#region src/ai/capabilities.ts
|
|
566
|
+
/**
|
|
567
|
+
* Per-model API capability switches.
|
|
568
|
+
*
|
|
569
|
+
* Claude 4.6+ models (Opus 4.6/4.7/4.8/5, Sonnet 4.6/5, Fable/Mythos 5) use
|
|
570
|
+
* adaptive thinking; on 4.7+ the legacy `thinking: { type: 'enabled',
|
|
571
|
+
* budget_tokens }` shape and non-default sampling params (`temperature`,
|
|
572
|
+
* `top_p`, `top_k`) are rejected with a 400. Older models (Haiku 4.5,
|
|
573
|
+
* Sonnet 4.5, …) still require `enabled` + budgetTokens for extended
|
|
574
|
+
* thinking and accept `temperature`.
|
|
575
|
+
*
|
|
576
|
+
* A BYOK deployment lets a caller name any model id, so call sites must
|
|
577
|
+
* branch on the resolved id rather than assume one API shape — these
|
|
578
|
+
* functions are that branch, written once. `providerId` is a plain string:
|
|
579
|
+
* only `'anthropic'` has provider-specific shapes today.
|
|
580
|
+
*/
|
|
581
|
+
const ADAPTIVE_THINKING_MODEL_PREFIXES = [
|
|
582
|
+
"claude-fable-5",
|
|
583
|
+
"claude-mythos-5",
|
|
584
|
+
"claude-opus-5",
|
|
585
|
+
"claude-opus-4-8",
|
|
586
|
+
"claude-opus-4-7",
|
|
587
|
+
"claude-opus-4-6",
|
|
588
|
+
"claude-sonnet-5",
|
|
589
|
+
"claude-sonnet-4-6"
|
|
590
|
+
];
|
|
591
|
+
/** True for Claude 4.6+ / 5-family models: adaptive thinking, no budgetTokens, no sampling params. */
|
|
592
|
+
function supportsAdaptiveThinking(modelId) {
|
|
593
|
+
return ADAPTIVE_THINKING_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Spreadable `streamText`/`generateText` settings for a thinking-enabled
|
|
597
|
+
* agent loop, shaped for the target model's API generation.
|
|
598
|
+
*
|
|
599
|
+
* - Adaptive models: `thinking: { type: 'adaptive' }` with summarized display
|
|
600
|
+
* (the default `omitted` streams empty reasoning parts to the client).
|
|
601
|
+
* Interleaved thinking is implicit — no beta header, no temperature.
|
|
602
|
+
* - Legacy models: extended thinking with a token budget, the interleaved
|
|
603
|
+
* thinking beta header, and the caller's temperature.
|
|
604
|
+
*/
|
|
605
|
+
function anthropicThinkingSettings(modelId, opts) {
|
|
606
|
+
if (supportsAdaptiveThinking(modelId)) return { providerOptions: { anthropic: { thinking: {
|
|
607
|
+
type: "adaptive",
|
|
608
|
+
display: "summarized"
|
|
609
|
+
} } } };
|
|
610
|
+
return {
|
|
611
|
+
temperature: opts.temperature,
|
|
612
|
+
headers: { "anthropic-beta": "interleaved-thinking-2025-05-14" },
|
|
613
|
+
providerOptions: { anthropic: { thinking: {
|
|
614
|
+
type: "enabled",
|
|
615
|
+
budgetTokens: opts.budgetTokens
|
|
616
|
+
} } }
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Models supporting Anthropic's server-side tool search
|
|
621
|
+
* (`tool_search_tool_regex_20251119`) with deferred tool loading — every
|
|
622
|
+
* adaptive-thinking model plus the 4.5 generation.
|
|
623
|
+
*/
|
|
624
|
+
const TOOL_SEARCH_MODEL_PREFIXES = [
|
|
625
|
+
...ADAPTIVE_THINKING_MODEL_PREFIXES,
|
|
626
|
+
"claude-opus-4-5",
|
|
627
|
+
"claude-sonnet-4-5",
|
|
628
|
+
"claude-haiku-4-5"
|
|
629
|
+
];
|
|
630
|
+
/**
|
|
631
|
+
* True when the provider+model pair supports Anthropic's tool search tool.
|
|
632
|
+
* Anthropic-only — like the thinking/beta-header shapes, `deferLoading`
|
|
633
|
+
* provider options and the search tool must never be emitted for another
|
|
634
|
+
* vendor. Unknown or pre-4.5 Anthropic model ids return false so callers
|
|
635
|
+
* fall back to shipping the full tool registry undeferred.
|
|
636
|
+
*/
|
|
637
|
+
function supportsToolSearch(providerId, modelId) {
|
|
638
|
+
return providerId === "anthropic" && TOOL_SEARCH_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Provider-neutral wrapper around {@link anthropicThinkingSettings}. Other
|
|
642
|
+
* providers have no thinking/beta-header surface — they just get the caller's
|
|
643
|
+
* temperature. Never emit `providerOptions.anthropic` (or the beta header) for
|
|
644
|
+
* another vendor's model, even though it would be ignored on the wire — it
|
|
645
|
+
* pollutes request logs and breaks request-shape tests.
|
|
646
|
+
*/
|
|
647
|
+
function thinkingSettingsFor(providerId, modelId, opts) {
|
|
648
|
+
if (providerId === "anthropic") return anthropicThinkingSettings(modelId, opts);
|
|
649
|
+
return { temperature: opts.temperature };
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
652
|
+
export { AI_PROVIDERS, AI_PROVIDER_IDS, AiRefusalError, DEFAULT_AI_PROVIDER, DEFAULT_AI_QUOTA, DEFAULT_EMBEDDING_PROVIDER, DEFAULT_MODEL_PRICING, EMBEDDING_PROVIDERS, EMBEDDING_PROVIDER_IDS, REFUSAL_FINISH_REASON, anthropicThinkingSettings, buildAiWorkflow, createAiQuotaService, createAiUsageAggregationService, createAiWorkflowHooks, createCostEstimator, createEmbeddingUsageAccumulator, createInstrumentedEmbeddingModel, createInstrumentedModel, createRefusalAwareModel, createUsageAccumulator, defineAiStep, embeddingDimensionsFor, estimateCostMicros, isAiRefusalError, readRefusalDetails, supportsAdaptiveThinking, supportsToolSearch, thinkingSettingsFor };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { f as EmbeddingProviderId, i as AiProviderId } from "../../provider-registry-bXGOmyeh.js";
|
|
2
|
+
import { EmbeddingModelV4, LanguageModelV4 } from "@ai-sdk/provider";
|
|
3
|
+
//#region src/ai/providers/index.d.ts
|
|
4
|
+
export type LanguageModelFactory = (modelId: string) => LanguageModelV4;
|
|
5
|
+
export type EmbeddingModelFactory = (modelId: string) => EmbeddingModelV4;
|
|
6
|
+
/** A language-model factory for a provider, loading its SDK on first use. */
|
|
7
|
+
export declare function createLanguageModelFactory(providerId: AiProviderId, apiKey: string, baseURL?: string): Promise<LanguageModelFactory>;
|
|
8
|
+
/** An embedding-model factory for a provider, loading its SDK on first use. */
|
|
9
|
+
export declare function createEmbeddingModelFactory(providerId: EmbeddingProviderId, apiKey: string, baseURL?: string): Promise<EmbeddingModelFactory>;
|
|
10
|
+
export interface LanguageModelProvider {
|
|
11
|
+
/** Which vendor the key belongs to — gate provider-specific request shapes on this (see `thinkingSettingsFor`). */
|
|
12
|
+
providerId: AiProviderId;
|
|
13
|
+
/** Cheap model for classifier-style passes, from the registry. */
|
|
14
|
+
classifierModelId: string;
|
|
15
|
+
/**
|
|
16
|
+
* A model wrapped for refusal detection: a declined generation throws
|
|
17
|
+
* `AiRefusalError` from `doGenerate` instead of returning empty content.
|
|
18
|
+
* Make every one-shot call site go through this and the behaviour is universal.
|
|
19
|
+
*/
|
|
20
|
+
languageModel(modelId?: string): LanguageModelV4;
|
|
21
|
+
}
|
|
22
|
+
export interface LanguageModelProviderConfig {
|
|
23
|
+
provider?: AiProviderId;
|
|
24
|
+
apiKey: string;
|
|
25
|
+
/** Overrides the registry's default model. */
|
|
26
|
+
defaultModel?: string;
|
|
27
|
+
/** Region-pinned endpoint, when the vendor serves one. Undefined = SDK default. */
|
|
28
|
+
baseUrl?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The seam a BYOK deployment resolves per scope: key + provider → models.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* const models = await createLanguageModelProvider({ provider: 'mistral', apiKey });
|
|
35
|
+
* const result = await generateText({ model: models.languageModel(), prompt });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function createLanguageModelProvider(config: LanguageModelProviderConfig): Promise<LanguageModelProvider>;
|
|
39
|
+
export interface EmbeddingModelProvider {
|
|
40
|
+
providerId: EmbeddingProviderId;
|
|
41
|
+
/** The resolved model id — what `embeddingDimensionsFor` should be asked about. */
|
|
42
|
+
modelId: string;
|
|
43
|
+
embeddingModel(): EmbeddingModelV4;
|
|
44
|
+
}
|
|
45
|
+
export interface EmbeddingModelProviderConfig {
|
|
46
|
+
provider?: EmbeddingProviderId;
|
|
47
|
+
apiKey: string;
|
|
48
|
+
/** Overrides the registry's default model. */
|
|
49
|
+
model?: string;
|
|
50
|
+
/** Region-pinned endpoint, when the vendor serves one. Undefined = SDK default. */
|
|
51
|
+
baseUrl?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function createEmbeddingModelProvider(config: EmbeddingModelProviderConfig): Promise<EmbeddingModelProvider>;
|
|
54
|
+
export interface LazyEmbeddingModelMeta {
|
|
55
|
+
/** Logged as the model's `provider`. */
|
|
56
|
+
provider: string;
|
|
57
|
+
modelId: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* An `EmbeddingModelV4` whose provider is resolved on first use — for
|
|
61
|
+
* synchronous wiring (a DI registration, a registry built at boot) that
|
|
62
|
+
* cannot await {@link createEmbeddingModelProvider}. The V4 contract allows
|
|
63
|
+
* `maxEmbeddingsPerCall` / `supportsParallelCalls` to be promises, so the
|
|
64
|
+
* laziness is expressible without a wrapper the caller has to unwrap: `embed()`
|
|
65
|
+
* awaits them itself. `load` runs at most once; a failure is retried on the
|
|
66
|
+
* next call rather than cached.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createLazyEmbeddingModel(meta: LazyEmbeddingModelMeta, load: () => Promise<EmbeddingModelV4>): EmbeddingModelV4;
|
|
69
|
+
//#endregion
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { a as EMBEDDING_PROVIDERS, t as AI_PROVIDERS, u as createRefusalAwareModel } from "../../provider-registry-DhNXw_CC.js";
|
|
2
|
+
//#region src/ai/providers/index.ts
|
|
3
|
+
const LLM_LOADERS = {
|
|
4
|
+
anthropic: async (apiKey, baseURL) => {
|
|
5
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
6
|
+
return createAnthropic({
|
|
7
|
+
apiKey,
|
|
8
|
+
baseURL
|
|
9
|
+
});
|
|
10
|
+
},
|
|
11
|
+
mistral: async (apiKey, baseURL) => {
|
|
12
|
+
const { createMistral } = await import("@ai-sdk/mistral");
|
|
13
|
+
return createMistral({
|
|
14
|
+
apiKey,
|
|
15
|
+
baseURL
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const EMBEDDING_LOADERS = {
|
|
20
|
+
openai: async (apiKey, baseURL) => {
|
|
21
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
22
|
+
const provider = createOpenAI({
|
|
23
|
+
apiKey,
|
|
24
|
+
baseURL
|
|
25
|
+
});
|
|
26
|
+
return (modelId) => provider.embedding(modelId);
|
|
27
|
+
},
|
|
28
|
+
mistral: async (apiKey, baseURL) => {
|
|
29
|
+
const { createMistral } = await import("@ai-sdk/mistral");
|
|
30
|
+
const provider = createMistral({
|
|
31
|
+
apiKey,
|
|
32
|
+
baseURL
|
|
33
|
+
});
|
|
34
|
+
return (modelId) => provider.embedding(modelId);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/** A language-model factory for a provider, loading its SDK on first use. */
|
|
38
|
+
function createLanguageModelFactory(providerId, apiKey, baseURL) {
|
|
39
|
+
return LLM_LOADERS[providerId](apiKey, baseURL);
|
|
40
|
+
}
|
|
41
|
+
/** An embedding-model factory for a provider, loading its SDK on first use. */
|
|
42
|
+
function createEmbeddingModelFactory(providerId, apiKey, baseURL) {
|
|
43
|
+
return EMBEDDING_LOADERS[providerId](apiKey, baseURL);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The seam a BYOK deployment resolves per scope: key + provider → models.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* const models = await createLanguageModelProvider({ provider: 'mistral', apiKey });
|
|
50
|
+
* const result = await generateText({ model: models.languageModel(), prompt });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
async function createLanguageModelProvider(config) {
|
|
54
|
+
const providerId = config.provider ?? "anthropic";
|
|
55
|
+
const meta = AI_PROVIDERS[providerId];
|
|
56
|
+
const factory = await createLanguageModelFactory(providerId, config.apiKey, config.baseUrl);
|
|
57
|
+
const defaultModel = config.defaultModel ?? meta.defaultModel;
|
|
58
|
+
return {
|
|
59
|
+
providerId,
|
|
60
|
+
classifierModelId: meta.classifierModel,
|
|
61
|
+
languageModel: (modelId) => createRefusalAwareModel(factory(modelId ?? defaultModel))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function createEmbeddingModelProvider(config) {
|
|
65
|
+
const providerId = config.provider ?? "openai";
|
|
66
|
+
const factory = await createEmbeddingModelFactory(providerId, config.apiKey, config.baseUrl);
|
|
67
|
+
const modelId = config.model ?? EMBEDDING_PROVIDERS[providerId].defaultModel;
|
|
68
|
+
return {
|
|
69
|
+
providerId,
|
|
70
|
+
modelId,
|
|
71
|
+
embeddingModel: () => factory(modelId)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* An `EmbeddingModelV4` whose provider is resolved on first use — for
|
|
76
|
+
* synchronous wiring (a DI registration, a registry built at boot) that
|
|
77
|
+
* cannot await {@link createEmbeddingModelProvider}. The V4 contract allows
|
|
78
|
+
* `maxEmbeddingsPerCall` / `supportsParallelCalls` to be promises, so the
|
|
79
|
+
* laziness is expressible without a wrapper the caller has to unwrap: `embed()`
|
|
80
|
+
* awaits them itself. `load` runs at most once; a failure is retried on the
|
|
81
|
+
* next call rather than cached.
|
|
82
|
+
*/
|
|
83
|
+
function createLazyEmbeddingModel(meta, load) {
|
|
84
|
+
let pending;
|
|
85
|
+
const model = () => {
|
|
86
|
+
pending ??= load().catch((error) => {
|
|
87
|
+
pending = void 0;
|
|
88
|
+
throw error;
|
|
89
|
+
});
|
|
90
|
+
return pending;
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
specificationVersion: "v4",
|
|
94
|
+
provider: meta.provider,
|
|
95
|
+
modelId: meta.modelId,
|
|
96
|
+
get maxEmbeddingsPerCall() {
|
|
97
|
+
return model().then((m) => m.maxEmbeddingsPerCall);
|
|
98
|
+
},
|
|
99
|
+
get supportsParallelCalls() {
|
|
100
|
+
return model().then((m) => m.supportsParallelCalls);
|
|
101
|
+
},
|
|
102
|
+
doEmbed: (options) => model().then((m) => m.doEmbed(options))
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { createEmbeddingModelFactory, createEmbeddingModelProvider, createLanguageModelFactory, createLanguageModelProvider, createLazyEmbeddingModel };
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { wrapLanguageModel } from "ai";
|
|
2
|
+
//#region src/ai/refusal.ts
|
|
3
|
+
/** The unified finish reason the AI SDK reports for a declined generation. */
|
|
4
|
+
const REFUSAL_FINISH_REASON = "content-filter";
|
|
5
|
+
/**
|
|
6
|
+
* A generation the model declined to produce.
|
|
7
|
+
*
|
|
8
|
+
* Distinct from a transport failure on purpose: it is deterministic, so
|
|
9
|
+
* retrying the same prompt on the same model produces the same refusal. Callers
|
|
10
|
+
* that classify retryability must treat it as terminal — `defineAiStep` does
|
|
11
|
+
* this for every AI workflow step.
|
|
12
|
+
*/
|
|
13
|
+
var AiRefusalError = class extends Error {
|
|
14
|
+
name = "AiRefusalError";
|
|
15
|
+
details;
|
|
16
|
+
constructor(details) {
|
|
17
|
+
super(refusalMessage(details));
|
|
18
|
+
this.details = details;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function refusalMessage(details) {
|
|
22
|
+
const parts = [`${details.modelId} declined to answer`];
|
|
23
|
+
if (details.category) parts.push(`(${details.category})`);
|
|
24
|
+
if (details.explanation) parts.push(`— ${details.explanation}`);
|
|
25
|
+
return parts.join(" ");
|
|
26
|
+
}
|
|
27
|
+
/** How deep to follow `cause` before giving up. Guards against pathological chains. */
|
|
28
|
+
const MAX_CAUSE_DEPTH = 8;
|
|
29
|
+
/**
|
|
30
|
+
* True when `error` is an {@link AiRefusalError}, or wraps one as its `cause`.
|
|
31
|
+
*
|
|
32
|
+
* Walks the chain because the SDK layer between the middleware and the caller
|
|
33
|
+
* may re-wrap: `generateObject`, for instance, reports parse failures as
|
|
34
|
+
* `NoObjectGeneratedError` with the original as `cause`. Depth-capped and
|
|
35
|
+
* cycle-safe.
|
|
36
|
+
*/
|
|
37
|
+
function isAiRefusalError(error) {
|
|
38
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39
|
+
let current = error;
|
|
40
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
|
|
41
|
+
if (typeof current !== "object" || current === null || seen.has(current)) return false;
|
|
42
|
+
if (current instanceof AiRefusalError) return true;
|
|
43
|
+
seen.add(current);
|
|
44
|
+
current = current.cause;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Pull the provider's refusal breakdown out of `providerMetadata`.
|
|
50
|
+
*
|
|
51
|
+
* Scans every provider namespace for a `stopDetails` record rather than reading
|
|
52
|
+
* `providerMetadata.anthropic` directly — the shape is Anthropic's today, and
|
|
53
|
+
* the surrounding module is deliberately provider-agnostic. A provider that
|
|
54
|
+
* reports a refusal with no breakdown simply yields no fields.
|
|
55
|
+
*/
|
|
56
|
+
function readRefusalDetails(providerMetadata) {
|
|
57
|
+
for (const namespace of Object.values(providerMetadata ?? {})) {
|
|
58
|
+
const stopDetails = namespace?.stopDetails;
|
|
59
|
+
if (typeof stopDetails !== "object" || stopDetails === null || Array.isArray(stopDetails)) continue;
|
|
60
|
+
const read = (key) => {
|
|
61
|
+
const value = stopDetails[key];
|
|
62
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
...read("category") ? { category: read("category") } : {},
|
|
66
|
+
...read("explanation") ? { explanation: read("explanation") } : {},
|
|
67
|
+
...read("recommendedModel") ? { recommendedModel: read("recommendedModel") } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Wrap a model so a declined generation throws {@link AiRefusalError} instead
|
|
74
|
+
* of returning empty content.
|
|
75
|
+
*
|
|
76
|
+
* **`doGenerate` only, on purpose.** One-shot calls — every AI workflow step,
|
|
77
|
+
* every ad-hoc generation service, both classifiers — have no way to present a
|
|
78
|
+
* refusal to anyone: their contract is "return the value or fail", so a refusal
|
|
79
|
+
* that does not throw becomes an empty field or an unparseable object several
|
|
80
|
+
* frames away from its cause. The streaming path is the opposite case: the help
|
|
81
|
+
* chat is a conversation, where a decline is a legitimate turn the operator
|
|
82
|
+
* should *read*, and it already has a refusal surface (the `refusal` data part)
|
|
83
|
+
* for exactly that. `ChatAgentService` branches on the finish reason in
|
|
84
|
+
* `onFinish` instead, so the stream stays a stream.
|
|
85
|
+
*/
|
|
86
|
+
function createRefusalAwareModel(model) {
|
|
87
|
+
return wrapLanguageModel({
|
|
88
|
+
model,
|
|
89
|
+
middleware: {
|
|
90
|
+
specificationVersion: "v4",
|
|
91
|
+
wrapGenerate: async ({ doGenerate }) => {
|
|
92
|
+
const result = await doGenerate();
|
|
93
|
+
if (result.finishReason.unified === "content-filter") throw new AiRefusalError({
|
|
94
|
+
modelId: model.modelId,
|
|
95
|
+
...readRefusalDetails(result.providerMetadata),
|
|
96
|
+
...result.finishReason.raw ? { rawFinishReason: result.finishReason.raw } : {}
|
|
97
|
+
});
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/ai/provider-registry.ts
|
|
105
|
+
/**
|
|
106
|
+
* The provider registry — pure metadata about the vendors a BYOK deployment
|
|
107
|
+
* can point at: ids, default and cheap models, where a key is created, and
|
|
108
|
+
* **where the vendor processes what you send it**. Dependency-free, so a
|
|
109
|
+
* browser bundle (a provider/model picker, a residency badge) can import it;
|
|
110
|
+
* the SDK factories that need `@ai-sdk/*` live in `octaflow/ai/providers`.
|
|
111
|
+
*
|
|
112
|
+
* Adding a provider: an id here with its metadata, a factory in
|
|
113
|
+
* `providers/`, and pricing rows in `cost.ts`.
|
|
114
|
+
*/
|
|
115
|
+
const AI_PROVIDER_IDS = ["anthropic", "mistral"];
|
|
116
|
+
const EMBEDDING_PROVIDER_IDS = ["openai", "mistral"];
|
|
117
|
+
/** What an unset provider resolves to. */
|
|
118
|
+
const DEFAULT_AI_PROVIDER = "anthropic";
|
|
119
|
+
const DEFAULT_EMBEDDING_PROVIDER = "openai";
|
|
120
|
+
const AI_PROVIDERS = {
|
|
121
|
+
anthropic: {
|
|
122
|
+
id: "anthropic",
|
|
123
|
+
label: "Anthropic",
|
|
124
|
+
residency: {
|
|
125
|
+
region: "third-country",
|
|
126
|
+
legalName: "Anthropic PBC",
|
|
127
|
+
country: "US"
|
|
128
|
+
},
|
|
129
|
+
defaultModel: "claude-sonnet-5",
|
|
130
|
+
classifierModel: "claude-haiku-4-5",
|
|
131
|
+
testModel: "claude-haiku-4-5",
|
|
132
|
+
keyPrefix: "sk-ant-",
|
|
133
|
+
consoleKeyUrl: "https://console.anthropic.com/settings/keys",
|
|
134
|
+
modelOptions: [
|
|
135
|
+
{
|
|
136
|
+
label: "Claude Opus 5",
|
|
137
|
+
value: "claude-opus-5"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: "Claude Opus 4.8",
|
|
141
|
+
value: "claude-opus-4-8"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: "Claude Sonnet 5",
|
|
145
|
+
value: "claude-sonnet-5"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
label: "Claude Haiku 4.5",
|
|
149
|
+
value: "claude-haiku-4-5"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
mistral: {
|
|
154
|
+
id: "mistral",
|
|
155
|
+
label: "Mistral",
|
|
156
|
+
residency: {
|
|
157
|
+
region: "eu",
|
|
158
|
+
legalName: "Mistral AI SAS",
|
|
159
|
+
country: "FR"
|
|
160
|
+
},
|
|
161
|
+
defaultModel: "mistral-medium-latest",
|
|
162
|
+
classifierModel: "mistral-small-latest",
|
|
163
|
+
testModel: "mistral-small-latest",
|
|
164
|
+
keyPrefix: null,
|
|
165
|
+
consoleKeyUrl: "https://console.mistral.ai/api-keys",
|
|
166
|
+
modelOptions: [
|
|
167
|
+
{
|
|
168
|
+
label: "Mistral Medium 3.5",
|
|
169
|
+
value: "mistral-medium-latest"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
label: "Mistral Large 3",
|
|
173
|
+
value: "mistral-large-latest"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: "Mistral Small 4",
|
|
177
|
+
value: "mistral-small-latest"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const EMBEDDING_PROVIDERS = {
|
|
183
|
+
openai: {
|
|
184
|
+
id: "openai",
|
|
185
|
+
label: "OpenAI",
|
|
186
|
+
residency: {
|
|
187
|
+
region: "third-country",
|
|
188
|
+
legalName: "OpenAI, L.L.C.",
|
|
189
|
+
country: "US"
|
|
190
|
+
},
|
|
191
|
+
defaultModel: "text-embedding-3-small",
|
|
192
|
+
consoleKeyUrl: "https://platform.openai.com/api-keys",
|
|
193
|
+
dimensionsByModel: {
|
|
194
|
+
"text-embedding-3-small": 1536,
|
|
195
|
+
"text-embedding-3-large": 3072
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
mistral: {
|
|
199
|
+
id: "mistral",
|
|
200
|
+
label: "Mistral",
|
|
201
|
+
residency: {
|
|
202
|
+
region: "eu",
|
|
203
|
+
legalName: "Mistral AI SAS",
|
|
204
|
+
country: "FR"
|
|
205
|
+
},
|
|
206
|
+
defaultModel: "mistral-embed",
|
|
207
|
+
consoleKeyUrl: "https://console.mistral.ai/api-keys",
|
|
208
|
+
dimensionsByModel: { "mistral-embed": 1024 }
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Vector dimensions for an embedding model id. Unknown ids fall back to
|
|
213
|
+
* `fallback` (default 1536, the common OpenAI-compatible dimension) so
|
|
214
|
+
* free-form model ids keep a working column.
|
|
215
|
+
*/
|
|
216
|
+
function embeddingDimensionsFor(modelId, fallback = 1536) {
|
|
217
|
+
for (const meta of Object.values(EMBEDDING_PROVIDERS)) {
|
|
218
|
+
const dims = meta.dimensionsByModel[modelId];
|
|
219
|
+
if (dims !== void 0) return dims;
|
|
220
|
+
}
|
|
221
|
+
return fallback;
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { EMBEDDING_PROVIDERS as a, AiRefusalError as c, isAiRefusalError as d, readRefusalDetails as f, DEFAULT_EMBEDDING_PROVIDER as i, REFUSAL_FINISH_REASON as l, AI_PROVIDER_IDS as n, EMBEDDING_PROVIDER_IDS as o, DEFAULT_AI_PROVIDER as r, embeddingDimensionsFor as s, AI_PROVIDERS as t, createRefusalAwareModel as u };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/ai/provider-registry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The provider registry — pure metadata about the vendors a BYOK deployment
|
|
4
|
+
* can point at: ids, default and cheap models, where a key is created, and
|
|
5
|
+
* **where the vendor processes what you send it**. Dependency-free, so a
|
|
6
|
+
* browser bundle (a provider/model picker, a residency badge) can import it;
|
|
7
|
+
* the SDK factories that need `@ai-sdk/*` live in `octaflow/ai/providers`.
|
|
8
|
+
*
|
|
9
|
+
* Adding a provider: an id here with its metadata, a factory in
|
|
10
|
+
* `providers/`, and pricing rows in `cost.ts`.
|
|
11
|
+
*/
|
|
12
|
+
declare const AI_PROVIDER_IDS: readonly ['anthropic', 'mistral'];
|
|
13
|
+
type AiProviderId = (typeof AI_PROVIDER_IDS)[number];
|
|
14
|
+
declare const EMBEDDING_PROVIDER_IDS: readonly ['openai', 'mistral'];
|
|
15
|
+
type EmbeddingProviderId = (typeof EMBEDDING_PROVIDER_IDS)[number];
|
|
16
|
+
/** What an unset provider resolves to. */
|
|
17
|
+
declare const DEFAULT_AI_PROVIDER: AiProviderId;
|
|
18
|
+
declare const DEFAULT_EMBEDDING_PROVIDER: EmbeddingProviderId;
|
|
19
|
+
/**
|
|
20
|
+
* Where a provider processes the payloads you send it.
|
|
21
|
+
*
|
|
22
|
+
* `'eu'` means the vendor is established in the EU/EEA and serves the API from
|
|
23
|
+
* there, so a call carries no third-country transfer under Art. 44 ff. GDPR.
|
|
24
|
+
* `'third-country'` means it does — your data-processing agreement then has
|
|
25
|
+
* to carry a transfer mechanism for that vendor.
|
|
26
|
+
*
|
|
27
|
+
* This is metadata about the *vendor endpoint called*, not a promise about a
|
|
28
|
+
* customer's own BYOK key: a customer who brings their own key contracts with
|
|
29
|
+
* the vendor directly.
|
|
30
|
+
*/
|
|
31
|
+
type AiProcessingRegion = 'eu' | 'third-country';
|
|
32
|
+
interface AiVendorResidency {
|
|
33
|
+
region: AiProcessingRegion;
|
|
34
|
+
/** Legal entity, as it appears in a subprocessor annex. */
|
|
35
|
+
legalName: string;
|
|
36
|
+
/** Country of establishment, ISO 3166-1 alpha-2. */
|
|
37
|
+
country: string;
|
|
38
|
+
}
|
|
39
|
+
interface AiProviderModelOption {
|
|
40
|
+
label: string;
|
|
41
|
+
value: string;
|
|
42
|
+
}
|
|
43
|
+
interface AiProviderMeta {
|
|
44
|
+
id: AiProviderId;
|
|
45
|
+
label: string;
|
|
46
|
+
/** Where calls to this provider are processed — drives a residency badge and the subprocessor analysis. */
|
|
47
|
+
residency: AiVendorResidency;
|
|
48
|
+
/** Default chat/workflow model when nothing sets one. */
|
|
49
|
+
defaultModel: string;
|
|
50
|
+
/** Cheap model for classifier-style passes. */
|
|
51
|
+
classifierModel: string;
|
|
52
|
+
/** Cheap model for a "test connection" ping. */
|
|
53
|
+
testModel: string;
|
|
54
|
+
/** Well-known API key prefix for a soft format hint, or null when the provider has none. */
|
|
55
|
+
keyPrefix: string | null;
|
|
56
|
+
/** Where the operator creates an API key. */
|
|
57
|
+
consoleKeyUrl: string;
|
|
58
|
+
/** Curated model choices for a picker (free-form custom ids stay allowed). */
|
|
59
|
+
modelOptions: AiProviderModelOption[];
|
|
60
|
+
}
|
|
61
|
+
declare const AI_PROVIDERS: Record<AiProviderId, AiProviderMeta>;
|
|
62
|
+
interface EmbeddingProviderMeta {
|
|
63
|
+
id: EmbeddingProviderId;
|
|
64
|
+
label: string;
|
|
65
|
+
/** See {@link AiProviderMeta.residency}. */
|
|
66
|
+
residency: AiVendorResidency;
|
|
67
|
+
defaultModel: string;
|
|
68
|
+
/** Where the operator creates an API key. */
|
|
69
|
+
consoleKeyUrl: string;
|
|
70
|
+
/** Vector dimensions per known model id — must match the `vector(N)` column in your schema. */
|
|
71
|
+
dimensionsByModel: Record<string, number>;
|
|
72
|
+
}
|
|
73
|
+
declare const EMBEDDING_PROVIDERS: Record<EmbeddingProviderId, EmbeddingProviderMeta>;
|
|
74
|
+
/**
|
|
75
|
+
* Vector dimensions for an embedding model id. Unknown ids fall back to
|
|
76
|
+
* `fallback` (default 1536, the common OpenAI-compatible dimension) so
|
|
77
|
+
* free-form model ids keep a working column.
|
|
78
|
+
*/
|
|
79
|
+
declare function embeddingDimensionsFor(modelId: string, fallback?: number): number;
|
|
80
|
+
//#endregion
|
|
81
|
+
export { AiProviderMeta as a, DEFAULT_AI_PROVIDER as c, EMBEDDING_PROVIDER_IDS as d, EmbeddingProviderId as f, AiProviderId as i, DEFAULT_EMBEDDING_PROVIDER as l, embeddingDimensionsFor as m, AI_PROVIDER_IDS as n, AiProviderModelOption as o, EmbeddingProviderMeta as p, AiProcessingRegion as r, AiVendorResidency as s, AI_PROVIDERS as t, EMBEDDING_PROVIDERS as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octaflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Durable DAG workflow engine: Zod-typed steps over Postgres + pg-boss, with an optional AI add-on (token/cost/quota). Layered single package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"types": "./dist/dispatcher-pgboss/index.d.ts",
|
|
25
25
|
"import": "./dist/dispatcher-pgboss/index.js",
|
|
26
26
|
"default": "./dist/dispatcher-pgboss/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./ai/providers": {
|
|
29
|
+
"types": "./dist/ai/providers/index.d.ts",
|
|
30
|
+
"import": "./dist/ai/providers/index.js",
|
|
31
|
+
"default": "./dist/ai/providers/index.js"
|
|
27
32
|
}
|
|
28
33
|
},
|
|
29
34
|
"engines": {
|
|
@@ -58,6 +63,9 @@
|
|
|
58
63
|
"zod"
|
|
59
64
|
],
|
|
60
65
|
"peerDependencies": {
|
|
66
|
+
"@ai-sdk/anthropic": "^4.0.0",
|
|
67
|
+
"@ai-sdk/mistral": "^4.0.0",
|
|
68
|
+
"@ai-sdk/openai": "^4.0.0",
|
|
61
69
|
"@ai-sdk/provider": "^4.0.0",
|
|
62
70
|
"ai": "^7.0.0",
|
|
63
71
|
"pg": "^8.0.0",
|
|
@@ -66,6 +74,15 @@
|
|
|
66
74
|
"zod": "^4.0.0"
|
|
67
75
|
},
|
|
68
76
|
"peerDependenciesMeta": {
|
|
77
|
+
"@ai-sdk/anthropic": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"@ai-sdk/mistral": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"@ai-sdk/openai": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
69
86
|
"@ai-sdk/provider": {
|
|
70
87
|
"optional": true
|
|
71
88
|
},
|
|
@@ -80,6 +97,9 @@
|
|
|
80
97
|
}
|
|
81
98
|
},
|
|
82
99
|
"devDependencies": {
|
|
100
|
+
"@ai-sdk/anthropic": "^4.0.49",
|
|
101
|
+
"@ai-sdk/mistral": "^4.0.39",
|
|
102
|
+
"@ai-sdk/openai": "^4.0.58",
|
|
83
103
|
"@ai-sdk/provider": "^4.0.10",
|
|
84
104
|
"@testcontainers/postgresql": "^12.1.0",
|
|
85
105
|
"@types/node": "^26.4.1",
|