@veryfront/ext-llm-google 0.1.985
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +247 -0
- package/esm/google-provider.d.ts +26 -0
- package/esm/google-provider.d.ts.map +1 -0
- package/esm/google-provider.js +191 -0
- package/esm/google-request-builder.d.ts +94 -0
- package/esm/google-request-builder.d.ts.map +1 -0
- package/esm/google-request-builder.js +253 -0
- package/esm/google-stream.d.ts +10 -0
- package/esm/google-stream.d.ts.map +1 -0
- package/esm/google-stream.js +158 -0
- package/esm/index.d.ts +12 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +30 -0
- package/esm/package.json +3 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# @veryfront/ext-llm-google
|
|
2
|
+
|
|
3
|
+
> **Category:** LLM | **Contract:** `LLMProvider` | **Built-in**
|
|
4
|
+
|
|
5
|
+
Provides Google Gemini models for Veryfront agents and chat, enabling `google/*` models for chat and embeddings via the `LLMProviderRegistry`.
|
|
6
|
+
|
|
7
|
+
## Registration
|
|
8
|
+
|
|
9
|
+
This extension is auto-enabled by core bootstrap. Add it to `veryfront.config.ts` only when you need to override the built-in registration:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import extGoogle from "@veryfront/ext-llm-google";
|
|
13
|
+
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
extensions: [extGoogle()],
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Environment Variables
|
|
20
|
+
|
|
21
|
+
| Variable | Required | Description |
|
|
22
|
+
| ------------------------------ | -------- | ------------------------------------------------------------------------------ |
|
|
23
|
+
| `GOOGLE_API_KEY` | Yes | Your Google AI API key (from [AI Studio](https://aistudio.google.com/apikey)). |
|
|
24
|
+
| `GOOGLE_GENERATIVE_AI_API_KEY` | No | Alternative name for the API key (checked as fallback). |
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
Once credentials are configured, use `google/*` model strings anywhere Veryfront expects a model identifier:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const response = await ai.chat("google/gemini-2.5-pro", {
|
|
32
|
+
prompt: [{ role: "user", content: "Hello" }],
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Embeddings
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const result = await ai.embed("google/text-embedding-005", {
|
|
40
|
+
values: ["search query"],
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Supported Models
|
|
45
|
+
|
|
46
|
+
Any model accessible through the Google Generative Language API:
|
|
47
|
+
|
|
48
|
+
- **Flagship:** `gemini-2.5-pro`, `gemini-2.5-flash`
|
|
49
|
+
- **Stable:** `gemini-2.0-flash`, `gemini-1.5-pro`, `gemini-1.5-flash`
|
|
50
|
+
- **Embeddings:** `text-embedding-005`, `text-embedding-004`
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
The extension accepts configuration through `LLMProviderConfig` when creating runtimes:
|
|
55
|
+
|
|
56
|
+
| Option | Type | Default | Description |
|
|
57
|
+
| ------------ | -------------- | -------------------------------------------------- | -------------------------------------- |
|
|
58
|
+
| `credential` | `string` | — | API key (typically from env var). |
|
|
59
|
+
| `baseURL` | `string` | `https://generativelanguage.googleapis.com/v1beta` | API base URL override. |
|
|
60
|
+
| `name` | `string` | `"google"` | Display name for errors and telemetry. |
|
|
61
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation. |
|
|
62
|
+
|
|
63
|
+
## Model Defaults
|
|
64
|
+
|
|
65
|
+
Gemini models use the Google `generateContent` / `streamGenerateContent` endpoints. Request mapping:
|
|
66
|
+
|
|
67
|
+
- `maxOutputTokens` → `generationConfig.maxOutputTokens`
|
|
68
|
+
- `temperature` → `generationConfig.temperature`
|
|
69
|
+
- `topP` → `generationConfig.topP`
|
|
70
|
+
- `topK` → `generationConfig.topK`
|
|
71
|
+
- `stopSequences` → `generationConfig.stopSequences`
|
|
72
|
+
- `seed` → `generationConfig.seed`
|
|
73
|
+
- System messages → `systemInstruction.parts`
|
|
74
|
+
|
|
75
|
+
## Extended Thinking
|
|
76
|
+
|
|
77
|
+
Gemini 2.5+ models support extended thinking via the unified `reasoning` option:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const response = await ai.chat("google/gemini-2.5-pro", {
|
|
81
|
+
prompt: messages,
|
|
82
|
+
reasoning: { enabled: true, effort: "high" },
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Effort levels map to Gemini `thinkingConfig.thinkingBudget`:
|
|
87
|
+
|
|
88
|
+
| Effort | Budget Tokens |
|
|
89
|
+
| -------- | ------------- |
|
|
90
|
+
| `low` | 512 |
|
|
91
|
+
| `medium` | 2048 |
|
|
92
|
+
| `high` | 8192 |
|
|
93
|
+
| `max` | -1 (dynamic) |
|
|
94
|
+
|
|
95
|
+
Set `budgetTokens` directly to override the effort mapping:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
reasoning: { enabled: true, budgetTokens: 4096 }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
When thinking is enabled, Gemini returns `thought` parts that the runtime emits as `reasoning-start` / `reasoning-delta` / `reasoning-end` stream events.
|
|
102
|
+
|
|
103
|
+
## Prompt Caching
|
|
104
|
+
|
|
105
|
+
Gemini uses a separate cached-content resource model. Create a cache via the Gemini REST API or SDK, then pass the resource name on each request:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const response = await ai.chat("google/gemini-2.5-pro", {
|
|
109
|
+
prompt: messages,
|
|
110
|
+
googleCachedContent: "cachedContents/abc123",
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
When a cached content resource is attached, the response `usageMetadata.cachedContentTokenCount` is surfaced as `cacheReadInputTokens` on the result.
|
|
115
|
+
|
|
116
|
+
## Provider Tools
|
|
117
|
+
|
|
118
|
+
Gemini supports provider-native tools alongside function declarations. Use the `provider` tool type with a `google.*` id:
|
|
119
|
+
|
|
120
|
+
### Code Execution
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
tools: [
|
|
124
|
+
{ type: "provider", name: "code_execution", id: "google.code_execution", args: {} },
|
|
125
|
+
];
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Google Search
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
tools: [
|
|
132
|
+
{ type: "provider", name: "google_search", id: "google.google_search", args: {} },
|
|
133
|
+
];
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Provider tools can be combined with regular function tools in the same request. When Google Search is used, the response includes `groundingMetadata` with web search queries, grounding chunks, and citation indices.
|
|
137
|
+
|
|
138
|
+
## Safety Settings
|
|
139
|
+
|
|
140
|
+
Configure per-request safety filters via `googleSafetySettings`:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const response = await ai.chat("google/gemini-2.5-pro", {
|
|
144
|
+
prompt: messages,
|
|
145
|
+
googleSafetySettings: [
|
|
146
|
+
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_ONLY_HIGH" },
|
|
147
|
+
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
See [Gemini safety settings](https://ai.google.dev/gemini-api/docs/safety-settings) for available categories and thresholds.
|
|
153
|
+
|
|
154
|
+
## Provider Options
|
|
155
|
+
|
|
156
|
+
Pass Gemini-specific options through `providerOptions`:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const response = await ai.chat("google/gemini-2.5-pro", {
|
|
160
|
+
prompt: messages,
|
|
161
|
+
providerOptions: {
|
|
162
|
+
google: {
|
|
163
|
+
generationConfig: { responseMimeType: "application/json" },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Provider options are merged into the request body after the standard fields, allowing access to any Gemini API feature not covered by the unified interface.
|
|
170
|
+
|
|
171
|
+
## User Identification and Labels
|
|
172
|
+
|
|
173
|
+
Gemini supports per-request `labels` for tracking and attribution:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const response = await ai.chat("google/gemini-2.5-pro", {
|
|
177
|
+
prompt: messages,
|
|
178
|
+
userId: "user_42", // maps to labels.user_id
|
|
179
|
+
requestLabels: { // explicit labels (wins over userId)
|
|
180
|
+
team: "search",
|
|
181
|
+
experiment: "v2",
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
When `requestLabels` is set, it takes precedence. Otherwise, `userId` is sent as `labels.user_id`.
|
|
187
|
+
|
|
188
|
+
## Unsupported Settings
|
|
189
|
+
|
|
190
|
+
The following settings emit `unsupported-setting` warnings and are silently dropped:
|
|
191
|
+
|
|
192
|
+
| Setting | Reason |
|
|
193
|
+
| ------------------ | --------------------------------------------------------------------------------------------------- |
|
|
194
|
+
| `presencePenalty` | Gemini `generateContent` does not accept presence penalty. |
|
|
195
|
+
| `frequencyPenalty` | Gemini `generateContent` does not accept frequency penalty. |
|
|
196
|
+
| `responseFormat` | Gemini uses `generationConfig.responseMimeType` + `responseSchema` instead (use `providerOptions`). |
|
|
197
|
+
|
|
198
|
+
## Error Handling
|
|
199
|
+
|
|
200
|
+
The extension surfaces typed provider errors:
|
|
201
|
+
|
|
202
|
+
| Error Class | Trigger | Retryable |
|
|
203
|
+
| ------------------------- | ----------------------------- | --------- |
|
|
204
|
+
| `ProviderOverloadedError` | HTTP 503 | Yes |
|
|
205
|
+
| `ProviderQuotaError` | HTTP 429 `RESOURCE_EXHAUSTED` | No |
|
|
206
|
+
| `ProviderRateLimitError` | HTTP 429 with `Retry-After` | Yes |
|
|
207
|
+
| `ProviderRequestError` | Other HTTP errors | No |
|
|
208
|
+
|
|
209
|
+
If the extension is not installed and a `google/*` model is requested:
|
|
210
|
+
|
|
211
|
+
> Google provider not installed. Add @veryfront/ext-llm-google to use google/* models.
|
|
212
|
+
|
|
213
|
+
## Tool Choice
|
|
214
|
+
|
|
215
|
+
The unified `toolChoice` option maps to Gemini's `functionCallingConfig`:
|
|
216
|
+
|
|
217
|
+
| Input | Gemini Mode | Effect |
|
|
218
|
+
| -------------------------------------- | ----------- | ------------------------------------ |
|
|
219
|
+
| `"auto"` | `AUTO` | Model decides whether to call tools. |
|
|
220
|
+
| `"any"` / `"required"` | `ANY` | Model must call at least one tool. |
|
|
221
|
+
| `"none"` | `NONE` | Model must not call tools. |
|
|
222
|
+
| `{ type: "tool", name: "fn" }` | `ANY` | Pinned to one function. |
|
|
223
|
+
| `{ type: "tools", names: ["a", "b"] }` | `ANY` | Restricted to named subset. |
|
|
224
|
+
|
|
225
|
+
## Running Tests
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# From the repository root
|
|
229
|
+
deno test --no-check --allow-all extensions/ext-google/
|
|
230
|
+
|
|
231
|
+
# Or from the extension directory
|
|
232
|
+
cd extensions/ext-google
|
|
233
|
+
deno task test
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The test suite covers:
|
|
237
|
+
|
|
238
|
+
- Generate and stream request/response mapping
|
|
239
|
+
- Extended thinking (thinkingConfig budget mapping, thought-part streaming)
|
|
240
|
+
- Embedding runtime (single and batch)
|
|
241
|
+
- Error classification (503, 429 RESOURCE_EXHAUSTED)
|
|
242
|
+
- Unsupported-setting warnings (presencePenalty, frequencyPenalty)
|
|
243
|
+
- User ID and request label forwarding
|
|
244
|
+
- Tool choice normalization (auto, any, none, single-tool, multi-tool)
|
|
245
|
+
- Grounding metadata pass-through (google_search)
|
|
246
|
+
- Provider tools (code_execution, google_search)
|
|
247
|
+
- Safety settings and cached content
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google provider - implements the {@link LLMProvider} contract for Google's
|
|
3
|
+
* Generative Language API (direct + via Veryfront Cloud).
|
|
4
|
+
*
|
|
5
|
+
* Ported from `src/provider/runtime-loader.ts` as part of PR 13.
|
|
6
|
+
*
|
|
7
|
+
* @module extensions/ext-llm-google/google-provider
|
|
8
|
+
*/
|
|
9
|
+
import type { LLMProvider, LLMProviderConfig } from "veryfront/extensions/llm";
|
|
10
|
+
import type { EmbeddingRuntime, ModelRuntime } from "veryfront/provider/types";
|
|
11
|
+
import { buildProviderError, isNumberArray, mergeUsage, parseRetryAfterMs, parseSseChunk, ProviderError, ProviderOverloadedError, ProviderQuotaError, ProviderRateLimitError, ProviderRequestError, TOOL_INPUT_PENDING_THRESHOLD_MS, unwrapToolInputSchema, withToolInputStatusTransitions } from "veryfront/provider/shared";
|
|
12
|
+
export { buildProviderError, isNumberArray, mergeUsage, parseRetryAfterMs, parseSseChunk, ProviderError, ProviderOverloadedError, ProviderQuotaError, ProviderRateLimitError, ProviderRequestError, TOOL_INPUT_PENDING_THRESHOLD_MS, unwrapToolInputSchema, withToolInputStatusTransitions, };
|
|
13
|
+
export interface GoogleRuntimeConfig {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
baseURL?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
fetch?: typeof globalThis.fetch;
|
|
18
|
+
}
|
|
19
|
+
export declare function createGoogleModelRuntime(config: GoogleRuntimeConfig, modelId: string): ModelRuntime;
|
|
20
|
+
export declare function createGoogleEmbeddingRuntime(config: GoogleRuntimeConfig, modelId: string): EmbeddingRuntime;
|
|
21
|
+
export declare class GoogleProvider implements LLMProvider {
|
|
22
|
+
readonly id = "google";
|
|
23
|
+
createModel(modelId: string, config: LLMProviderConfig): ModelRuntime;
|
|
24
|
+
createEmbedding(modelId: string, config: LLMProviderConfig): EmbeddingRuntime;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=google-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-provider.d.ts","sourceRoot":"","sources":["../src/google-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EACL,kBAAkB,EAMlB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EAKpB,+BAA+B,EAC/B,qBAAqB,EACrB,8BAA8B,EAC/B,MAAM,2BAA2B,CAAC;AAgBnC,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,+BAA+B,EAC/B,qBAAqB,EACrB,8BAA8B,GAC/B,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAsFD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,MAAM,GACd,YAAY,CAkEd;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,MAAM,GACd,gBAAgB,CA6ClB;AAED,qBAAa,cAAe,YAAW,WAAW;IAChD,QAAQ,CAAC,EAAE,YAAY;IAEvB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,YAAY;IAYrE,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,gBAAgB;CAW9E"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google provider - implements the {@link LLMProvider} contract for Google's
|
|
3
|
+
* Generative Language API (direct + via Veryfront Cloud).
|
|
4
|
+
*
|
|
5
|
+
* Ported from `src/provider/runtime-loader.ts` as part of PR 13.
|
|
6
|
+
*
|
|
7
|
+
* @module extensions/ext-llm-google/google-provider
|
|
8
|
+
*/
|
|
9
|
+
import { buildProviderError, createGoogleRequestInit, createWarningCollector, getGoogleEmbeddingUrl, getGoogleGenerateContentUrl, getGoogleStreamGenerateContentUrl, isNumberArray, mergeUsage, parseRetryAfterMs, parseSseChunk, ProviderError, ProviderOverloadedError, ProviderQuotaError, ProviderRateLimitError, ProviderRequestError, readRecord, requestJson, requestStream, stringifyJsonValue, TOOL_INPUT_PENDING_THRESHOLD_MS, unwrapToolInputSchema, withToolInputStatusTransitions, } from "veryfront/provider/shared";
|
|
10
|
+
import { buildGoogleGenerateContentRequest, } from "./google-request-builder.js";
|
|
11
|
+
import { extractFirstGoogleCandidate, extractGoogleCandidateParts, extractGoogleUsage, normalizeGoogleFinishReason, streamGoogleCompatibleParts, } from "./google-stream.js";
|
|
12
|
+
// Re-export error classes so extension tests can import them from this module
|
|
13
|
+
// and from `veryfront/provider/shared` interchangeably.
|
|
14
|
+
export { buildProviderError, isNumberArray, mergeUsage, parseRetryAfterMs, parseSseChunk, ProviderError, ProviderOverloadedError, ProviderQuotaError, ProviderRateLimitError, ProviderRequestError, TOOL_INPUT_PENDING_THRESHOLD_MS, unwrapToolInputSchema, withToolInputStatusTransitions, };
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Google-specific types
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Google helper functions
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
function extractGoogleEmbedding(payload) {
|
|
22
|
+
const record = readRecord(payload);
|
|
23
|
+
const embeddings = record?.embeddings;
|
|
24
|
+
if (Array.isArray(embeddings) && embeddings.length > 0) {
|
|
25
|
+
const firstEmbedding = readRecord(embeddings[0]);
|
|
26
|
+
const values = firstEmbedding?.values;
|
|
27
|
+
if (isNumberArray(values)) {
|
|
28
|
+
return values;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const embedding = readRecord(record?.embedding);
|
|
32
|
+
const values = embedding?.values;
|
|
33
|
+
if (isNumberArray(values)) {
|
|
34
|
+
return values;
|
|
35
|
+
}
|
|
36
|
+
throw new Error("Invalid Google embedding response: embedding vector missing");
|
|
37
|
+
}
|
|
38
|
+
function extractGoogleUsageTokens(payload) {
|
|
39
|
+
const record = readRecord(payload);
|
|
40
|
+
const usageMetadata = readRecord(record?.usageMetadata);
|
|
41
|
+
const promptTokenCount = usageMetadata?.promptTokenCount;
|
|
42
|
+
return typeof promptTokenCount === "number" ? promptTokenCount : undefined;
|
|
43
|
+
}
|
|
44
|
+
function buildGoogleGenerateResult(payload) {
|
|
45
|
+
const parts = extractGoogleCandidateParts(payload);
|
|
46
|
+
const content = [];
|
|
47
|
+
for (const [index, part] of parts.entries()) {
|
|
48
|
+
if (typeof part.text === "string" && part.text.length > 0) {
|
|
49
|
+
content.push({ type: "text", text: part.text });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const functionCall = readRecord(part.functionCall);
|
|
53
|
+
if (typeof functionCall?.name === "string") {
|
|
54
|
+
content.push({
|
|
55
|
+
type: "tool-call",
|
|
56
|
+
toolCallId: typeof functionCall.id === "string" ? functionCall.id : `tool-${index}`,
|
|
57
|
+
toolName: functionCall.name,
|
|
58
|
+
input: stringifyJsonValue(functionCall.args ?? {}),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Gemini grounding (google_search / google_search_retrieval) returns
|
|
63
|
+
// a per-candidate groundingMetadata object with web search queries,
|
|
64
|
+
// grounding chunks, and citation indices into the response text.
|
|
65
|
+
// Pass it through opaquely so callers can render footnotes / source
|
|
66
|
+
// chips / "Search results" UI without parsing the wire shape.
|
|
67
|
+
const candidate = extractFirstGoogleCandidate(payload);
|
|
68
|
+
const groundingMetadata = readRecord(candidate?.groundingMetadata);
|
|
69
|
+
return {
|
|
70
|
+
content,
|
|
71
|
+
finishReason: normalizeGoogleFinishReason(candidate?.finishReason),
|
|
72
|
+
usage: extractGoogleUsage(payload),
|
|
73
|
+
...(groundingMetadata ? { groundingMetadata } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function createGoogleModelRuntime(config, modelId) {
|
|
77
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
78
|
+
return {
|
|
79
|
+
provider: config.name ?? "google",
|
|
80
|
+
modelId,
|
|
81
|
+
specificationVersion: "v3",
|
|
82
|
+
supportedUrls: {},
|
|
83
|
+
doGenerate(optionsForRuntime) {
|
|
84
|
+
const options = optionsForRuntime;
|
|
85
|
+
const url = getGoogleGenerateContentUrl(config.baseURL, modelId);
|
|
86
|
+
const warnings = createWarningCollector();
|
|
87
|
+
const body = buildGoogleGenerateContentRequest(config.name ?? "google", options, warnings);
|
|
88
|
+
return requestJson({
|
|
89
|
+
url,
|
|
90
|
+
fetchImpl,
|
|
91
|
+
providerLabel: config.name ?? "google",
|
|
92
|
+
providerKind: "google",
|
|
93
|
+
init: createGoogleRequestInit({
|
|
94
|
+
apiKey: config.apiKey,
|
|
95
|
+
extraHeaders: options.headers,
|
|
96
|
+
body: JSON.stringify(body),
|
|
97
|
+
signal: options.abortSignal,
|
|
98
|
+
}),
|
|
99
|
+
}).then((payload) => {
|
|
100
|
+
const drained = warnings.drain();
|
|
101
|
+
return {
|
|
102
|
+
...buildGoogleGenerateResult(payload),
|
|
103
|
+
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
doStream(optionsForRuntime) {
|
|
108
|
+
const options = optionsForRuntime;
|
|
109
|
+
const url = getGoogleStreamGenerateContentUrl(config.baseURL, modelId);
|
|
110
|
+
const warnings = createWarningCollector();
|
|
111
|
+
const body = buildGoogleGenerateContentRequest(config.name ?? "google", options, warnings);
|
|
112
|
+
return requestStream({
|
|
113
|
+
url,
|
|
114
|
+
fetchImpl,
|
|
115
|
+
providerLabel: config.name ?? "google",
|
|
116
|
+
providerKind: "google",
|
|
117
|
+
init: createGoogleRequestInit({
|
|
118
|
+
apiKey: config.apiKey,
|
|
119
|
+
extraHeaders: options.headers,
|
|
120
|
+
body: JSON.stringify(body),
|
|
121
|
+
signal: options.abortSignal,
|
|
122
|
+
}),
|
|
123
|
+
}).then((responseStream) => {
|
|
124
|
+
const drained = warnings.drain();
|
|
125
|
+
return {
|
|
126
|
+
stream: ReadableStream.from(withToolInputStatusTransitions(streamGoogleCompatibleParts(responseStream))),
|
|
127
|
+
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function createGoogleEmbeddingRuntime(config, modelId) {
|
|
134
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
135
|
+
return {
|
|
136
|
+
provider: config.name ?? "google",
|
|
137
|
+
modelId,
|
|
138
|
+
supportsParallelCalls: true,
|
|
139
|
+
doEmbed({ values, abortSignal }) {
|
|
140
|
+
if (values.length === 0) {
|
|
141
|
+
return Promise.resolve({
|
|
142
|
+
embeddings: [],
|
|
143
|
+
warnings: [],
|
|
144
|
+
rawResponse: { embeddings: [] },
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const url = getGoogleEmbeddingUrl(config.baseURL, modelId);
|
|
148
|
+
return Promise.all(values.map((value) => requestJson({
|
|
149
|
+
url,
|
|
150
|
+
fetchImpl,
|
|
151
|
+
providerLabel: config.name ?? "google",
|
|
152
|
+
providerKind: "google",
|
|
153
|
+
init: createGoogleRequestInit({
|
|
154
|
+
apiKey: config.apiKey,
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
content: {
|
|
157
|
+
parts: [{ text: value }],
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
signal: abortSignal,
|
|
161
|
+
}),
|
|
162
|
+
}))).then((payloads) => ({
|
|
163
|
+
embeddings: payloads.map(extractGoogleEmbedding),
|
|
164
|
+
usage: {
|
|
165
|
+
tokens: payloads.reduce((total, payload) => total + (extractGoogleUsageTokens(payload) ?? 0), 0),
|
|
166
|
+
},
|
|
167
|
+
rawResponse: payloads,
|
|
168
|
+
warnings: [],
|
|
169
|
+
}));
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export class GoogleProvider {
|
|
174
|
+
id = "google";
|
|
175
|
+
createModel(modelId, config) {
|
|
176
|
+
return createGoogleModelRuntime({
|
|
177
|
+
apiKey: config.credential,
|
|
178
|
+
baseURL: config.baseURL,
|
|
179
|
+
name: config.name ?? "google",
|
|
180
|
+
fetch: config.fetch,
|
|
181
|
+
}, modelId);
|
|
182
|
+
}
|
|
183
|
+
createEmbedding(modelId, config) {
|
|
184
|
+
return createGoogleEmbeddingRuntime({
|
|
185
|
+
apiKey: config.credential,
|
|
186
|
+
baseURL: config.baseURL,
|
|
187
|
+
name: config.name ?? "google",
|
|
188
|
+
fetch: config.fetch,
|
|
189
|
+
}, modelId);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { RuntimePromptMessage } from "veryfront/provider/shared";
|
|
2
|
+
export type RuntimeToolDefinition = {
|
|
3
|
+
type: "function";
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
inputSchema: unknown;
|
|
7
|
+
} | {
|
|
8
|
+
type: "provider";
|
|
9
|
+
name: string;
|
|
10
|
+
id: `${string}.${string}`;
|
|
11
|
+
args: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
type ProviderReasoningEffort = "low" | "medium" | "high" | "max";
|
|
14
|
+
type ProviderReasoningOption = {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
effort?: ProviderReasoningEffort;
|
|
17
|
+
budgetTokens?: number;
|
|
18
|
+
};
|
|
19
|
+
export type OpenAICompatibleLanguageOptions = {
|
|
20
|
+
prompt: RuntimePromptMessage[];
|
|
21
|
+
maxOutputTokens?: number;
|
|
22
|
+
temperature?: number;
|
|
23
|
+
topP?: number;
|
|
24
|
+
topK?: number;
|
|
25
|
+
stopSequences?: string[];
|
|
26
|
+
tools?: RuntimeToolDefinition[];
|
|
27
|
+
toolChoice?: unknown;
|
|
28
|
+
seed?: number;
|
|
29
|
+
presencePenalty?: number;
|
|
30
|
+
frequencyPenalty?: number;
|
|
31
|
+
headers?: HeadersInit;
|
|
32
|
+
providerOptions?: Record<string, unknown>;
|
|
33
|
+
includeRawChunks?: boolean;
|
|
34
|
+
abortSignal?: AbortSignal;
|
|
35
|
+
cacheControl?: unknown;
|
|
36
|
+
reasoning?: ProviderReasoningOption;
|
|
37
|
+
userId?: string;
|
|
38
|
+
requestLabels?: Record<string, string>;
|
|
39
|
+
serviceTier?: "auto" | "default" | "flex" | "scale";
|
|
40
|
+
parallelToolCalls?: boolean;
|
|
41
|
+
responseFormat?: {
|
|
42
|
+
type: "text";
|
|
43
|
+
} | {
|
|
44
|
+
type: "json";
|
|
45
|
+
} | {
|
|
46
|
+
type: "json_schema";
|
|
47
|
+
name: string;
|
|
48
|
+
schema: unknown;
|
|
49
|
+
description?: string;
|
|
50
|
+
strict?: boolean;
|
|
51
|
+
};
|
|
52
|
+
anthropicContainer?: unknown;
|
|
53
|
+
googleCachedContent?: string;
|
|
54
|
+
googleSafetySettings?: Array<{
|
|
55
|
+
category: string;
|
|
56
|
+
threshold: string;
|
|
57
|
+
}>;
|
|
58
|
+
mcpServers?: Array<Record<string, unknown>>;
|
|
59
|
+
};
|
|
60
|
+
type WarningCollector = {
|
|
61
|
+
push(warning: {
|
|
62
|
+
type: "unsupported-setting" | "other";
|
|
63
|
+
setting?: string;
|
|
64
|
+
details?: string;
|
|
65
|
+
provider: string;
|
|
66
|
+
}): void;
|
|
67
|
+
drain(): Array<{
|
|
68
|
+
type: "unsupported-setting" | "other";
|
|
69
|
+
setting?: string;
|
|
70
|
+
details?: string;
|
|
71
|
+
provider: string;
|
|
72
|
+
}>;
|
|
73
|
+
};
|
|
74
|
+
type GoogleCompatibleContent = {
|
|
75
|
+
role: "user" | "model";
|
|
76
|
+
parts: Array<Record<string, unknown>>;
|
|
77
|
+
};
|
|
78
|
+
type GoogleCompatibleRequest = {
|
|
79
|
+
contents: GoogleCompatibleContent[];
|
|
80
|
+
systemInstruction?: {
|
|
81
|
+
parts: Array<{
|
|
82
|
+
text: string;
|
|
83
|
+
}>;
|
|
84
|
+
};
|
|
85
|
+
tools?: Array<Record<string, unknown>>;
|
|
86
|
+
toolConfig?: {
|
|
87
|
+
functionCallingConfig: Record<string, unknown>;
|
|
88
|
+
};
|
|
89
|
+
generationConfig?: Record<string, unknown>;
|
|
90
|
+
[key: string]: unknown;
|
|
91
|
+
};
|
|
92
|
+
export declare function buildGoogleGenerateContentRequest(providerName: string, options: OpenAICompatibleLanguageOptions, warnings: WarningCollector): GoogleCompatibleRequest;
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=google-request-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-request-builder.d.ts","sourceRoot":"","sources":["../src/google-request-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { readProviderOptions, readRecord, unwrapToolInputSchema } from "veryfront/provider/shared";
|
|
2
|
+
function toGoogleContents(prompt) {
|
|
3
|
+
const systemParts = [];
|
|
4
|
+
const contents = [];
|
|
5
|
+
for (const message of prompt) {
|
|
6
|
+
switch (message.role) {
|
|
7
|
+
case "system":
|
|
8
|
+
if (message.content.length > 0) {
|
|
9
|
+
systemParts.push(message.content);
|
|
10
|
+
}
|
|
11
|
+
break;
|
|
12
|
+
case "user":
|
|
13
|
+
contents.push({
|
|
14
|
+
role: "user",
|
|
15
|
+
parts: toGoogleUserParts(message.content),
|
|
16
|
+
});
|
|
17
|
+
break;
|
|
18
|
+
case "assistant": {
|
|
19
|
+
const parts = [];
|
|
20
|
+
for (const part of message.content) {
|
|
21
|
+
if (part.type === "text") {
|
|
22
|
+
parts.push({ text: part.text });
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (part.type === "reasoning") {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
parts.push({
|
|
29
|
+
functionCall: {
|
|
30
|
+
id: part.toolCallId,
|
|
31
|
+
name: part.toolName,
|
|
32
|
+
args: part.input,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
contents.push({ role: "model", parts });
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "tool":
|
|
40
|
+
contents.push({
|
|
41
|
+
role: "user",
|
|
42
|
+
parts: message.content.map((part) => ({
|
|
43
|
+
functionResponse: {
|
|
44
|
+
id: part.toolCallId,
|
|
45
|
+
name: part.toolName,
|
|
46
|
+
response: {
|
|
47
|
+
result: part.output.value,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
})),
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
...(systemParts.length > 0
|
|
57
|
+
? { systemInstruction: { parts: systemParts.map((text) => ({ text })) } }
|
|
58
|
+
: {}),
|
|
59
|
+
contents,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function toGoogleUserParts(parts) {
|
|
63
|
+
const content = [];
|
|
64
|
+
for (const part of parts) {
|
|
65
|
+
if (part.type === "text") {
|
|
66
|
+
if (part.text.length > 0) {
|
|
67
|
+
content.push({ text: part.text });
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (part.type === "image" || part.mediaType.startsWith("image/")) {
|
|
72
|
+
content.push({
|
|
73
|
+
fileData: {
|
|
74
|
+
mimeType: part.mediaType,
|
|
75
|
+
fileUri: part.url,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return content;
|
|
81
|
+
}
|
|
82
|
+
function toGoogleTools(tools) {
|
|
83
|
+
if (!tools) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const functionDeclarations = [];
|
|
87
|
+
const providerEntries = [];
|
|
88
|
+
for (const tool of tools) {
|
|
89
|
+
if (tool.type === "function") {
|
|
90
|
+
functionDeclarations.push({
|
|
91
|
+
name: tool.name,
|
|
92
|
+
...(typeof tool.description === "string" ? { description: tool.description } : {}),
|
|
93
|
+
parameters: unwrapToolInputSchema(tool.inputSchema),
|
|
94
|
+
});
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!tool.id.startsWith("google.")) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const providerType = tool.id.slice("google.".length);
|
|
101
|
+
if (providerType.length === 0) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const camelKey = providerType.replace(/_([a-z])/g, (_, ch) => ch.toUpperCase());
|
|
105
|
+
providerEntries.push({ [camelKey]: tool.args ?? {} });
|
|
106
|
+
}
|
|
107
|
+
const result = [];
|
|
108
|
+
if (functionDeclarations.length > 0) {
|
|
109
|
+
result.push({ functionDeclarations });
|
|
110
|
+
}
|
|
111
|
+
result.push(...providerEntries);
|
|
112
|
+
return result.length > 0 ? result : undefined;
|
|
113
|
+
}
|
|
114
|
+
function normalizeGoogleToolChoice(toolChoice) {
|
|
115
|
+
if (toolChoice === undefined) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
if (typeof toolChoice === "string") {
|
|
119
|
+
switch (toolChoice) {
|
|
120
|
+
case "none":
|
|
121
|
+
return { functionCallingConfig: { mode: "NONE" } };
|
|
122
|
+
case "any":
|
|
123
|
+
case "required":
|
|
124
|
+
return { functionCallingConfig: { mode: "ANY" } };
|
|
125
|
+
default:
|
|
126
|
+
return { functionCallingConfig: { mode: "AUTO" } };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const record = readRecord(toolChoice);
|
|
130
|
+
if (!record)
|
|
131
|
+
return undefined;
|
|
132
|
+
if (record.type === "tool" && typeof record.name === "string") {
|
|
133
|
+
return {
|
|
134
|
+
functionCallingConfig: {
|
|
135
|
+
mode: "ANY",
|
|
136
|
+
allowedFunctionNames: [record.name],
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (record.type === "tools" && Array.isArray(record.names)) {
|
|
141
|
+
const names = record.names.filter((n) => typeof n === "string");
|
|
142
|
+
if (names.length > 0) {
|
|
143
|
+
return {
|
|
144
|
+
functionCallingConfig: {
|
|
145
|
+
mode: "ANY",
|
|
146
|
+
allowedFunctionNames: names,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (record.type === "auto") {
|
|
152
|
+
return { functionCallingConfig: { mode: "AUTO" } };
|
|
153
|
+
}
|
|
154
|
+
if (record.type === "none") {
|
|
155
|
+
return { functionCallingConfig: { mode: "NONE" } };
|
|
156
|
+
}
|
|
157
|
+
if (record.type === "any" || record.type === "required") {
|
|
158
|
+
return { functionCallingConfig: { mode: "ANY" } };
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
function resolveGoogleThinkingConfig(option) {
|
|
163
|
+
if (!option || option.enabled !== true) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const config = { includeThoughts: true };
|
|
167
|
+
if (typeof option.budgetTokens === "number") {
|
|
168
|
+
config.thinkingBudget = option.budgetTokens;
|
|
169
|
+
return config;
|
|
170
|
+
}
|
|
171
|
+
switch (option.effort) {
|
|
172
|
+
case "low":
|
|
173
|
+
config.thinkingBudget = 512;
|
|
174
|
+
break;
|
|
175
|
+
case "high":
|
|
176
|
+
config.thinkingBudget = 8192;
|
|
177
|
+
break;
|
|
178
|
+
case "max":
|
|
179
|
+
config.thinkingBudget = -1;
|
|
180
|
+
break;
|
|
181
|
+
case "medium":
|
|
182
|
+
default:
|
|
183
|
+
config.thinkingBudget = 2048;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
return config;
|
|
187
|
+
}
|
|
188
|
+
function buildGoogleGenerationConfig(options) {
|
|
189
|
+
const thinkingConfig = resolveGoogleThinkingConfig(options.reasoning);
|
|
190
|
+
const config = {
|
|
191
|
+
...(options.maxOutputTokens !== undefined ? { maxOutputTokens: options.maxOutputTokens } : {}),
|
|
192
|
+
...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
|
|
193
|
+
...(options.topP !== undefined ? { topP: options.topP } : {}),
|
|
194
|
+
...(options.topK !== undefined ? { topK: options.topK } : {}),
|
|
195
|
+
...(options.stopSequences && options.stopSequences.length > 0
|
|
196
|
+
? { stopSequences: options.stopSequences }
|
|
197
|
+
: {}),
|
|
198
|
+
...(options.seed !== undefined ? { seed: options.seed } : {}),
|
|
199
|
+
...(thinkingConfig ? { thinkingConfig } : {}),
|
|
200
|
+
};
|
|
201
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
202
|
+
}
|
|
203
|
+
export function buildGoogleGenerateContentRequest(providerName, options, warnings) {
|
|
204
|
+
if (options.presencePenalty !== undefined) {
|
|
205
|
+
warnings.push({
|
|
206
|
+
type: "unsupported-setting",
|
|
207
|
+
provider: "google",
|
|
208
|
+
setting: "presencePenalty",
|
|
209
|
+
details: "Gemini generateContent does not accept presencePenalty; the value was dropped.",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (options.frequencyPenalty !== undefined) {
|
|
213
|
+
warnings.push({
|
|
214
|
+
type: "unsupported-setting",
|
|
215
|
+
provider: "google",
|
|
216
|
+
setting: "frequencyPenalty",
|
|
217
|
+
details: "Gemini generateContent does not accept frequencyPenalty; the value was dropped.",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (options.responseFormat && options.responseFormat.type !== "text") {
|
|
221
|
+
warnings.push({
|
|
222
|
+
type: "unsupported-setting",
|
|
223
|
+
provider: "google",
|
|
224
|
+
setting: "responseFormat",
|
|
225
|
+
details: "Gemini uses generationConfig.responseMimeType + responseSchema for structured outputs, which is a separate surface and not yet wired through this option.",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const { systemInstruction, contents } = toGoogleContents(options.prompt);
|
|
229
|
+
const generationConfig = buildGoogleGenerationConfig(options);
|
|
230
|
+
const labels = options.requestLabels && Object.keys(options.requestLabels).length > 0
|
|
231
|
+
? options.requestLabels
|
|
232
|
+
: typeof options.userId === "string" && options.userId.length > 0
|
|
233
|
+
? { user_id: options.userId }
|
|
234
|
+
: undefined;
|
|
235
|
+
const body = {
|
|
236
|
+
contents,
|
|
237
|
+
...(systemInstruction ? { systemInstruction } : {}),
|
|
238
|
+
...(toGoogleTools(options.tools) ? { tools: toGoogleTools(options.tools) } : {}),
|
|
239
|
+
...(normalizeGoogleToolChoice(options.toolChoice)
|
|
240
|
+
? { toolConfig: normalizeGoogleToolChoice(options.toolChoice) }
|
|
241
|
+
: {}),
|
|
242
|
+
...(generationConfig ? { generationConfig } : {}),
|
|
243
|
+
...(labels ? { labels } : {}),
|
|
244
|
+
...(typeof options.googleCachedContent === "string" && options.googleCachedContent.length > 0
|
|
245
|
+
? { cachedContent: options.googleCachedContent }
|
|
246
|
+
: {}),
|
|
247
|
+
...(options.googleSafetySettings && options.googleSafetySettings.length > 0
|
|
248
|
+
? { safetySettings: options.googleSafetySettings }
|
|
249
|
+
: {}),
|
|
250
|
+
};
|
|
251
|
+
Object.assign(body, readProviderOptions(options.providerOptions, "google", providerName));
|
|
252
|
+
return body;
|
|
253
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RuntimeUsage } from "veryfront/provider/shared";
|
|
2
|
+
export declare function normalizeGoogleFinishReason(raw: unknown): string | {
|
|
3
|
+
unified: string;
|
|
4
|
+
raw: string;
|
|
5
|
+
} | null;
|
|
6
|
+
export declare function extractGoogleUsage(payload: unknown): RuntimeUsage | undefined;
|
|
7
|
+
export declare function extractFirstGoogleCandidate(payload: unknown): Record<string, unknown> | undefined;
|
|
8
|
+
export declare function extractGoogleCandidateParts(payload: unknown): Array<Record<string, unknown>>;
|
|
9
|
+
export declare function streamGoogleCompatibleParts(stream: ReadableStream<Uint8Array>): AsyncIterable<unknown>;
|
|
10
|
+
//# sourceMappingURL=google-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-stream.d.ts","sourceRoot":"","sources":["../src/google-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,2BAA2B,CAAC;AAEnC,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,OAAO,GACX,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgBlD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAsB7E;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAQjG;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAY5F;AAED,wBAAuB,2BAA2B,CAChD,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACjC,aAAa,CAAC,OAAO,CAAC,CAgHxB"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { parseSseChunk, readRecord, stringifyJsonValue, } from "veryfront/provider/shared";
|
|
2
|
+
export function normalizeGoogleFinishReason(raw) {
|
|
3
|
+
if (typeof raw !== "string") {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
switch (raw) {
|
|
7
|
+
case "STOP":
|
|
8
|
+
return { unified: "stop", raw };
|
|
9
|
+
case "MAX_TOKENS":
|
|
10
|
+
return { unified: "length", raw };
|
|
11
|
+
case "SAFETY":
|
|
12
|
+
case "RECITATION":
|
|
13
|
+
return { unified: "content-filter", raw };
|
|
14
|
+
default:
|
|
15
|
+
return raw.toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function extractGoogleUsage(payload) {
|
|
19
|
+
const record = readRecord(payload);
|
|
20
|
+
const usage = readRecord(record?.usageMetadata);
|
|
21
|
+
if (!usage) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const inputTokens = usage.promptTokenCount;
|
|
25
|
+
const outputTokens = usage.candidatesTokenCount;
|
|
26
|
+
const totalTokens = usage.totalTokenCount;
|
|
27
|
+
const cachedContentTokenCount = usage.cachedContentTokenCount;
|
|
28
|
+
const thoughtsTokenCount = usage.thoughtsTokenCount;
|
|
29
|
+
return {
|
|
30
|
+
inputTokens: typeof inputTokens === "number" ? inputTokens : undefined,
|
|
31
|
+
outputTokens: typeof outputTokens === "number" ? outputTokens : undefined,
|
|
32
|
+
totalTokens: typeof totalTokens === "number" ? totalTokens : undefined,
|
|
33
|
+
...(typeof cachedContentTokenCount === "number"
|
|
34
|
+
? { cacheReadInputTokens: cachedContentTokenCount }
|
|
35
|
+
: {}),
|
|
36
|
+
...(typeof thoughtsTokenCount === "number" ? { reasoningTokens: thoughtsTokenCount } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function extractFirstGoogleCandidate(payload) {
|
|
40
|
+
const record = readRecord(payload);
|
|
41
|
+
const candidates = record?.candidates;
|
|
42
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return readRecord(candidates[0]);
|
|
46
|
+
}
|
|
47
|
+
export function extractGoogleCandidateParts(payload) {
|
|
48
|
+
const candidate = extractFirstGoogleCandidate(payload);
|
|
49
|
+
const content = readRecord(candidate?.content);
|
|
50
|
+
const parts = content?.parts;
|
|
51
|
+
if (!Array.isArray(parts)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return parts.flatMap((part) => {
|
|
55
|
+
const record = readRecord(part);
|
|
56
|
+
return record ? [record] : [];
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function* streamGoogleCompatibleParts(stream) {
|
|
60
|
+
const decoder = new TextDecoder();
|
|
61
|
+
let buffer = "";
|
|
62
|
+
const seenToolCalls = new Set();
|
|
63
|
+
let reasoningId = null;
|
|
64
|
+
let reasoningIndex = 0;
|
|
65
|
+
let finishReason = null;
|
|
66
|
+
let usage;
|
|
67
|
+
for await (const chunk of stream) {
|
|
68
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
69
|
+
const parsed = parseSseChunk(buffer);
|
|
70
|
+
buffer = parsed.remainder;
|
|
71
|
+
for (const event of parsed.events) {
|
|
72
|
+
if (event === "[DONE]") {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
usage = extractGoogleUsage(event) ?? usage;
|
|
76
|
+
const candidate = extractFirstGoogleCandidate(event);
|
|
77
|
+
const normalizedFinishReason = normalizeGoogleFinishReason(candidate?.finishReason);
|
|
78
|
+
if (normalizedFinishReason) {
|
|
79
|
+
finishReason = normalizedFinishReason;
|
|
80
|
+
}
|
|
81
|
+
for (const [index, part] of extractGoogleCandidateParts(event).entries()) {
|
|
82
|
+
const isThought = part.thought === true;
|
|
83
|
+
if (isThought && typeof part.text === "string" && part.text.length > 0) {
|
|
84
|
+
if (!reasoningId) {
|
|
85
|
+
reasoningId = `reasoning-${reasoningIndex++}`;
|
|
86
|
+
yield {
|
|
87
|
+
type: "reasoning-start",
|
|
88
|
+
id: reasoningId,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
yield {
|
|
92
|
+
type: "reasoning-delta",
|
|
93
|
+
id: reasoningId,
|
|
94
|
+
delta: part.text,
|
|
95
|
+
};
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (reasoningId) {
|
|
99
|
+
yield {
|
|
100
|
+
type: "reasoning-end",
|
|
101
|
+
id: reasoningId,
|
|
102
|
+
};
|
|
103
|
+
reasoningId = null;
|
|
104
|
+
}
|
|
105
|
+
if (typeof part.text === "string" && part.text.length > 0) {
|
|
106
|
+
yield { type: "text-delta", delta: part.text };
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const functionCall = readRecord(part.functionCall);
|
|
110
|
+
if (typeof functionCall?.name !== "string") {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const toolCallId = typeof functionCall.id === "string" ? functionCall.id : `tool-${index}`;
|
|
114
|
+
if (seenToolCalls.has(toolCallId)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const serializedInput = stringifyJsonValue(functionCall.args ?? {});
|
|
118
|
+
seenToolCalls.add(toolCallId);
|
|
119
|
+
yield {
|
|
120
|
+
type: "tool-input-start",
|
|
121
|
+
id: toolCallId,
|
|
122
|
+
toolName: functionCall.name,
|
|
123
|
+
};
|
|
124
|
+
yield {
|
|
125
|
+
type: "tool-input-delta",
|
|
126
|
+
id: toolCallId,
|
|
127
|
+
delta: serializedInput,
|
|
128
|
+
};
|
|
129
|
+
yield {
|
|
130
|
+
type: "tool-call",
|
|
131
|
+
toolCallId,
|
|
132
|
+
toolName: functionCall.name,
|
|
133
|
+
input: serializedInput,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (buffer.trim().length > 0) {
|
|
139
|
+
const parsed = parseSseChunk(`${buffer}\n\n`);
|
|
140
|
+
for (const event of parsed.events) {
|
|
141
|
+
if (event === "[DONE]") {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
usage = extractGoogleUsage(event) ?? usage;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (reasoningId) {
|
|
148
|
+
yield {
|
|
149
|
+
type: "reasoning-end",
|
|
150
|
+
id: reasoningId,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
yield {
|
|
154
|
+
type: "finish",
|
|
155
|
+
finishReason,
|
|
156
|
+
...(usage ? { usage } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @veryfront/ext-llm-google — registers the Google provider into the
|
|
3
|
+
* core `LLMProviderRegistry`.
|
|
4
|
+
*
|
|
5
|
+
* @module extensions/ext-llm-google
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtensionFactory } from "veryfront/extensions";
|
|
8
|
+
import { GoogleProvider } from "./google-provider.js";
|
|
9
|
+
declare const extGoogle: ExtensionFactory;
|
|
10
|
+
export default extGoogle;
|
|
11
|
+
export { GoogleProvider };
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,QAAA,MAAM,SAAS,EAAE,gBAmBhB,CAAC;AAEF,eAAe,SAAS,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @veryfront/ext-llm-google — registers the Google provider into the
|
|
3
|
+
* core `LLMProviderRegistry`.
|
|
4
|
+
*
|
|
5
|
+
* @module extensions/ext-llm-google
|
|
6
|
+
*/
|
|
7
|
+
import { LLMProviderRegistryName } from "veryfront/extensions/llm";
|
|
8
|
+
import { GoogleProvider } from "./google-provider.js";
|
|
9
|
+
const extGoogle = () => {
|
|
10
|
+
const provider = new GoogleProvider();
|
|
11
|
+
return {
|
|
12
|
+
name: "ext-llm-google",
|
|
13
|
+
version: "0.1.0",
|
|
14
|
+
contracts: {
|
|
15
|
+
provides: ["LLMProvider:google"],
|
|
16
|
+
requires: [LLMProviderRegistryName],
|
|
17
|
+
},
|
|
18
|
+
capabilities: [],
|
|
19
|
+
setup(ctx) {
|
|
20
|
+
const registry = ctx.require(LLMProviderRegistryName);
|
|
21
|
+
registry.register(provider);
|
|
22
|
+
ctx.logger.info("[ext-llm-google] Google provider registered");
|
|
23
|
+
},
|
|
24
|
+
teardown() {
|
|
25
|
+
// No resources to release.
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export default extGoogle;
|
|
30
|
+
export { GoogleProvider };
|
package/esm/package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veryfront/ext-llm-google",
|
|
3
|
+
"version": "0.1.985",
|
|
4
|
+
"description": "Veryfront first-party extension package for ext-llm-google",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"veryfront",
|
|
7
|
+
"extension",
|
|
8
|
+
"ext-llm-google"
|
|
9
|
+
],
|
|
10
|
+
"author": "Veryfront",
|
|
11
|
+
"homepage": "https://github.com/veryfront/veryfront-code/tree/main/extensions/ext-llm-google",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/veryfront/veryfront-code.git",
|
|
15
|
+
"directory": "extensions/ext-llm-google"
|
|
16
|
+
},
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/veryfront/veryfront-code/issues"
|
|
20
|
+
},
|
|
21
|
+
"module": "./esm/index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./esm/index.js",
|
|
25
|
+
"types": "./esm/index.d.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"veryfront": {
|
|
36
|
+
"extension": true,
|
|
37
|
+
"contracts": {
|
|
38
|
+
"provides": [
|
|
39
|
+
"LLMProvider:google"
|
|
40
|
+
],
|
|
41
|
+
"requires": [
|
|
42
|
+
"LLMProviderRegistry"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"capabilities": []
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"veryfront": "^0.1.985"
|
|
49
|
+
},
|
|
50
|
+
"type": "module",
|
|
51
|
+
"types": "./esm/index.d.ts",
|
|
52
|
+
"files": [
|
|
53
|
+
"esm",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"NOTICE",
|
|
56
|
+
"README.md"
|
|
57
|
+
]
|
|
58
|
+
}
|