@core-ai/google-genai 0.3.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/index.js +244 -31
  2. package/package.json +2 -2
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,22 +375,53 @@ 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,
304
- reasoningTokens: 0,
305
- totalTokens: 0
382
+ inputTokenDetails: {
383
+ cacheReadTokens: 0,
384
+ cacheWriteTokens: 0
385
+ },
386
+ outputTokenDetails: {}
306
387
  };
307
388
  for await (const chunk of stream) {
308
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
+ }
309
405
  if (chunk.text) {
406
+ if (reasoningOpen) {
407
+ reasoningOpen = false;
408
+ yield {
409
+ type: "reasoning-end"
410
+ };
411
+ }
310
412
  yield {
311
- type: "content-delta",
413
+ type: "text-delta",
312
414
  text: chunk.text
313
415
  };
314
416
  }
315
417
  const functionCalls = chunk.functionCalls ?? [];
316
418
  if (functionCalls.length > 0) {
419
+ if (reasoningOpen) {
420
+ reasoningOpen = false;
421
+ yield {
422
+ type: "reasoning-end"
423
+ };
424
+ }
317
425
  sawToolCalls = true;
318
426
  for (const [index, functionCall] of functionCalls.entries()) {
319
427
  const mappedCall = mapFunctionCall(functionCall, index);
@@ -356,6 +464,11 @@ async function* transformStream(stream) {
356
464
  finishReason = candidateFinishReason;
357
465
  }
358
466
  }
467
+ if (reasoningOpen) {
468
+ yield {
469
+ type: "reasoning-end"
470
+ };
471
+ }
359
472
  for (const toolCall of bufferedToolCalls.values()) {
360
473
  yield {
361
474
  type: "tool-call-end",
@@ -371,17 +484,117 @@ async function* transformStream(stream) {
371
484
  usage
372
485
  };
373
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
+ }
374
582
  function mapUsage(response, fallback) {
375
583
  const inputTokens = response.usageMetadata?.promptTokenCount ?? fallback?.inputTokens ?? 0;
376
584
  const textTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
377
- const reasoningTokens = response.usageMetadata?.thoughtsTokenCount ?? fallback?.reasoningTokens ?? 0;
378
- const outputTokens = textTokens + reasoningTokens;
379
- const totalTokens = response.usageMetadata?.totalTokenCount ?? fallback?.totalTokens ?? inputTokens + outputTokens;
585
+ const reasoningTokens = response.usageMetadata?.thoughtsTokenCount ?? fallback?.outputTokenDetails?.reasoningTokens;
586
+ const outputTokens = textTokens + (reasoningTokens ?? 0);
587
+ const cacheReadTokens = response.usageMetadata?.cachedContentTokenCount ?? fallback?.inputTokenDetails.cacheReadTokens ?? 0;
380
588
  return {
381
589
  inputTokens,
382
590
  outputTokens,
383
- reasoningTokens,
384
- totalTokens
591
+ inputTokenDetails: {
592
+ cacheReadTokens,
593
+ cacheWriteTokens: 0
594
+ },
595
+ outputTokenDetails: {
596
+ ...reasoningTokens !== void 0 ? { reasoningTokens } : {}
597
+ }
385
598
  };
386
599
  }
387
600
 
@@ -488,7 +701,7 @@ async function* transformStructuredOutputStream(stream, schema, provider, toolNa
488
701
  let contentBuffer = "";
489
702
  const toolArgumentDeltas = /* @__PURE__ */ new Map();
490
703
  for await (const event of stream) {
491
- if (event.type === "content-delta") {
704
+ if (event.type === "text-delta") {
492
705
  contentBuffer += event.text;
493
706
  yield {
494
707
  type: "object-delta",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/google-genai",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Google GenAI provider package for @core-ai/core-ai",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",
@@ -44,7 +44,7 @@
44
44
  "test:watch": "vitest"
45
45
  },
46
46
  "dependencies": {
47
- "@core-ai/core-ai": "^0.3.0",
47
+ "@core-ai/core-ai": "^0.5.0",
48
48
  "@google/genai": "^1.42.0",
49
49
  "zod-to-json-schema": "^3.25.1"
50
50
  },