@contentgrowth/llm-service 0.7.9 → 0.8.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": "@contentgrowth/llm-service",
3
- "version": "0.7.9",
3
+ "version": "0.8.0",
4
4
  "description": "Unified LLM Service for Content Growth",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -113,29 +113,18 @@ export class GeminiProvider extends BaseLLMProvider {
113
113
  break;
114
114
  case 'assistant':
115
115
  role = 'model';
116
- parts = [];
117
-
118
- // Always include text if present (Thought)
119
- if (msg.content) {
120
- parts.push({ text: msg.content });
121
- }
122
-
123
- // Append tool calls if present (Call)
124
116
  if (msg.tool_calls) {
125
- const callParts = msg.tool_calls.map(tc => ({
126
- functionCall: { name: tc.function.name, args: tc.function.arguments }
127
- }));
128
- parts.push(...callParts);
129
- }
130
-
131
- // Safety fallback: if nothing was added, ensure valid structure
132
- // CRITICAL: Newer Gemini models (and SDK 1.34+) REQUIRE a non-empty thought_signature (text)
133
- // before a function call. If we have tool calls but no content, we must fabricate one.
134
- const hasText = parts.some(p => p.text);
135
- if (!hasText && msg.tool_calls) {
136
- parts.unshift({ text: "Processing tool execution..." });
137
- } else if (parts.length === 0) {
138
- parts.push({ text: "" }); // Fallback for truly empty message (shouldn't happen)
117
+ parts = msg.tool_calls.map(tc => {
118
+ const part = {
119
+ functionCall: { name: tc.function.name, args: tc.function.arguments || tc.function.args }
120
+ };
121
+ if (tc.thought_signature) {
122
+ part.thought_signature = tc.thought_signature;
123
+ }
124
+ return part;
125
+ });
126
+ } else {
127
+ parts = [{ text: msg.content || '' }];
139
128
  }
140
129
  break;
141
130
  case 'tool':
@@ -228,6 +217,10 @@ export class GeminiProvider extends BaseLLMProvider {
228
217
  }
229
218
  if (part.functionCall) {
230
219
  if (!toolCalls) toolCalls = [];
220
+ // Preserve thought_signature if present (Gemini 3 requirement)
221
+ if (part.thought_signature) {
222
+ part.functionCall.thought_signature = part.thought_signature;
223
+ }
231
224
  toolCalls.push(part.functionCall);
232
225
  }
233
226
  }
@@ -252,7 +245,11 @@ export class GeminiProvider extends BaseLLMProvider {
252
245
 
253
246
  return {
254
247
  content: textContent,
255
- tool_calls: toolCalls ? (Array.isArray(toolCalls) ? toolCalls : [toolCalls]).map(fc => ({ type: 'function', function: fc })) : null,
248
+ tool_calls: toolCalls ? (Array.isArray(toolCalls) ? toolCalls : [toolCalls]).map(fc => ({
249
+ type: 'function',
250
+ function: fc,
251
+ thought_signature: fc.thought_signature
252
+ })) : null,
256
253
  finishReason: normalizedFinishReason, // Standardized: 'completed', 'truncated', etc.
257
254
  _rawFinishReason: candidate.finishReason, // Keep original for debugging
258
255
  _responseFormat: options.responseFormat,