@effect-uai/google 0.2.0 → 0.4.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 (66) hide show
  1. package/dist/Gemini.d.mts +105 -0
  2. package/dist/Gemini.d.mts.map +1 -0
  3. package/dist/Gemini.mjs +170 -0
  4. package/dist/Gemini.mjs.map +1 -0
  5. package/dist/GeminiEmbedding.d.mts +64 -0
  6. package/dist/GeminiEmbedding.d.mts.map +1 -0
  7. package/dist/GeminiEmbedding.mjs +167 -0
  8. package/dist/GeminiEmbedding.mjs.map +1 -0
  9. package/dist/GeminiSynthesizer.d.mts +48 -0
  10. package/dist/GeminiSynthesizer.d.mts.map +1 -0
  11. package/dist/GeminiSynthesizer.mjs +95 -0
  12. package/dist/GeminiSynthesizer.mjs.map +1 -0
  13. package/dist/GeminiSynthesizer.test.d.mts +1 -0
  14. package/dist/GeminiSynthesizer.test.mjs +84 -0
  15. package/dist/GeminiSynthesizer.test.mjs.map +1 -0
  16. package/dist/GeminiTranscriber.d.mts +42 -0
  17. package/dist/GeminiTranscriber.d.mts.map +1 -0
  18. package/dist/GeminiTranscriber.mjs +80 -0
  19. package/dist/GeminiTranscriber.mjs.map +1 -0
  20. package/dist/GeminiTranscriber.test.d.mts +1 -0
  21. package/dist/GeminiTranscriber.test.mjs +125 -0
  22. package/dist/GeminiTranscriber.test.mjs.map +1 -0
  23. package/dist/LyriaGenerator.d.mts +57 -0
  24. package/dist/LyriaGenerator.d.mts.map +1 -0
  25. package/dist/LyriaGenerator.mjs +202 -0
  26. package/dist/LyriaGenerator.mjs.map +1 -0
  27. package/dist/LyriaGenerator.test.d.mts +1 -0
  28. package/dist/LyriaGenerator.test.mjs +138 -0
  29. package/dist/LyriaGenerator.test.mjs.map +1 -0
  30. package/dist/{chunk-CfYAbeIz.mjs → chunk-uyGKjUfl.mjs} +2 -1
  31. package/dist/codec.d.mts +107 -0
  32. package/dist/codec.d.mts.map +1 -0
  33. package/dist/codec.mjs +151 -0
  34. package/dist/codec.mjs.map +1 -0
  35. package/dist/dist-DMsg8YLb.mjs +13782 -0
  36. package/dist/dist-DMsg8YLb.mjs.map +1 -0
  37. package/dist/geminiSpeechCodec.d.mts +37 -0
  38. package/dist/geminiSpeechCodec.d.mts.map +1 -0
  39. package/dist/geminiSpeechCodec.mjs +110 -0
  40. package/dist/geminiSpeechCodec.mjs.map +1 -0
  41. package/dist/geminiSpeechCodec.test.d.mts +1 -0
  42. package/dist/geminiSpeechCodec.test.mjs +78 -0
  43. package/dist/geminiSpeechCodec.test.mjs.map +1 -0
  44. package/dist/index.d.mts +8 -214
  45. package/dist/index.mjs +8 -289
  46. package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
  47. package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
  48. package/dist/models.d.mts +61 -0
  49. package/dist/models.d.mts.map +1 -0
  50. package/dist/models.mjs +1 -0
  51. package/package.json +32 -12
  52. package/src/Gemini.ts +3 -3
  53. package/src/GeminiEmbedding.ts +351 -0
  54. package/src/GeminiSynthesizer.test.ts +93 -0
  55. package/src/GeminiSynthesizer.ts +214 -0
  56. package/src/GeminiTranscriber.test.ts +136 -0
  57. package/src/GeminiTranscriber.ts +204 -0
  58. package/src/LyriaGenerator.test.ts +162 -0
  59. package/src/LyriaGenerator.ts +394 -0
  60. package/src/codec.ts +42 -35
  61. package/src/geminiSpeechCodec.test.ts +56 -0
  62. package/src/geminiSpeechCodec.ts +137 -0
  63. package/src/index.ts +6 -2
  64. package/src/models.ts +104 -0
  65. package/dist/index.d.mts.map +0 -1
  66. package/dist/index.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,289 +1,8 @@
1
- import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
- import { Array, Context, Effect, Layer, Match, Option, Redacted, Result, Schema, Stream, pipe } from "effect";
3
- import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
- import * as AiError from "@effect-uai/core/AiError";
5
- import { LanguageModel } from "@effect-uai/core/LanguageModel";
6
- import { JsonParseError } from "@effect-uai/core/JSONL";
7
- import * as SSE from "@effect-uai/core/SSE";
8
- import { matchType } from "@effect-uai/core/Match";
9
- //#region src/codec.ts
10
- var codec_exports = /* @__PURE__ */ __exportAll({
11
- WireChunk: () => WireChunk,
12
- accumulatorToTurn: () => accumulatorToTurn,
13
- buildRequestBody: () => buildRequestBody,
14
- emptyAccumulator: () => emptyAccumulator,
15
- ingestChunk: () => ingestChunk
16
- });
17
- const TextPart = Schema.Struct({
18
- text: Schema.String,
19
- /**
20
- * Gemini's flag for chain-of-thought parts. When `true`, the part is
21
- * reasoning trace, not the model's user-facing answer. Maps onto
22
- * `reasoning_delta { kind: "trace" }` in the canonical view.
23
- */
24
- thought: Schema.optional(Schema.Boolean)
25
- });
26
- const Part = Schema.Union([TextPart]);
27
- const Content = Schema.Struct({
28
- role: Schema.optional(Schema.String),
29
- parts: Schema.optional(Schema.Array(Part))
30
- });
31
- const Candidate = Schema.Struct({
32
- content: Schema.optional(Content),
33
- finishReason: Schema.optional(Schema.String),
34
- index: Schema.optional(Schema.Number)
35
- });
36
- const UsageMetadata = Schema.Struct({
37
- promptTokenCount: Schema.optional(Schema.Number),
38
- candidatesTokenCount: Schema.optional(Schema.Number),
39
- totalTokenCount: Schema.optional(Schema.Number),
40
- cachedContentTokenCount: Schema.optional(Schema.Number),
41
- thoughtsTokenCount: Schema.optional(Schema.Number)
42
- });
43
- const WireChunk = Schema.Struct({
44
- candidates: Schema.optional(Schema.Array(Candidate)),
45
- usageMetadata: Schema.optional(UsageMetadata)
46
- });
47
- const blockText = Match.type().pipe(matchType("input_text", (b) => b.text), matchType("input_image", () => ""), matchType("output_text", (b) => b.text), matchType("refusal", (b) => b.text), Match.exhaustive);
48
- const messageText = (message) => message.content.map(blockText).join("");
49
- const blockToParts = Match.type().pipe(matchType("input_text", (b) => b.text.length === 0 ? [] : [{ text: b.text }]), matchType("input_image", (b) => b.source._tag === "base64" ? [{ inlineData: {
50
- mimeType: b.source.media_type,
51
- data: b.source.data
52
- } }] : []), matchType("output_text", (b) => b.text.length === 0 ? [] : [{ text: b.text }]), matchType("refusal", () => []), Match.exhaustive);
53
- const messageToContent = (message) => {
54
- const parts = pipe(message.content, Array.flatMap(blockToParts));
55
- if (parts.length === 0) return Result.failVoid;
56
- return Match.value(message.role).pipe(Match.when("user", () => Result.succeed({
57
- role: "user",
58
- parts
59
- })), Match.when("assistant", () => Result.succeed({
60
- role: "model",
61
- parts
62
- })), Match.when("system", () => Result.failVoid), Match.exhaustive);
63
- };
64
- const systemMessageText = (message) => {
65
- if (message.role !== "system") return Result.failVoid;
66
- const text = messageText(message);
67
- return text.length === 0 ? Result.failVoid : Result.succeed(text);
68
- };
69
- const messages = (history) => pipe(history, Array.filterMap((item) => item.type === "message" ? Result.succeed(item) : Result.failVoid));
70
- const buildRequestBody = (history, generationConfig) => {
71
- const msgs = messages(history);
72
- const systemTexts = pipe(msgs, Array.filterMap(systemMessageText));
73
- return {
74
- contents: pipe(msgs, Array.filterMap(messageToContent)),
75
- ...systemTexts.length > 0 && { systemInstruction: { parts: [{ text: systemTexts.join("\n") }] } },
76
- ...Option.match(generationConfig, {
77
- onNone: () => ({}),
78
- onSome: (cfg) => ({ generationConfig: cfg })
79
- })
80
- };
81
- };
82
- const finishReasonToStop = (reason) => Option.match(reason, {
83
- onNone: () => "stop",
84
- onSome: (r) => r === "MAX_TOKENS" ? "max_tokens" : "stop"
85
- });
86
- const emptyAccumulator = {
87
- text: "",
88
- reasoning: "",
89
- finishReason: Option.none(),
90
- usage: {}
91
- };
92
- const chunkParts = (chunk) => pipe(chunk.candidates?.[0]?.content?.parts ?? [], Array.filterMap((p) => p.text.length === 0 ? Result.failVoid : Result.succeed({
93
- kind: p.thought === true ? "reasoning" : "text",
94
- text: p.text
95
- })));
96
- const sumByKind = (parts, kind) => pipe(parts, Array.filterMap((p) => p.kind === kind ? Result.succeed(p.text) : Result.failVoid)).join("");
97
- const mergeUsage = (prev, next) => next === void 0 ? prev : {
98
- ...prev,
99
- ...next.promptTokenCount !== void 0 && { input_tokens: next.promptTokenCount },
100
- ...next.candidatesTokenCount !== void 0 && { output_tokens: next.candidatesTokenCount },
101
- ...next.totalTokenCount !== void 0 && { total_tokens: next.totalTokenCount },
102
- ...next.cachedContentTokenCount !== void 0 && { input_tokens_details: { cached_tokens: next.cachedContentTokenCount } },
103
- ...next.thoughtsTokenCount !== void 0 && { output_tokens_details: { reasoning_tokens: next.thoughtsTokenCount } }
104
- };
105
- const ingestChunk = (acc, chunk) => {
106
- const parts = chunkParts(chunk);
107
- const finishReason = Option.fromNullishOr(chunk.candidates?.[0]?.finishReason);
108
- return {
109
- parts,
110
- finished: Option.isSome(finishReason),
111
- accumulator: {
112
- text: acc.text + sumByKind(parts, "text"),
113
- reasoning: acc.reasoning + sumByKind(parts, "reasoning"),
114
- finishReason: Option.orElse(finishReason, () => acc.finishReason),
115
- usage: mergeUsage(acc.usage, chunk.usageMetadata)
116
- }
117
- };
118
- };
119
- const accumulatorToTurn = (acc) => ({
120
- stop_reason: finishReasonToStop(acc.finishReason),
121
- usage: { ...acc.usage },
122
- items: [...acc.reasoning.length > 0 ? [{
123
- type: "reasoning",
124
- summary: acc.reasoning
125
- }] : [], ...acc.text.length === 0 ? [] : [{
126
- type: "message",
127
- role: "assistant",
128
- content: [{
129
- type: "output_text",
130
- text: acc.text
131
- }]
132
- }]]
133
- });
134
- //#endregion
135
- //#region src/Gemini.ts
136
- /**
137
- * Gemini's native event vocabulary. Aliased from `WireChunk` so the public
138
- * surface matches `@effect-uai/responses` and `@effect-uai/anthropic`. Each
139
- * `ProviderEvent` is a full `GenerateContentResponse` chunk (not a per-field
140
- * delta) - that's how Gemini's SSE works.
141
- */
142
- const ProviderEvent = WireChunk;
143
- /**
144
- * Provider-typed service tag. Yield this when you want Gemini-specific
145
- * options (`thinkingBudget`); yield the generic `LanguageModel` tag for
146
- * provider-portable code. Both are registered by `layer`.
147
- */
148
- var Gemini = class extends Context.Service()("@betalyra/effect-uai/providers/google/Gemini") {};
149
- const buildGenerationConfig = (request) => {
150
- const cfg = {
151
- ...request.temperature !== void 0 && { temperature: request.temperature },
152
- ...request.maxOutputTokens !== void 0 && { maxOutputTokens: request.maxOutputTokens },
153
- ...request.topP !== void 0 && { topP: request.topP },
154
- ...request.thinkingBudget !== void 0 && { thinkingConfig: { thinkingBudget: request.thinkingBudget } },
155
- ...request.structured !== void 0 && {
156
- responseMimeType: "application/json",
157
- responseJsonSchema: request.structured.schema["~standard"].jsonSchema.input({ target: "draft-2020-12" })
158
- }
159
- };
160
- return Object.keys(cfg).length === 0 ? Option.none() : Option.some(cfg);
161
- };
162
- const decodeChunk = Schema.decodeUnknownEffect(WireChunk);
163
- /**
164
- * Parse one SSE event's `data` payload into a `WireChunk`. Unlike the other
165
- * providers, Gemini's wire vocabulary is non-discriminated (a single
166
- * permissive struct, all fields optional) - there's no natural shape for
167
- * an `_unknown` variant, so JSON parse / schema decode failures flow
168
- * through as transport errors rather than being silently dropped.
169
- */
170
- const sseEventToChunk = (ev) => Effect.try({
171
- try: () => JSON.parse(ev.data),
172
- catch: (cause) => new JsonParseError({
173
- line: ev.data,
174
- cause
175
- })
176
- }).pipe(Effect.flatMap(decodeChunk));
177
- const httpStatusError = (status, body) => {
178
- const provider = "gemini";
179
- const raw = body;
180
- if (status === 429) return new AiError.RateLimited({
181
- provider,
182
- raw
183
- });
184
- if (status === 408 || status === 504) return new AiError.Timeout({
185
- provider,
186
- raw
187
- });
188
- if (status === 401) return new AiError.AuthFailed({
189
- provider,
190
- subtype: "auth",
191
- raw
192
- });
193
- if (status === 403) return new AiError.AuthFailed({
194
- provider,
195
- subtype: "permission",
196
- raw
197
- });
198
- if (status === 402) return new AiError.AuthFailed({
199
- provider,
200
- subtype: "billing",
201
- raw
202
- });
203
- if (status === 413) return new AiError.ContextLengthExceeded({
204
- provider,
205
- raw
206
- });
207
- if (status >= 500) return new AiError.Unavailable({
208
- provider,
209
- status,
210
- raw
211
- });
212
- return new AiError.InvalidRequest({
213
- provider,
214
- raw
215
- });
216
- };
217
- const buildNativeStream = (cfg) => {
218
- const baseUrl = cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
219
- return (request) => Stream.unwrap(Effect.gen(function* () {
220
- const client = yield* HttpClient.HttpClient;
221
- const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`;
222
- const generationConfig = buildGenerationConfig(request);
223
- const body = buildRequestBody(request.history, generationConfig);
224
- const httpRequest = HttpClientRequest.post(url).pipe(HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe(body), HttpClientRequest.accept("text/event-stream"));
225
- const response = yield* client.execute(httpRequest).pipe(Effect.mapError((cause) => new AiError.Unavailable({
226
- provider: "gemini",
227
- raw: cause
228
- })));
229
- if (response.status >= 400) {
230
- const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
231
- return Stream.fail(httpStatusError(response.status, text));
232
- }
233
- return response.stream.pipe(Stream.mapError((cause) => new AiError.Unavailable({
234
- provider: "gemini",
235
- raw: cause
236
- })), SSE.fromBytes, Stream.mapEffect((ev) => sseEventToChunk(ev).pipe(Effect.mapError((cause) => new AiError.Unavailable({
237
- provider: "gemini",
238
- raw: cause
239
- })))));
240
- }));
241
- };
242
- /**
243
- * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.
244
- * Threads a fresh `Accumulator` so chunk-level text/usage merging works
245
- * across the run.
246
- */
247
- const toCanonical = (s) => s.pipe(Stream.mapAccum(() => emptyAccumulator, (acc, chunk) => {
248
- const result = ingestChunk(acc, chunk);
249
- const partDeltas = result.parts.map((p) => p.kind === "text" ? {
250
- type: "text_delta",
251
- text: p.text
252
- } : {
253
- type: "reasoning_delta",
254
- text: p.text,
255
- kind: "trace"
256
- });
257
- const deltas = result.finished ? [...partDeltas, {
258
- type: "turn_complete",
259
- turn: accumulatorToTurn(result.accumulator)
260
- }] : partDeltas;
261
- return [result.accumulator, deltas];
262
- }));
263
- /**
264
- * Build a `GeminiService` value. For Layer-based setup, prefer `layer`.
265
- */
266
- const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => {
267
- const streamNative = (request) => buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client));
268
- return {
269
- streamNative,
270
- streamTurn: (request) => toCanonical(streamNative(request)),
271
- toCanonical
272
- };
273
- });
274
- /**
275
- * Layer that registers both the provider-specific `Gemini` tag and the
276
- * generic `LanguageModel` tag, sharing one underlying implementation.
277
- *
278
- * The generic tag accepts `CommonRequest`; the typed tag accepts the full
279
- * `GeminiRequest` surface.
280
- */
281
- const layer = (cfg) => {
282
- const typed = Layer.effect(Gemini, make(cfg));
283
- const generic = Layer.effect(LanguageModel, Effect.map(make(cfg), (s) => ({ streamTurn: (request) => s.streamTurn(request) })));
284
- return Layer.merge(typed, generic);
285
- };
286
- //#endregion
287
- export { Gemini, ProviderEvent, codec_exports as codec, layer, make, toCanonical };
288
-
289
- //# sourceMappingURL=index.mjs.map
1
+ import { t as codec_exports } from "./codec.mjs";
2
+ import { t as Gemini_exports } from "./Gemini.mjs";
3
+ import { t as GeminiEmbedding_exports } from "./GeminiEmbedding.mjs";
4
+ import { t as GeminiSynthesizer_exports } from "./GeminiSynthesizer.mjs";
5
+ import { t as GeminiTranscriber_exports } from "./GeminiTranscriber.mjs";
6
+ import { t as LyriaGenerator_exports } from "./LyriaGenerator.mjs";
7
+ import "./models.mjs";
8
+ export { Gemini_exports as Gemini, GeminiEmbedding_exports as GeminiEmbedding, GeminiSynthesizer_exports as GeminiSynthesizer, GeminiTranscriber_exports as GeminiTranscriber, LyriaGenerator_exports as LyriaGenerator, codec_exports as codec };