@plurnk/plurnk-providers 1.19.0 → 1.19.2

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.
Files changed (44) hide show
  1. package/SPEC.md +19 -48
  2. package/dist/AiSdkProvider.d.ts +1 -1
  3. package/dist/AiSdkProvider.d.ts.map +1 -1
  4. package/dist/AiSdkProvider.js +0 -3
  5. package/dist/AiSdkProvider.js.map +1 -1
  6. package/dist/AiSdkRequestBody.d.ts.map +1 -1
  7. package/dist/AiSdkRequestBody.js +0 -9
  8. package/dist/AiSdkRequestBody.js.map +1 -1
  9. package/dist/Mock.d.ts +1 -2
  10. package/dist/Mock.d.ts.map +1 -1
  11. package/dist/Mock.js +0 -1
  12. package/dist/Mock.js.map +1 -1
  13. package/dist/accounting.d.ts.map +1 -1
  14. package/dist/accounting.js +0 -25
  15. package/dist/accounting.js.map +1 -1
  16. package/dist/aiSdkTransport.d.ts +0 -8
  17. package/dist/aiSdkTransport.d.ts.map +1 -1
  18. package/dist/aiSdkTransport.js +4 -92
  19. package/dist/aiSdkTransport.js.map +1 -1
  20. package/dist/catalogProvider.d.ts.map +1 -1
  21. package/dist/catalogProvider.js +3 -5
  22. package/dist/catalogProvider.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/sdkModels.d.ts.map +1 -1
  26. package/dist/sdkModels.js +0 -10
  27. package/dist/sdkModels.js.map +1 -1
  28. package/dist/types.d.ts +0 -9
  29. package/dist/types.d.ts.map +1 -1
  30. package/package.json +5 -6
  31. package/src/AiSdkProvider.test.ts +0 -120
  32. package/src/AiSdkProvider.ts +1 -4
  33. package/src/AiSdkRequestBody.ts +0 -8
  34. package/src/Mock.ts +1 -3
  35. package/src/accounting.test.ts +3 -14
  36. package/src/accounting.ts +0 -26
  37. package/src/aiSdkTransport.test.ts +0 -61
  38. package/src/aiSdkTransport.ts +4 -95
  39. package/src/catalogProvider.test.ts +5 -128
  40. package/src/catalogProvider.ts +3 -4
  41. package/src/index.ts +1 -1
  42. package/src/sdkModels.test.ts +0 -10
  43. package/src/sdkModels.ts +0 -10
  44. package/src/types.ts +0 -11
@@ -868,71 +868,6 @@ test("{§provider-connectivity} a wedged transport that ignores the abort still
868
868
  );
869
869
  });
870
870
 
871
- test("compatible xAI wire usage becomes an exact tick charge without raw-body capture", async () => {
872
- const p = testProvider({
873
- model: "grok-test",
874
- url: "http://x/v1/chat/completions",
875
- fetchTimeoutMs: 5_000,
876
- temperature: 0.2,
877
- repeatPenalty: 1.15,
878
- reasoning: { mode: "off", budget: null },
879
- retryAttempts: 0,
880
- streaming: false,
881
- normalizeCost: providerCostNormalizer("@ai-sdk/xai"),
882
- });
883
- installFetchJson({
884
- id: "response-1",
885
- model: "grok-test",
886
- choices: [{ message: { content: "ok" }, finish_reason: "stop" }],
887
- usage: {
888
- prompt_tokens: 2,
889
- completion_tokens: 1,
890
- total_tokens: 3,
891
- cost_in_usd_ticks: 15_493_500,
892
- },
893
- });
894
- const response = await p.generate({ workerId: "xai", messages: [] });
895
- assert.deepEqual(response.accounting[0]?.cost, {
896
- kind: "charged",
897
- amount: { amount: "15493500", currency: "USDTICK" },
898
- usdEquivalent: "0.00154935",
899
- source: "xAI response usage.cost_in_usd_ticks",
900
- });
901
- assert.equal(response.rawBody, undefined);
902
- });
903
-
904
- test("streamed xAI final usage retains its exact tick charge", async () => {
905
- const p = testProvider({
906
- model: "grok-test",
907
- url: "http://x/v1/chat/completions",
908
- fetchTimeoutMs: 5_000,
909
- temperature: 0.2,
910
- repeatPenalty: 1.15,
911
- reasoning: { mode: "off", budget: null },
912
- retryAttempts: 0,
913
- normalizeCost: providerCostNormalizer("@ai-sdk/xai"),
914
- });
915
- installFetch([
916
- { choices: [{ delta: { content: "ok" }, finish_reason: "stop" }] },
917
- {
918
- choices: [],
919
- usage: {
920
- prompt_tokens: 2,
921
- completion_tokens: 1,
922
- total_tokens: 3,
923
- cost_in_usd_ticks: 15_493_500,
924
- },
925
- },
926
- ]);
927
- const response = await p.generate({ workerId: "xai", messages: [] });
928
- assert.deepEqual(response.accounting[0]?.cost, {
929
- kind: "charged",
930
- amount: { amount: "15493500", currency: "USDTICK" },
931
- usdEquivalent: "0.00154935",
932
- source: "xAI response usage.cost_in_usd_ticks",
933
- });
934
- });
935
-
936
871
  test("generate surfaces and normalizes an out-of-set finish_reason", async () => {
937
872
  const warnings: Array<{ message: string; code?: string }> = [];
938
873
  mock.method(process, "emitWarning", (message: string | Error, options?: string | { code?: string }) => {
@@ -1058,7 +993,6 @@ test("generate aggregates reasoning deltas under multiple field names", async ()
1058
993
  installFetch([{ choices: [{ delta: { reasoning_content: "be", thinking: "cause" } }] }]);
1059
994
  const { assistant } = await p.generate({ workerId: "r", messages: [] });
1060
995
  assert.equal(assistant.reasoning, "because");
1061
- assert.equal("reasoningEncrypted" in assistant, false); // open reasoning only -> field absent
1062
996
  });
1063
997
 
1064
998
  for (const style of ["think-tags", "template-think", "template-channel"] as const) test(`{§provider-reasoning-observer} ${style} reasoning arrives while the response is still open`, async () => {
@@ -1277,60 +1211,6 @@ test("{§provider-tagged-reasoning} grammar evidence retains the exact pre-proje
1277
1211
  });
1278
1212
  });
1279
1213
 
1280
- test("encrypted reasoning (non-streamed): encrypted entries normalize and text entries stay separate", async () => {
1281
- // The live o4-mini-via-OpenRouter shape: reasoning null, one encrypted entry.
1282
- installFetchJson({ model: "m", choices: [{ message: {
1283
- content: "4", reasoning: null,
1284
- reasoning_details: [
1285
- { type: "reasoning.encrypted", data: "gAAAAABqBLOB", format: "openai-responses-v1", id: "rs_1", index: 0 },
1286
- { type: "reasoning.text", text: "never surfaced here" },
1287
- ],
1288
- }, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } });
1289
- const p = testProvider({ model: "m", url: "http://x/v1/chat/completions", fetchTimeoutMs: 5000, temperature: 0.2, repeatPenalty: 1.15, reasoning: { mode: "off", budget: null }, retryAttempts: 0, streaming: false });
1290
- const { assistant } = await p.generate({ workerId: "r", messages: [] });
1291
- // Wire detail ID is preserved; the assistant-message location supports the
1292
- // derived classification but supplies no downstream client entity ID.
1293
- assert.deepEqual(assistant.reasoningEncrypted, [{ id: "rs_1", subtype: "message", encrypted: [{ data: "gAAAAABqBLOB", format: "openai-responses-v1" }] }]);
1294
- assert.equal(assistant.reasoning, null); // Encrypted turn: nothing readable.
1295
- assert.equal(assistant.content, "4");
1296
- });
1297
-
1298
- test("distinct encrypted-reasoning wire ids stay distinct items", async () => {
1299
- installFetchJson({ model: "m", choices: [{ message: { content: "ok", reasoning: null, reasoning_details: [
1300
- { type: "reasoning.encrypted", data: "AAA", format: "openai-responses-v1", id: "rs_1" },
1301
- { type: "reasoning.encrypted", data: "BBB", format: "openai-responses-v1", id: "rs_2" },
1302
- ] }, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } });
1303
- const p = testProvider({ model: "m", url: "http://x/v1/chat/completions", fetchTimeoutMs: 5000, temperature: 0.2, repeatPenalty: 1.15, reasoning: { mode: "off", budget: null }, retryAttempts: 0, streaming: false });
1304
- const { assistant } = await p.generate({ workerId: "r", messages: [] });
1305
- assert.equal(assistant.reasoningEncrypted?.length, 2);
1306
- assert.deepEqual(assistant.reasoningEncrypted?.map((i) => i.id), ["rs_1", "rs_2"]);
1307
- });
1308
-
1309
- test("assistant-message location classifies encrypted reasoning without inventing a missing detail id", async () => {
1310
- installFetchJson({ model: "m", choices: [{ message: { content: "ok", reasoning_details: [
1311
- { type: "reasoning.encrypted", data: "OPAQUE", format: "openai-responses-v1", id: null, index: 0 },
1312
- ] }, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } });
1313
- const p = testProvider({ model: "m", url: "http://x/v1/chat/completions", fetchTimeoutMs: 5000, temperature: 0.2, repeatPenalty: 1.15, reasoning: { mode: "off", budget: null }, retryAttempts: 0, streaming: false });
1314
- const { assistant } = await p.generate({ workerId: "r", messages: [] });
1315
- assert.deepEqual(assistant.reasoningEncrypted, [{
1316
- id: null,
1317
- subtype: "message",
1318
- encrypted: [{ data: "OPAQUE", format: "openai-responses-v1" }],
1319
- }]);
1320
- });
1321
-
1322
- test("encrypted reasoning (streamed): chunked blob concatenates per entry index", async () => {
1323
- const p = testProvider({ model: "m", url: "http://x/v1/chat/completions", fetchTimeoutMs: 5000, temperature: 0.2, repeatPenalty: 1.15, reasoning: { mode: "off", budget: null }, retryAttempts: 0 });
1324
- installFetch([
1325
- { choices: [{ delta: { reasoning_details: [{ type: "reasoning.encrypted", data: "gAAAA", format: "openai-responses-v1", id: "rs_1", index: 0 }] } }] },
1326
- { choices: [{ delta: { reasoning_details: [{ type: "reasoning.encrypted", data: "BqXYZ", id: "rs_1", index: 0 }] } }] },
1327
- { choices: [{ delta: { content: "4" }, finish_reason: "stop" }] },
1328
- ]);
1329
- const { assistant } = await p.generate({ workerId: "r", messages: [] });
1330
- assert.deepEqual(assistant.reasoningEncrypted, [{ id: "rs_1", subtype: "message", encrypted: [{ data: "gAAAABqXYZ", format: "openai-responses-v1" }] }]);
1331
- assert.equal(assistant.content, "4");
1332
- });
1333
-
1334
1214
  test("reasoningStyle 'think' follows activation (magnitude is irrelevant to the boolean wire control)", async () => {
1335
1215
  const on = testProvider({ model: "m", url: "http://x/v1/chat/completions", fetchTimeoutMs: 5000, temperature: 0.2, repeatPenalty: 1.15, reasoning: { mode: "adaptive", budget: null }, retryAttempts: 0, reasoningStyle: "think" });
1336
1216
  let calls = installFetch([{ choices: [{ delta: { content: "x" } }] }]);
@@ -54,7 +54,7 @@ const raceAgainstDeadline = async <T>(work: PromiseLike<T>, signal: AbortSignal)
54
54
 
55
55
  // Backend wire spellings for the resolved reasoning intent. The switch beside each
56
56
  // mapping retains any backend-specific omission/explicit-disable constraint.
57
- export type ReasoningStyle = "none" | "think" | "include_reasoning" | "effort" | "effort_explicit" | "effort_required" | "thinking_effort" | "thinking_config" | "template" | "anthropic";
57
+ export type ReasoningStyle = "none" | "think" | "include_reasoning" | "effort" | "effort_explicit" | "effort_required" | "thinking_effort" | "template" | "anthropic";
58
58
 
59
59
  export type NativeReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
60
60
  export type CompatibleReasoningEffort = NativeReasoningEffort | "max";
@@ -993,9 +993,6 @@ export default class AiSdkProvider implements Provider {
993
993
  const assistant = {
994
994
  content: raw.content,
995
995
  reasoning: raw.reasoning.length > 0 ? raw.reasoning : null,
996
- ...(raw.reasoningEncrypted.length > 0
997
- ? { reasoningEncrypted: raw.reasoningEncrypted }
998
- : {}),
999
996
  model: raw.model,
1000
997
  ...(logprobs !== undefined ? { logprobs, meanLogprob } : {}),
1001
998
  };
@@ -201,14 +201,6 @@ export default class AiSdkRequestBody {
201
201
  thinking: { type: "enabled" },
202
202
  reasoning_effort: fixedEffort(mode),
203
203
  };
204
- // {§google-reasoning-request} — Gemini's OpenAI-compatible extension. Readable
205
- // thoughts ride on every reasoning request; Gemini refuses `reasoning_effort` beside a
206
- // thinking_config, so the level travels inside it. Gemini cannot turn reasoning off.
207
- case "thinking_config": {
208
- if (mode === "off") throw new TypeError(`${this.#source}: thinking_config reasoning has no off projection`);
209
- const level = mode === "adaptive" ? {} : { thinking_level: fixedEffort(mode) };
210
- return { extra_body: { google: { thinking_config: { include_thoughts: true, ...level } } } };
211
- }
212
204
  // Anthropic-compatible native dynamic or manual budget mode.
213
205
  case "anthropic": return mode === "off"
214
206
  ? { thinking: { type: "disabled" } }
package/src/Mock.ts CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { chatMessageText } from "./types.ts";
9
9
  import type { InputModality } from "./types.ts";
10
- import type { ChatMessage, FinishReason, GrammarEvidence, PromptTokenMeasurement, Provider, ProviderAssistant, ProviderCost, ProviderEncryptedReasoningItem, ProviderRequestAccounting, ProviderRequestCapacity, ProviderResponse, ProviderUsage } from "./types.ts";
10
+ import type { ChatMessage, FinishReason, GrammarEvidence, PromptTokenMeasurement, Provider, ProviderAssistant, ProviderCost, ProviderRequestAccounting, ProviderRequestCapacity, ProviderResponse, ProviderUsage } from "./types.ts";
11
11
  import { resolveGenerationEnvelopeFromEnv } from "./env.ts";
12
12
  import { REASONING_POLICIES } from "@plurnk/plurnk-contracts";
13
13
  import { validateProviderRequestAccounting } from "./accounting.ts";
@@ -20,7 +20,6 @@ export type MockAssistant = {
20
20
  finishReason?: FinishReason;
21
21
  model?: string;
22
22
  // Provider-normalized encrypted reasoning fixture.
23
- reasoningEncrypted?: ReadonlyArray<ProviderEncryptedReasoningItem>;
24
23
  // Pre-parsed ops — intg-only escape hatch. Typed `unknown[]` so the
25
24
  // framework carries no parser dependency; plurnk-service
26
25
  // casts these to PlurnkStatement[] on its side. Production providers never
@@ -172,7 +171,6 @@ export default class Mock implements Provider {
172
171
  const assistant: MockReturnedAssistant = {
173
172
  content: a.content,
174
173
  reasoning: a.reasoning,
175
- ...(a.reasoningEncrypted !== undefined ? { reasoningEncrypted: a.reasoningEncrypted } : {}),
176
174
  finishReason: a.finishReason ?? "stop",
177
175
  model: a.model ?? "mock",
178
176
  ...(a.ops !== undefined ? { ops: a.ops } : {}),
@@ -16,17 +16,6 @@ const evidence = ({ providerMetadata, usage, charge }: {
16
16
  response: { id: "response-1" },
17
17
  });
18
18
 
19
- test("xAI response ticks normalize to a directly charged request", () => {
20
- const normalize = providerCostNormalizer("@ai-sdk/xai");
21
- assert.notEqual(normalize, undefined);
22
- assert.deepEqual(normalize!(evidence({ usage: { cost_in_usd_ticks: 15_493_500 } })), {
23
- kind: "charged",
24
- amount: { amount: "15493500", currency: "USDTICK" },
25
- usdEquivalent: "0.00154935",
26
- source: "xAI response usage.cost_in_usd_ticks",
27
- });
28
- });
29
-
30
19
  test("OpenRouter response cost normalizes without rate reconstruction", () => {
31
20
  const normalize = providerCostNormalizer("@openrouter/ai-sdk-provider");
32
21
  assert.notEqual(normalize, undefined);
@@ -49,10 +38,10 @@ test("DeepInfra's documented response estimate remains estimated", () => {
49
38
 
50
39
  test("response cost normalization is an explicit adapter capability", () => {
51
40
  assert.equal(providerCostNormalizer("@ai-sdk/anthropic"), undefined);
52
- assert.equal(providerCostNormalizer("@ai-sdk/xai")!(evidence({ usage: {} })), undefined);
41
+ assert.equal(providerCostNormalizer("@ai-sdk/deepinfra")!(evidence({ usage: {} })), undefined);
53
42
  assert.throws(
54
- () => providerCostNormalizer("@ai-sdk/xai")!(evidence({ usage: { cost_in_usd_ticks: "1" } })),
55
- /cost_in_usd_ticks must be numeric/,
43
+ () => providerCostNormalizer("@ai-sdk/deepinfra")!(evidence({ usage: { estimated_cost: "1" } })),
44
+ /estimated_cost must be numeric/,
56
45
  );
57
46
  });
58
47
 
package/src/accounting.ts CHANGED
@@ -31,31 +31,6 @@ const decimalFromNumber = (value: number, subject: string): string => {
31
31
  return `${digits.slice(0, point)}.${digits.slice(point)}`;
32
32
  };
33
33
 
34
- const usdFromTicks = (ticks: number): string => {
35
- if (!Number.isSafeInteger(ticks) || ticks < 0) {
36
- throw new TypeError("xAI costInUsdTicks must be a non-negative safe integer");
37
- }
38
- const digits = String(ticks).padStart(11, "0");
39
- const integer = digits.slice(0, -10).replace(/^0+(?=\d)/, "");
40
- const fraction = digits.slice(-10).replace(/0+$/, "");
41
- return fraction === "" ? integer : `${integer}.${fraction}`;
42
- };
43
-
44
- const xaiCost: ProviderCostNormalizer = ({ usage }) => {
45
- const wireUsage = recordOf(usage);
46
- if (wireUsage === null || !("cost_in_usd_ticks" in wireUsage)) return undefined;
47
- const ticks = wireUsage.cost_in_usd_ticks;
48
- if (typeof ticks !== "number") {
49
- throw new TypeError("xAI usage.cost_in_usd_ticks must be numeric");
50
- }
51
- return {
52
- kind: "charged",
53
- amount: { amount: String(ticks), currency: "USDTICK" },
54
- usdEquivalent: usdFromTicks(ticks),
55
- source: "xAI response usage.cost_in_usd_ticks",
56
- };
57
- };
58
-
59
34
  const openRouterCost: ProviderCostNormalizer = ({ providerMetadata }) => {
60
35
  const usage = recordOf(recordOf(recordOf(providerMetadata)?.openrouter)?.usage);
61
36
  if (usage === null || !("cost" in usage)) return undefined;
@@ -84,7 +59,6 @@ export const providerCostNormalizer = (
84
59
  sdkPackage: string,
85
60
  ): ProviderCostNormalizer | undefined => {
86
61
  switch (sdkPackage) {
87
- case "@ai-sdk/xai": return xaiCost;
88
62
  case "@ai-sdk/deepinfra": return deepInfraCost;
89
63
  case "@openrouter/ai-sdk-provider": return openRouterCost;
90
64
  default: return undefined;
@@ -417,64 +417,3 @@ test("inconsistent usage counters refuse normalization without failing the respo
417
417
  });
418
418
  });
419
419
 
420
- test("{§google-thought-response}: Gemini's flagged thought content is reasoning, never emission", async (t) => {
421
- const chunk = (delta: Record<string, unknown>, extra: Record<string, unknown> = {}) => ({
422
- id: "gemini-1", object: "chat.completion.chunk", created: 1, model: "gemini-3.8-flash",
423
- choices: [{ index: 0, delta, finish_reason: null }], ...extra,
424
- });
425
- const thought = (content: string) => ({ role: "assistant", content, extra_content: { google: { thought: true } } });
426
- const sse = (chunks: object[]) => `${chunks.map((c) => `data: ${JSON.stringify(c)}\n\n`).join("")}data: [DONE]\n\n`;
427
-
428
- await t.test("streamed: flagged deltas are observed and settled as reasoning; the answer stays clean", async () => {
429
- const reasoning: string[] = [];
430
- const text: string[] = [];
431
- const result = await executeOpenAICompatible({
432
- ...request,
433
- streaming: true,
434
- observeReasoning: (delta) => reasoning.push(delta),
435
- observeText: (delta) => text.push(delta),
436
- fetch: async () => new Response(sse([
437
- chunk(thought("<thought>**Checking 391**\n\n")),
438
- chunk(thought("17 × 23 = 391.")),
439
- chunk({ content: "</thought>No. " }),
440
- chunk({ content: "391 = 17 × 23.", extra_content: { google: { thought_signature: "c2ln" } } }),
441
- { ...chunk({}), choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 5, completion_tokens: 8, total_tokens: 90 } },
442
- ]), { headers: { "content-type": "text/event-stream" } }),
443
- });
444
- assert.equal(result.content, "No. 391 = 17 × 23.");
445
- assert.equal(result.reasoning, "**Checking 391**\n\n17 × 23 = 391.");
446
- assert.equal(result.reasoningProjected, true);
447
- assert.deepEqual(reasoning, ["**Checking 391**\n\n", "17 × 23 = 391."]);
448
- assert.deepEqual(text, ["No. ", "391 = 17 × 23."]);
449
- });
450
-
451
- await t.test("whole: the flagged message's leading <thought> wrapper is reasoning and the remainder is content", async () => {
452
- const result = await executeOpenAICompatible({
453
- ...request,
454
- fetch: async () => new Response(JSON.stringify({
455
- id: "gemini-2", object: "chat.completion", created: 1, model: "gemini-3.8-flash",
456
- choices: [{ index: 0, finish_reason: "stop", message: {
457
- role: "assistant",
458
- content: "<thought>**Checking 391**\n17 × 23 = 391.</thought>No. It is divisible by 17 and 23.",
459
- extra_content: { google: { thought: true, thought_signature: "c2ln" } },
460
- } }],
461
- usage: { prompt_tokens: 5, completion_tokens: 9, total_tokens: 90 },
462
- }), { headers: { "content-type": "application/json" } }),
463
- });
464
- assert.equal(result.content, "No. It is divisible by 17 and 23.");
465
- assert.equal(result.reasoning, "**Checking 391**\n17 × 23 = 391.");
466
- assert.equal(result.reasoningProjected, true);
467
- });
468
-
469
- await t.test("unflagged content is never inspected for the wrapper", async () => {
470
- const result = await executeOpenAICompatible({
471
- ...request,
472
- fetch: async () => new Response(JSON.stringify({
473
- id: "plain", object: "chat.completion", created: 1, model: "m",
474
- choices: [{ index: 0, finish_reason: "stop", message: { role: "assistant", content: "<thought>literal</thought>tail" } }],
475
- }), { headers: { "content-type": "application/json" } }),
476
- });
477
- assert.equal(result.content, "<thought>literal</thought>tail");
478
- assert.equal(result.reasoning, "");
479
- });
480
- });
@@ -162,38 +162,6 @@ const recordOf = (value: unknown): Record<string, unknown> | null =>
162
162
  ? value as Record<string, unknown>
163
163
  : null;
164
164
 
165
- // {§google-thought-response} — Gemini behind an OpenAI-compatible endpoint returns its readable
166
- // thought summary as ordinary `content` wrapped `<thought>…</thought>` and flagged
167
- // `extra_content.google.thought`. Whole, the flagged message holds wrapper and answer; streamed,
168
- // the flagged deltas hold `<thought>` and the thought, and the first unflagged delta opens with
169
- // `</thought>` before the answer.
170
- const googleThoughtFlagged = (message: Record<string, unknown> | null): boolean =>
171
- recordOf(recordOf(message?.extra_content)?.google)?.thought === true;
172
-
173
- const THOUGHT_OPEN = "<thought>";
174
- const THOUGHT_CLOSE = "</thought>";
175
- const splitGoogleThought = (content: string): { thought: string; answer: string } | null => {
176
- if (!content.startsWith(THOUGHT_OPEN)) return null;
177
- const close = content.indexOf(THOUGHT_CLOSE);
178
- if (close === -1) return null;
179
- return { thought: content.slice(THOUGHT_OPEN.length, close), answer: content.slice(close + THOUGHT_CLOSE.length) };
180
- };
181
-
182
- const unwrapThoughtDelta = (text: string): string => {
183
- const opened = text.startsWith(THOUGHT_OPEN) ? text.slice(THOUGHT_OPEN.length) : text;
184
- return opened.endsWith(THOUGHT_CLOSE) ? opened.slice(0, -THOUGHT_CLOSE.length) : opened;
185
- };
186
-
187
- const rawChunkThought = (value: unknown): boolean => {
188
- const choices = recordOf(value)?.choices;
189
- return Array.isArray(choices) && googleThoughtFlagged(recordOf(recordOf(choices[0])?.delta));
190
- };
191
-
192
- const wholeResponseThought = (values: readonly unknown[]): boolean => values.some((value) => {
193
- const choices = recordOf(value)?.choices;
194
- return Array.isArray(choices) && googleThoughtFlagged(recordOf(recordOf(choices[0])?.message));
195
- });
196
-
197
165
  const metadataOf = (values: readonly unknown[]): Record<string, unknown> => {
198
166
  const metadata: Record<string, unknown> = {};
199
167
  for (const value of values) {
@@ -233,11 +201,6 @@ export type AiSdkTransportResponse = {
233
201
  usage?: ProviderUsage;
234
202
  usageRefusal?: UsageRefusal;
235
203
  metadata: Record<string, unknown>;
236
- reasoningEncrypted: Array<{
237
- id: string | null;
238
- subtype: string;
239
- encrypted: Array<{ data: string; format: string | null }>;
240
- }>;
241
204
  logprobs: TokenLogprob[];
242
205
  chargeEvidence: ProviderChargeEvidence;
243
206
  rawBody?: unknown;
@@ -495,17 +458,15 @@ const executeModelOnce = async (
495
458
  const accountingUsage = wireUsageEvidenceOf(values);
496
459
  const reasoningText = evidence.reasoning || result.reasoningText || "";
497
460
  const rawFinishReason = result.rawFinishReason;
498
- const thought = wholeResponseThought(values) ? splitGoogleThought(result.text) : null;
499
461
  return {
500
462
  model: result.response.modelId,
501
- content: thought === null ? result.text : thought.answer,
463
+ content: result.text,
502
464
  reasoning: reasoningText,
503
465
  reasoningProjected: evidence.reasoningProjected,
504
466
  finishReason: finishReasonOf(rawFinishReason),
505
467
  ...(rawFinishReason === undefined ? {} : { rawFinishReason }),
506
468
  ...settledUsage(values, result.usage),
507
469
  metadata: metadataOf(values),
508
- reasoningEncrypted: evidence.reasoningEncrypted,
509
470
  logprobs: evidence.logprobs,
510
471
  chargeEvidence: {
511
472
  ...(wireChargeEvidenceOf(values) === undefined
@@ -535,31 +496,17 @@ const executeModelOnce = async (
535
496
  const rawChunks: unknown[] = [];
536
497
  let streamError: unknown;
537
498
  let outputObserved = false;
538
- // The SDK enqueues each raw chunk before the deltas it yields, so the latest raw chunk's
539
- // thought flag classifies the text deltas that follow ({§google-thought-response}).
540
- let thoughtChunk = false;
541
- let thoughtSeen = false;
542
499
  let answer = "";
543
500
  try {
544
501
  for await (const part of result.fullStream) {
545
502
  if (part.type === "raw") {
546
503
  rawChunks.push(part.rawValue);
547
- thoughtChunk = rawChunkThought(part.rawValue);
548
504
  }
549
505
  if (part.type === "text-delta" && part.text.length > 0) {
550
506
  outputObserved = true;
551
507
  liftAttemptDeadline();
552
- if (thoughtChunk) {
553
- thoughtSeen = true;
554
- const thought = unwrapThoughtDelta(part.text);
555
- if (thought.length > 0) request.observeReasoning?.(thought);
556
- } else {
557
- const text = thoughtSeen && answer.length === 0 && part.text.startsWith(THOUGHT_CLOSE)
558
- ? part.text.slice(THOUGHT_CLOSE.length)
559
- : part.text;
560
- answer += text;
561
- if (text.length > 0) request.observeText?.(text);
562
- }
508
+ answer += part.text;
509
+ request.observeText?.(part.text);
563
510
  }
564
511
  if (part.type === "reasoning-delta" && part.text.length > 0) {
565
512
  outputObserved = true;
@@ -580,7 +527,7 @@ const executeModelOnce = async (
580
527
  }
581
528
  const evidence = extractEvidence(rawChunks);
582
529
  const accountingUsage = wireUsageEvidenceOf(rawChunks);
583
- const content = thoughtSeen ? answer : await result.text;
530
+ const content = await result.text;
584
531
  const reasoningText = evidence.reasoning || (await result.reasoningText) || "";
585
532
  const rawFinishReason = await result.rawFinishReason;
586
533
  const [response, providerMetadata, warnings] = await Promise.all([
@@ -597,7 +544,6 @@ const executeModelOnce = async (
597
544
  ...(rawFinishReason === undefined ? {} : { rawFinishReason }),
598
545
  ...settledUsage(rawChunks, await result.usage),
599
546
  metadata: metadataOf(rawChunks),
600
- reasoningEncrypted: evidence.reasoningEncrypted,
601
547
  logprobs: evidence.logprobs,
602
548
  chargeEvidence: {
603
549
  ...(wireChargeEvidenceOf(rawChunks) === undefined
@@ -704,16 +650,13 @@ export const transportFailureEvidence = (
704
650
  };
705
651
 
706
652
  const extractEvidence = (values: unknown[]): {
707
- reasoningEncrypted: AiSdkTransportResponse["reasoningEncrypted"];
708
653
  logprobs: TokenLogprob[];
709
654
  reasoning: string;
710
655
  reasoningProjected: boolean;
711
656
  } => {
712
- const encrypted = new Map<string, AiSdkTransportResponse["reasoningEncrypted"][number]>();
713
657
  const logprobs: TokenLogprob[] = [];
714
658
  let reasoning = "";
715
659
  let reasoningProjected = false;
716
- let anonymous = 0;
717
660
  for (const value of values) {
718
661
  const choices = recordOf(value)?.choices;
719
662
  if (!Array.isArray(choices)) continue;
@@ -746,42 +689,8 @@ const extractEvidence = (values: unknown[]): {
746
689
  reasoning += message[key];
747
690
  }
748
691
  }
749
- if (googleThoughtFlagged(message) && typeof message.content === "string") {
750
- const thought = delta !== null ? unwrapThoughtDelta(message.content) : splitGoogleThought(message.content)?.thought;
751
- if (thought !== undefined) {
752
- reasoningProjected = true;
753
- reasoning += thought;
754
- }
755
- }
756
- if (!Array.isArray(message.reasoning_details)) continue;
757
- for (const value of message.reasoning_details) {
758
- const detail = recordOf(value);
759
- if (detail?.type !== "reasoning.encrypted" || typeof detail.data !== "string") continue;
760
- const id = typeof detail.id === "string" ? detail.id : null;
761
- const key = typeof detail.index === "number"
762
- ? `index:${detail.index}`
763
- : id === null ? `anonymous:${anonymous++}` : `id:${id}`;
764
- const item: AiSdkTransportResponse["reasoningEncrypted"][number] = encrypted.get(key) ?? {
765
- id,
766
- // {§provider-encrypted-reasoning} The documented wire location
767
- // is the assistant message. `id` above still identifies only
768
- // this provider detail, never a downstream message entity.
769
- subtype: "message",
770
- encrypted: [],
771
- };
772
- const format = typeof detail.format === "string" ? detail.format : null;
773
- const prior = item.encrypted.at(-1);
774
- if (prior !== undefined) {
775
- prior.data += detail.data;
776
- if (prior.format === null && format !== null) prior.format = format;
777
- } else {
778
- item.encrypted.push({ data: detail.data, format });
779
- }
780
- encrypted.set(key, item);
781
- }
782
692
  }
783
693
  return {
784
- reasoningEncrypted: [...encrypted.values()],
785
694
  logprobs,
786
695
  reasoning,
787
696
  reasoningProjected,