bloby-bot 0.47.6 → 0.47.7

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": "bloby-bot",
3
- "version": "0.47.6",
3
+ "version": "0.47.7",
4
4
  "releaseNotes": [
5
5
  "1. # voice note (PTT bubble)",
6
6
  "2. # audio file + caption",
@@ -100,8 +100,12 @@ function toGeminiParts(content: PiContentBlock[]): any[] {
100
100
  } else if (b.type === 'image') {
101
101
  parts.push({ inlineData: { mimeType: b.mediaType, data: b.data } });
102
102
  } else if (b.type === 'tool_use') {
103
- // Assistant turn: the model asked to invoke a tool.
104
- parts.push({ functionCall: { name: b.name, args: b.input || {} } });
103
+ // Assistant turn: the model asked to invoke a tool. Thinking-capable
104
+ // Gemini 3.x rejects (HTTP 400) any echoed functionCall whose
105
+ // thoughtSignature is missing, so we forward it verbatim when present.
106
+ const part: any = { functionCall: { name: b.name, args: b.input || {} } };
107
+ if (b.thoughtSignature) part.thoughtSignature = b.thoughtSignature;
108
+ parts.push(part);
105
109
  } else if (b.type === 'tool_result') {
106
110
  // Function responses can be strings, objects, or even error markers.
107
111
  // Wrap text in `{ output: ... }` (Gemini's docs use a flexible
@@ -262,6 +266,9 @@ export async function* streamGoogle(req: PiStreamRequest): AsyncIterable<PiStrea
262
266
  id,
263
267
  name: part.functionCall.name,
264
268
  input: part.functionCall.args || {},
269
+ // Thinking-capable models attach a signature that we must echo
270
+ // back unchanged on the next turn. Optional on non-thinking models.
271
+ thoughtSignature: typeof part.thoughtSignature === 'string' ? part.thoughtSignature : undefined,
265
272
  };
266
273
  continue;
267
274
  }
@@ -17,7 +17,10 @@ export type PiRole = 'user' | 'assistant' | 'tool';
17
17
  export type PiContentBlock =
18
18
  | { type: 'text'; text: string }
19
19
  | { type: 'image'; mediaType: string; data: string } // base64
20
- | { type: 'tool_use'; id: string; name: string; input: any }
20
+ // `thoughtSignature` is a Gemini 3.x thinking-model field. Pi-flavored
21
+ // providers that emit reasoning attach it to function-call parts; the API
22
+ // rejects the next turn with HTTP 400 if we don't echo it back verbatim.
23
+ | { type: 'tool_use'; id: string; name: string; input: any; thoughtSignature?: string }
21
24
  | { type: 'tool_result'; toolUseId: string; content: string; isError?: boolean };
22
25
 
23
26
  export interface PiMessage {
@@ -50,7 +53,7 @@ export type PiStopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'error' | 'a
50
53
  export type PiStreamEvent =
51
54
  | { type: 'text_delta'; delta: string }
52
55
  | { type: 'text_end'; text: string }
53
- | { type: 'tool_use'; id: string; name: string; input: any }
56
+ | { type: 'tool_use'; id: string; name: string; input: any; thoughtSignature?: string }
54
57
  | { type: 'done'; stopReason: PiStopReason; usage?: PiUsage }
55
58
  | { type: 'error'; error: string };
56
59
 
@@ -68,7 +68,7 @@ export function createPiSession(init: PiSessionInit): PiSession {
68
68
  /** One stream round — collect the assistant blocks the model emits this pass. */
69
69
  interface RoundResult {
70
70
  text: string;
71
- toolUses: { id: string; name: string; input: any }[];
71
+ toolUses: { id: string; name: string; input: any; thoughtSignature?: string }[];
72
72
  errored: boolean;
73
73
  }
74
74
 
@@ -100,7 +100,12 @@ export function createPiSession(init: PiSessionInit): PiSession {
100
100
  result.text = evt.text;
101
101
  break;
102
102
  case 'tool_use':
103
- result.toolUses.push({ id: evt.id, name: evt.name, input: evt.input });
103
+ result.toolUses.push({
104
+ id: evt.id,
105
+ name: evt.name,
106
+ input: evt.input,
107
+ thoughtSignature: evt.thoughtSignature,
108
+ });
104
109
  init.onEvent({ type: 'tool_use', id: evt.id, name: evt.name, input: evt.input });
105
110
  break;
106
111
  case 'error':
@@ -157,7 +162,15 @@ export function createPiSession(init: PiSessionInit): PiSession {
157
162
  assistantContent.push({ type: 'text', text });
158
163
  }
159
164
  for (const tu of toolUses) {
160
- assistantContent.push({ type: 'tool_use', id: tu.id, name: tu.name, input: tu.input });
165
+ assistantContent.push({
166
+ type: 'tool_use',
167
+ id: tu.id,
168
+ name: tu.name,
169
+ input: tu.input,
170
+ // Forward Gemini's thoughtSignature unchanged so the next turn's
171
+ // request echoes it back; without it the API rejects with 400.
172
+ thoughtSignature: tu.thoughtSignature,
173
+ });
161
174
  }
162
175
  if (assistantContent.length > 0) {
163
176
  messages.push({ role: 'assistant', content: assistantContent });