@broberg/ai-sdk 0.37.2 → 0.38.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/{chunk-CTPTMHNX.js → chunk-QUOZWQ7G.js} +32 -14
- package/dist/chunk-QUOZWQ7G.js.map +1 -0
- package/dist/index.d.ts +39 -2
- package/dist/index.js +77 -7
- package/dist/index.js.map +1 -1
- package/dist/pricing.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-CTPTMHNX.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -75,6 +75,24 @@ interface Message {
|
|
|
75
75
|
toolCalls?: ToolCallLike[];
|
|
76
76
|
/** Set on `tool` role messages — which call this result answers. */
|
|
77
77
|
toolCallId?: string;
|
|
78
|
+
/** F049 — Mistral's `prefix`: start the model's reply with this text instead of
|
|
79
|
+
* asking it to. Only valid on the LAST message, and only when that message is an
|
|
80
|
+
* `assistant` one; anything else throws rather than being sent, because Mistral
|
|
81
|
+
* documents no behaviour for it elsewhere.
|
|
82
|
+
*
|
|
83
|
+
* Mistral's own first-listed use case is Language Adherence, and it pairs with a
|
|
84
|
+
* system instruction rather than replacing one — they warn that a prefix alone
|
|
85
|
+
* gives "noisy and unpredictable answers". Measured on this repo's own data: an
|
|
86
|
+
* explicit language rule in the system prompt already took 5/15 wrong answers to
|
|
87
|
+
* 0/15, and every leak began in the FIRST words ("Tak for din henvendelse"), which
|
|
88
|
+
* is exactly what a prefix pins.
|
|
89
|
+
*
|
|
90
|
+
* **The SDK strips the prefix back off the response**, in `chat` and `chatStream`
|
|
91
|
+
* alike, so a caller never has to. Mistral's own example does
|
|
92
|
+
* `content[len(prefix):]` by hand; forgetting it puts "Here is the answer in
|
|
93
|
+
* Norwegian:" at the top of a real customer's email — a defect that reads as
|
|
94
|
+
* formatting rather than as a bug. */
|
|
95
|
+
prefix?: boolean;
|
|
78
96
|
}
|
|
79
97
|
/** SDK-level tool definition. Adapters convert this to each provider's format
|
|
80
98
|
* (F4.5). `parameters` is a JSON Schema object. */
|
|
@@ -635,6 +653,10 @@ declare const messageSchema: z.ZodObject<{
|
|
|
635
653
|
arguments?: Record<string, unknown> | undefined;
|
|
636
654
|
}>, "many">>;
|
|
637
655
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
656
|
+
/** F049 — see Message.prefix. Position/role validity is enforced in the adapter,
|
|
657
|
+
* not here: the rule is about a message's place in the ARRAY, which a per-message
|
|
658
|
+
* schema cannot see. */
|
|
659
|
+
prefix: z.ZodOptional<z.ZodBoolean>;
|
|
638
660
|
}, "strip", z.ZodTypeAny, {
|
|
639
661
|
content: string | ({
|
|
640
662
|
text: string;
|
|
@@ -651,6 +673,7 @@ declare const messageSchema: z.ZodObject<{
|
|
|
651
673
|
args?: Record<string, unknown> | undefined;
|
|
652
674
|
arguments?: Record<string, unknown> | undefined;
|
|
653
675
|
}[] | undefined;
|
|
676
|
+
prefix?: boolean | undefined;
|
|
654
677
|
toolCallId?: string | undefined;
|
|
655
678
|
}, {
|
|
656
679
|
content: string | ({
|
|
@@ -668,6 +691,7 @@ declare const messageSchema: z.ZodObject<{
|
|
|
668
691
|
args?: Record<string, unknown> | undefined;
|
|
669
692
|
arguments?: Record<string, unknown> | undefined;
|
|
670
693
|
}[] | undefined;
|
|
694
|
+
prefix?: boolean | undefined;
|
|
671
695
|
toolCallId?: string | undefined;
|
|
672
696
|
}>;
|
|
673
697
|
declare const chatInputSchema: z.ZodObject<{
|
|
@@ -760,6 +784,10 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
760
784
|
arguments?: Record<string, unknown> | undefined;
|
|
761
785
|
}>, "many">>;
|
|
762
786
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
787
|
+
/** F049 — see Message.prefix. Position/role validity is enforced in the adapter,
|
|
788
|
+
* not here: the rule is about a message's place in the ARRAY, which a per-message
|
|
789
|
+
* schema cannot see. */
|
|
790
|
+
prefix: z.ZodOptional<z.ZodBoolean>;
|
|
763
791
|
}, "strip", z.ZodTypeAny, {
|
|
764
792
|
content: string | ({
|
|
765
793
|
text: string;
|
|
@@ -776,6 +804,7 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
776
804
|
args?: Record<string, unknown> | undefined;
|
|
777
805
|
arguments?: Record<string, unknown> | undefined;
|
|
778
806
|
}[] | undefined;
|
|
807
|
+
prefix?: boolean | undefined;
|
|
779
808
|
toolCallId?: string | undefined;
|
|
780
809
|
}, {
|
|
781
810
|
content: string | ({
|
|
@@ -793,6 +822,7 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
793
822
|
args?: Record<string, unknown> | undefined;
|
|
794
823
|
arguments?: Record<string, unknown> | undefined;
|
|
795
824
|
}[] | undefined;
|
|
825
|
+
prefix?: boolean | undefined;
|
|
796
826
|
toolCallId?: string | undefined;
|
|
797
827
|
}>, "many">>;
|
|
798
828
|
system: z.ZodOptional<z.ZodString>;
|
|
@@ -833,6 +863,7 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
833
863
|
args?: Record<string, unknown> | undefined;
|
|
834
864
|
arguments?: Record<string, unknown> | undefined;
|
|
835
865
|
}[] | undefined;
|
|
866
|
+
prefix?: boolean | undefined;
|
|
836
867
|
toolCallId?: string | undefined;
|
|
837
868
|
}[] | undefined;
|
|
838
869
|
tools?: {
|
|
@@ -877,6 +908,7 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
877
908
|
args?: Record<string, unknown> | undefined;
|
|
878
909
|
arguments?: Record<string, unknown> | undefined;
|
|
879
910
|
}[] | undefined;
|
|
911
|
+
prefix?: boolean | undefined;
|
|
880
912
|
toolCallId?: string | undefined;
|
|
881
913
|
}[] | undefined;
|
|
882
914
|
tools?: {
|
|
@@ -2182,6 +2214,11 @@ interface OpenAICompatibleConfig {
|
|
|
2182
2214
|
extraHeaders?: Record<string, string>;
|
|
2183
2215
|
/** Injectable fetch for the streaming path (tests). */
|
|
2184
2216
|
fetch?: typeof fetch;
|
|
2217
|
+
/** F049 — the provider understands `prefix: true` on a trailing assistant message.
|
|
2218
|
+
* Mistral only. A prefix sent anywhere else is REFUSED rather than dropped: a
|
|
2219
|
+
* silently ignored flag means the call succeeds, the language is not pinned, and
|
|
2220
|
+
* the caller believes it is. */
|
|
2221
|
+
supportsPrefix?: boolean;
|
|
2185
2222
|
/** OpenRouter ground-truth cost (F010): send `usage:{include:true}` and use the
|
|
2186
2223
|
* response's `usage.cost` (USD) as costUsd, falling back to the pricing table.
|
|
2187
2224
|
* Only OpenRouter returns this field — openai/deepinfra leave it false. */
|
|
@@ -2214,8 +2251,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
2214
2251
|
* wires the live adapters. */
|
|
2215
2252
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
2216
2253
|
|
|
2217
|
-
declare const VERSION: "0.
|
|
2218
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
2254
|
+
declare const VERSION: "0.38.0";
|
|
2255
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.38.0";
|
|
2219
2256
|
|
|
2220
2257
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
2221
2258
|
* per-call override.
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getPrice,
|
|
15
15
|
pricingFreshness,
|
|
16
16
|
pricingGeneratedAt
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-QUOZWQ7G.js";
|
|
18
18
|
|
|
19
19
|
// src/transport/http.ts
|
|
20
20
|
async function httpTransport(req) {
|
|
@@ -619,6 +619,7 @@ function autoCacheKey(messages) {
|
|
|
619
619
|
function toOpenAIMessage(m) {
|
|
620
620
|
if (typeof m.content === "string") {
|
|
621
621
|
const base = { role: m.role, content: m.content };
|
|
622
|
+
if (m.prefix) base.prefix = true;
|
|
622
623
|
if (m.toolCallId) base.tool_call_id = m.toolCallId;
|
|
623
624
|
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
624
625
|
base.tool_calls = m.toolCalls.map((tc) => ({
|
|
@@ -640,7 +641,70 @@ function toOpenAIMessage(m) {
|
|
|
640
641
|
});
|
|
641
642
|
return { role: m.role, content };
|
|
642
643
|
}
|
|
644
|
+
function makeStreamPrefixStripper(prefix) {
|
|
645
|
+
if (!prefix) return (d) => d;
|
|
646
|
+
let seen = "";
|
|
647
|
+
let done = false;
|
|
648
|
+
let trimNext = false;
|
|
649
|
+
return (delta) => {
|
|
650
|
+
if (done) {
|
|
651
|
+
if (!trimNext) return delta;
|
|
652
|
+
const t = delta.trimStart();
|
|
653
|
+
if (t.length === 0) return "";
|
|
654
|
+
trimNext = false;
|
|
655
|
+
return t;
|
|
656
|
+
}
|
|
657
|
+
seen += delta;
|
|
658
|
+
if (seen.length < prefix.length) {
|
|
659
|
+
if (!prefix.startsWith(seen)) {
|
|
660
|
+
done = true;
|
|
661
|
+
return seen;
|
|
662
|
+
}
|
|
663
|
+
return "";
|
|
664
|
+
}
|
|
665
|
+
done = true;
|
|
666
|
+
const rest = seen.startsWith(prefix) ? seen.slice(prefix.length) : seen;
|
|
667
|
+
if (rest.length > 0) return rest.trimStart();
|
|
668
|
+
trimNext = true;
|
|
669
|
+
return "";
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function stripPrefix(text, prefix) {
|
|
673
|
+
if (!prefix) return text;
|
|
674
|
+
return text.startsWith(prefix) ? text.slice(prefix.length).trimStart() : text;
|
|
675
|
+
}
|
|
676
|
+
function prefixText(messages) {
|
|
677
|
+
const last = messages[messages.length - 1];
|
|
678
|
+
if (!last?.prefix) return void 0;
|
|
679
|
+
return typeof last.content === "string" ? last.content : void 0;
|
|
680
|
+
}
|
|
681
|
+
function assertPrefixUsage(messages, config) {
|
|
682
|
+
const at = messages.findIndex((m) => m.prefix === true);
|
|
683
|
+
if (at === -1) return;
|
|
684
|
+
if (!config.supportsPrefix) {
|
|
685
|
+
throw new Error(
|
|
686
|
+
`${config.name} adapter: message.prefix is not supported by "${config.name}" \u2014 only mistral implements it. Remove the flag, or route this call with override:{provider:"mistral", model:"<a mistral model>"}.`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
if (at !== messages.length - 1) {
|
|
690
|
+
throw new Error(
|
|
691
|
+
`${config.name} adapter: message.prefix is only valid on the LAST message (found at index ${at} of ${messages.length}). The provider documents no behaviour for it elsewhere.`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
const last = messages[at];
|
|
695
|
+
if (last.role !== "assistant") {
|
|
696
|
+
throw new Error(
|
|
697
|
+
`${config.name} adapter: message.prefix is only valid on an "assistant" message (found on "${last.role}"). The prefix IS the start of the assistant's reply.`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
if (typeof last.content !== "string") {
|
|
701
|
+
throw new Error(
|
|
702
|
+
`${config.name} adapter: a prefix message's content must be a plain string (got content blocks). The prefix is text the reply continues from.`
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
643
706
|
function buildChatBody(req, config) {
|
|
707
|
+
assertPrefixUsage(req.messages, config);
|
|
644
708
|
const body = {
|
|
645
709
|
model: req.spec.model,
|
|
646
710
|
messages: req.messages.map(toOpenAIMessage)
|
|
@@ -680,7 +744,7 @@ function makeOpenAICompatibleAdapter(config) {
|
|
|
680
744
|
}
|
|
681
745
|
const data = res.json;
|
|
682
746
|
const msg = data.choices?.[0]?.message;
|
|
683
|
-
const text = contentToText(msg?.content);
|
|
747
|
+
const text = stripPrefix(contentToText(msg?.content), prefixText(req.messages));
|
|
684
748
|
const toolCalls = msg?.tool_calls?.map(
|
|
685
749
|
(tc) => fromProviderToolCall(tc, "openai")
|
|
686
750
|
);
|
|
@@ -713,6 +777,7 @@ function makeOpenAICompatibleAdapter(config) {
|
|
|
713
777
|
if (!apiKey) {
|
|
714
778
|
throw new Error(`${config.name} adapter: API key not set (env ${config.name.toUpperCase()}_API_KEY)`);
|
|
715
779
|
}
|
|
780
|
+
const stripStreamPrefix = makeStreamPrefixStripper(prefixText(req.messages));
|
|
716
781
|
const body = {
|
|
717
782
|
...buildChatBody(req, config),
|
|
718
783
|
stream: true,
|
|
@@ -744,7 +809,8 @@ function makeOpenAICompatibleAdapter(config) {
|
|
|
744
809
|
if (choice) {
|
|
745
810
|
const delta = choice.delta ?? {};
|
|
746
811
|
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
747
|
-
|
|
812
|
+
const out = stripStreamPrefix(delta.content);
|
|
813
|
+
if (out.length > 0) yield { type: "text", delta: out };
|
|
748
814
|
}
|
|
749
815
|
for (const tc of delta.tool_calls ?? []) {
|
|
750
816
|
const idx = tc.index ?? 0;
|
|
@@ -1296,7 +1362,7 @@ var VOXTRAL_PRICE_PER_MIN = {
|
|
|
1296
1362
|
};
|
|
1297
1363
|
function mistralAdapter(config = {}) {
|
|
1298
1364
|
const baseUrl = config.baseUrl ?? "https://api.mistral.ai/v1";
|
|
1299
|
-
const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey, supportsPromptCacheKey: true });
|
|
1365
|
+
const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey, supportsPromptCacheKey: true, supportsPrefix: true });
|
|
1300
1366
|
function key() {
|
|
1301
1367
|
const k = config.apiKey ?? process.env.MISTRAL_API_KEY;
|
|
1302
1368
|
if (!k) throw new Error("mistral adapter: API key not set (env MISTRAL_API_KEY)");
|
|
@@ -2812,7 +2878,11 @@ var messageSchema = z.object({
|
|
|
2812
2878
|
role: z.enum(["system", "user", "assistant", "tool"]),
|
|
2813
2879
|
content: z.union([z.string(), z.array(contentPartSchema)]),
|
|
2814
2880
|
toolCalls: z.array(toolCallSchema).optional(),
|
|
2815
|
-
toolCallId: z.string().optional()
|
|
2881
|
+
toolCallId: z.string().optional(),
|
|
2882
|
+
/** F049 — see Message.prefix. Position/role validity is enforced in the adapter,
|
|
2883
|
+
* not here: the rule is about a message's place in the ARRAY, which a per-message
|
|
2884
|
+
* schema cannot see. */
|
|
2885
|
+
prefix: z.boolean().optional()
|
|
2816
2886
|
});
|
|
2817
2887
|
var callOptions = {
|
|
2818
2888
|
tier: tierSchema.optional(),
|
|
@@ -2997,8 +3067,8 @@ var aiConfigSchema = z.object({
|
|
|
2997
3067
|
});
|
|
2998
3068
|
|
|
2999
3069
|
// src/version.ts
|
|
3000
|
-
var VERSION = "0.
|
|
3001
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
3070
|
+
var VERSION = "0.38.0";
|
|
3071
|
+
var SDK_TAG = "@broberg/ai-sdk@0.38.0";
|
|
3002
3072
|
|
|
3003
3073
|
// src/cost/sinks/upmetrics.ts
|
|
3004
3074
|
function upmetricsSink(config) {
|