@core-ai/google-genai 0.4.0 → 0.5.1

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.
Files changed (2) hide show
  1. package/dist/index.js +233 -30
  2. package/package.json +2 -3
package/dist/index.js CHANGED
@@ -16,6 +16,65 @@ import {
16
16
  } from "@google/genai";
17
17
  import { zodToJsonSchema } from "zod-to-json-schema";
18
18
 
19
+ // src/model-capabilities.ts
20
+ var DEFAULT_CAPABILITIES = {
21
+ reasoning: {
22
+ thinkingParam: "thinkingBudget",
23
+ canDisableThinking: true
24
+ }
25
+ };
26
+ var MODEL_CAPABILITIES = {
27
+ "gemini-3-pro": {
28
+ reasoning: {
29
+ thinkingParam: "thinkingLevel",
30
+ canDisableThinking: false
31
+ }
32
+ },
33
+ "gemini-2.5-pro": {
34
+ reasoning: {
35
+ thinkingParam: "thinkingBudget",
36
+ canDisableThinking: false
37
+ }
38
+ },
39
+ "gemini-2.5-flash": {
40
+ reasoning: {
41
+ thinkingParam: "thinkingBudget",
42
+ canDisableThinking: true
43
+ }
44
+ },
45
+ "gemini-2.5-flash-lite": {
46
+ reasoning: {
47
+ thinkingParam: "thinkingBudget",
48
+ canDisableThinking: true
49
+ }
50
+ }
51
+ };
52
+ function getGoogleModelCapabilities(modelId) {
53
+ const normalizedModelId = normalizeModelId(modelId);
54
+ return MODEL_CAPABILITIES[normalizedModelId] ?? DEFAULT_CAPABILITIES;
55
+ }
56
+ function normalizeModelId(modelId) {
57
+ return modelId.replace(/-\d{8}$/, "");
58
+ }
59
+ function toGoogleThinkingLevel(effort) {
60
+ if (effort === "high" || effort === "max") {
61
+ return "HIGH";
62
+ }
63
+ return "LOW";
64
+ }
65
+ function toGoogleThinkingBudget(effort) {
66
+ if (effort === "minimal") {
67
+ return 1024;
68
+ }
69
+ if (effort === "low") {
70
+ return 4096;
71
+ }
72
+ if (effort === "medium") {
73
+ return 16384;
74
+ }
75
+ return 32768;
76
+ }
77
+
19
78
  // src/object-utils.ts
20
79
  function asObject(value) {
21
80
  if (value && typeof value === "object" && !Array.isArray(value)) {
@@ -46,18 +105,31 @@ function convertMessages(messages) {
46
105
  }
47
106
  if (message.role === "assistant") {
48
107
  const assistantParts = [];
49
- if (message.content) {
50
- assistantParts.push({ text: message.content });
51
- }
52
- for (const toolCall of message.toolCalls ?? []) {
53
- toolCallNameById.set(toolCall.id, toolCall.name);
54
- assistantParts.push({
55
- functionCall: {
56
- id: toolCall.id,
57
- name: toolCall.name,
58
- args: toolCall.arguments
59
- }
60
- });
108
+ for (const part of message.parts) {
109
+ if (part.type === "text") {
110
+ assistantParts.push({ text: part.text });
111
+ continue;
112
+ }
113
+ if (part.type === "tool-call") {
114
+ toolCallNameById.set(part.toolCall.id, part.toolCall.name);
115
+ assistantParts.push({
116
+ functionCall: {
117
+ id: part.toolCall.id,
118
+ name: part.toolCall.name,
119
+ args: part.toolCall.arguments
120
+ }
121
+ });
122
+ continue;
123
+ }
124
+ const thoughtPart = {
125
+ text: part.text,
126
+ thought: true
127
+ };
128
+ const thoughtSignature = part.providerMetadata?.["thoughtSignature"];
129
+ if (typeof thoughtSignature === "string") {
130
+ thoughtPart["thoughtSignature"] = thoughtSignature;
131
+ }
132
+ assistantParts.push(thoughtPart);
61
133
  }
62
134
  contents.push({
63
135
  role: "model",
@@ -177,6 +249,7 @@ function createStructuredOutputOptions(options) {
177
249
  const toolName = getStructuredOutputToolName(options);
178
250
  return {
179
251
  messages: options.messages,
252
+ reasoning: options.reasoning,
180
253
  tools: {
181
254
  structured_output: {
182
255
  name: toolName,
@@ -232,7 +305,8 @@ function createGenerateRequest(modelId, options) {
232
305
  ...options.config?.topP !== void 0 ? { topP: options.config.topP } : {},
233
306
  ...options.config?.stopSequences ? { stopSequences: options.config.stopSequences } : {},
234
307
  ...options.config?.frequencyPenalty !== void 0 ? { frequencyPenalty: options.config.frequencyPenalty } : {},
235
- ...options.config?.presencePenalty !== void 0 ? { presencePenalty: options.config.presencePenalty } : {}
308
+ ...options.config?.presencePenalty !== void 0 ? { presencePenalty: options.config.presencePenalty } : {},
309
+ ...mapReasoningToConfig(modelId, options)
236
310
  }
237
311
  };
238
312
  const providerOptions = options.providerOptions;
@@ -250,31 +324,34 @@ function createGenerateRequest(modelId, options) {
250
324
  };
251
325
  }
252
326
  function mapGenerateResponse(response) {
253
- const toolCalls = parseFunctionCalls(response.functionCalls);
327
+ const parts = extractAssistantParts(response);
328
+ const toolCalls = parts.flatMap(
329
+ (part) => part.type === "tool-call" ? [part.toolCall] : []
330
+ );
331
+ const content = parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
332
+ const reasoning = parts.flatMap((part) => part.type === "reasoning" ? [part.text] : []).join("");
254
333
  const finishReason = mapFinishReason(
255
334
  response.candidates?.[0]?.finishReason ?? void 0
256
335
  );
257
336
  if (!response.candidates?.[0]) {
258
337
  return {
259
- content: null,
338
+ parts,
339
+ content: content.length > 0 ? content : null,
340
+ reasoning: reasoning.length > 0 ? reasoning : null,
260
341
  toolCalls,
261
342
  finishReason: toolCalls.length > 0 ? "tool-calls" : finishReason,
262
343
  usage: mapUsage(response)
263
344
  };
264
345
  }
265
346
  return {
266
- content: response.text ?? null,
347
+ parts,
348
+ content: content.length > 0 ? content : null,
349
+ reasoning: reasoning.length > 0 ? reasoning : null,
267
350
  toolCalls,
268
351
  finishReason: toolCalls.length > 0 ? "tool-calls" : finishReason,
269
352
  usage: mapUsage(response)
270
353
  };
271
354
  }
272
- function parseFunctionCalls(calls) {
273
- if (!calls || calls.length === 0) {
274
- return [];
275
- }
276
- return calls.map((call, index) => mapFunctionCall(call, index));
277
- }
278
355
  function mapFunctionCall(toolCall, index) {
279
356
  return {
280
357
  id: toolCall.id ?? `tool-${index}`,
@@ -298,6 +375,7 @@ async function* transformStream(stream) {
298
375
  const bufferedToolCalls = /* @__PURE__ */ new Map();
299
376
  let finishReason = "unknown";
300
377
  let sawToolCalls = false;
378
+ let reasoningOpen = false;
301
379
  let usage = {
302
380
  inputTokens: 0,
303
381
  outputTokens: 0,
@@ -305,20 +383,45 @@ async function* transformStream(stream) {
305
383
  cacheReadTokens: 0,
306
384
  cacheWriteTokens: 0
307
385
  },
308
- outputTokenDetails: {
309
- reasoningTokens: 0
310
- }
386
+ outputTokenDetails: {}
311
387
  };
312
388
  for await (const chunk of stream) {
313
389
  usage = mapUsage(chunk, usage);
390
+ const reasoningDeltas = extractReasoningDeltas(chunk);
391
+ if (reasoningDeltas.length > 0) {
392
+ if (!reasoningOpen) {
393
+ reasoningOpen = true;
394
+ yield {
395
+ type: "reasoning-start"
396
+ };
397
+ }
398
+ for (const delta of reasoningDeltas) {
399
+ yield {
400
+ type: "reasoning-delta",
401
+ text: delta
402
+ };
403
+ }
404
+ }
314
405
  if (chunk.text) {
406
+ if (reasoningOpen) {
407
+ reasoningOpen = false;
408
+ yield {
409
+ type: "reasoning-end"
410
+ };
411
+ }
315
412
  yield {
316
- type: "content-delta",
413
+ type: "text-delta",
317
414
  text: chunk.text
318
415
  };
319
416
  }
320
417
  const functionCalls = chunk.functionCalls ?? [];
321
418
  if (functionCalls.length > 0) {
419
+ if (reasoningOpen) {
420
+ reasoningOpen = false;
421
+ yield {
422
+ type: "reasoning-end"
423
+ };
424
+ }
322
425
  sawToolCalls = true;
323
426
  for (const [index, functionCall] of functionCalls.entries()) {
324
427
  const mappedCall = mapFunctionCall(functionCall, index);
@@ -361,6 +464,11 @@ async function* transformStream(stream) {
361
464
  finishReason = candidateFinishReason;
362
465
  }
363
466
  }
467
+ if (reasoningOpen) {
468
+ yield {
469
+ type: "reasoning-end"
470
+ };
471
+ }
364
472
  for (const toolCall of bufferedToolCalls.values()) {
365
473
  yield {
366
474
  type: "tool-call-end",
@@ -376,11 +484,106 @@ async function* transformStream(stream) {
376
484
  usage
377
485
  };
378
486
  }
487
+ function mapReasoningToConfig(modelId, options) {
488
+ if (!options.reasoning) {
489
+ return {};
490
+ }
491
+ const capabilities = getGoogleModelCapabilities(modelId);
492
+ const providerConfig = asObject(options.providerOptions?.["config"]);
493
+ const providerThinkingConfig = asObject(providerConfig["thinkingConfig"]);
494
+ if (Object.keys(providerThinkingConfig).length > 0) {
495
+ return {};
496
+ }
497
+ if (capabilities.reasoning.thinkingParam === "thinkingLevel") {
498
+ return {
499
+ thinkingConfig: {
500
+ thinkingLevel: toGoogleThinkingLevel(options.reasoning.effort),
501
+ includeThoughts: true
502
+ }
503
+ };
504
+ }
505
+ return {
506
+ thinkingConfig: {
507
+ thinkingBudget: toGoogleThinkingBudget(options.reasoning.effort),
508
+ includeThoughts: true
509
+ }
510
+ };
511
+ }
512
+ function extractAssistantParts(response) {
513
+ const parts = [];
514
+ const seenToolCalls = /* @__PURE__ */ new Set();
515
+ const candidateParts = response.candidates?.[0]?.content?.parts ?? [];
516
+ for (const part of candidateParts) {
517
+ if (part.thought) {
518
+ const thoughtText = typeof part.text === "string" ? part.text : "";
519
+ if (thoughtText.length === 0) {
520
+ continue;
521
+ }
522
+ const thoughtSignature = typeof part.thoughtSignature === "string" ? part.thoughtSignature : void 0;
523
+ parts.push({
524
+ type: "reasoning",
525
+ text: thoughtText,
526
+ ...thoughtSignature ? {
527
+ providerMetadata: {
528
+ thoughtSignature
529
+ }
530
+ } : {}
531
+ });
532
+ continue;
533
+ }
534
+ if (part.functionCall) {
535
+ const toolCall = mapFunctionCall(part.functionCall, 0);
536
+ const key = `${toolCall.id}:${toolCall.name}`;
537
+ if (!seenToolCalls.has(key)) {
538
+ seenToolCalls.add(key);
539
+ parts.push({
540
+ type: "tool-call",
541
+ toolCall
542
+ });
543
+ }
544
+ continue;
545
+ }
546
+ if (typeof part.text === "string" && part.text.length > 0) {
547
+ parts.push({
548
+ type: "text",
549
+ text: part.text
550
+ });
551
+ }
552
+ }
553
+ for (const [index, functionCall] of (response.functionCalls ?? []).entries()) {
554
+ const toolCall = mapFunctionCall(functionCall, index);
555
+ const key = `${toolCall.id}:${toolCall.name}`;
556
+ if (seenToolCalls.has(key)) {
557
+ continue;
558
+ }
559
+ seenToolCalls.add(key);
560
+ parts.push({
561
+ type: "tool-call",
562
+ toolCall
563
+ });
564
+ }
565
+ if (parts.length === 0 && response.text) {
566
+ parts.push({
567
+ type: "text",
568
+ text: response.text
569
+ });
570
+ }
571
+ return parts;
572
+ }
573
+ function extractReasoningDeltas(response) {
574
+ const candidateParts = response.candidates?.[0]?.content?.parts ?? [];
575
+ return candidateParts.flatMap((part) => {
576
+ if (!part.thought || typeof part.text !== "string" || part.text.length === 0) {
577
+ return [];
578
+ }
579
+ return [part.text];
580
+ });
581
+ }
379
582
  function mapUsage(response, fallback) {
380
583
  const inputTokens = response.usageMetadata?.promptTokenCount ?? fallback?.inputTokens ?? 0;
381
584
  const textTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
382
- const reasoningTokens = response.usageMetadata?.thoughtsTokenCount ?? fallback?.outputTokenDetails.reasoningTokens ?? 0;
383
- const outputTokens = textTokens + reasoningTokens;
585
+ const reasoningTokens = response.usageMetadata?.thoughtsTokenCount ?? fallback?.outputTokenDetails?.reasoningTokens;
586
+ const outputTokens = textTokens + (reasoningTokens ?? 0);
384
587
  const cacheReadTokens = response.usageMetadata?.cachedContentTokenCount ?? fallback?.inputTokenDetails.cacheReadTokens ?? 0;
385
588
  return {
386
589
  inputTokens,
@@ -390,7 +593,7 @@ function mapUsage(response, fallback) {
390
593
  cacheWriteTokens: 0
391
594
  },
392
595
  outputTokenDetails: {
393
- reasoningTokens
596
+ ...reasoningTokens !== void 0 ? { reasoningTokens } : {}
394
597
  }
395
598
  };
396
599
  }
@@ -498,7 +701,7 @@ async function* transformStructuredOutputStream(stream, schema, provider, toolNa
498
701
  let contentBuffer = "";
499
702
  const toolArgumentDeltas = /* @__PURE__ */ new Map();
500
703
  for await (const event of stream) {
501
- if (event.type === "content-delta") {
704
+ if (event.type === "text-delta") {
502
705
  contentBuffer += event.text;
503
706
  yield {
504
707
  type: "object-delta",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/google-genai",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Google GenAI provider package for @core-ai/core-ai",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",
@@ -39,12 +39,11 @@
39
39
  "build": "tsup",
40
40
  "lint": "eslint src/ --max-warnings 0",
41
41
  "check-types": "tsc --noEmit",
42
- "prepublishOnly": "npm run build",
43
42
  "test": "vitest run",
44
43
  "test:watch": "vitest"
45
44
  },
46
45
  "dependencies": {
47
- "@core-ai/core-ai": "^0.4.0",
46
+ "@core-ai/core-ai": "^0.5.1",
48
47
  "@google/genai": "^1.42.0",
49
48
  "zod-to-json-schema": "^3.25.1"
50
49
  },