@oh-my-pi/pi-ai 4.5.0 → 4.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-ai",
3
- "version": "4.5.0",
3
+ "version": "4.7.0",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -30,7 +30,7 @@ import { sanitizeSurrogates } from "../utils/sanitize-unicode";
30
30
  import { transformMessages } from "./transorm-messages";
31
31
 
32
32
  // Stealth mode: Mimic Claude Code headers while avoiding tool name collisions.
33
- export const claudeCodeVersion = "1.0.83";
33
+ export const claudeCodeVersion = "2.1.2";
34
34
  export const claudeToolPrefix = "proxy_";
35
35
  export const claudeCodeSystemInstruction = "You are Claude Code, Anthropic's official CLI for Claude.";
36
36
  export const claudeCodeHeaders = {
@@ -512,6 +512,10 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
512
512
  });
513
513
  } else {
514
514
  currentBlock.text += part.text;
515
+ currentBlock.textSignature = retainThoughtSignature(
516
+ currentBlock.textSignature,
517
+ part.thoughtSignature,
518
+ );
515
519
  stream.push({
516
520
  type: "text_delta",
517
521
  contentIndex: blockIndex(),
@@ -13,18 +13,19 @@ type GoogleApiType = "google-generative-ai" | "google-gemini-cli" | "google-vert
13
13
  * Determines whether a streamed Gemini `Part` should be treated as "thinking".
14
14
  *
15
15
  * Protocol note (Gemini / Vertex AI thought signatures):
16
- * - `thoughtSignature` may appear without `thought: true` (including in empty-text parts at the end of streaming).
16
+ * - `thought: true` is the definitive marker for thinking content (thought summaries).
17
+ * - `thoughtSignature` is an encrypted representation of the model's internal thought process
18
+ * used to preserve reasoning context across multi-turn interactions.
19
+ * - `thoughtSignature` can appear on ANY part type (text, functionCall, etc.) - it does NOT
20
+ * indicate the part itself is thinking content.
21
+ * - For non-functionCall responses, the signature appears on the last part for context replay.
17
22
  * - When persisting/replaying model outputs, signature-bearing parts must be preserved as-is;
18
23
  * do not merge/move signatures across parts.
19
- * - Our streaming representation uses content blocks, so we classify any non-empty `thoughtSignature`
20
- * as thinking to avoid leaking thought content into normal assistant text.
21
24
  *
22
- * Some Google backends send thought content with `thoughtSignature` but omit `thought: true`
23
- * on subsequent deltas. We treat any non-empty `thoughtSignature` as thinking to avoid
24
- * leaking thought text into the normal assistant text stream.
25
+ * See: https://ai.google.dev/gemini-api/docs/thought-signatures
25
26
  */
26
27
  export function isThinkingPart(part: Pick<Part, "thought" | "thoughtSignature">): boolean {
27
- return part.thought === true || (typeof part.thoughtSignature === "string" && part.thoughtSignature.length > 0);
28
+ return part.thought === true;
28
29
  }
29
30
 
30
31
  /**
@@ -214,9 +215,9 @@ const UNSUPPORTED_SCHEMA_FIELDS = new Set([
214
215
  "format",
215
216
  ]);
216
217
 
217
- export function sanitizeSchemaForGoogle(value: unknown): unknown {
218
+ function sanitizeSchemaImpl(value: unknown, isInsideProperties: boolean): unknown {
218
219
  if (Array.isArray(value)) {
219
- return value.map((entry) => sanitizeSchemaForGoogle(entry));
220
+ return value.map((entry) => sanitizeSchemaImpl(entry, isInsideProperties));
220
221
  }
221
222
 
222
223
  if (!value || typeof value !== "object") {
@@ -247,7 +248,7 @@ export function sanitizeSchemaForGoogle(value: unknown): unknown {
247
248
  // Copy description and other top-level fields (not the combiner)
248
249
  for (const [key, entry] of Object.entries(obj)) {
249
250
  if (key !== combiner && !(key in result)) {
250
- result[key] = sanitizeSchemaForGoogle(entry);
251
+ result[key] = sanitizeSchemaImpl(entry, false);
251
252
  }
252
253
  }
253
254
  return result;
@@ -258,13 +259,16 @@ export function sanitizeSchemaForGoogle(value: unknown): unknown {
258
259
  // Regular field processing
259
260
  let constValue: unknown;
260
261
  for (const [key, entry] of Object.entries(obj)) {
261
- if (UNSUPPORTED_SCHEMA_FIELDS.has(key)) continue;
262
+ // Only strip unsupported schema keywords when NOT inside "properties" object
263
+ // Inside "properties", keys are property names (e.g., "pattern") not schema keywords
264
+ if (!isInsideProperties && UNSUPPORTED_SCHEMA_FIELDS.has(key)) continue;
262
265
  if (key === "const") {
263
266
  constValue = entry;
264
267
  continue;
265
268
  }
266
269
  if (key === "additionalProperties" && entry === false) continue;
267
- result[key] = sanitizeSchemaForGoogle(entry);
270
+ // When key is "properties", child keys are property names, not schema keywords
271
+ result[key] = sanitizeSchemaImpl(entry, key === "properties");
268
272
  }
269
273
 
270
274
  if (constValue !== undefined) {
@@ -289,6 +293,10 @@ export function sanitizeSchemaForGoogle(value: unknown): unknown {
289
293
  return result;
290
294
  }
291
295
 
296
+ export function sanitizeSchemaForGoogle(value: unknown): unknown {
297
+ return sanitizeSchemaImpl(value, false);
298
+ }
299
+
292
300
  function sanitizeToolForGoogle(tool: Tool): Tool {
293
301
  return {
294
302
  name: tool.name,
@@ -143,6 +143,10 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
143
143
  });
144
144
  } else {
145
145
  currentBlock.text += part.text;
146
+ currentBlock.textSignature = retainThoughtSignature(
147
+ currentBlock.textSignature,
148
+ part.thoughtSignature,
149
+ );
146
150
  stream.push({
147
151
  type: "text_delta",
148
152
  contentIndex: blockIndex(),