@effect-uai/google 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/index.d.mts +5 -214
- package/dist/index.mjs +5 -289
- package/dist/models.d.mts +27 -0
- package/dist/models.d.mts.map +1 -0
- package/dist/models.mjs +1 -0
- package/package.json +20 -12
- package/src/Gemini.ts +3 -3
- package/src/GeminiEmbedding.ts +351 -0
- package/src/codec.ts +42 -35
- package/src/index.ts +3 -2
- package/src/models.ts +20 -0
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { WireChunk } from "./codec.mjs";
|
|
2
|
+
import { GoogleModel } from "./models.mjs";
|
|
3
|
+
import { Context, Effect, Layer, Redacted, Schema, Stream } from "effect";
|
|
4
|
+
import { HttpClient } from "effect/unstable/http";
|
|
5
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
6
|
+
import { CommonRequest, LanguageModel } from "@effect-uai/core/LanguageModel";
|
|
7
|
+
import { TurnEvent } from "@effect-uai/core/Turn";
|
|
8
|
+
|
|
9
|
+
//#region src/Gemini.d.ts
|
|
10
|
+
declare namespace Gemini_d_exports {
|
|
11
|
+
export { Config, Gemini, GeminiRequest, GeminiService, ProviderEvent, layer, make, toCanonical };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Gemini's native event vocabulary. Aliased from `WireChunk` so the public
|
|
15
|
+
* surface matches `@effect-uai/responses` and `@effect-uai/anthropic`. Each
|
|
16
|
+
* `ProviderEvent` is a full `GenerateContentResponse` chunk (not a per-field
|
|
17
|
+
* delta) - that's how Gemini's SSE works.
|
|
18
|
+
*/
|
|
19
|
+
declare const ProviderEvent: Schema.Struct<{
|
|
20
|
+
readonly candidates: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
21
|
+
readonly content: Schema.optional<Schema.Struct<{
|
|
22
|
+
readonly role: Schema.optional<Schema.String>;
|
|
23
|
+
readonly parts: Schema.optional<Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
24
|
+
readonly text: Schema.String;
|
|
25
|
+
readonly thought: Schema.optional<Schema.Boolean>;
|
|
26
|
+
}>]>>>;
|
|
27
|
+
}>>;
|
|
28
|
+
readonly finishReason: Schema.optional<Schema.String>;
|
|
29
|
+
readonly index: Schema.optional<Schema.Number>;
|
|
30
|
+
}>>>;
|
|
31
|
+
readonly usageMetadata: Schema.optional<Schema.Struct<{
|
|
32
|
+
readonly promptTokenCount: Schema.optional<Schema.Number>;
|
|
33
|
+
readonly candidatesTokenCount: Schema.optional<Schema.Number>;
|
|
34
|
+
readonly totalTokenCount: Schema.optional<Schema.Number>;
|
|
35
|
+
readonly cachedContentTokenCount: Schema.optional<Schema.Number>;
|
|
36
|
+
readonly thoughtsTokenCount: Schema.optional<Schema.Number>;
|
|
37
|
+
}>>;
|
|
38
|
+
}>;
|
|
39
|
+
type ProviderEvent = WireChunk;
|
|
40
|
+
type GeminiRequest = Omit<CommonRequest, "model"> & {
|
|
41
|
+
/**
|
|
42
|
+
* Narrows `CommonRequest.model` (`string`) to the typed `GoogleModel`
|
|
43
|
+
* literal union for autocomplete.
|
|
44
|
+
*/
|
|
45
|
+
readonly model: GoogleModel;
|
|
46
|
+
/**
|
|
47
|
+
* Gemini 2.5 thinking budget. `0` disables thinking entirely (lowest
|
|
48
|
+
* latency); higher values let the model think longer. Forwarded as
|
|
49
|
+
* `generationConfig.thinkingConfig.thinkingBudget`.
|
|
50
|
+
*/
|
|
51
|
+
readonly thinkingBudget?: number;
|
|
52
|
+
};
|
|
53
|
+
type GeminiService = {
|
|
54
|
+
/**
|
|
55
|
+
* Stream the provider's native event vocabulary (post-SSE-decode). For
|
|
56
|
+
* Gemini, each event is a full `GenerateContentResponse` chunk. Use this
|
|
57
|
+
* when you need access to fields the canonical view doesn't surface
|
|
58
|
+
* (`thoughtSignature`, granular `safetyRatings`, etc.). For
|
|
59
|
+
* provider-portable code, use `streamTurn`.
|
|
60
|
+
*/
|
|
61
|
+
readonly streamNative: (request: GeminiRequest) => Stream.Stream<ProviderEvent, AiError.AiError>;
|
|
62
|
+
/**
|
|
63
|
+
* Stream canonical `TurnEvent`s. Implemented as
|
|
64
|
+
* `streamNative |> toCanonical`.
|
|
65
|
+
*/
|
|
66
|
+
readonly streamTurn: (request: GeminiRequest) => Stream.Stream<TurnEvent, AiError.AiError>;
|
|
67
|
+
/**
|
|
68
|
+
* Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.
|
|
69
|
+
* Threads a fresh `Accumulator` so chunk-level text/usage merging happens
|
|
70
|
+
* per stream.
|
|
71
|
+
*/
|
|
72
|
+
readonly toCanonical: <E, R>(s: Stream.Stream<ProviderEvent, E, R>) => Stream.Stream<TurnEvent, E, R>;
|
|
73
|
+
};
|
|
74
|
+
declare const Gemini_base: Context.ServiceClass<Gemini, "@betalyra/effect-uai/providers/google/Gemini", GeminiService>;
|
|
75
|
+
/**
|
|
76
|
+
* Provider-typed service tag. Yield this when you want Gemini-specific
|
|
77
|
+
* options (`thinkingBudget`); yield the generic `LanguageModel` tag for
|
|
78
|
+
* provider-portable code. Both are registered by `layer`.
|
|
79
|
+
*/
|
|
80
|
+
declare class Gemini extends Gemini_base {}
|
|
81
|
+
type Config = {
|
|
82
|
+
readonly apiKey: Redacted.Redacted;
|
|
83
|
+
readonly baseUrl?: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.
|
|
87
|
+
* Threads a fresh `Accumulator` so chunk-level text/usage merging works
|
|
88
|
+
* across the run.
|
|
89
|
+
*/
|
|
90
|
+
declare const toCanonical: <E, R>(s: Stream.Stream<ProviderEvent, E, R>) => Stream.Stream<TurnEvent, E, R>;
|
|
91
|
+
/**
|
|
92
|
+
* Build a `GeminiService` value. For Layer-based setup, prefer `layer`.
|
|
93
|
+
*/
|
|
94
|
+
declare const make: (cfg: Config) => Effect.Effect<GeminiService, never, HttpClient.HttpClient>;
|
|
95
|
+
/**
|
|
96
|
+
* Layer that registers both the provider-specific `Gemini` tag and the
|
|
97
|
+
* generic `LanguageModel` tag, sharing one underlying implementation.
|
|
98
|
+
*
|
|
99
|
+
* The generic tag accepts `CommonRequest`; the typed tag accepts the full
|
|
100
|
+
* `GeminiRequest` surface.
|
|
101
|
+
*/
|
|
102
|
+
declare const layer: (cfg: Config) => Layer.Layer<Gemini | LanguageModel, never, HttpClient.HttpClient>;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { Config, Gemini, GeminiRequest, GeminiService, ProviderEvent, layer, make, Gemini_d_exports as t, toCanonical };
|
|
105
|
+
//# sourceMappingURL=Gemini.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Gemini.d.mts","names":[],"sources":["../src/Gemini.ts"],"mappings":";;;;;;;;;;;;;;;;;;cA2Ba,aAAA,EAAa,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;KACd,aAAA,GAAgB,SAAA;AAAA,KAMhB,aAAA,GAAgB,IAAA,CAAK,aAAA;;;;;WAKtB,KAAA,EAAO,WAAA;;;;;;WAMP,cAAA;AAAA;AAAA,KAGC,aAAA;;;;;;;;WAQD,YAAA,GAAe,OAAA,EAAS,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EA7BhE;;;;EAAA,SAkCf,UAAA,GAAa,OAAA,EAAS,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;;;;;;WAMzE,WAAA,SACP,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAChC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;AAAA;AAAA,cAClC,WAAA;;;;;;cAOY,MAAA,SAAe,WAAA;AAAA,KAIhB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;;cAiHE,WAAA,SACX,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;;;;cAgClB,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,aAAA,SAAsB,UAAA,CAAW,UAAA;;;;;;;;cAkBrE,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,aAAA,SAAsB,UAAA,CAAW,UAAA"}
|
package/dist/Gemini.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
+
import { WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, ingestChunk } from "./codec.mjs";
|
|
3
|
+
import { Context, Effect, Layer, Option, Redacted, Schema, Stream } from "effect";
|
|
4
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
5
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
6
|
+
import { LanguageModel } from "@effect-uai/core/LanguageModel";
|
|
7
|
+
import { JsonParseError } from "@effect-uai/core/JSONL";
|
|
8
|
+
import * as SSE from "@effect-uai/core/SSE";
|
|
9
|
+
//#region src/Gemini.ts
|
|
10
|
+
var Gemini_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
Gemini: () => Gemini,
|
|
12
|
+
ProviderEvent: () => ProviderEvent,
|
|
13
|
+
layer: () => layer,
|
|
14
|
+
make: () => make,
|
|
15
|
+
toCanonical: () => toCanonical
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Gemini's native event vocabulary. Aliased from `WireChunk` so the public
|
|
19
|
+
* surface matches `@effect-uai/responses` and `@effect-uai/anthropic`. Each
|
|
20
|
+
* `ProviderEvent` is a full `GenerateContentResponse` chunk (not a per-field
|
|
21
|
+
* delta) - that's how Gemini's SSE works.
|
|
22
|
+
*/
|
|
23
|
+
const ProviderEvent = WireChunk;
|
|
24
|
+
/**
|
|
25
|
+
* Provider-typed service tag. Yield this when you want Gemini-specific
|
|
26
|
+
* options (`thinkingBudget`); yield the generic `LanguageModel` tag for
|
|
27
|
+
* provider-portable code. Both are registered by `layer`.
|
|
28
|
+
*/
|
|
29
|
+
var Gemini = class extends Context.Service()("@betalyra/effect-uai/providers/google/Gemini") {};
|
|
30
|
+
const buildGenerationConfig = (request) => {
|
|
31
|
+
const cfg = {
|
|
32
|
+
...request.temperature !== void 0 && { temperature: request.temperature },
|
|
33
|
+
...request.maxOutputTokens !== void 0 && { maxOutputTokens: request.maxOutputTokens },
|
|
34
|
+
...request.topP !== void 0 && { topP: request.topP },
|
|
35
|
+
...request.thinkingBudget !== void 0 && { thinkingConfig: { thinkingBudget: request.thinkingBudget } },
|
|
36
|
+
...request.structured !== void 0 && {
|
|
37
|
+
responseMimeType: "application/json",
|
|
38
|
+
responseJsonSchema: request.structured.schema["~standard"].jsonSchema.input({ target: "draft-2020-12" })
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return Object.keys(cfg).length === 0 ? Option.none() : Option.some(cfg);
|
|
42
|
+
};
|
|
43
|
+
const decodeChunk = Schema.decodeUnknownEffect(WireChunk);
|
|
44
|
+
/**
|
|
45
|
+
* Parse one SSE event's `data` payload into a `WireChunk`. Unlike the other
|
|
46
|
+
* providers, Gemini's wire vocabulary is non-discriminated (a single
|
|
47
|
+
* permissive struct, all fields optional) - there's no natural shape for
|
|
48
|
+
* an `_unknown` variant, so JSON parse / schema decode failures flow
|
|
49
|
+
* through as transport errors rather than being silently dropped.
|
|
50
|
+
*/
|
|
51
|
+
const sseEventToChunk = (ev) => Effect.try({
|
|
52
|
+
try: () => JSON.parse(ev.data),
|
|
53
|
+
catch: (cause) => new JsonParseError({
|
|
54
|
+
line: ev.data,
|
|
55
|
+
cause
|
|
56
|
+
})
|
|
57
|
+
}).pipe(Effect.flatMap(decodeChunk));
|
|
58
|
+
const httpStatusError = (status, body) => {
|
|
59
|
+
const provider = "gemini";
|
|
60
|
+
const raw = body;
|
|
61
|
+
if (status === 429) return new AiError.RateLimited({
|
|
62
|
+
provider,
|
|
63
|
+
raw
|
|
64
|
+
});
|
|
65
|
+
if (status === 408 || status === 504) return new AiError.Timeout({
|
|
66
|
+
provider,
|
|
67
|
+
raw
|
|
68
|
+
});
|
|
69
|
+
if (status === 401) return new AiError.AuthFailed({
|
|
70
|
+
provider,
|
|
71
|
+
subtype: "auth",
|
|
72
|
+
raw
|
|
73
|
+
});
|
|
74
|
+
if (status === 403) return new AiError.AuthFailed({
|
|
75
|
+
provider,
|
|
76
|
+
subtype: "permission",
|
|
77
|
+
raw
|
|
78
|
+
});
|
|
79
|
+
if (status === 402) return new AiError.AuthFailed({
|
|
80
|
+
provider,
|
|
81
|
+
subtype: "billing",
|
|
82
|
+
raw
|
|
83
|
+
});
|
|
84
|
+
if (status === 413) return new AiError.ContextLengthExceeded({
|
|
85
|
+
provider,
|
|
86
|
+
raw
|
|
87
|
+
});
|
|
88
|
+
if (status >= 500) return new AiError.Unavailable({
|
|
89
|
+
provider,
|
|
90
|
+
status,
|
|
91
|
+
raw
|
|
92
|
+
});
|
|
93
|
+
return new AiError.InvalidRequest({
|
|
94
|
+
provider,
|
|
95
|
+
raw
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const buildNativeStream = (cfg) => {
|
|
99
|
+
const baseUrl = cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
100
|
+
return (request) => Stream.unwrap(Effect.gen(function* () {
|
|
101
|
+
const client = yield* HttpClient.HttpClient;
|
|
102
|
+
const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`;
|
|
103
|
+
const generationConfig = buildGenerationConfig(request);
|
|
104
|
+
const body = buildRequestBody(request.history, generationConfig);
|
|
105
|
+
const httpRequest = HttpClientRequest.post(url).pipe(HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe(body), HttpClientRequest.accept("text/event-stream"));
|
|
106
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError((cause) => new AiError.Unavailable({
|
|
107
|
+
provider: "gemini",
|
|
108
|
+
raw: cause
|
|
109
|
+
})));
|
|
110
|
+
if (response.status >= 400) {
|
|
111
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
112
|
+
return Stream.fail(httpStatusError(response.status, text));
|
|
113
|
+
}
|
|
114
|
+
return response.stream.pipe(Stream.mapError((cause) => new AiError.Unavailable({
|
|
115
|
+
provider: "gemini",
|
|
116
|
+
raw: cause
|
|
117
|
+
})), SSE.fromBytes, Stream.mapEffect((ev) => sseEventToChunk(ev).pipe(Effect.mapError((cause) => new AiError.Unavailable({
|
|
118
|
+
provider: "gemini",
|
|
119
|
+
raw: cause
|
|
120
|
+
})))));
|
|
121
|
+
}));
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.
|
|
125
|
+
* Threads a fresh `Accumulator` so chunk-level text/usage merging works
|
|
126
|
+
* across the run.
|
|
127
|
+
*/
|
|
128
|
+
const toCanonical = (s) => s.pipe(Stream.mapAccum(() => emptyAccumulator, (acc, chunk) => {
|
|
129
|
+
const result = ingestChunk(acc, chunk);
|
|
130
|
+
const partDeltas = result.parts.map((p) => p.kind === "text" ? {
|
|
131
|
+
type: "text_delta",
|
|
132
|
+
text: p.text
|
|
133
|
+
} : {
|
|
134
|
+
type: "reasoning_delta",
|
|
135
|
+
text: p.text,
|
|
136
|
+
kind: "trace"
|
|
137
|
+
});
|
|
138
|
+
const deltas = result.finished ? [...partDeltas, {
|
|
139
|
+
type: "turn_complete",
|
|
140
|
+
turn: accumulatorToTurn(result.accumulator)
|
|
141
|
+
}] : partDeltas;
|
|
142
|
+
return [result.accumulator, deltas];
|
|
143
|
+
}));
|
|
144
|
+
/**
|
|
145
|
+
* Build a `GeminiService` value. For Layer-based setup, prefer `layer`.
|
|
146
|
+
*/
|
|
147
|
+
const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => {
|
|
148
|
+
const streamNative = (request) => buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client));
|
|
149
|
+
return {
|
|
150
|
+
streamNative,
|
|
151
|
+
streamTurn: (request) => toCanonical(streamNative(request)),
|
|
152
|
+
toCanonical
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
/**
|
|
156
|
+
* Layer that registers both the provider-specific `Gemini` tag and the
|
|
157
|
+
* generic `LanguageModel` tag, sharing one underlying implementation.
|
|
158
|
+
*
|
|
159
|
+
* The generic tag accepts `CommonRequest`; the typed tag accepts the full
|
|
160
|
+
* `GeminiRequest` surface.
|
|
161
|
+
*/
|
|
162
|
+
const layer = (cfg) => {
|
|
163
|
+
const typed = Layer.effect(Gemini, make(cfg));
|
|
164
|
+
const generic = Layer.effect(LanguageModel, Effect.map(make(cfg), (s) => ({ streamTurn: (request) => s.streamTurn(request) })));
|
|
165
|
+
return Layer.merge(typed, generic);
|
|
166
|
+
};
|
|
167
|
+
//#endregion
|
|
168
|
+
export { Gemini, ProviderEvent, layer, make, Gemini_exports as t, toCanonical };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=Gemini.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Gemini.mjs","names":[],"sources":["../src/Gemini.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n} from \"@effect-uai/core/LanguageModel\"\nimport { JsonParseError } from \"@effect-uai/core/JSONL\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport type { TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type GenerationConfig,\n WireChunk,\n accumulatorToTurn,\n buildRequestBody,\n emptyAccumulator,\n ingestChunk,\n} from \"./codec.js\"\nimport type { GoogleModel } from \"./models.js\"\n\n/**\n * Gemini's native event vocabulary. Aliased from `WireChunk` so the public\n * surface matches `@effect-uai/responses` and `@effect-uai/anthropic`. Each\n * `ProviderEvent` is a full `GenerateContentResponse` chunk (not a per-field\n * delta) - that's how Gemini's SSE works.\n */\nexport const ProviderEvent = WireChunk\nexport type ProviderEvent = WireChunk\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type GeminiRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `GoogleModel`\n * literal union for autocomplete.\n */\n readonly model: GoogleModel\n /**\n * Gemini 2.5 thinking budget. `0` disables thinking entirely (lowest\n * latency); higher values let the model think longer. Forwarded as\n * `generationConfig.thinkingConfig.thinkingBudget`.\n */\n readonly thinkingBudget?: number\n}\n\nexport type GeminiService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode). For\n * Gemini, each event is a full `GenerateContentResponse` chunk. Use this\n * when you need access to fields the canonical view doesn't surface\n * (`thoughtSignature`, granular `safetyRatings`, etc.). For\n * provider-portable code, use `streamTurn`.\n */\n readonly streamNative: (request: GeminiRequest) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: GeminiRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` so chunk-level text/usage merging happens\n * per stream.\n */\n readonly toCanonical: <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n ) => Stream.Stream<TurnEvent, E, R>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Gemini-specific\n * options (`thinkingBudget`); yield the generic `LanguageModel` tag for\n * provider-portable code. Both are registered by `layer`.\n */\nexport class Gemini extends Context.Service<Gemini, GeminiService>()(\n \"@betalyra/effect-uai/providers/google/Gemini\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst buildGenerationConfig = (request: GeminiRequest): Option.Option<GenerationConfig> => {\n const cfg: GenerationConfig = {\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && { maxOutputTokens: request.maxOutputTokens }),\n ...(request.topP !== undefined && { topP: request.topP }),\n ...(request.thinkingBudget !== undefined && {\n thinkingConfig: { thinkingBudget: request.thinkingBudget },\n }),\n ...(request.structured !== undefined && {\n responseMimeType: \"application/json\",\n responseJsonSchema: request.structured.schema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n }),\n }),\n }\n return Object.keys(cfg).length === 0 ? Option.none() : Option.some(cfg)\n}\n\n// ---------------------------------------------------------------------------\n// SSE event → wire chunk\n// ---------------------------------------------------------------------------\n\nconst decodeChunk = Schema.decodeUnknownEffect(WireChunk)\n\n/**\n * Parse one SSE event's `data` payload into a `WireChunk`. Unlike the other\n * providers, Gemini's wire vocabulary is non-discriminated (a single\n * permissive struct, all fields optional) - there's no natural shape for\n * an `_unknown` variant, so JSON parse / schema decode failures flow\n * through as transport errors rather than being silently dropped.\n */\nconst sseEventToChunk = (ev: SSE.Event) =>\n Effect.try({\n try: () => JSON.parse(ev.data) as unknown,\n catch: (cause) => new JsonParseError({ line: ev.data, cause }),\n }).pipe(Effect.flatMap(decodeChunk))\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const baseUrl = cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n return (\n request: GeminiRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const url = `${baseUrl}/models/${request.model}:streamGenerateContent?alt=sse`\n const generationConfig = buildGenerationConfig(request)\n const body = buildRequestBody(request.history, generationConfig)\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError => new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect((ev) =>\n sseEventToChunk(ev).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause }),\n ),\n ),\n ),\n )\n }),\n )\n}\n\n/**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `Accumulator` so chunk-level text/usage merging works\n * across the run.\n */\nexport const toCanonical = <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n): Stream.Stream<TurnEvent, E, R> =>\n s.pipe(\n Stream.mapAccum(\n () => emptyAccumulator,\n (acc, chunk) => {\n const result = ingestChunk(acc, chunk)\n const partDeltas = result.parts.map((p) =>\n p.kind === \"text\"\n ? { type: \"text_delta\" as const, text: p.text }\n : { type: \"reasoning_delta\" as const, text: p.text, kind: \"trace\" as const },\n )\n const deltas = result.finished\n ? [\n ...partDeltas,\n {\n type: \"turn_complete\" as const,\n turn: accumulatorToTurn(result.accumulator),\n },\n ]\n : partDeltas\n return [result.accumulator, deltas] as const\n },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `GeminiService` value. For Layer-based setup, prefer `layer`.\n */\nexport const make = (cfg: Config): Effect.Effect<GeminiService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => {\n const streamNative: GeminiService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamNative,\n streamTurn: (request) => toCanonical(streamNative(request)),\n toCanonical,\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Gemini` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n *\n * The generic tag accepts `CommonRequest`; the typed tag accepts the full\n * `GeminiRequest` surface.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Gemini | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Gemini, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as GeminiRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { GoogleEmbeddingModel } from "./models.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Redacted } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { CommonEmbedRequest, EmbedManyResponse, EmbedResponse, EmbeddingModel } from "@effect-uai/core/EmbeddingModel";
|
|
6
|
+
import { EmbedInput } from "@effect-uai/core/Embedding";
|
|
7
|
+
|
|
8
|
+
//#region src/GeminiEmbedding.d.ts
|
|
9
|
+
declare namespace GeminiEmbedding_d_exports {
|
|
10
|
+
export { Config, GeminiEmbedManyRequest, GeminiEmbedRequest, GeminiEmbedding, GeminiEmbeddingService, GoogleEmbeddingTask, layer, make };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gemini's task-type enum, exposed under our friendly names. Mapped to the
|
|
14
|
+
* wire `taskType` values inside the codec. Honoured by
|
|
15
|
+
* `gemini-embedding-001`; ignored by `gemini-embedding-2` (which expects
|
|
16
|
+
* task instructions in the prompt text instead).
|
|
17
|
+
*/
|
|
18
|
+
type GoogleEmbeddingTask = "query" | "document" | "similarity" | "classification" | "clustering" | "qa" | "fact_verification" | "code_query";
|
|
19
|
+
type GeminiEmbedRequest = Omit<CommonEmbedRequest, "model" | "task" | "encoding"> & {
|
|
20
|
+
/** Narrows `CommonEmbedRequest.model` to the typed Google union. */readonly model: GoogleEmbeddingModel;
|
|
21
|
+
/**
|
|
22
|
+
* Widens the cross-provider `"query" | "document"` to the full Google
|
|
23
|
+
* task enum. Optional. Honoured by `gemini-embedding-001`; ignored by
|
|
24
|
+
* `gemini-embedding-2`.
|
|
25
|
+
*/
|
|
26
|
+
readonly task?: GoogleEmbeddingTask;
|
|
27
|
+
/**
|
|
28
|
+
* Optional document title. Only meaningful for `RETRIEVAL_DOCUMENT`
|
|
29
|
+
* tasks on `gemini-embedding-001`.
|
|
30
|
+
*/
|
|
31
|
+
readonly title?: string;
|
|
32
|
+
};
|
|
33
|
+
type GeminiEmbedManyRequest = Omit<GeminiEmbedRequest, "input"> & {
|
|
34
|
+
readonly inputs: ReadonlyArray<EmbedInput>;
|
|
35
|
+
};
|
|
36
|
+
type GeminiEmbeddingService = {
|
|
37
|
+
readonly embed: (request: GeminiEmbedRequest) => Effect.Effect<EmbedResponse, AiError.AiError>;
|
|
38
|
+
readonly embedMany: (request: GeminiEmbedManyRequest) => Effect.Effect<EmbedManyResponse, AiError.AiError>;
|
|
39
|
+
};
|
|
40
|
+
declare const GeminiEmbedding_base: Context.ServiceClass<GeminiEmbedding, "@betalyra/effect-uai/providers/google/GeminiEmbedding", GeminiEmbeddingService>;
|
|
41
|
+
/**
|
|
42
|
+
* Provider-typed service tag. Yield this when you want Gemini-specific
|
|
43
|
+
* options (full `task` enum, `title`); yield the generic `EmbeddingModel`
|
|
44
|
+
* tag for provider-portable code. Both are registered by `layer`.
|
|
45
|
+
*/
|
|
46
|
+
declare class GeminiEmbedding extends GeminiEmbedding_base {}
|
|
47
|
+
type Config = {
|
|
48
|
+
readonly apiKey: Redacted.Redacted;
|
|
49
|
+
readonly baseUrl?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Build a `GeminiEmbeddingService` value. For Layer-based setup, prefer
|
|
53
|
+
* `layer`.
|
|
54
|
+
*/
|
|
55
|
+
declare const make: (cfg: Config) => Effect.Effect<GeminiEmbeddingService, never, HttpClient.HttpClient>;
|
|
56
|
+
/**
|
|
57
|
+
* Layer that registers both the provider-specific `GeminiEmbedding` tag
|
|
58
|
+
* and the generic `EmbeddingModel` tag, sharing one underlying
|
|
59
|
+
* implementation.
|
|
60
|
+
*/
|
|
61
|
+
declare const layer: (cfg: Config) => Layer.Layer<GeminiEmbedding | EmbeddingModel, never, HttpClient.HttpClient>;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { Config, GeminiEmbedManyRequest, GeminiEmbedRequest, GeminiEmbedding, GeminiEmbeddingService, GoogleEmbeddingTask, layer, make, GeminiEmbedding_d_exports as t };
|
|
64
|
+
//# sourceMappingURL=GeminiEmbedding.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeminiEmbedding.d.mts","names":[],"sources":["../src/GeminiEmbedding.ts"],"mappings":";;;;;;;;;;;;;;;;;KAyBY,mBAAA;AAAA,KAUA,kBAAA,GAAqB,IAAA,CAAK,kBAAA;+EAE3B,KAAA,EAAO,oBAAA;;;;AAZlB;;WAkBW,IAAA,GAAO,mBAAA;EAlBa;;AAU/B;;EAV+B,SAuBpB,KAAA;AAAA;AAAA,KAGC,sBAAA,GAAyB,IAAA,CAAK,kBAAA;EAAA,SAC/B,MAAA,EAAQ,aAAA,CAAc,UAAA;AAAA;AAAA,KAGrB,sBAAA;EAAA,SACD,KAAA,GAAQ,OAAA,EAAS,kBAAA,KAAuB,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EAAA,SAC7E,SAAA,GACP,OAAA,EAAS,sBAAA,KACN,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,OAAA,CAAQ,OAAA;AAAA;AAAA,cAC/C,oBAAA;;;;;;cAOY,eAAA,SAAwB,oBAAA;AAAA,KAIzB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;cAuPE,IAAA,GACX,GAAA,EAAK,MAAA,KACJ,MAAA,CAAO,MAAA,CAAO,sBAAA,SAA+B,UAAA,CAAW,UAAA;;;;;;cAa9C,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,eAAA,GAAkB,cAAA,SAAuB,UAAA,CAAW,UAAA"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
+
import { Context, Effect, Encoding, Layer, Match, Redacted, Schema } from "effect";
|
|
3
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { EmbeddingModel } from "@effect-uai/core/EmbeddingModel";
|
|
6
|
+
//#region src/GeminiEmbedding.ts
|
|
7
|
+
var GeminiEmbedding_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
+
GeminiEmbedding: () => GeminiEmbedding,
|
|
9
|
+
layer: () => layer,
|
|
10
|
+
make: () => make
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Provider-typed service tag. Yield this when you want Gemini-specific
|
|
14
|
+
* options (full `task` enum, `title`); yield the generic `EmbeddingModel`
|
|
15
|
+
* tag for provider-portable code. Both are registered by `layer`.
|
|
16
|
+
*/
|
|
17
|
+
var GeminiEmbedding = class extends Context.Service()("@betalyra/effect-uai/providers/google/GeminiEmbedding") {};
|
|
18
|
+
const taskToWire = {
|
|
19
|
+
query: "RETRIEVAL_QUERY",
|
|
20
|
+
document: "RETRIEVAL_DOCUMENT",
|
|
21
|
+
similarity: "SEMANTIC_SIMILARITY",
|
|
22
|
+
classification: "CLASSIFICATION",
|
|
23
|
+
clustering: "CLUSTERING",
|
|
24
|
+
qa: "QUESTION_ANSWERING",
|
|
25
|
+
fact_verification: "FACT_VERIFICATION",
|
|
26
|
+
code_query: "CODE_RETRIEVAL_QUERY"
|
|
27
|
+
};
|
|
28
|
+
const urlRejected = (param) => new AiError.InvalidRequest({
|
|
29
|
+
provider: "gemini",
|
|
30
|
+
param,
|
|
31
|
+
raw: "URL-form image inputs aren't supported; pass base64 or bytes"
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Pre-uploading via Google's Files API isn't free, so we reject `url`
|
|
35
|
+
* sources up front rather than silently dropping them. Users with HTTPS
|
|
36
|
+
* URLs should fetch + pass `bytes` (or `base64`).
|
|
37
|
+
*/
|
|
38
|
+
const imageSourceToPart = (param) => Match.type().pipe(Match.tag("base64", (s) => Effect.succeed({ inlineData: {
|
|
39
|
+
mimeType: s.mimeType,
|
|
40
|
+
data: s.base64
|
|
41
|
+
} })), Match.tag("bytes", (s) => Effect.succeed({ inlineData: {
|
|
42
|
+
mimeType: s.mimeType,
|
|
43
|
+
data: Encoding.encodeBase64(s.bytes)
|
|
44
|
+
} })), Match.tag("url", () => Effect.fail(urlRejected(param))), Match.exhaustive);
|
|
45
|
+
const contentPartToPart = (param, part) => "text" in part ? Effect.succeed({ text: part.text }) : imageSourceToPart(param)(part.image);
|
|
46
|
+
const inputToContent = (input) => {
|
|
47
|
+
if (typeof input === "string") return Effect.succeed({ parts: [{ text: input }] });
|
|
48
|
+
if ("text" in input) return Effect.succeed({ parts: [{ text: input.text }] });
|
|
49
|
+
if ("image" in input) return imageSourceToPart("input.image")(input.image).pipe(Effect.map((part) => ({ parts: [part] })));
|
|
50
|
+
return Effect.forEach(input.content, (p) => contentPartToPart("input.content[].image", p)).pipe(Effect.map((parts) => ({ parts })));
|
|
51
|
+
};
|
|
52
|
+
const wireOptions = (request) => ({
|
|
53
|
+
...request.task !== void 0 && { taskType: taskToWire[request.task] },
|
|
54
|
+
...request.dimensions !== void 0 && { outputDimensionality: request.dimensions },
|
|
55
|
+
...request.title !== void 0 && { title: request.title }
|
|
56
|
+
});
|
|
57
|
+
const buildSingleBody = (request) => inputToContent(request.input).pipe(Effect.map((content) => ({
|
|
58
|
+
content,
|
|
59
|
+
...wireOptions(request)
|
|
60
|
+
})));
|
|
61
|
+
const buildBatchBody = (request) => {
|
|
62
|
+
const modelPath = `models/${request.model}`;
|
|
63
|
+
const options = wireOptions(request);
|
|
64
|
+
return Effect.forEach(request.inputs, (input) => inputToContent(input).pipe(Effect.map((content) => ({
|
|
65
|
+
model: modelPath,
|
|
66
|
+
content,
|
|
67
|
+
...options
|
|
68
|
+
})))).pipe(Effect.map((requests) => ({ requests })));
|
|
69
|
+
};
|
|
70
|
+
const WireEmbedding = Schema.Struct({ values: Schema.Array(Schema.Number) });
|
|
71
|
+
const WireSingleResponse = Schema.Struct({ embedding: WireEmbedding });
|
|
72
|
+
const WireBatchResponse = Schema.Struct({ embeddings: Schema.Array(WireEmbedding) });
|
|
73
|
+
const valuesToEmbedding = (values) => ({
|
|
74
|
+
_tag: "float32",
|
|
75
|
+
vector: Float32Array.from(values)
|
|
76
|
+
});
|
|
77
|
+
const emptyUsage = {};
|
|
78
|
+
const httpStatusError = (status, body) => {
|
|
79
|
+
const provider = "gemini";
|
|
80
|
+
const raw = body;
|
|
81
|
+
if (status === 429) return new AiError.RateLimited({
|
|
82
|
+
provider,
|
|
83
|
+
raw
|
|
84
|
+
});
|
|
85
|
+
if (status === 408 || status === 504) return new AiError.Timeout({
|
|
86
|
+
provider,
|
|
87
|
+
raw
|
|
88
|
+
});
|
|
89
|
+
if (status === 401) return new AiError.AuthFailed({
|
|
90
|
+
provider,
|
|
91
|
+
subtype: "auth",
|
|
92
|
+
raw
|
|
93
|
+
});
|
|
94
|
+
if (status === 403) return new AiError.AuthFailed({
|
|
95
|
+
provider,
|
|
96
|
+
subtype: "permission",
|
|
97
|
+
raw
|
|
98
|
+
});
|
|
99
|
+
if (status === 402) return new AiError.AuthFailed({
|
|
100
|
+
provider,
|
|
101
|
+
subtype: "billing",
|
|
102
|
+
raw
|
|
103
|
+
});
|
|
104
|
+
if (status === 413) return new AiError.ContextLengthExceeded({
|
|
105
|
+
provider,
|
|
106
|
+
raw
|
|
107
|
+
});
|
|
108
|
+
if (status >= 500) return new AiError.Unavailable({
|
|
109
|
+
provider,
|
|
110
|
+
status,
|
|
111
|
+
raw
|
|
112
|
+
});
|
|
113
|
+
return new AiError.InvalidRequest({
|
|
114
|
+
provider,
|
|
115
|
+
raw
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
const transportFailure = (cause) => new AiError.Unavailable({
|
|
119
|
+
provider: "gemini",
|
|
120
|
+
raw: cause
|
|
121
|
+
});
|
|
122
|
+
const postJson = (cfg, url, body) => Effect.gen(function* () {
|
|
123
|
+
const client = yield* HttpClient.HttpClient;
|
|
124
|
+
const httpRequest = HttpClientRequest.post(url).pipe(HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey)), HttpClientRequest.bodyJsonUnsafe(body));
|
|
125
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
126
|
+
if (response.status >= 400) {
|
|
127
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
128
|
+
return yield* Effect.fail(httpStatusError(response.status, text));
|
|
129
|
+
}
|
|
130
|
+
return yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
131
|
+
});
|
|
132
|
+
const decodeSingle = (json) => Schema.decodeUnknownEffect(WireSingleResponse)(json).pipe(Effect.mapError(transportFailure));
|
|
133
|
+
const decodeBatch = (json) => Schema.decodeUnknownEffect(WireBatchResponse)(json).pipe(Effect.mapError(transportFailure));
|
|
134
|
+
const baseUrl = (cfg) => cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
135
|
+
const embedImpl = (cfg) => (request) => buildSingleBody(request).pipe(Effect.flatMap((body) => postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:embedContent`, body)), Effect.flatMap(decodeSingle), Effect.map((decoded) => ({
|
|
136
|
+
embedding: valuesToEmbedding(decoded.embedding.values),
|
|
137
|
+
usage: emptyUsage
|
|
138
|
+
})));
|
|
139
|
+
const embedManyImpl = (cfg) => (request) => buildBatchBody(request).pipe(Effect.flatMap((body) => postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:batchEmbedContents`, body)), Effect.flatMap(decodeBatch), Effect.map((decoded) => ({
|
|
140
|
+
embeddings: decoded.embeddings.map((e) => valuesToEmbedding(e.values)),
|
|
141
|
+
usage: emptyUsage
|
|
142
|
+
})));
|
|
143
|
+
/**
|
|
144
|
+
* Build a `GeminiEmbeddingService` value. For Layer-based setup, prefer
|
|
145
|
+
* `layer`.
|
|
146
|
+
*/
|
|
147
|
+
const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
|
|
148
|
+
embed: (request) => embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
149
|
+
embedMany: (request) => embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client))
|
|
150
|
+
}));
|
|
151
|
+
/**
|
|
152
|
+
* Layer that registers both the provider-specific `GeminiEmbedding` tag
|
|
153
|
+
* and the generic `EmbeddingModel` tag, sharing one underlying
|
|
154
|
+
* implementation.
|
|
155
|
+
*/
|
|
156
|
+
const layer = (cfg) => {
|
|
157
|
+
const typed = Layer.effect(GeminiEmbedding, make(cfg));
|
|
158
|
+
const generic = Layer.effect(EmbeddingModel, Effect.map(make(cfg), (s) => ({
|
|
159
|
+
embed: (req) => s.embed(req),
|
|
160
|
+
embedMany: (req) => s.embedMany(req)
|
|
161
|
+
})));
|
|
162
|
+
return Layer.merge(typed, generic);
|
|
163
|
+
};
|
|
164
|
+
//#endregion
|
|
165
|
+
export { GeminiEmbedding, layer, make, GeminiEmbedding_exports as t };
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=GeminiEmbedding.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeminiEmbedding.mjs","names":[],"sources":["../src/GeminiEmbedding.ts"],"sourcesContent":["import { Context, Effect, Encoding, Layer, Match, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { Embedding, EmbedContentPart, EmbedInput, Usage } from \"@effect-uai/core/Embedding\"\nimport {\n type CommonEmbedManyRequest,\n type CommonEmbedRequest,\n EmbeddingModel,\n type EmbeddingModelService,\n type EmbedManyResponse,\n type EmbedResponse,\n} from \"@effect-uai/core/EmbeddingModel\"\nimport type { ImageSource } from \"@effect-uai/core/Image\"\nimport type { GoogleEmbeddingModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Gemini's task-type enum, exposed under our friendly names. Mapped to the\n * wire `taskType` values inside the codec. Honoured by\n * `gemini-embedding-001`; ignored by `gemini-embedding-2` (which expects\n * task instructions in the prompt text instead).\n */\nexport type GoogleEmbeddingTask =\n | \"query\"\n | \"document\"\n | \"similarity\"\n | \"classification\"\n | \"clustering\"\n | \"qa\"\n | \"fact_verification\"\n | \"code_query\"\n\nexport type GeminiEmbedRequest = Omit<CommonEmbedRequest, \"model\" | \"task\" | \"encoding\"> & {\n /** Narrows `CommonEmbedRequest.model` to the typed Google union. */\n readonly model: GoogleEmbeddingModel\n /**\n * Widens the cross-provider `\"query\" | \"document\"` to the full Google\n * task enum. Optional. Honoured by `gemini-embedding-001`; ignored by\n * `gemini-embedding-2`.\n */\n readonly task?: GoogleEmbeddingTask\n /**\n * Optional document title. Only meaningful for `RETRIEVAL_DOCUMENT`\n * tasks on `gemini-embedding-001`.\n */\n readonly title?: string\n}\n\nexport type GeminiEmbedManyRequest = Omit<GeminiEmbedRequest, \"input\"> & {\n readonly inputs: ReadonlyArray<EmbedInput>\n}\n\nexport type GeminiEmbeddingService = {\n readonly embed: (request: GeminiEmbedRequest) => Effect.Effect<EmbedResponse, AiError.AiError>\n readonly embedMany: (\n request: GeminiEmbedManyRequest,\n ) => Effect.Effect<EmbedManyResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Gemini-specific\n * options (full `task` enum, `title`); yield the generic `EmbeddingModel`\n * tag for provider-portable code. Both are registered by `layer`.\n */\nexport class GeminiEmbedding extends Context.Service<GeminiEmbedding, GeminiEmbeddingService>()(\n \"@betalyra/effect-uai/providers/google/GeminiEmbedding\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\nconst taskToWire: Record<GoogleEmbeddingTask, string> = {\n query: \"RETRIEVAL_QUERY\",\n document: \"RETRIEVAL_DOCUMENT\",\n similarity: \"SEMANTIC_SIMILARITY\",\n classification: \"CLASSIFICATION\",\n clustering: \"CLUSTERING\",\n qa: \"QUESTION_ANSWERING\",\n fact_verification: \"FACT_VERIFICATION\",\n code_query: \"CODE_RETRIEVAL_QUERY\",\n}\n\ntype WireTextPart = {\n readonly text: string\n}\ntype WireInlineDataPart = {\n readonly inlineData: { readonly mimeType: string; readonly data: string }\n}\ntype WirePart = WireTextPart | WireInlineDataPart\ntype WireContent = {\n readonly parts: ReadonlyArray<WirePart>\n}\n\nconst urlRejected = (param: string): AiError.AiError =>\n new AiError.InvalidRequest({\n provider: \"gemini\",\n param,\n raw: \"URL-form image inputs aren't supported; pass base64 or bytes\",\n })\n\n/**\n * Pre-uploading via Google's Files API isn't free, so we reject `url`\n * sources up front rather than silently dropping them. Users with HTTPS\n * URLs should fetch + pass `bytes` (or `base64`).\n */\nconst imageSourceToPart = (param: string) =>\n Match.type<ImageSource>().pipe(\n Match.tag(\n \"base64\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({ inlineData: { mimeType: s.mimeType, data: s.base64 } }),\n ),\n Match.tag(\n \"bytes\",\n (s): Effect.Effect<WirePart, AiError.AiError> =>\n Effect.succeed({\n inlineData: { mimeType: s.mimeType, data: Encoding.encodeBase64(s.bytes) },\n }),\n ),\n Match.tag(\n \"url\",\n (): Effect.Effect<WirePart, AiError.AiError> => Effect.fail(urlRejected(param)),\n ),\n Match.exhaustive,\n )\n\nconst contentPartToPart = (\n param: string,\n part: EmbedContentPart,\n): Effect.Effect<WirePart, AiError.AiError> =>\n \"text\" in part ? Effect.succeed({ text: part.text }) : imageSourceToPart(param)(part.image)\n\nconst inputToContent = (input: EmbedInput): Effect.Effect<WireContent, AiError.AiError> => {\n if (typeof input === \"string\") return Effect.succeed({ parts: [{ text: input }] })\n if (\"text\" in input) return Effect.succeed({ parts: [{ text: input.text }] })\n if (\"image\" in input) {\n return imageSourceToPart(\"input.image\")(input.image).pipe(\n Effect.map((part) => ({ parts: [part] })),\n )\n }\n return Effect.forEach(input.content, (p) => contentPartToPart(\"input.content[].image\", p)).pipe(\n Effect.map((parts) => ({ parts })),\n )\n}\n\ntype WireSingleBody = {\n readonly content: WireContent\n readonly taskType?: string\n readonly outputDimensionality?: number\n readonly title?: string\n}\n\nconst wireOptions = (\n request: Pick<GeminiEmbedRequest, \"task\" | \"dimensions\" | \"title\">,\n): Omit<WireSingleBody, \"content\"> => ({\n ...(request.task !== undefined && { taskType: taskToWire[request.task] }),\n ...(request.dimensions !== undefined && { outputDimensionality: request.dimensions }),\n ...(request.title !== undefined && { title: request.title }),\n})\n\nconst buildSingleBody = (\n request: GeminiEmbedRequest,\n): Effect.Effect<WireSingleBody, AiError.AiError> =>\n inputToContent(request.input).pipe(\n Effect.map((content) => ({ content, ...wireOptions(request) })),\n )\n\ntype WireBatchEntry = WireSingleBody & {\n readonly model: string\n}\ntype WireBatchBody = {\n readonly requests: ReadonlyArray<WireBatchEntry>\n}\n\nconst buildBatchBody = (\n request: GeminiEmbedManyRequest,\n): Effect.Effect<WireBatchBody, AiError.AiError> => {\n const modelPath = `models/${request.model}`\n const options = wireOptions(request)\n return Effect.forEach(request.inputs, (input) =>\n inputToContent(input).pipe(\n Effect.map((content): WireBatchEntry => ({ model: modelPath, content, ...options })),\n ),\n ).pipe(Effect.map((requests) => ({ requests })))\n}\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireEmbedding = Schema.Struct({\n values: Schema.Array(Schema.Number),\n})\n\nconst WireSingleResponse = Schema.Struct({\n embedding: WireEmbedding,\n})\n\nconst WireBatchResponse = Schema.Struct({\n embeddings: Schema.Array(WireEmbedding),\n})\n\nconst valuesToEmbedding = (values: ReadonlyArray<number>): Embedding => ({\n _tag: \"float32\",\n vector: Float32Array.from(values),\n})\n\n// Gemini's embed endpoints don't return token-count metadata. Estimating\n// client-side would be provider-specific cruft; honest `undefined` is better.\nconst emptyUsage: Usage = {}\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"gemini\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"gemini\", raw: cause })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing - send a JSON body, error-map status, return parsed JSON.\n// ---------------------------------------------------------------------------\n\nconst postJson = (\n cfg: Config,\n url: string,\n body: unknown,\n): Effect.Effect<unknown, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey)),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n return yield* response.json.pipe(Effect.mapError(transportFailure))\n })\n\nconst decodeSingle = (json: unknown) =>\n Schema.decodeUnknownEffect(WireSingleResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst decodeBatch = (json: unknown) =>\n Schema.decodeUnknownEffect(WireBatchResponse)(json).pipe(Effect.mapError(transportFailure))\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst embedImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedRequest,\n ): Effect.Effect<EmbedResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildSingleBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:embedContent`, body),\n ),\n Effect.flatMap(decodeSingle),\n Effect.map(\n (decoded): EmbedResponse => ({\n embedding: valuesToEmbedding(decoded.embedding.values),\n usage: emptyUsage,\n }),\n ),\n )\n\nconst embedManyImpl =\n (cfg: Config) =>\n (\n request: GeminiEmbedManyRequest,\n ): Effect.Effect<EmbedManyResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildBatchBody(request).pipe(\n Effect.flatMap((body) =>\n postJson(cfg, `${baseUrl(cfg)}/models/${request.model}:batchEmbedContents`, body),\n ),\n Effect.flatMap(decodeBatch),\n Effect.map(\n (decoded): EmbedManyResponse => ({\n embeddings: decoded.embeddings.map((e) => valuesToEmbedding(e.values)),\n usage: emptyUsage,\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `GeminiEmbeddingService` value. For Layer-based setup, prefer\n * `layer`.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<GeminiEmbeddingService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n embed: (request) =>\n embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n embedMany: (request) =>\n embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer that registers both the provider-specific `GeminiEmbedding` tag\n * and the generic `EmbeddingModel` tag, sharing one underlying\n * implementation.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<GeminiEmbedding | EmbeddingModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(GeminiEmbedding, make(cfg))\n const generic = Layer.effect(\n EmbeddingModel,\n Effect.map(\n make(cfg),\n (s): EmbeddingModelService => ({\n embed: (req: CommonEmbedRequest) => s.embed(req as GeminiEmbedRequest),\n embedMany: (req: CommonEmbedManyRequest) => s.embedMany(req as GeminiEmbedManyRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|