@core-ai/google-genai 0.2.0 → 0.3.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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +280 -87
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @core-ai/google-genai
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@core-ai/google-genai.svg)](https://www.npmjs.com/package/@core-ai/google-genai)
4
+
3
5
  Google GenAI provider package for `@core-ai/core-ai`.
4
6
 
5
7
  ## Installation
package/dist/index.js CHANGED
@@ -2,15 +2,31 @@
2
2
  import { GoogleGenAI } from "@google/genai";
3
3
 
4
4
  // src/chat-model.ts
5
- import { createStreamResult } from "@core-ai/core-ai";
5
+ import {
6
+ StructuredOutputNoObjectGeneratedError,
7
+ StructuredOutputParseError,
8
+ StructuredOutputValidationError,
9
+ createObjectStreamResult,
10
+ createStreamResult
11
+ } from "@core-ai/core-ai";
6
12
 
7
13
  // src/chat-adapter.ts
8
14
  import {
9
- ApiError,
10
15
  FunctionCallingConfigMode
11
16
  } from "@google/genai";
12
17
  import { zodToJsonSchema } from "zod-to-json-schema";
13
- import { ProviderError } from "@core-ai/core-ai";
18
+
19
+ // src/object-utils.ts
20
+ function asObject(value) {
21
+ if (value && typeof value === "object" && !Array.isArray(value)) {
22
+ return value;
23
+ }
24
+ return {};
25
+ }
26
+
27
+ // src/chat-adapter.ts
28
+ var DEFAULT_STRUCTURED_OUTPUT_TOOL_NAME = "core_ai_generate_object";
29
+ var DEFAULT_STRUCTURED_OUTPUT_TOOL_DESCRIPTION = "Return a JSON object that matches the requested schema.";
14
30
  function convertMessages(messages) {
15
31
  const systemParts = [];
16
32
  const contents = [];
@@ -101,17 +117,17 @@ function convertUserContentPart(part) {
101
117
  };
102
118
  }
103
119
  function convertTools(tools) {
104
- const functionDeclarations = Object.values(tools).map(
105
- (tool) => {
106
- const schema = zodToJsonSchema(tool.parameters);
107
- const { $schema: _schema, ...parametersJsonSchema } = schema;
108
- return {
109
- name: tool.name,
110
- description: tool.description,
111
- parametersJsonSchema
112
- };
113
- }
114
- );
120
+ const functionDeclarations = Object.values(
121
+ tools
122
+ ).map((tool) => {
123
+ const schema = zodToJsonSchema(tool.parameters);
124
+ const { $schema: _schema, ...parametersJsonSchema } = schema;
125
+ return {
126
+ name: tool.name,
127
+ description: tool.description,
128
+ parametersJsonSchema
129
+ };
130
+ });
115
131
  if (functionDeclarations.length === 0) {
116
132
  return [];
117
133
  }
@@ -150,6 +166,33 @@ function convertToolChoice(choice) {
150
166
  }
151
167
  };
152
168
  }
169
+ function getStructuredOutputToolName(options) {
170
+ const trimmedName = options.schemaName?.trim();
171
+ if (trimmedName && trimmedName.length > 0) {
172
+ return trimmedName;
173
+ }
174
+ return DEFAULT_STRUCTURED_OUTPUT_TOOL_NAME;
175
+ }
176
+ function createStructuredOutputOptions(options) {
177
+ const toolName = getStructuredOutputToolName(options);
178
+ return {
179
+ messages: options.messages,
180
+ tools: {
181
+ structured_output: {
182
+ name: toolName,
183
+ description: options.schemaDescription ?? DEFAULT_STRUCTURED_OUTPUT_TOOL_DESCRIPTION,
184
+ parameters: options.schema
185
+ }
186
+ },
187
+ toolChoice: {
188
+ type: "tool",
189
+ toolName
190
+ },
191
+ config: options.config,
192
+ providerOptions: options.providerOptions,
193
+ signal: options.signal
194
+ };
195
+ }
153
196
  function isToolResultContent(content) {
154
197
  if (content.role !== "user" || !content.parts || content.parts.length === 0) {
155
198
  return false;
@@ -282,7 +325,9 @@ async function* transformStream(stream) {
282
325
  toolCallId: mappedCall.id,
283
326
  toolName: mappedCall.name
284
327
  };
285
- const serializedArguments = JSON.stringify(mappedCall.arguments);
328
+ const serializedArguments = JSON.stringify(
329
+ mappedCall.arguments
330
+ );
286
331
  if (serializedArguments !== "{}") {
287
332
  yield {
288
333
  type: "tool-call-delta",
@@ -339,13 +384,11 @@ function mapUsage(response, fallback) {
339
384
  totalTokens
340
385
  };
341
386
  }
342
- function asObject(value) {
343
- if (value && typeof value === "object" && !Array.isArray(value)) {
344
- return value;
345
- }
346
- return {};
347
- }
348
- function wrapError(error) {
387
+
388
+ // src/google-error.ts
389
+ import { ApiError } from "@google/genai";
390
+ import { ProviderError } from "@core-ai/core-ai";
391
+ function wrapGoogleError(error) {
349
392
  if (error instanceof ApiError) {
350
393
  return new ProviderError(error.message, "google", error.status, error);
351
394
  }
@@ -359,33 +402,215 @@ function wrapError(error) {
359
402
 
360
403
  // src/chat-model.ts
361
404
  function createGoogleGenAIChatModel(client, modelId) {
405
+ const provider = "google";
406
+ async function callGenerateContentApi(request) {
407
+ try {
408
+ return await client.models.generateContent(request);
409
+ } catch (error) {
410
+ throw wrapGoogleError(error);
411
+ }
412
+ }
413
+ async function callGenerateContentStreamApi(request) {
414
+ try {
415
+ return await client.models.generateContentStream(request);
416
+ } catch (error) {
417
+ throw wrapGoogleError(error);
418
+ }
419
+ }
420
+ async function generateChat(options) {
421
+ const request = createGenerateRequest(modelId, options);
422
+ const response = await callGenerateContentApi(request);
423
+ return mapGenerateResponse(response);
424
+ }
425
+ async function streamChat(options) {
426
+ const request = createGenerateRequest(modelId, options);
427
+ const stream = await callGenerateContentStreamApi(request);
428
+ return createStreamResult(transformStream(stream));
429
+ }
362
430
  return {
363
- provider: "google",
431
+ provider,
364
432
  modelId,
365
- async generate(options) {
366
- try {
367
- const request = createGenerateRequest(modelId, options);
368
- const response = await client.models.generateContent(request);
369
- return mapGenerateResponse(response);
370
- } catch (error) {
371
- throw wrapError(error);
372
- }
433
+ generate: generateChat,
434
+ stream: streamChat,
435
+ async generateObject(options) {
436
+ const structuredOptions = createStructuredOutputOptions(options);
437
+ const result = await generateChat(structuredOptions);
438
+ const toolName = getStructuredOutputToolName(options);
439
+ const object = extractStructuredObject(
440
+ result,
441
+ options.schema,
442
+ provider,
443
+ toolName
444
+ );
445
+ return {
446
+ object,
447
+ finishReason: result.finishReason,
448
+ usage: result.usage
449
+ };
373
450
  },
374
- async stream(options) {
375
- try {
376
- const request = createGenerateRequest(modelId, options);
377
- const stream = await client.models.generateContentStream(request);
378
- return createStreamResult(transformStream(stream));
379
- } catch (error) {
380
- throw wrapError(error);
381
- }
451
+ async streamObject(options) {
452
+ const structuredOptions = createStructuredOutputOptions(options);
453
+ const stream = await streamChat(structuredOptions);
454
+ const toolName = getStructuredOutputToolName(options);
455
+ return createObjectStreamResult(
456
+ transformStructuredOutputStream(
457
+ stream,
458
+ options.schema,
459
+ provider,
460
+ toolName
461
+ )
462
+ );
382
463
  }
383
464
  };
384
465
  }
466
+ function extractStructuredObject(result, schema, provider, toolName) {
467
+ const structuredToolCall = result.toolCalls.find(
468
+ (toolCall) => toolCall.name === toolName
469
+ );
470
+ if (structuredToolCall) {
471
+ return validateStructuredToolArguments(
472
+ schema,
473
+ structuredToolCall.arguments,
474
+ provider
475
+ );
476
+ }
477
+ const rawOutput = result.content?.trim();
478
+ if (rawOutput && rawOutput.length > 0) {
479
+ return parseAndValidateStructuredPayload(schema, rawOutput, provider);
480
+ }
481
+ throw new StructuredOutputNoObjectGeneratedError(
482
+ "model did not emit a structured object payload",
483
+ provider
484
+ );
485
+ }
486
+ async function* transformStructuredOutputStream(stream, schema, provider, toolName) {
487
+ let validatedObject;
488
+ let contentBuffer = "";
489
+ const toolArgumentDeltas = /* @__PURE__ */ new Map();
490
+ for await (const event of stream) {
491
+ if (event.type === "content-delta") {
492
+ contentBuffer += event.text;
493
+ yield {
494
+ type: "object-delta",
495
+ text: event.text
496
+ };
497
+ continue;
498
+ }
499
+ if (event.type === "tool-call-delta") {
500
+ const previous = toolArgumentDeltas.get(event.toolCallId) ?? "";
501
+ toolArgumentDeltas.set(
502
+ event.toolCallId,
503
+ `${previous}${event.argumentsDelta}`
504
+ );
505
+ yield {
506
+ type: "object-delta",
507
+ text: event.argumentsDelta
508
+ };
509
+ continue;
510
+ }
511
+ if (event.type === "tool-call-end" && event.toolCall.name === toolName) {
512
+ validatedObject = validateStructuredToolArguments(
513
+ schema,
514
+ event.toolCall.arguments,
515
+ provider
516
+ );
517
+ yield {
518
+ type: "object",
519
+ object: validatedObject
520
+ };
521
+ continue;
522
+ }
523
+ if (event.type === "finish") {
524
+ if (validatedObject === void 0) {
525
+ const fallbackPayload = getFallbackStructuredPayload(
526
+ contentBuffer,
527
+ toolArgumentDeltas
528
+ );
529
+ if (!fallbackPayload) {
530
+ throw new StructuredOutputNoObjectGeneratedError(
531
+ "structured output stream ended without an object payload",
532
+ provider
533
+ );
534
+ }
535
+ validatedObject = parseAndValidateStructuredPayload(
536
+ schema,
537
+ fallbackPayload,
538
+ provider
539
+ );
540
+ yield {
541
+ type: "object",
542
+ object: validatedObject
543
+ };
544
+ }
545
+ yield {
546
+ type: "finish",
547
+ finishReason: event.finishReason,
548
+ usage: event.usage
549
+ };
550
+ }
551
+ }
552
+ }
553
+ function getFallbackStructuredPayload(contentBuffer, toolArgumentDeltas) {
554
+ for (const delta of toolArgumentDeltas.values()) {
555
+ const trimmed = delta.trim();
556
+ if (trimmed.length > 0) {
557
+ return trimmed;
558
+ }
559
+ }
560
+ const trimmedContent = contentBuffer.trim();
561
+ if (trimmedContent.length > 0) {
562
+ return trimmedContent;
563
+ }
564
+ return void 0;
565
+ }
566
+ function validateStructuredToolArguments(schema, toolArguments, provider) {
567
+ return validateStructuredObject(
568
+ schema,
569
+ toolArguments,
570
+ provider,
571
+ JSON.stringify(toolArguments)
572
+ );
573
+ }
574
+ function parseAndValidateStructuredPayload(schema, rawPayload, provider) {
575
+ const parsedPayload = parseJson(rawPayload, provider);
576
+ return validateStructuredObject(schema, parsedPayload, provider, rawPayload);
577
+ }
578
+ function parseJson(rawOutput, provider) {
579
+ try {
580
+ return JSON.parse(rawOutput);
581
+ } catch (error) {
582
+ throw new StructuredOutputParseError(
583
+ "failed to parse structured output as JSON",
584
+ provider,
585
+ {
586
+ rawOutput,
587
+ cause: error
588
+ }
589
+ );
590
+ }
591
+ }
592
+ function validateStructuredObject(schema, value, provider, rawOutput) {
593
+ const parsed = schema.safeParse(value);
594
+ if (parsed.success) {
595
+ return parsed.data;
596
+ }
597
+ throw new StructuredOutputValidationError(
598
+ "structured output does not match schema",
599
+ provider,
600
+ formatZodIssues(parsed.error.issues),
601
+ {
602
+ rawOutput
603
+ }
604
+ );
605
+ }
606
+ function formatZodIssues(issues) {
607
+ return issues.map((issue) => {
608
+ const path = issue.path.length > 0 ? issue.path.map((segment) => String(segment)).join(".") : "<root>";
609
+ return `${path}: ${issue.message}`;
610
+ });
611
+ }
385
612
 
386
613
  // src/embedding-model.ts
387
- import { ApiError as ApiError2 } from "@google/genai";
388
- import { ProviderError as ProviderError2 } from "@core-ai/core-ai";
389
614
  function createGoogleGenAIEmbeddingModel(client, modelId) {
390
615
  return {
391
616
  provider: "google",
@@ -407,48 +632,33 @@ function createGoogleGenAIEmbeddingModel(client, modelId) {
407
632
  ...providerOptions,
408
633
  config: {
409
634
  ...baseRequest.config,
410
- ...asObject2(providerOptions["config"])
635
+ ...asObject(providerOptions["config"])
411
636
  }
412
637
  } : baseRequest;
413
638
  const response = await client.models.embedContent(request);
639
+ const tokenCounts = (response.embeddings ?? []).map((item) => item.statistics?.tokenCount).filter(
640
+ (tokenCount) => typeof tokenCount === "number"
641
+ );
642
+ const usage = tokenCounts.length > 0 ? {
643
+ inputTokens: tokenCounts.reduce(
644
+ (total, tokenCount) => total + tokenCount,
645
+ 0
646
+ )
647
+ } : void 0;
414
648
  return {
415
649
  embeddings: (response.embeddings ?? []).map(
416
650
  (item) => item.values ?? []
417
651
  ),
418
- usage: {
419
- inputTokens: (response.embeddings ?? []).reduce(
420
- (total, item) => total + (item.statistics?.tokenCount ?? 0),
421
- 0
422
- )
423
- }
652
+ usage
424
653
  };
425
654
  } catch (error) {
426
- throw wrapError2(error);
655
+ throw wrapGoogleError(error);
427
656
  }
428
657
  }
429
658
  };
430
659
  }
431
- function wrapError2(error) {
432
- if (error instanceof ApiError2) {
433
- return new ProviderError2(error.message, "google", error.status, error);
434
- }
435
- return new ProviderError2(
436
- error instanceof Error ? error.message : String(error),
437
- "google",
438
- void 0,
439
- error
440
- );
441
- }
442
- function asObject2(value) {
443
- if (value && typeof value === "object" && !Array.isArray(value)) {
444
- return value;
445
- }
446
- return {};
447
- }
448
660
 
449
661
  // src/image-model.ts
450
- import { ApiError as ApiError3 } from "@google/genai";
451
- import { ProviderError as ProviderError3 } from "@core-ai/core-ai";
452
662
  function createGoogleGenAIImageModel(client, modelId) {
453
663
  return {
454
664
  provider: "google",
@@ -469,7 +679,7 @@ function createGoogleGenAIImageModel(client, modelId) {
469
679
  ...providerOptions,
470
680
  config: {
471
681
  ...baseRequest.config,
472
- ...asObject3(providerOptions["config"])
682
+ ...asObject(providerOptions["config"])
473
683
  }
474
684
  } : baseRequest;
475
685
  const response = await client.models.generateImages(request);
@@ -481,22 +691,11 @@ function createGoogleGenAIImageModel(client, modelId) {
481
691
  }))
482
692
  };
483
693
  } catch (error) {
484
- throw wrapError3(error);
694
+ throw wrapGoogleError(error);
485
695
  }
486
696
  }
487
697
  };
488
698
  }
489
- function wrapError3(error) {
490
- if (error instanceof ApiError3) {
491
- return new ProviderError3(error.message, "google", error.status, error);
492
- }
493
- return new ProviderError3(
494
- error instanceof Error ? error.message : String(error),
495
- "google",
496
- void 0,
497
- error
498
- );
499
- }
500
699
  function mapSizeToImageConfig(size) {
501
700
  if (!size) {
502
701
  return {};
@@ -531,12 +730,6 @@ function greatestCommonDivisor(a, b) {
531
730
  }
532
731
  return x === 0 ? 1 : x;
533
732
  }
534
- function asObject3(value) {
535
- if (value && typeof value === "object" && !Array.isArray(value)) {
536
- return value;
537
- }
538
- return {};
539
- }
540
733
 
541
734
  // src/provider.ts
542
735
  function createGoogleGenAI(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/google-genai",
3
- "version": "0.2.0",
3
+ "version": "0.3.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,12 +44,12 @@
44
44
  "test:watch": "vitest"
45
45
  },
46
46
  "dependencies": {
47
- "@core-ai/core-ai": "^0.2.0",
47
+ "@core-ai/core-ai": "^0.3.0",
48
48
  "@google/genai": "^1.42.0",
49
- "zod-to-json-schema": "^3.24.5"
49
+ "zod-to-json-schema": "^3.25.1"
50
50
  },
51
51
  "peerDependencies": {
52
- "zod": "^3.25.76"
52
+ "zod": "^3.25.0 || ^4.0.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@core-ai/eslint-config": "^0.0.0",