@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.
- package/dist/Gemini.d.mts +105 -0
- package/dist/Gemini.d.mts.map +1 -0
- package/dist/Gemini.mjs +170 -0
- package/dist/Gemini.mjs.map +1 -0
- package/dist/GeminiEmbedding.d.mts +64 -0
- package/dist/GeminiEmbedding.d.mts.map +1 -0
- package/dist/GeminiEmbedding.mjs +167 -0
- package/dist/GeminiEmbedding.mjs.map +1 -0
- package/dist/GeminiSynthesizer.d.mts +48 -0
- package/dist/GeminiSynthesizer.d.mts.map +1 -0
- package/dist/GeminiSynthesizer.mjs +95 -0
- package/dist/GeminiSynthesizer.mjs.map +1 -0
- package/dist/GeminiSynthesizer.test.d.mts +1 -0
- package/dist/GeminiSynthesizer.test.mjs +84 -0
- package/dist/GeminiSynthesizer.test.mjs.map +1 -0
- package/dist/GeminiTranscriber.d.mts +42 -0
- package/dist/GeminiTranscriber.d.mts.map +1 -0
- package/dist/GeminiTranscriber.mjs +80 -0
- package/dist/GeminiTranscriber.mjs.map +1 -0
- package/dist/GeminiTranscriber.test.d.mts +1 -0
- package/dist/GeminiTranscriber.test.mjs +125 -0
- package/dist/GeminiTranscriber.test.mjs.map +1 -0
- package/dist/LyriaGenerator.d.mts +57 -0
- package/dist/LyriaGenerator.d.mts.map +1 -0
- package/dist/LyriaGenerator.mjs +202 -0
- package/dist/LyriaGenerator.mjs.map +1 -0
- package/dist/LyriaGenerator.test.d.mts +1 -0
- package/dist/LyriaGenerator.test.mjs +138 -0
- package/dist/LyriaGenerator.test.mjs.map +1 -0
- package/dist/{chunk-CfYAbeIz.mjs → chunk-uyGKjUfl.mjs} +2 -1
- package/dist/codec.d.mts +107 -0
- package/dist/codec.d.mts.map +1 -0
- package/dist/codec.mjs +151 -0
- package/dist/codec.mjs.map +1 -0
- package/dist/dist-DMsg8YLb.mjs +13782 -0
- package/dist/dist-DMsg8YLb.mjs.map +1 -0
- package/dist/geminiSpeechCodec.d.mts +37 -0
- package/dist/geminiSpeechCodec.d.mts.map +1 -0
- package/dist/geminiSpeechCodec.mjs +110 -0
- package/dist/geminiSpeechCodec.mjs.map +1 -0
- package/dist/geminiSpeechCodec.test.d.mts +1 -0
- package/dist/geminiSpeechCodec.test.mjs +78 -0
- package/dist/geminiSpeechCodec.test.mjs.map +1 -0
- package/dist/index.d.mts +8 -214
- package/dist/index.mjs +8 -289
- package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
- package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
- package/dist/models.d.mts +61 -0
- package/dist/models.d.mts.map +1 -0
- package/dist/models.mjs +1 -0
- package/package.json +32 -12
- package/src/Gemini.ts +3 -3
- package/src/GeminiEmbedding.ts +351 -0
- package/src/GeminiSynthesizer.test.ts +93 -0
- package/src/GeminiSynthesizer.ts +214 -0
- package/src/GeminiTranscriber.test.ts +136 -0
- package/src/GeminiTranscriber.ts +204 -0
- package/src/LyriaGenerator.test.ts +162 -0
- package/src/LyriaGenerator.ts +394 -0
- package/src/codec.ts +42 -35
- package/src/geminiSpeechCodec.test.ts +56 -0
- package/src/geminiSpeechCodec.ts +137 -0
- package/src/index.ts +6 -2
- package/src/models.ts +104 -0
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,289 +1,8 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
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 };
|