@f5-sales-demo/pi-ai 21.15.1 → 21.17.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/CHANGELOG.md CHANGED
@@ -6,6 +6,9 @@
6
6
 
7
7
  ### Changed
8
8
 
9
+ - Replaced the bundled Vertex Gemini 3.7 Flash model and provider default with GA Gemini 3.8 Flash,
10
+ retaining HIGH thinking by default and the strict Vertex tool-call contract
11
+ ([#3751](https://github.com/f5-sales-demo/xcsh/issues/3751)).
9
12
  - Replaced Vertex Gemini 3.6 Flash with GA Gemini 3.7 Flash and made full Vertex Gemini Flash and Pro models default to HIGH thinking while preserving supported explicit effort overrides ([#3460](https://github.com/f5-sales-demo/xcsh/issues/3460)).
10
13
 
11
14
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/pi-ai",
4
- "version": "21.15.1",
4
+ "version": "21.17.0",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -46,7 +46,7 @@
46
46
  "@anthropic-ai/sdk": "^0.115",
47
47
  "@aws-sdk/client-bedrock-runtime": "^3",
48
48
  "@bufbuild/protobuf": "^2.11",
49
- "@f5-sales-demo/pi-utils": "21.15.1",
49
+ "@f5-sales-demo/pi-utils": "21.17.0",
50
50
  "@google/genai": "^2.15",
51
51
  "@sinclair/typebox": "^0.34",
52
52
  "@smithy/node-http-handler": "^4.4",
package/src/models.json CHANGED
@@ -10316,9 +10316,9 @@
10316
10316
  }
10317
10317
  },
10318
10318
  "google-vertex": {
10319
- "gemini-3.7-flash": {
10320
- "id": "gemini-3.7-flash",
10321
- "name": "Gemini 3.7 Flash",
10319
+ "gemini-3.8-flash": {
10320
+ "id": "gemini-3.8-flash",
10321
+ "name": "Gemini 3.8 Flash",
10322
10322
  "api": "google-vertex",
10323
10323
  "provider": "google-vertex",
10324
10324
  "baseUrl": "https://{location}-aiplatform.googleapis.com",
@@ -10338,10 +10338,6 @@
10338
10338
  "thinking": {
10339
10339
  "mode": "google-level",
10340
10340
  "supportedLevels": [
10341
- {
10342
- "effort": "minimal",
10343
- "description": "Very brief reasoning"
10344
- },
10345
10341
  {
10346
10342
  "effort": "low",
10347
10343
  "description": "Light reasoning"
@@ -276,7 +276,7 @@ export const DEFAULT_MODEL_PER_PROVIDER: Record<KnownProvider, string> = {
276
276
  "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
277
277
  "google-antigravity": "gemini-3-pro-high",
278
278
  "google-gemini-cli": "gemini-2.5-pro",
279
- "google-vertex": "gemini-3-pro-preview",
279
+ "google-vertex": "gemini-3.8-flash",
280
280
  minimax: "MiniMax-M2.5",
281
281
  "minimax-code": "MiniMax-M2.5",
282
282
  "minimax-code-cn": "MiniMax-M2.5",
@@ -65,7 +65,7 @@ function resolveThoughtSignature(isSameProviderAndModel: boolean, signature: str
65
65
  * Models with strict Google tool round trips require explicit matching call/response IDs.
66
66
  */
67
67
  export function requiresToolCallId(modelId: string): boolean {
68
- return modelId.startsWith("claude-") || modelId === "gemini-3.7-flash";
68
+ return modelId.startsWith("claude-") || modelId === "gemini-3.8-flash";
69
69
  }
70
70
 
71
71
  function getGeminiMajorVersion(modelId: string): number | undefined {
@@ -177,7 +177,7 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
177
177
  ...(requiresToolCallId(model.id) ? { id: block.id } : {}),
178
178
  },
179
179
  };
180
- if (model.provider === "google-vertex" && model.id !== "gemini-3.7-flash" && part?.functionCall?.id) {
180
+ if (model.provider === "google-vertex" && model.id !== "gemini-3.8-flash" && part?.functionCall?.id) {
181
181
  delete part.functionCall.id; // Vertex AI does not support 'id' in functionCall
182
182
  }
183
183
  if (effectiveSignature) {
@@ -230,7 +230,7 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
230
230
 
231
231
  if (
232
232
  model.provider === "google-vertex" &&
233
- model.id !== "gemini-3.7-flash" &&
233
+ model.id !== "gemini-3.8-flash" &&
234
234
  functionResponsePart.functionResponse?.id
235
235
  ) {
236
236
  delete functionResponsePart.functionResponse.id; // Vertex AI does not support 'id' in functionResponse
@@ -450,7 +450,7 @@ export async function resolveGoogleVertexProject(
450
450
  }
451
451
 
452
452
  export function resolveGoogleVertexLocation(options?: GoogleVertexOptions): string {
453
- // Gemini 3.7 Flash Corporate is served through the global endpoint only.
453
+ // Gemini 3.8 Flash Corporate is served through the global endpoint only.
454
454
  // Ignore inherited provider/environment locations so a request cannot drift.
455
455
  void options;
456
456
  return "global";
@@ -484,28 +484,31 @@ export function buildGoogleVertexParams(
484
484
  options: GoogleVertexOptions = {},
485
485
  ): GenerateContentParameters {
486
486
  const contents = convertMessages(model, context);
487
- const isGemini37Flash = model.id === "gemini-3.7-flash";
487
+ const isGemini38Flash = model.id === "gemini-3.8-flash";
488
+ if (isGemini38Flash && options.thinking?.level === "MINIMAL") {
489
+ throw new Error("MINIMAL thinking is not supported by Gemini 3.8 Flash");
490
+ }
488
491
 
489
492
  const generationConfig: GoogleVertexSamplingConfig = {};
490
- if (!isGemini37Flash && options.temperature !== undefined) {
493
+ if (!isGemini38Flash && options.temperature !== undefined) {
491
494
  generationConfig.temperature = options.temperature;
492
495
  }
493
496
  if (options.maxTokens !== undefined) {
494
497
  generationConfig.maxOutputTokens = options.maxTokens;
495
498
  }
496
- if (!isGemini37Flash && options.topP !== undefined) {
499
+ if (!isGemini38Flash && options.topP !== undefined) {
497
500
  generationConfig.topP = options.topP;
498
501
  }
499
- if (!isGemini37Flash && options.topK !== undefined) {
502
+ if (!isGemini38Flash && options.topK !== undefined) {
500
503
  generationConfig.topK = options.topK;
501
504
  }
502
- if (!isGemini37Flash && options.minP !== undefined) {
505
+ if (!isGemini38Flash && options.minP !== undefined) {
503
506
  generationConfig.minP = options.minP;
504
507
  }
505
- if (!isGemini37Flash && options.presencePenalty !== undefined) {
508
+ if (!isGemini38Flash && options.presencePenalty !== undefined) {
506
509
  generationConfig.presencePenalty = options.presencePenalty;
507
510
  }
508
- if (!isGemini37Flash && options.repetitionPenalty !== undefined) {
511
+ if (!isGemini38Flash && options.repetitionPenalty !== undefined) {
509
512
  generationConfig.repetitionPenalty = options.repetitionPenalty;
510
513
  }
511
514
 
@@ -527,11 +530,11 @@ export function buildGoogleVertexParams(
527
530
  config.toolConfig = undefined;
528
531
  }
529
532
 
530
- if (model.reasoning && (options.thinking?.enabled || isGemini37Flash)) {
533
+ if (model.reasoning && (options.thinking?.enabled || isGemini38Flash)) {
531
534
  const cfg: ThinkingConfig = { includeThoughts: true };
532
535
  if (options.thinking?.level !== undefined) {
533
536
  cfg.thinkingLevel = THINKING_LEVEL_MAP[options.thinking.level];
534
- } else if (isGemini37Flash) {
537
+ } else if (isGemini38Flash) {
535
538
  cfg.thinkingLevel = ThinkingLevel.HIGH;
536
539
  } else if (options.thinking?.budgetTokens !== undefined) {
537
540
  cfg.thinkingBudget = options.thinking.budgetTokens;
@@ -63,7 +63,12 @@ import {
63
63
  transformRequestBody,
64
64
  } from "./openai-codex/request-transformer";
65
65
  import { parseCodexError } from "./openai-codex/response-handler";
66
- import { encodeTextSignatureV1, mapOpenAIResponsesStopReason, parseTextSignature } from "./openai-responses-shared";
66
+ import {
67
+ encodeTextSignatureV1,
68
+ mapOpenAIResponsesStopReason,
69
+ parseTextSignature,
70
+ resolveAssistantMessagePhase,
71
+ } from "./openai-responses-shared";
67
72
  import { transformMessages } from "./transform-messages";
68
73
 
69
74
  export interface OpenAICodexResponsesOptions extends StreamOptions {
@@ -747,11 +752,21 @@ function handleCodexStreamEvent(args: {
747
752
  runtime.currentBlock = createOutputBlockForItem(item);
748
753
  if (!runtime.currentBlock) return firstTokenTime;
749
754
  output.content.push(runtime.currentBlock);
750
- stream.push({
751
- type: getOutputBlockStartEventType(runtime.currentBlock),
752
- contentIndex: blockIndex(),
753
- partial: output,
754
- });
755
+ if (runtime.currentBlock.type === "text") {
756
+ stream.push({
757
+ type: "text_start",
758
+ contentIndex: blockIndex(),
759
+ phase: runtime.currentBlock.phase ?? "final_answer",
760
+ partial: output,
761
+ });
762
+ } else {
763
+ const type = getOutputBlockStartEventType(runtime.currentBlock);
764
+ if (type === "thinking_start") {
765
+ stream.push({ type, contentIndex: blockIndex(), partial: output });
766
+ } else {
767
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
768
+ }
769
+ }
755
770
  return firstTokenTime;
756
771
  }
757
772
 
@@ -838,7 +853,7 @@ function createOutputBlockForItem(item: CodexEventItem): CodexOutputBlock | null
838
853
  return { type: "thinking", thinking: "" };
839
854
  }
840
855
  if (item.type === "message") {
841
- return { type: "text", text: "" };
856
+ return { type: "text", text: "", phase: resolveAssistantMessagePhase(item.phase) };
842
857
  }
843
858
  if (item.type === "function_call") {
844
859
  return {
@@ -986,12 +1001,14 @@ function handleOutputItemDone(
986
1001
  runtime.currentBlock.text = item.content
987
1002
  .map(content => (content.type === "output_text" ? content.text : content.refusal))
988
1003
  .join("");
989
- const phase = item.phase === "commentary" || item.phase === "final_answer" ? item.phase : undefined;
1004
+ const phase = resolveAssistantMessagePhase(item.phase);
1005
+ runtime.currentBlock.phase = phase;
990
1006
  runtime.currentBlock.textSignature = encodeTextSignatureV1(item.id, phase);
991
1007
  stream.push({
992
1008
  type: "text_end",
993
1009
  contentIndex: blockIndex(),
994
1010
  content: runtime.currentBlock.text,
1011
+ phase,
995
1012
  partial: output,
996
1013
  });
997
1014
  runtime.currentBlock = null;
@@ -2175,7 +2192,7 @@ function convertMessages(model: Model<"openai-codex-responses">, context: Contex
2175
2192
  content: [{ type: "output_text", text: textBlock.text.toWellFormed(), annotations: [] }],
2176
2193
  status: "completed",
2177
2194
  id: msgId,
2178
- phase: parsedSignature?.phase,
2195
+ phase: parsedSignature?.phase ?? textBlock.phase,
2179
2196
  } satisfies ResponseOutputMessage);
2180
2197
  continue;
2181
2198
  }
@@ -14,6 +14,7 @@ import { calculateCost } from "../models";
14
14
  import type {
15
15
  Api,
16
16
  AssistantMessage,
17
+ AssistantMessagePhase,
17
18
  ImageContent,
18
19
  Model,
19
20
  StopReason,
@@ -33,6 +34,10 @@ export function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phase
33
34
  return JSON.stringify(payload);
34
35
  }
35
36
 
37
+ export function resolveAssistantMessagePhase(phase: unknown): AssistantMessagePhase {
38
+ return phase === "commentary" ? "commentary" : "final_answer";
39
+ }
40
+
36
41
  export function parseTextSignature(
37
42
  signature: string | undefined,
38
43
  ): { id: string; phase?: TextSignatureV1["phase"] } | undefined {
@@ -152,7 +157,7 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
152
157
  content: [{ type: "output_text", text: block.text.toWellFormed(), annotations: [] }],
153
158
  status: "completed",
154
159
  id: msgId,
155
- phase: parsedSignature?.phase,
160
+ phase: parsedSignature?.phase ?? block.phase,
156
161
  } satisfies ResponseOutputMessage);
157
162
  continue;
158
163
  }
@@ -262,9 +267,14 @@ export async function processResponsesStream<TApi extends Api>(
262
267
  stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
263
268
  } else if (item.type === "message") {
264
269
  currentItem = item;
265
- currentBlock = { type: "text", text: "" };
270
+ currentBlock = { type: "text", text: "", phase: resolveAssistantMessagePhase(item.phase) };
266
271
  output.content.push(currentBlock);
267
- stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
272
+ stream.push({
273
+ type: "text_start",
274
+ contentIndex: blockIndex(),
275
+ phase: currentBlock.phase ?? "final_answer",
276
+ partial: output,
277
+ });
268
278
  } else if (item.type === "function_call") {
269
279
  currentItem = item;
270
280
  currentBlock = {
@@ -380,11 +390,13 @@ export async function processResponsesStream<TApi extends Api>(
380
390
  currentBlock.text = item.content
381
391
  .map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? "")))
382
392
  .join("");
383
- currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
393
+ currentBlock.phase = resolveAssistantMessagePhase(item.phase);
394
+ currentBlock.textSignature = encodeTextSignatureV1(item.id, currentBlock.phase);
384
395
  stream.push({
385
396
  type: "text_end",
386
397
  contentIndex: blockIndex(),
387
398
  content: currentBlock.text,
399
+ phase: currentBlock.phase ?? "final_answer",
388
400
  partial: output,
389
401
  });
390
402
  currentBlock = null;
package/src/types.ts CHANGED
@@ -258,10 +258,12 @@ export type StreamFunction<TApi extends Api> = (
258
258
  options: OptionsForApi<TApi>,
259
259
  ) => AssistantMessageEventStream;
260
260
 
261
+ export type AssistantMessagePhase = "commentary" | "final_answer";
262
+
261
263
  export interface TextSignatureV1 {
262
264
  v: 1;
263
265
  id: string;
264
- phase?: "commentary" | "final_answer";
266
+ phase?: AssistantMessagePhase;
265
267
  }
266
268
 
267
269
  /**
@@ -288,6 +290,8 @@ export interface WebCitation {
288
290
  export interface TextContent {
289
291
  type: "text";
290
292
  text: string;
293
+ /** Semantic role supplied by Responses. Absence has final-answer semantics. */
294
+ phase?: AssistantMessagePhase;
291
295
  textSignature?: string; // e.g., for OpenAI responses, message metadata (legacy id string or TextSignatureV1 JSON)
292
296
  /** Structured citations for this span, when the provider ran a server-side search. */
293
297
  citations?: WebCitation[];
@@ -468,9 +472,15 @@ export interface Context {
468
472
 
469
473
  export type AssistantMessageEvent =
470
474
  | { type: "start"; contentIndex?: undefined; partial: AssistantMessage }
471
- | { type: "text_start"; contentIndex: number; partial: AssistantMessage }
475
+ | { type: "text_start"; contentIndex: number; phase?: AssistantMessagePhase; partial: AssistantMessage }
472
476
  | { type: "text_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
473
- | { type: "text_end"; contentIndex: number; content: string; partial: AssistantMessage }
477
+ | {
478
+ type: "text_end";
479
+ contentIndex: number;
480
+ content: string;
481
+ phase?: AssistantMessagePhase;
482
+ partial: AssistantMessage;
483
+ }
474
484
  | { type: "thinking_start"; contentIndex: number; partial: AssistantMessage }
475
485
  | { type: "thinking_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
476
486
  | { type: "thinking_end"; contentIndex: number; content: string; partial: AssistantMessage }