@oh-my-pi/pi-ai 4.3.2 → 4.4.6

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.3.2",
3
+ "version": "4.4.6",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -356,7 +356,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
356
356
  let endStreamError: Error | null = null;
357
357
  let currentTextBlock: (TextContent & { index: number }) | null = null;
358
358
  let currentThinkingBlock: (ThinkingContent & { index: number }) | null = null;
359
- let currentToolCall: (ToolCall & { index: number; partialJson: string }) | null = null;
359
+ let currentToolCall: ToolCallState | null = null;
360
360
  const usageState: UsageState = { sawTokenDelta: false };
361
361
 
362
362
  const state: BlockState = {
@@ -526,13 +526,15 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
526
526
  return stream;
527
527
  };
528
528
 
529
+ type ToolCallState = ToolCall & { index: number; partialJson?: string; kind: "mcp" | "todo_write" };
530
+
529
531
  interface BlockState {
530
532
  currentTextBlock: (TextContent & { index: number }) | null;
531
533
  currentThinkingBlock: (ThinkingContent & { index: number }) | null;
532
- currentToolCall: (ToolCall & { index: number; partialJson: string }) | null;
534
+ currentToolCall: ToolCallState | null;
533
535
  setTextBlock: (b: (TextContent & { index: number }) | null) => void;
534
536
  setThinkingBlock: (b: (ThinkingContent & { index: number }) | null) => void;
535
- setToolCall: (t: (ToolCall & { index: number; partialJson: string }) | null) => void;
537
+ setToolCall: (t: ToolCallState | null) => void;
536
538
  }
537
539
 
538
540
  interface UsageState {
@@ -1542,6 +1544,42 @@ function decodeMcpCall(args: {
1542
1544
  };
1543
1545
  }
1544
1546
 
1547
+ function mapTodoStatusValue(status?: number): "pending" | "in_progress" | "completed" {
1548
+ switch (status) {
1549
+ case 2:
1550
+ return "in_progress";
1551
+ case 3:
1552
+ return "completed";
1553
+ default:
1554
+ return "pending";
1555
+ }
1556
+ }
1557
+
1558
+ interface CursorTodoItem {
1559
+ id?: string;
1560
+ content?: string;
1561
+ status?: number;
1562
+ }
1563
+
1564
+ interface CursorUpdateTodosToolCall {
1565
+ updateTodosToolCall?: { args?: { todos?: CursorTodoItem[] } };
1566
+ }
1567
+
1568
+ function buildTodoWriteArgs(toolCall: CursorUpdateTodosToolCall): {
1569
+ todos: Array<{ id?: string; content: string; activeForm: string; status: "pending" | "in_progress" | "completed" }>;
1570
+ } | null {
1571
+ const todos = toolCall.updateTodosToolCall?.args?.todos;
1572
+ if (!todos) return null;
1573
+ return {
1574
+ todos: todos.map((todo) => ({
1575
+ id: typeof todo.id === "string" && todo.id.length > 0 ? todo.id : undefined,
1576
+ content: typeof todo.content === "string" ? todo.content : "",
1577
+ activeForm: typeof todo.content === "string" ? todo.content : "",
1578
+ status: mapTodoStatusValue(typeof todo.status === "number" ? todo.status : undefined),
1579
+ })),
1580
+ };
1581
+ }
1582
+
1545
1583
  function buildMcpResultFromToolResult(_mcpCall: CursorMcpCall, toolResult: ToolResultMessage) {
1546
1584
  if (toolResult.isError) {
1547
1585
  return buildMcpErrorResult(toolResultToText(toolResult) || "MCP tool failed");
@@ -1654,13 +1692,31 @@ function processInteractionUpdate(
1654
1692
  const mcpCall = toolCall.mcpToolCall;
1655
1693
  if (mcpCall) {
1656
1694
  const args = mcpCall.args || {};
1657
- const block: ToolCall & { index: number; partialJson: string } = {
1695
+ const block: ToolCallState = {
1658
1696
  type: "toolCall",
1659
1697
  id: args.toolCallId || crypto.randomUUID(),
1660
1698
  name: args.name || args.toolName || "",
1661
1699
  arguments: {},
1662
1700
  index: output.content.length,
1663
1701
  partialJson: "",
1702
+ kind: "mcp",
1703
+ };
1704
+ output.content.push(block);
1705
+ state.setToolCall(block);
1706
+ stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
1707
+ return;
1708
+ }
1709
+
1710
+ const todoArgs = buildTodoWriteArgs(toolCall);
1711
+ if (todoArgs) {
1712
+ const callId = update.message.value.callId || crypto.randomUUID();
1713
+ const block: ToolCallState = {
1714
+ type: "toolCall",
1715
+ id: callId,
1716
+ name: "todo_write",
1717
+ arguments: todoArgs,
1718
+ index: output.content.length,
1719
+ kind: "todo_write",
1664
1720
  };
1665
1721
  output.content.push(block);
1666
1722
  state.setToolCall(block);
@@ -1668,23 +1724,31 @@ function processInteractionUpdate(
1668
1724
  }
1669
1725
  }
1670
1726
  } else if (updateCase === "toolCallDelta" || updateCase === "partialToolCall") {
1671
- if (state.currentToolCall) {
1727
+ if (state.currentToolCall?.kind === "mcp") {
1672
1728
  const delta = update.message.value.argsTextDelta || "";
1673
- state.currentToolCall.partialJson += delta;
1674
- state.currentToolCall.arguments = parseStreamingJson(state.currentToolCall.partialJson);
1729
+ state.currentToolCall.partialJson = `${state.currentToolCall.partialJson ?? ""}${delta}`;
1730
+ state.currentToolCall.arguments = parseStreamingJson(state.currentToolCall.partialJson ?? "");
1675
1731
  const idx = output.content.indexOf(state.currentToolCall);
1676
1732
  stream.push({ type: "toolcall_delta", contentIndex: idx, delta, partial: output });
1677
1733
  }
1678
1734
  } else if (updateCase === "toolCallCompleted") {
1679
1735
  if (state.currentToolCall) {
1680
1736
  const toolCall = update.message.value.toolCall;
1681
- const decodedArgs = decodeMcpArgsMap(toolCall?.mcpToolCall?.args?.args);
1682
- if (decodedArgs) {
1683
- state.currentToolCall.arguments = decodedArgs;
1737
+ if (state.currentToolCall.kind === "mcp") {
1738
+ const decodedArgs = decodeMcpArgsMap(toolCall?.mcpToolCall?.args?.args);
1739
+ if (decodedArgs) {
1740
+ state.currentToolCall.arguments = decodedArgs;
1741
+ }
1742
+ } else if (state.currentToolCall.kind === "todo_write" && toolCall) {
1743
+ const todoArgs = buildTodoWriteArgs(toolCall);
1744
+ if (todoArgs) {
1745
+ state.currentToolCall.arguments = todoArgs;
1746
+ }
1684
1747
  }
1685
1748
  const idx = output.content.indexOf(state.currentToolCall);
1686
1749
  delete (state.currentToolCall as any).partialJson;
1687
1750
  delete (state.currentToolCall as any).index;
1751
+ delete (state.currentToolCall as any).kind;
1688
1752
  stream.push({ type: "toolcall_end", contentIndex: idx, toolCall: state.currentToolCall, partial: output });
1689
1753
  state.setToolCall(null);
1690
1754
  }
@@ -1722,7 +1786,7 @@ function createBlobId(data: Uint8Array): Uint8Array {
1722
1786
  return new Uint8Array(createHash("sha256").update(data).digest());
1723
1787
  }
1724
1788
 
1725
- const CURSOR_NATIVE_TOOL_NAMES = new Set(["bash", "read", "write", "delete", "ls", "grep", "lsp"]);
1789
+ const CURSOR_NATIVE_TOOL_NAMES = new Set(["bash", "read", "write", "delete", "ls", "grep", "lsp", "todo_write"]);
1726
1790
 
1727
1791
  function buildMcpToolDefinitions(tools: Tool[] | undefined): McpToolDefinition[] {
1728
1792
  if (!tools || tools.length === 0) {
@@ -695,7 +695,7 @@ function buildRequest(
695
695
  }
696
696
 
697
697
  if (context.tools && context.tools.length > 0) {
698
- request.tools = convertTools(context.tools);
698
+ request.tools = convertTools(context.tools, model);
699
699
  if (options.toolChoice) {
700
700
  request.toolConfig = {
701
701
  functionCallingConfig: {
@@ -189,7 +189,20 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
189
189
  return contents;
190
190
  }
191
191
 
192
- function sanitizeSchemaForGoogle(value: unknown): unknown {
192
+ const UNSUPPORTED_SCHEMA_FIELDS = new Set([
193
+ "$schema",
194
+ "$ref",
195
+ "$defs",
196
+ "$dynamicRef",
197
+ "$dynamicAnchor",
198
+ "format",
199
+ "examples",
200
+ "prefixItems",
201
+ "unevaluatedProperties",
202
+ "unevaluatedItems",
203
+ ]);
204
+
205
+ export function sanitizeSchemaForGoogle(value: unknown): unknown {
193
206
  if (Array.isArray(value)) {
194
207
  return value.map((entry) => sanitizeSchemaForGoogle(entry));
195
208
  }
@@ -202,10 +215,16 @@ function sanitizeSchemaForGoogle(value: unknown): unknown {
202
215
  let constValue: unknown | undefined;
203
216
 
204
217
  for (const [key, entry] of Object.entries(value)) {
218
+ if (UNSUPPORTED_SCHEMA_FIELDS.has(key)) {
219
+ continue;
220
+ }
205
221
  if (key === "const") {
206
222
  constValue = entry;
207
223
  continue;
208
224
  }
225
+ if (key === "additionalProperties" && entry === false) {
226
+ continue;
227
+ }
209
228
  result[key] = sanitizeSchemaForGoogle(entry);
210
229
  }
211
230
 
@@ -221,22 +240,31 @@ function sanitizeSchemaForGoogle(value: unknown): unknown {
221
240
  return result;
222
241
  }
223
242
 
243
+ function sanitizeToolNoop(tool: Tool): Tool {
244
+ return {
245
+ name: tool.name,
246
+ description: tool.description,
247
+ parameters: structuredClone(tool.parameters),
248
+ };
249
+ }
250
+ function sanitizeToolGoogle(tool: Tool): Tool {
251
+ return {
252
+ name: tool.name,
253
+ description: tool.description,
254
+ parameters: sanitizeSchemaForGoogle(tool.parameters) as any,
255
+ };
256
+ }
257
+
224
258
  /**
225
259
  * Convert tools to Gemini function declarations format.
226
260
  */
227
261
  export function convertTools(
228
262
  tools: Tool[],
263
+ model: Model<"google-generative-ai" | "google-gemini-cli" | "google-vertex">,
229
264
  ): { functionDeclarations: { name: string; description?: string; parameters: Schema }[] }[] | undefined {
230
265
  if (tools.length === 0) return undefined;
231
- return [
232
- {
233
- functionDeclarations: tools.map((tool) => ({
234
- name: tool.name,
235
- description: tool.description,
236
- parameters: sanitizeSchemaForGoogle(tool.parameters) as Schema,
237
- })),
238
- },
239
- ];
266
+ const toolSanitizer = model?.id.startsWith("gemini-") ? sanitizeToolGoogle : sanitizeToolNoop;
267
+ return [{ functionDeclarations: tools.map(toolSanitizer) }];
240
268
  }
241
269
 
242
270
  /**
@@ -326,7 +326,7 @@ function buildParams(
326
326
  const config: GenerateContentConfig = {
327
327
  ...(Object.keys(generationConfig).length > 0 && generationConfig),
328
328
  ...(context.systemPrompt && { systemInstruction: sanitizeSurrogates(context.systemPrompt) }),
329
- ...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools) }),
329
+ ...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools, model) }),
330
330
  };
331
331
 
332
332
  if (context.tools && context.tools.length > 0 && options.toolChoice) {
@@ -28,8 +28,11 @@ import {
28
28
  mapStopReason,
29
29
  mapToolChoice,
30
30
  retainThoughtSignature,
31
+ sanitizeSchemaForGoogle,
31
32
  } from "./google-shared";
32
33
 
34
+ export { sanitizeSchemaForGoogle };
35
+
33
36
  export interface GoogleOptions extends StreamOptions {
34
37
  toolChoice?: "auto" | "none" | "any";
35
38
  thinking?: {
@@ -294,7 +297,7 @@ function buildParams(
294
297
  const config: GenerateContentConfig = {
295
298
  ...(Object.keys(generationConfig).length > 0 && generationConfig),
296
299
  ...(context.systemPrompt && { systemInstruction: sanitizeSurrogates(context.systemPrompt) }),
297
- ...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools) }),
300
+ ...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools, model) }),
298
301
  };
299
302
 
300
303
  if (context.tools && context.tools.length > 0 && options.toolChoice) {