@effect-uai/google 0.9.0 → 0.11.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 +4 -2
- package/dist/Gemini.d.mts.map +1 -1
- package/dist/Gemini.mjs +18 -6
- package/dist/Gemini.mjs.map +1 -1
- package/dist/GeminiEmbedding.mjs +1 -1
- package/dist/GeminiEmbedding.mjs.map +1 -1
- package/dist/GeminiSynthesizer.d.mts.map +1 -1
- package/dist/GeminiSynthesizer.mjs +2 -2
- package/dist/GeminiSynthesizer.mjs.map +1 -1
- package/dist/GeminiSynthesizer.test.mjs +1 -1
- package/dist/GeminiTools.d.mts +42 -0
- package/dist/GeminiTools.d.mts.map +1 -0
- package/dist/GeminiTools.mjs +64 -0
- package/dist/GeminiTools.mjs.map +1 -0
- package/dist/GoogleDeepResearch.d.mts +70 -0
- package/dist/GoogleDeepResearch.d.mts.map +1 -0
- package/dist/GoogleDeepResearch.mjs +269 -0
- package/dist/GoogleDeepResearch.mjs.map +1 -0
- package/dist/LyriaGenerator.mjs +1 -1
- package/dist/LyriaGenerator.mjs.map +1 -1
- package/dist/LyriaGenerator.test.mjs +1 -1
- package/dist/codec.d.mts +22 -5
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +52 -30
- package/dist/codec.mjs.map +1 -1
- package/dist/{dist-DMsg8YLb.mjs → dist-CWoAuwS9.mjs} +7 -7
- package/dist/{dist-DMsg8YLb.mjs.map → dist-CWoAuwS9.mjs.map} +1 -1
- package/dist/geminiSpeechCodec.test.mjs +1 -1
- package/dist/geminiTools.test.d.mts +1 -0
- package/dist/geminiTools.test.mjs +80 -0
- package/dist/geminiTools.test.mjs.map +1 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +3 -1
- package/dist/models.d.mts +11 -1
- package/dist/models.d.mts.map +1 -1
- package/package.json +8 -4
- package/src/Gemini.ts +21 -3
- package/src/GeminiEmbedding.ts +1 -1
- package/src/GeminiSynthesizer.ts +4 -2
- package/src/GeminiTools.ts +95 -0
- package/src/GoogleDeepResearch.ts +446 -0
- package/src/LyriaGenerator.ts +1 -1
- package/src/codec.ts +81 -28
- package/src/geminiTools.test.ts +88 -0
- package/src/index.ts +2 -0
- package/src/models.ts +15 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Match, Redacted, Ref, Schema, Stream } from "effect";
|
|
3
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import * as SSE from "@effect-uai/core/SSE";
|
|
6
|
+
import { TurnEvent } from "@effect-uai/core/Turn";
|
|
7
|
+
import { DeepResearch, fromJob } from "@effect-uai/core/DeepResearch";
|
|
8
|
+
import * as Items from "@effect-uai/core/Items";
|
|
9
|
+
import * as Job from "@effect-uai/core/Job";
|
|
10
|
+
//#region src/GoogleDeepResearch.ts
|
|
11
|
+
var GoogleDeepResearch_exports = /* @__PURE__ */ __exportAll({
|
|
12
|
+
GoogleDeepResearch: () => GoogleDeepResearch,
|
|
13
|
+
WireInteraction: () => WireInteraction,
|
|
14
|
+
jobStateOf: () => jobStateOf,
|
|
15
|
+
layer: () => layer,
|
|
16
|
+
make: () => make
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Provider-typed tag for Gemini deep research over the Interactions API
|
|
20
|
+
* (`POST /v1beta/interactions`, `agent: deep-research-*`). Yield this for the
|
|
21
|
+
* agent-id knob; yield the generic `DeepResearch` tag for portable code.
|
|
22
|
+
*
|
|
23
|
+
* `research` / `submit` / `collect` drive a background job by polling;
|
|
24
|
+
* `researchStream` consumes the real Interactions SSE (`step.delta` text /
|
|
25
|
+
* thought, terminating in `interaction.completed`). Preview surface: the SSE
|
|
26
|
+
* event shapes and the completed-interaction citation shape are decoded
|
|
27
|
+
* defensively and best confirmed against a live response.
|
|
28
|
+
*/
|
|
29
|
+
var GoogleDeepResearch = class extends Context.Service()("@betalyra/effect-uai/providers/google/GoogleDeepResearch") {};
|
|
30
|
+
const WireSubmit = Schema.Struct({ id: Schema.String });
|
|
31
|
+
const decodeSubmit = Schema.decodeUnknownEffect(WireSubmit);
|
|
32
|
+
const WireTextPart = Schema.Struct({
|
|
33
|
+
type: Schema.optional(Schema.NullOr(Schema.String)),
|
|
34
|
+
text: Schema.optional(Schema.NullOr(Schema.String))
|
|
35
|
+
});
|
|
36
|
+
const WireStep = Schema.Struct({
|
|
37
|
+
type: Schema.optional(Schema.NullOr(Schema.String)),
|
|
38
|
+
text: Schema.optional(Schema.NullOr(Schema.String)),
|
|
39
|
+
content: Schema.optional(Schema.NullOr(Schema.Array(WireTextPart)))
|
|
40
|
+
});
|
|
41
|
+
const WireInteraction = Schema.Struct({
|
|
42
|
+
id: Schema.optional(Schema.NullOr(Schema.String)),
|
|
43
|
+
status: Schema.optional(Schema.NullOr(Schema.String)),
|
|
44
|
+
outputs: Schema.optional(Schema.NullOr(Schema.Array(WireTextPart))),
|
|
45
|
+
steps: Schema.optional(Schema.NullOr(Schema.Array(WireStep))),
|
|
46
|
+
error: Schema.optional(Schema.NullOr(Schema.Struct({
|
|
47
|
+
message: Schema.optional(Schema.NullOr(Schema.String)),
|
|
48
|
+
code: Schema.optional(Schema.NullOr(Schema.String))
|
|
49
|
+
})))
|
|
50
|
+
});
|
|
51
|
+
const decodeInteraction = Schema.decodeUnknownEffect(WireInteraction);
|
|
52
|
+
const textOfParts = (parts) => (parts ?? []).filter((p) => (p.type ?? "text") === "text" && p.text != null).map((p) => p.text).join("");
|
|
53
|
+
const reportText = (wire) => {
|
|
54
|
+
const fromOutputs = textOfParts(wire.outputs);
|
|
55
|
+
if (fromOutputs.length > 0) return fromOutputs;
|
|
56
|
+
const lastStep = (wire.steps ?? []).at(-1);
|
|
57
|
+
const fromStep = textOfParts(lastStep?.content);
|
|
58
|
+
return fromStep.length > 0 ? fromStep : lastStep?.text ?? "";
|
|
59
|
+
};
|
|
60
|
+
const turnFromInteraction = (wire) => ({
|
|
61
|
+
items: [{
|
|
62
|
+
type: "message",
|
|
63
|
+
role: "assistant",
|
|
64
|
+
content: [{
|
|
65
|
+
type: "output_text",
|
|
66
|
+
text: reportText(wire)
|
|
67
|
+
}],
|
|
68
|
+
providerData: wire
|
|
69
|
+
}],
|
|
70
|
+
usage: {},
|
|
71
|
+
stop_reason: "stop"
|
|
72
|
+
});
|
|
73
|
+
const jobStateOf = (wire) => Match.value(wire.status ?? "").pipe(Match.whenOr("queued", "pending", () => ({ _tag: "Pending" })), Match.whenOr("in_progress", "running", "processing", () => ({ _tag: "Running" })), Match.whenOr("completed", "succeeded", () => ({
|
|
74
|
+
_tag: "Succeeded",
|
|
75
|
+
result: turnFromInteraction(wire)
|
|
76
|
+
})), Match.whenOr("failed", "error", "cancelled", () => ({
|
|
77
|
+
_tag: "Failed",
|
|
78
|
+
...wire.error?.message != null && { reason: wire.error.message },
|
|
79
|
+
raw: wire
|
|
80
|
+
})), Match.orElse(() => ({ _tag: "Running" })));
|
|
81
|
+
const baseUrl = (cfg) => cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
82
|
+
const authHeader = (cfg) => HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey));
|
|
83
|
+
const transportFailure = (cause) => new AiError.Unavailable({
|
|
84
|
+
provider: "google",
|
|
85
|
+
raw: cause
|
|
86
|
+
});
|
|
87
|
+
const httpStatusError = (status, body) => {
|
|
88
|
+
const provider = "google";
|
|
89
|
+
const raw = body;
|
|
90
|
+
if (status === 429) return new AiError.RateLimited({
|
|
91
|
+
provider,
|
|
92
|
+
raw
|
|
93
|
+
});
|
|
94
|
+
if (status === 408 || status === 504) return new AiError.Timeout({
|
|
95
|
+
provider,
|
|
96
|
+
raw
|
|
97
|
+
});
|
|
98
|
+
if (status === 401) return new AiError.AuthFailed({
|
|
99
|
+
provider,
|
|
100
|
+
subtype: "auth",
|
|
101
|
+
raw
|
|
102
|
+
});
|
|
103
|
+
if (status === 403) return new AiError.AuthFailed({
|
|
104
|
+
provider,
|
|
105
|
+
subtype: "permission",
|
|
106
|
+
raw
|
|
107
|
+
});
|
|
108
|
+
if (status === 402) return new AiError.AuthFailed({
|
|
109
|
+
provider,
|
|
110
|
+
subtype: "billing",
|
|
111
|
+
raw
|
|
112
|
+
});
|
|
113
|
+
if (status === 413) return new AiError.ContextLengthExceeded({
|
|
114
|
+
provider,
|
|
115
|
+
raw
|
|
116
|
+
});
|
|
117
|
+
if (status >= 500) return new AiError.Unavailable({
|
|
118
|
+
provider,
|
|
119
|
+
status,
|
|
120
|
+
raw
|
|
121
|
+
});
|
|
122
|
+
return new AiError.InvalidRequest({
|
|
123
|
+
provider,
|
|
124
|
+
raw
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const inputText = (history) => history.filter(Items.isMessage).filter((m) => m.role === "user").flatMap((m) => m.content).filter(Items.isInputText).map((b) => b.text).join("\n");
|
|
128
|
+
const submitBody = (request) => ({
|
|
129
|
+
agent: request.model ?? "deep-research-preview-04-2026",
|
|
130
|
+
input: inputText(request.history),
|
|
131
|
+
background: true,
|
|
132
|
+
store: true,
|
|
133
|
+
agent_config: {
|
|
134
|
+
type: "deep-research",
|
|
135
|
+
thinking_summaries: "auto"
|
|
136
|
+
},
|
|
137
|
+
tools: [{ type: "google_search" }]
|
|
138
|
+
});
|
|
139
|
+
const submitJob = (cfg, request) => Effect.gen(function* () {
|
|
140
|
+
const client = yield* HttpClient.HttpClient;
|
|
141
|
+
const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/interactions`).pipe(authHeader(cfg), HttpClientRequest.bodyJsonUnsafe(submitBody(request)));
|
|
142
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
143
|
+
if (response.status >= 400) {
|
|
144
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
145
|
+
return yield* httpStatusError(response.status, text);
|
|
146
|
+
}
|
|
147
|
+
const wire = yield* decodeSubmit(yield* response.json.pipe(Effect.mapError(transportFailure))).pipe(Effect.mapError(transportFailure));
|
|
148
|
+
return Job.jobRef("google", wire.id);
|
|
149
|
+
});
|
|
150
|
+
const pollJob = (cfg, ref) => Effect.gen(function* () {
|
|
151
|
+
const client = yield* HttpClient.HttpClient;
|
|
152
|
+
const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/interactions/${ref.id}`).pipe(authHeader(cfg));
|
|
153
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
154
|
+
if (response.status >= 400) {
|
|
155
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
156
|
+
return yield* httpStatusError(response.status, text);
|
|
157
|
+
}
|
|
158
|
+
return jobStateOf(yield* decodeInteraction(yield* response.json.pipe(Effect.mapError(transportFailure))).pipe(Effect.mapError(transportFailure)));
|
|
159
|
+
});
|
|
160
|
+
const unsupported = (capability) => new AiError.Unsupported({
|
|
161
|
+
provider: "google",
|
|
162
|
+
capability,
|
|
163
|
+
reason: "Gemini deep research (preview) exposes no verified cancel endpoint."
|
|
164
|
+
});
|
|
165
|
+
const WireDelta = Schema.Struct({
|
|
166
|
+
type: Schema.optional(Schema.NullOr(Schema.String)),
|
|
167
|
+
text: Schema.optional(Schema.NullOr(Schema.String)),
|
|
168
|
+
content: Schema.optional(Schema.NullOr(Schema.Struct({
|
|
169
|
+
type: Schema.optional(Schema.NullOr(Schema.String)),
|
|
170
|
+
text: Schema.optional(Schema.NullOr(Schema.String))
|
|
171
|
+
})))
|
|
172
|
+
});
|
|
173
|
+
const WireStreamEvent = Schema.Struct({
|
|
174
|
+
event_type: Schema.optional(Schema.NullOr(Schema.String)),
|
|
175
|
+
delta: Schema.optional(Schema.NullOr(WireDelta)),
|
|
176
|
+
interaction: Schema.optional(Schema.NullOr(WireInteraction)),
|
|
177
|
+
error: Schema.optional(Schema.NullOr(Schema.Struct({ message: Schema.optional(Schema.NullOr(Schema.String)) })))
|
|
178
|
+
});
|
|
179
|
+
const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
|
|
180
|
+
const decodeStreamEvent = Schema.decodeUnknownEffect(WireStreamEvent);
|
|
181
|
+
const completedTurn = (wire, streamed) => wire != null && reportText(wire).length > 0 ? turnFromInteraction(wire) : {
|
|
182
|
+
items: [{
|
|
183
|
+
type: "message",
|
|
184
|
+
role: "assistant",
|
|
185
|
+
content: [{
|
|
186
|
+
type: "output_text",
|
|
187
|
+
text: streamed
|
|
188
|
+
}],
|
|
189
|
+
...wire != null && { providerData: wire }
|
|
190
|
+
}],
|
|
191
|
+
usage: {},
|
|
192
|
+
stop_reason: "stop"
|
|
193
|
+
};
|
|
194
|
+
const streamEventToTurnEvents = (acc, ev) => {
|
|
195
|
+
if (ev.event_type === "interaction.error") return Effect.fail(new AiError.GenerationFailed({
|
|
196
|
+
provider: "google",
|
|
197
|
+
...ev.error?.message != null && { message: ev.error.message },
|
|
198
|
+
raw: ev
|
|
199
|
+
}));
|
|
200
|
+
if (ev.event_type === "step.delta") {
|
|
201
|
+
const delta = ev.delta;
|
|
202
|
+
if (delta?.text != null) {
|
|
203
|
+
const text = delta.text;
|
|
204
|
+
return Effect.as(Ref.update(acc, (streamed) => streamed + text), [TurnEvent.TextDelta({ text })]);
|
|
205
|
+
}
|
|
206
|
+
if (delta?.content?.text != null) return Effect.succeed([TurnEvent.ReasoningDelta({
|
|
207
|
+
text: delta.content.text,
|
|
208
|
+
kind: "summary"
|
|
209
|
+
})]);
|
|
210
|
+
return Effect.succeed([]);
|
|
211
|
+
}
|
|
212
|
+
if (ev.event_type === "interaction.completed") return Effect.map(Ref.get(acc), (streamed) => [TurnEvent.TurnComplete({ turn: completedTurn(ev.interaction, streamed) })]);
|
|
213
|
+
return Effect.succeed([]);
|
|
214
|
+
};
|
|
215
|
+
const streamCreate = (cfg, request) => Stream.unwrap(Effect.gen(function* () {
|
|
216
|
+
const client = yield* HttpClient.HttpClient;
|
|
217
|
+
const acc = yield* Ref.make("");
|
|
218
|
+
const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/interactions?stream=true`).pipe(authHeader(cfg), HttpClientRequest.bodyJsonUnsafe({
|
|
219
|
+
...submitBody(request),
|
|
220
|
+
stream: true
|
|
221
|
+
}), HttpClientRequest.accept("text/event-stream"));
|
|
222
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
223
|
+
yield* Effect.logDebug("google interactions stream response", {
|
|
224
|
+
status: response.status,
|
|
225
|
+
contentType: response.headers["content-type"]
|
|
226
|
+
});
|
|
227
|
+
if (response.status >= 400) {
|
|
228
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
229
|
+
return Stream.fail(httpStatusError(response.status, text));
|
|
230
|
+
}
|
|
231
|
+
return response.stream.pipe(Stream.mapError(transportFailure), SSE.fromBytes, Stream.tap((ev) => Effect.logDebug("google interactions sse frame", {
|
|
232
|
+
event: ev.event,
|
|
233
|
+
data: ev.data
|
|
234
|
+
})), Stream.filter((ev) => ev.event !== "done" && ev.data.trim() !== "[DONE]"), Stream.mapEffect((ev) => parseJsonUnknown(ev.data).pipe(Effect.flatMap(decodeStreamEvent), Effect.mapError(transportFailure), Effect.flatMap((wire) => streamEventToTurnEvents(acc, wire)))), Stream.flatMap(Stream.fromIterable));
|
|
235
|
+
}));
|
|
236
|
+
const jobOps = (cfg, client) => {
|
|
237
|
+
const withClient = (e) => Effect.provideService(e, HttpClient.HttpClient, client);
|
|
238
|
+
return {
|
|
239
|
+
submit: (request) => withClient(submitJob(cfg, request)),
|
|
240
|
+
poll: (ref) => withClient(pollJob(cfg, ref)),
|
|
241
|
+
cancel: () => Effect.fail(unsupported("cancel"))
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
const buildService = (cfg, client, ops, toGoogle) => ({
|
|
245
|
+
...fromJob(ops, cfg.job),
|
|
246
|
+
researchStream: (request) => Stream.provideService(streamCreate(cfg, toGoogle(request)), HttpClient.HttpClient, client)
|
|
247
|
+
});
|
|
248
|
+
/** Build a `GoogleDeepResearchService`. For Layer setup, prefer {@link layer}. */
|
|
249
|
+
const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => buildService(cfg, client, jobOps(cfg, client), (request) => request));
|
|
250
|
+
/**
|
|
251
|
+
* Layer registering the provider-typed `GoogleDeepResearch` tag and the generic
|
|
252
|
+
* `DeepResearch` tag. No capability markers: the surface (poll, collect,
|
|
253
|
+
* synthesized stream) is universal.
|
|
254
|
+
*/
|
|
255
|
+
const layer = (cfg) => {
|
|
256
|
+
const typed = Layer.effect(GoogleDeepResearch, make(cfg));
|
|
257
|
+
const generic = Layer.effect(DeepResearch, Effect.map(HttpClient.HttpClient, (client) => {
|
|
258
|
+
const ops = jobOps(cfg, client);
|
|
259
|
+
return buildService(cfg, client, {
|
|
260
|
+
...ops,
|
|
261
|
+
submit: (request) => ops.submit(request)
|
|
262
|
+
}, (request) => request);
|
|
263
|
+
}));
|
|
264
|
+
return Layer.merge(typed, generic);
|
|
265
|
+
};
|
|
266
|
+
//#endregion
|
|
267
|
+
export { GoogleDeepResearch, WireInteraction, jobStateOf, layer, make, GoogleDeepResearch_exports as t };
|
|
268
|
+
|
|
269
|
+
//# sourceMappingURL=GoogleDeepResearch.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GoogleDeepResearch.mjs","names":[],"sources":["../src/GoogleDeepResearch.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Redacted, Ref, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n DeepResearch,\n type DeepResearchService,\n type DeepResearchServiceShape,\n fromJob,\n type ResearchJobOps,\n} from \"@effect-uai/core/DeepResearch\"\nimport * as Items from \"@effect-uai/core/Items\"\nimport * as Job from \"@effect-uai/core/Job\"\nimport type { ResearchJobRef, ResearchRequest, ResearchState } from \"@effect-uai/core/Research\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { type Turn, TurnEvent } from \"@effect-uai/core/Turn\"\nimport type { GoogleDeepResearchModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Gemini-typed research request. Narrows `model` to the deep-research agent ids.\n * Provider-portable code uses the generic `DeepResearch` tag instead.\n */\nexport type GoogleResearchRequest = Omit<ResearchRequest, \"model\"> & {\n readonly model?: GoogleDeepResearchModel\n}\n\nexport type GoogleDeepResearchService = DeepResearchServiceShape<GoogleResearchRequest>\n\n/**\n * Provider-typed tag for Gemini deep research over the Interactions API\n * (`POST /v1beta/interactions`, `agent: deep-research-*`). Yield this for the\n * agent-id knob; yield the generic `DeepResearch` tag for portable code.\n *\n * `research` / `submit` / `collect` drive a background job by polling;\n * `researchStream` consumes the real Interactions SSE (`step.delta` text /\n * thought, terminating in `interaction.completed`). Preview surface: the SSE\n * event shapes and the completed-interaction citation shape are decoded\n * defensively and best confirmed against a live response.\n */\nexport class GoogleDeepResearch extends Context.Service<\n GoogleDeepResearch,\n GoogleDeepResearchService\n>()(\"@betalyra/effect-uai/providers/google/GoogleDeepResearch\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n /** Poll cadence / overall timeout for the derived poll loops. */\n readonly job?: Job.JobConfig\n}\n\n// ---------------------------------------------------------------------------\n// Wire codec\n// ---------------------------------------------------------------------------\n\nconst WireSubmit = Schema.Struct({ id: Schema.String })\nconst decodeSubmit = Schema.decodeUnknownEffect(WireSubmit)\n\n// A completed interaction's text lives at `outputs[].text` (SDK `outputs`) or,\n// in some doc examples, `steps[].content[].text`. Decode both defensively and\n// extract from whichever is populated. Citations are undocumented on this\n// preview surface, so none are mapped yet (the report text carries inline\n// `[n]` markers); enrich once a live response confirms the shape.\nconst WireTextPart = Schema.Struct({\n type: Schema.optional(Schema.NullOr(Schema.String)),\n text: Schema.optional(Schema.NullOr(Schema.String)),\n})\nconst WireStep = Schema.Struct({\n type: Schema.optional(Schema.NullOr(Schema.String)),\n text: Schema.optional(Schema.NullOr(Schema.String)),\n content: Schema.optional(Schema.NullOr(Schema.Array(WireTextPart))),\n})\nexport const WireInteraction = Schema.Struct({\n id: Schema.optional(Schema.NullOr(Schema.String)),\n status: Schema.optional(Schema.NullOr(Schema.String)),\n outputs: Schema.optional(Schema.NullOr(Schema.Array(WireTextPart))),\n steps: Schema.optional(Schema.NullOr(Schema.Array(WireStep))),\n error: Schema.optional(\n Schema.NullOr(\n Schema.Struct({\n message: Schema.optional(Schema.NullOr(Schema.String)),\n code: Schema.optional(Schema.NullOr(Schema.String)),\n }),\n ),\n ),\n})\nexport type WireInteraction = typeof WireInteraction.Type\nconst decodeInteraction = Schema.decodeUnknownEffect(WireInteraction)\n\nconst textOfParts = (parts: ReadonlyArray<typeof WireTextPart.Type> | null | undefined): string =>\n (parts ?? [])\n .filter((p) => (p.type ?? \"text\") === \"text\" && p.text != null)\n .map((p) => p.text)\n .join(\"\")\n\nconst reportText = (wire: WireInteraction): string => {\n const fromOutputs = textOfParts(wire.outputs)\n if (fromOutputs.length > 0) return fromOutputs\n const lastStep = (wire.steps ?? []).at(-1)\n const fromStep = textOfParts(lastStep?.content)\n return fromStep.length > 0 ? fromStep : (lastStep?.text ?? \"\")\n}\n\nconst turnFromInteraction = (wire: WireInteraction): Turn => ({\n items: [\n {\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"output_text\", text: reportText(wire) }],\n providerData: wire,\n },\n ],\n usage: {},\n stop_reason: \"stop\",\n})\n\n// Map the interaction status onto the generic `JobState`.\nexport const jobStateOf = (wire: WireInteraction): ResearchState =>\n Match.value(wire.status ?? \"\").pipe(\n Match.whenOr(\"queued\", \"pending\", (): ResearchState => ({ _tag: \"Pending\" })),\n Match.whenOr(\n \"in_progress\",\n \"running\",\n \"processing\",\n (): ResearchState => ({ _tag: \"Running\" }),\n ),\n Match.whenOr(\n \"completed\",\n \"succeeded\",\n (): ResearchState => ({ _tag: \"Succeeded\", result: turnFromInteraction(wire) }),\n ),\n Match.whenOr(\n \"failed\",\n \"error\",\n \"cancelled\",\n (): ResearchState => ({\n _tag: \"Failed\",\n ...(wire.error?.message != null && { reason: wire.error.message }),\n raw: wire,\n }),\n ),\n Match.orElse((): ResearchState => ({ _tag: \"Running\" })),\n )\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst authHeader = (cfg: Config) =>\n HttpClientRequest.setHeader(\"x-goog-api-key\", Redacted.value(cfg.apiKey))\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"google\", raw: cause })\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"google\"\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\n// Deep research takes one question. Concatenate the user turns' text into the\n// Interactions `input` string.\nconst inputText = (history: ReadonlyArray<Items.HistoryItem>): string =>\n history\n .filter(Items.isMessage)\n .filter((m) => m.role === \"user\")\n .flatMap((m) => m.content)\n .filter(Items.isInputText)\n .map((b) => b.text)\n .join(\"\\n\")\n\nconst submitBody = (request: GoogleResearchRequest): Record<string, unknown> => ({\n agent: request.model ?? \"deep-research-preview-04-2026\",\n input: inputText(request.history),\n background: true,\n store: true,\n agent_config: { type: \"deep-research\", thinking_summaries: \"auto\" },\n tools: [{ type: \"google_search\" }],\n})\n\nconst submitJob = (\n cfg: Config,\n request: GoogleResearchRequest,\n): Effect.Effect<ResearchJobRef, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/interactions`).pipe(\n authHeader(cfg),\n HttpClientRequest.bodyJsonUnsafe(submitBody(request)),\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* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeSubmit(json).pipe(Effect.mapError(transportFailure))\n return Job.jobRef<Turn>(\"google\", wire.id)\n })\n\nconst pollJob = (\n cfg: Config,\n ref: ResearchJobRef,\n): Effect.Effect<ResearchState, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/interactions/${ref.id}`).pipe(\n authHeader(cfg),\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* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeInteraction(json).pipe(Effect.mapError(transportFailure))\n return jobStateOf(wire)\n })\n\n// No verified cancel endpoint on the preview Interactions surface.\nconst unsupported = (capability: string): AiError.Unsupported =>\n new AiError.Unsupported({\n provider: \"google\",\n capability,\n reason: \"Gemini deep research (preview) exposes no verified cancel endpoint.\",\n })\n\n// ---------------------------------------------------------------------------\n// Streaming (Interactions SSE)\n// ---------------------------------------------------------------------------\n\n// The Interactions stream emits `step.delta` events in two shapes: the report\n// body arrives as `delta: { type: \"text\", text }`, while a thinking summary\n// arrives as `delta: { type: \"thought_summary\", content: { text } }` (and a\n// content-free `thought_signature` we skip). Terminates in `interaction.completed`\n// / `interaction.error`, then an OpenAI-style `[DONE]` sentinel we drop upstream.\nconst WireDelta = Schema.Struct({\n type: Schema.optional(Schema.NullOr(Schema.String)),\n text: Schema.optional(Schema.NullOr(Schema.String)),\n content: Schema.optional(\n Schema.NullOr(\n Schema.Struct({\n type: Schema.optional(Schema.NullOr(Schema.String)),\n text: Schema.optional(Schema.NullOr(Schema.String)),\n }),\n ),\n ),\n})\nconst WireStreamEvent = Schema.Struct({\n event_type: Schema.optional(Schema.NullOr(Schema.String)),\n delta: Schema.optional(Schema.NullOr(WireDelta)),\n interaction: Schema.optional(Schema.NullOr(WireInteraction)),\n error: Schema.optional(\n Schema.NullOr(Schema.Struct({ message: Schema.optional(Schema.NullOr(Schema.String)) })),\n ),\n})\ntype WireStreamEvent = typeof WireStreamEvent.Type\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\nconst decodeStreamEvent = Schema.decodeUnknownEffect(WireStreamEvent)\n\n// Prefer the completed interaction's decoded text; when the terminal event is\n// bare (or its text decodes empty) fall back to the accumulated stream deltas,\n// so `TurnComplete.turn` always carries the report that streamed.\nconst completedTurn = (wire: WireInteraction | null | undefined, streamed: string): Turn =>\n wire != null && reportText(wire).length > 0\n ? turnFromInteraction(wire)\n : {\n items: [\n {\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"output_text\", text: streamed }],\n ...(wire != null && { providerData: wire }),\n },\n ],\n usage: {},\n stop_reason: \"stop\",\n }\n\n// Map one wire event onto `TurnEvent`s, appending text deltas to `acc` (the\n// `completedTurn` fallback) and lifting `interaction.error` to a typed failure.\nconst streamEventToTurnEvents = (\n acc: Ref.Ref<string>,\n ev: WireStreamEvent,\n): Effect.Effect<ReadonlyArray<TurnEvent>, AiError.AiError> => {\n if (ev.event_type === \"interaction.error\") {\n return Effect.fail(\n new AiError.GenerationFailed({\n provider: \"google\",\n ...(ev.error?.message != null && { message: ev.error.message }),\n raw: ev,\n }),\n )\n }\n if (ev.event_type === \"step.delta\") {\n const delta = ev.delta\n // Report body: `delta.text`. Accumulate it so the terminal `TurnComplete`\n // carries the report even when `interaction.completed` arrives text-free.\n if (delta?.text != null) {\n const text = delta.text\n return Effect.as(\n Ref.update(acc, (streamed) => streamed + text),\n [TurnEvent.TextDelta({ text })],\n )\n }\n // Thinking summary: nested under `delta.content.text`. Surfaced as reasoning,\n // not accumulated into the report.\n if (delta?.content?.text != null) {\n return Effect.succeed([\n TurnEvent.ReasoningDelta({ text: delta.content.text, kind: \"summary\" }),\n ])\n }\n return Effect.succeed([])\n }\n if (ev.event_type === \"interaction.completed\") {\n return Effect.map(Ref.get(acc), (streamed) => [\n TurnEvent.TurnComplete({ turn: completedTurn(ev.interaction, streamed) }),\n ])\n }\n return Effect.succeed([])\n}\n\n// A streaming interaction create (`?stream=true`): consume the SSE directly,\n// mapping each event onto `TurnEvent`s via {@link streamEventToTurnEvents}.\nconst streamCreate = (\n cfg: Config,\n request: GoogleResearchRequest,\n): Stream.Stream<TurnEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const acc = yield* Ref.make(\"\")\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/interactions?stream=true`).pipe(\n authHeader(cfg),\n HttpClientRequest.bodyJsonUnsafe({ ...submitBody(request), stream: true }),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n // Preview surface: log the response status + content-type so an\n // unexpected `application/json` (i.e. `?stream=true` not triggering SSE)\n // is visible at LOG_LEVEL=Debug rather than presenting as a silent hang.\n yield* Effect.logDebug(\"google interactions stream response\", {\n status: response.status,\n contentType: response.headers[\"content-type\"],\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 return response.stream.pipe(\n Stream.mapError(transportFailure),\n SSE.fromBytes,\n // Log each raw frame before decode: the ground truth for the (unverified)\n // event_type / delta shapes this codec keys on.\n Stream.tap((ev) =>\n Effect.logDebug(\"google interactions sse frame\", { event: ev.event, data: ev.data }),\n ),\n // The stream ends with an OpenAI-style `[DONE]` sentinel that is not JSON;\n // drop it (and any blank frame) before the JSON decode.\n Stream.filter((ev) => ev.event !== \"done\" && ev.data.trim() !== \"[DONE]\"),\n Stream.mapEffect((ev) =>\n parseJsonUnknown(ev.data).pipe(\n Effect.flatMap(decodeStreamEvent),\n Effect.mapError(transportFailure),\n Effect.flatMap((wire) => streamEventToTurnEvents(acc, wire)),\n ),\n ),\n Stream.flatMap(Stream.fromIterable),\n )\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nconst jobOps = (\n cfg: Config,\n client: HttpClient.HttpClient,\n): ResearchJobOps<GoogleResearchRequest> => {\n const withClient = <A>(e: Effect.Effect<A, AiError.AiError, HttpClient.HttpClient>) =>\n Effect.provideService(e, HttpClient.HttpClient, client)\n return {\n submit: (request) => withClient(submitJob(cfg, request)),\n poll: (ref) => withClient(pollJob(cfg, ref)),\n cancel: () => Effect.fail(unsupported(\"cancel\")),\n }\n}\n\n// fromJob's default surface, with `researchStream` overridden to the real\n// Interactions SSE (its submit-then-attach default only synthesizes progress).\nconst buildService = <Req extends ResearchRequest>(\n cfg: Config,\n client: HttpClient.HttpClient,\n ops: ResearchJobOps<Req>,\n toGoogle: (request: Req) => GoogleResearchRequest,\n): DeepResearchServiceShape<Req> => ({\n ...fromJob(ops, cfg.job),\n researchStream: (request) =>\n Stream.provideService(streamCreate(cfg, toGoogle(request)), HttpClient.HttpClient, client),\n})\n\n/** Build a `GoogleDeepResearchService`. For Layer setup, prefer {@link layer}. */\nexport const make = (\n cfg: Config,\n): Effect.Effect<GoogleDeepResearchService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) =>\n buildService(cfg, client, jobOps(cfg, client), (request) => request),\n )\n\n/**\n * Layer registering the provider-typed `GoogleDeepResearch` tag and the generic\n * `DeepResearch` tag. No capability markers: the surface (poll, collect,\n * synthesized stream) is universal.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<GoogleDeepResearch | DeepResearch, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(GoogleDeepResearch, make(cfg))\n const generic = Layer.effect(\n DeepResearch,\n Effect.map(HttpClient.HttpClient, (client): DeepResearchService => {\n const ops = jobOps(cfg, client)\n const genericOps: ResearchJobOps<ResearchRequest> = {\n ...ops,\n submit: (request) => ops.submit(request as GoogleResearchRequest),\n }\n return buildService(cfg, client, genericOps, (request) => request as GoogleResearchRequest)\n }),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
package/dist/LyriaGenerator.mjs
CHANGED
|
@@ -188,7 +188,7 @@ const streamGenerationFromUnsupported = (_input, _request) => {
|
|
|
188
188
|
reason: "Lyria 3 sync does not support bidirectional sessions. Use the Lyria RealTime Layer (WebSocket BidiGenerateMusic) for interactive mid-session prompts."
|
|
189
189
|
}));
|
|
190
190
|
};
|
|
191
|
-
const make = (cfg) => Effect.map(HttpClient.HttpClient
|
|
191
|
+
const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => ({
|
|
192
192
|
generate: (request) => generateImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
193
193
|
streamGeneration: (request) => streamGenerationImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),
|
|
194
194
|
streamGenerationFrom: streamGenerationFromUnsupported
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LyriaGenerator.mjs","names":[],"sources":["../src/LyriaGenerator.ts"],"sourcesContent":["import {\n Context,\n Duration,\n Effect,\n Encoding,\n Layer,\n Match,\n Redacted,\n Result,\n Schema,\n Stream,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport { warnDroppedWhen } from \"@effect-uai/core/Capabilities\"\nimport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n GenerateResult,\n MusicResult,\n MusicSessionInput,\n MusicStreamEvent,\n} from \"@effect-uai/core/Music\"\nimport { singleVariant } from \"@effect-uai/core/Music\"\nimport { MusicGenerator, type MusicGeneratorService } from \"@effect-uai/core/MusicGenerator\"\nimport type { LyriaModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Lyria-typed generate request. Narrows `model` to `LyriaModel`.\n *\n * Lyria 3 sync `generateContent` has no structured wire fields for\n * `lyrics` / `duration` / `seed` / `instrumental` / `bpm` / `scale` /\n * etc. Those fields on the Common request are logged as\n * dropped-bucket-2 hints; the adapter never rewrites or augments the\n * caller's prompt on their behalf. If you want lyrics or vocal\n * suppression, include them in your `prompt` text yourself.\n *\n * Output format: only `audio/mp3` and `audio/wav` are accepted; other\n * containers fail `Unsupported`. The Clip model variant is fixed at\n * `audio/mp3`.\n */\nexport type LyriaGenerateRequest = Omit<CommonGenerateMusicRequest, \"model\"> & {\n readonly model: LyriaModel\n}\n\nexport type LyriaGeneratorService = {\n readonly generate: (\n request: LyriaGenerateRequest,\n ) => Effect.Effect<GenerateResult, AiError.AiError>\n readonly streamGeneration: (\n request: LyriaGenerateRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamGenerationFrom: <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ) => Stream.Stream<MusicStreamEvent, AiError.AiError | E, R>\n}\n\nexport class LyriaGenerator extends Context.Service<LyriaGenerator, LyriaGeneratorService>()(\n \"@betalyra/effect-uai/providers/google/LyriaGenerator\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec: AudioFormat → Lyria mimeType\n// ---------------------------------------------------------------------------\n\nexport type LyriaResponseMimeType = \"audio/mp3\" | \"audio/wav\"\n\nexport const containerToMimeType: (\n container: AudioFormat[\"container\"],\n) => Effect.Effect<LyriaResponseMimeType, AiError.AiError> = Match.type<\n AudioFormat[\"container\"]\n>().pipe(\n Match.when(\"mp3\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/mp3\")),\n Match.when(\"wav\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/wav\")),\n Match.whenOr(\"ogg\", \"opus\", \"flac\", \"aac\", \"webm\", \"raw\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"outputFormat\",\n reason: `Lyria 3 only produces audio/mp3 or audio/wav; ${c} is not available. Use container: \"mp3\" or \"wav\".`,\n }),\n ),\n ),\n Match.exhaustive,\n)\n\n/**\n * Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3, it\n * does not accept a `responseFormat` on `generationConfig` (the live\n * API rejects unknown fields). Pro accepts the documented\n * `responseFormat` knob.\n */\nconst isClipModel = (model: string): boolean => model.includes(\"clip\")\n\nexport const realizedFormat: (mime: LyriaResponseMimeType) => AudioFormat =\n Match.type<LyriaResponseMimeType>().pipe(\n Match.when(\n \"audio/mp3\",\n (): AudioFormat => ({ container: \"mp3\", encoding: \"mp3\", sampleRate: 44100, channels: 2 }),\n ),\n Match.when(\n \"audio/wav\",\n (): AudioFormat => ({\n container: \"wav\",\n encoding: \"pcm_s16le\",\n sampleRate: 44100,\n channels: 2,\n }),\n ),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// Bucket-2 warn-and-drop for Common fields Lyria 3 can't honor\n// ---------------------------------------------------------------------------\n\n/**\n * Warn (don't fail) when the caller passed Common fields Lyria 3 has\n * no wire field for. Bucket-2 per capabilities policy: provider has\n * no structured interpretation; the output is still valid music, just\n * less aligned with the caller's hint. We never embed these into the\n * prompt on the caller's behalf, prompt construction is the\n * developer's job.\n */\nconst warnDroppedHints = (request: LyriaGenerateRequest): Effect.Effect<void> =>\n Effect.all(\n [\n warnDroppedWhen(request.lyrics, {\n provider: \"lyria\",\n capability: \"lyrics\",\n field: \"lyrics\",\n reason:\n \"Lyria 3 sync has no `lyrics` wire field. If you want vocals to follow specific lyrics, embed them in your prompt yourself (e.g. with `[Verse]` / `[Chorus]` tags).\",\n }),\n warnDroppedWhen(\n request.duration !== undefined ? Duration.toMillis(request.duration) : undefined,\n {\n provider: \"lyria\",\n capability: \"duration\",\n field: \"duration\",\n reason:\n \"Lyria 3 clip is fixed at 30 s; pro derives duration from prompt content. There is no wire field for duration. If you want to steer length, mention it in your prompt.\",\n },\n ),\n warnDroppedWhen(request.seed, {\n provider: \"lyria\",\n capability: \"seed\",\n field: \"seed\",\n reason:\n \"Lyria 3 (Gemini surface) does not expose a seed parameter. Output is non-deterministic across identical prompts. Use Lyria 2 (Vertex) for seeded generation.\",\n }),\n ],\n { discard: true },\n )\n\n// ---------------------------------------------------------------------------\n// Wire response schema\n// ---------------------------------------------------------------------------\n\nconst InlineData = Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n})\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n inlineData: Schema.optional(InlineData),\n})\n\nconst Content = Schema.Struct({\n parts: Schema.optional(Schema.Array(Part)),\n})\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\n})\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n})\n\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"lyria\", raw: cause })\n\nconst httpStatusError: (status: number, body: string) => AiError.AiError = (status, body) =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: \"lyria\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"lyria\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: \"lyria\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"lyria\", raw: body }),\n ),\n )\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"lyria\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Find the first inline-data audio part and the concatenated text parts\n * from a Wire response.\n */\nconst extractParts = (\n wire: typeof Wire.Type,\n): { readonly inline?: typeof InlineData.Type; readonly text?: string } => {\n const allParts = (wire.candidates ?? []).flatMap((c) => c.content?.parts ?? [])\n const inline = allParts.find((p) => p.inlineData !== undefined)?.inlineData\n const textParts = allParts.flatMap((p) => (p.text !== undefined ? [p.text] : []))\n const text = textParts.length === 0 ? undefined : textParts.join(\"\\n\")\n return {\n ...(inline !== undefined && { inline }),\n ...(text !== undefined && { text }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst generateImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Effect.Effect<GenerateResult, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const requestedContainer = (request.outputFormat?.container ??\n \"mp3\") satisfies AudioFormat[\"container\"]\n const requestedMime = yield* containerToMimeType(requestedContainer)\n // lyria-3-clip-preview has no `responseFormat` wire field and always\n // emits mp3, so there is no format to send and no 400 to surface. Rather\n // than keep a per-model table that rejects `wav`, report the format the\n // model actually produces (mp3) so the result is never mislabeled.\n // lyria-3-pro honours the requested container.\n const mimeType = isClipModel(request.model) ? (\"audio/mp3\" as const) : requestedMime\n yield* warnDroppedHints(request)\n const body = isClipModel(request.model)\n ? { contents: [{ parts: [{ text: request.prompt }] }] }\n : {\n contents: [{ parts: [{ text: request.prompt }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\", \"TEXT\"],\n responseFormat: { audio: { mimeType } },\n },\n }\n const httpRequest = HttpClientRequest.post(\n `${baseUrl(cfg)}/models/${request.model}:generateContent`,\n ).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* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeWire(json).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.GenerationFailed({ provider: \"lyria\", raw: cause }),\n ),\n )\n const { inline, text } = extractParts(wire)\n if (inline === undefined) {\n return yield* new AiError.GenerationFailed({\n provider: \"lyria\",\n raw: {\n message: \"Lyria response had no inlineData audio part.\",\n hint: \"Likely a prompt-filter rejection. Lyria filters references to real artists / copyrighted song lyrics. Rephrase with descriptive style language instead of artist names.\",\n textParts: text,\n response: json,\n },\n })\n }\n const bytes = yield* decodeBase64ToBytes(inline.data)\n const result: MusicResult = {\n audio: { format: realizedFormat(mimeType), bytes },\n provider: \"lyria\",\n ...(text !== undefined && { lyrics: text }),\n watermark: \"synthid\",\n }\n return singleVariant(result)\n })\n\nconst streamGenerationImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.map(generateImpl(cfg)(request), (result) =>\n Stream.succeed<AudioChunk>({ bytes: result.primary.audio.bytes }),\n ),\n )\n\n/**\n * Lyria 3 sync has no bidirectional session at the wire level, that's\n * Lyria RealTime (separate Layer). The provider Layer here also does\n * NOT register `MusicInteractiveSession`, so callers using\n * `MusicGenerator.streamGenerationFrom` against only this Layer get a\n * compile-time error before this runtime fallback ever fires.\n */\nconst streamGenerationFromUnsupported = <E, R>(\n _input: Stream.Stream<MusicSessionInput, E, R>,\n _request: CommonStreamGenerateMusicRequest,\n): Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> => {\n const fail: Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> = Stream.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"streamGenerationFrom\",\n reason:\n \"Lyria 3 sync does not support bidirectional sessions. Use the Lyria RealTime Layer (WebSocket BidiGenerateMusic) for interactive mid-session prompts.\",\n }),\n )\n return fail\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<LyriaGeneratorService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n generate: (request) =>\n generateImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamGeneration: (request) =>\n streamGenerationImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n streamGenerationFrom: streamGenerationFromUnsupported,\n }))\n\n/**\n * Layer that registers both the provider-specific `LyriaGenerator` tag\n * and the generic `MusicGenerator` tag.\n *\n * Does NOT register the `MusicInteractiveSession` capability marker,\n * Lyria 3 sync has no bidirectional session at the wire level. Code\n * calling `MusicGenerator.streamGenerationFrom` will fail to typecheck\n * against this Layer alone, which is the intended UX.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<LyriaGenerator | MusicGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(LyriaGenerator, make(cfg)),\n Layer.effect(\n MusicGenerator,\n Effect.map(\n make(cfg),\n (s): MusicGeneratorService => ({\n generate: (req: CommonGenerateMusicRequest) =>\n s.generate({ ...req, model: req.model as LyriaModel }),\n streamGeneration: (req: CommonStreamGenerateMusicRequest) =>\n s.streamGeneration({ ...req, model: req.model as LyriaModel }),\n streamGenerationFrom: s.streamGenerationFrom,\n }),\n ),\n ),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LyriaGenerator.mjs","names":[],"sources":["../src/LyriaGenerator.ts"],"sourcesContent":["import {\n Context,\n Duration,\n Effect,\n Encoding,\n Layer,\n Match,\n Redacted,\n Result,\n Schema,\n Stream,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioChunk, AudioFormat } from \"@effect-uai/core/Audio\"\nimport { warnDroppedWhen } from \"@effect-uai/core/Capabilities\"\nimport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n GenerateResult,\n MusicResult,\n MusicSessionInput,\n MusicStreamEvent,\n} from \"@effect-uai/core/Music\"\nimport { singleVariant } from \"@effect-uai/core/Music\"\nimport { MusicGenerator, type MusicGeneratorService } from \"@effect-uai/core/MusicGenerator\"\nimport type { LyriaModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Lyria-typed generate request. Narrows `model` to `LyriaModel`.\n *\n * Lyria 3 sync `generateContent` has no structured wire fields for\n * `lyrics` / `duration` / `seed` / `instrumental` / `bpm` / `scale` /\n * etc. Those fields on the Common request are logged as\n * dropped-bucket-2 hints; the adapter never rewrites or augments the\n * caller's prompt on their behalf. If you want lyrics or vocal\n * suppression, include them in your `prompt` text yourself.\n *\n * Output format: only `audio/mp3` and `audio/wav` are accepted; other\n * containers fail `Unsupported`. The Clip model variant is fixed at\n * `audio/mp3`.\n */\nexport type LyriaGenerateRequest = Omit<CommonGenerateMusicRequest, \"model\"> & {\n readonly model: LyriaModel\n}\n\nexport type LyriaGeneratorService = {\n readonly generate: (\n request: LyriaGenerateRequest,\n ) => Effect.Effect<GenerateResult, AiError.AiError>\n readonly streamGeneration: (\n request: LyriaGenerateRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n readonly streamGenerationFrom: <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ) => Stream.Stream<MusicStreamEvent, AiError.AiError | E, R>\n}\n\nexport class LyriaGenerator extends Context.Service<LyriaGenerator, LyriaGeneratorService>()(\n \"@betalyra/effect-uai/providers/google/LyriaGenerator\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec: AudioFormat → Lyria mimeType\n// ---------------------------------------------------------------------------\n\nexport type LyriaResponseMimeType = \"audio/mp3\" | \"audio/wav\"\n\nexport const containerToMimeType: (\n container: AudioFormat[\"container\"],\n) => Effect.Effect<LyriaResponseMimeType, AiError.AiError> = Match.type<\n AudioFormat[\"container\"]\n>().pipe(\n Match.when(\"mp3\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/mp3\")),\n Match.when(\"wav\", () => Effect.succeed<LyriaResponseMimeType>(\"audio/wav\")),\n Match.whenOr(\"ogg\", \"opus\", \"flac\", \"aac\", \"webm\", \"raw\", (c) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"outputFormat\",\n reason: `Lyria 3 only produces audio/mp3 or audio/wav; ${c} is not available. Use container: \"mp3\" or \"wav\".`,\n }),\n ),\n ),\n Match.exhaustive,\n)\n\n/**\n * Is this a Lyria 3 Clip model variant? Clip is fixed at 30 s MP3, it\n * does not accept a `responseFormat` on `generationConfig` (the live\n * API rejects unknown fields). Pro accepts the documented\n * `responseFormat` knob.\n */\nconst isClipModel = (model: string): boolean => model.includes(\"clip\")\n\nexport const realizedFormat: (mime: LyriaResponseMimeType) => AudioFormat =\n Match.type<LyriaResponseMimeType>().pipe(\n Match.when(\n \"audio/mp3\",\n (): AudioFormat => ({ container: \"mp3\", encoding: \"mp3\", sampleRate: 44100, channels: 2 }),\n ),\n Match.when(\n \"audio/wav\",\n (): AudioFormat => ({\n container: \"wav\",\n encoding: \"pcm_s16le\",\n sampleRate: 44100,\n channels: 2,\n }),\n ),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// Bucket-2 warn-and-drop for Common fields Lyria 3 can't honor\n// ---------------------------------------------------------------------------\n\n/**\n * Warn (don't fail) when the caller passed Common fields Lyria 3 has\n * no wire field for. Bucket-2 per capabilities policy: provider has\n * no structured interpretation; the output is still valid music, just\n * less aligned with the caller's hint. We never embed these into the\n * prompt on the caller's behalf, prompt construction is the\n * developer's job.\n */\nconst warnDroppedHints = (request: LyriaGenerateRequest): Effect.Effect<void> =>\n Effect.all(\n [\n warnDroppedWhen(request.lyrics, {\n provider: \"lyria\",\n capability: \"lyrics\",\n field: \"lyrics\",\n reason:\n \"Lyria 3 sync has no `lyrics` wire field. If you want vocals to follow specific lyrics, embed them in your prompt yourself (e.g. with `[Verse]` / `[Chorus]` tags).\",\n }),\n warnDroppedWhen(\n request.duration !== undefined ? Duration.toMillis(request.duration) : undefined,\n {\n provider: \"lyria\",\n capability: \"duration\",\n field: \"duration\",\n reason:\n \"Lyria 3 clip is fixed at 30 s; pro derives duration from prompt content. There is no wire field for duration. If you want to steer length, mention it in your prompt.\",\n },\n ),\n warnDroppedWhen(request.seed, {\n provider: \"lyria\",\n capability: \"seed\",\n field: \"seed\",\n reason:\n \"Lyria 3 (Gemini surface) does not expose a seed parameter. Output is non-deterministic across identical prompts. Use Lyria 2 (Vertex) for seeded generation.\",\n }),\n ],\n { discard: true },\n )\n\n// ---------------------------------------------------------------------------\n// Wire response schema\n// ---------------------------------------------------------------------------\n\nconst InlineData = Schema.Struct({\n mimeType: Schema.optional(Schema.String),\n data: Schema.String,\n})\n\nconst Part = Schema.Struct({\n text: Schema.optional(Schema.String),\n inlineData: Schema.optional(InlineData),\n})\n\nconst Content = Schema.Struct({\n parts: Schema.optional(Schema.Array(Part)),\n})\n\nconst Candidate = Schema.Struct({\n content: Schema.optional(Content),\n})\n\nconst Wire = Schema.Struct({\n candidates: Schema.optional(Schema.Array(Candidate)),\n})\n\nconst decodeWire = Schema.decodeUnknownEffect(Wire)\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string =>\n cfg.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\"\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"lyria\", raw: cause })\n\nconst httpStatusError: (status: number, body: string) => AiError.AiError = (status, body) =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: \"lyria\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"lyria\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"lyria\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: \"lyria\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"lyria\", raw: body }),\n ),\n )\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(new AiError.InvalidRequest({ provider: \"lyria\", param: \"audio\", raw: cause })),\n })\n\n/**\n * Find the first inline-data audio part and the concatenated text parts\n * from a Wire response.\n */\nconst extractParts = (\n wire: typeof Wire.Type,\n): { readonly inline?: typeof InlineData.Type; readonly text?: string } => {\n const allParts = (wire.candidates ?? []).flatMap((c) => c.content?.parts ?? [])\n const inline = allParts.find((p) => p.inlineData !== undefined)?.inlineData\n const textParts = allParts.flatMap((p) => (p.text !== undefined ? [p.text] : []))\n const text = textParts.length === 0 ? undefined : textParts.join(\"\\n\")\n return {\n ...(inline !== undefined && { inline }),\n ...(text !== undefined && { text }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst generateImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Effect.Effect<GenerateResult, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const requestedContainer = (request.outputFormat?.container ??\n \"mp3\") satisfies AudioFormat[\"container\"]\n const requestedMime = yield* containerToMimeType(requestedContainer)\n // lyria-3-clip-preview has no `responseFormat` wire field and always\n // emits mp3, so there is no format to send and no 400 to surface. Rather\n // than keep a per-model table that rejects `wav`, report the format the\n // model actually produces (mp3) so the result is never mislabeled.\n // lyria-3-pro honours the requested container.\n const mimeType = isClipModel(request.model) ? (\"audio/mp3\" as const) : requestedMime\n yield* warnDroppedHints(request)\n const body = isClipModel(request.model)\n ? { contents: [{ parts: [{ text: request.prompt }] }] }\n : {\n contents: [{ parts: [{ text: request.prompt }] }],\n generationConfig: {\n responseModalities: [\"AUDIO\", \"TEXT\"],\n responseFormat: { audio: { mimeType } },\n },\n }\n const httpRequest = HttpClientRequest.post(\n `${baseUrl(cfg)}/models/${request.model}:generateContent`,\n ).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* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n const wire = yield* decodeWire(json).pipe(\n Effect.mapError(\n (cause): AiError.AiError =>\n new AiError.GenerationFailed({ provider: \"lyria\", raw: cause }),\n ),\n )\n const { inline, text } = extractParts(wire)\n if (inline === undefined) {\n return yield* new AiError.GenerationFailed({\n provider: \"lyria\",\n raw: {\n message: \"Lyria response had no inlineData audio part.\",\n hint: \"Likely a prompt-filter rejection. Lyria filters references to real artists / copyrighted song lyrics. Rephrase with descriptive style language instead of artist names.\",\n textParts: text,\n response: json,\n },\n })\n }\n const bytes = yield* decodeBase64ToBytes(inline.data)\n const result: MusicResult = {\n audio: { format: realizedFormat(mimeType), bytes },\n provider: \"lyria\",\n ...(text !== undefined && { lyrics: text }),\n watermark: \"synthid\",\n }\n return singleVariant(result)\n })\n\nconst streamGenerationImpl =\n (cfg: Config) =>\n (\n request: LyriaGenerateRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.map(generateImpl(cfg)(request), (result) =>\n Stream.succeed<AudioChunk>({ bytes: result.primary.audio.bytes }),\n ),\n )\n\n/**\n * Lyria 3 sync has no bidirectional session at the wire level, that's\n * Lyria RealTime (separate Layer). The provider Layer here also does\n * NOT register `MusicInteractiveSession`, so callers using\n * `MusicGenerator.streamGenerationFrom` against only this Layer get a\n * compile-time error before this runtime fallback ever fires.\n */\nconst streamGenerationFromUnsupported = <E, R>(\n _input: Stream.Stream<MusicSessionInput, E, R>,\n _request: CommonStreamGenerateMusicRequest,\n): Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> => {\n const fail: Stream.Stream<MusicStreamEvent, AiError.AiError | E, R> = Stream.fail(\n new AiError.Unsupported({\n provider: \"lyria\",\n capability: \"streamGenerationFrom\",\n reason:\n \"Lyria 3 sync does not support bidirectional sessions. Use the Lyria RealTime Layer (WebSocket BidiGenerateMusic) for interactive mid-session prompts.\",\n }),\n )\n return fail\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<LyriaGeneratorService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n generate: (request) =>\n generateImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n streamGeneration: (request) =>\n streamGenerationImpl(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client)),\n streamGenerationFrom: streamGenerationFromUnsupported,\n }))\n\n/**\n * Layer that registers both the provider-specific `LyriaGenerator` tag\n * and the generic `MusicGenerator` tag.\n *\n * Does NOT register the `MusicInteractiveSession` capability marker,\n * Lyria 3 sync has no bidirectional session at the wire level. Code\n * calling `MusicGenerator.streamGenerationFrom` will fail to typecheck\n * against this Layer alone, which is the intended UX.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<LyriaGenerator | MusicGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(LyriaGenerator, make(cfg)),\n Layer.effect(\n MusicGenerator,\n Effect.map(\n make(cfg),\n (s): MusicGeneratorService => ({\n generate: (req: CommonGenerateMusicRequest) =>\n s.generate({ ...req, model: req.model as LyriaModel }),\n streamGeneration: (req: CommonStreamGenerateMusicRequest) =>\n s.streamGeneration({ ...req, model: req.model as LyriaModel }),\n streamGenerationFrom: s.streamGenerationFrom,\n }),\n ),\n ),\n )\n"],"mappings":";;;;;;;;;;;;;;;AA+DA,IAAa,iBAAb,cAAoC,QAAQ,SAAgD,CAC1F,uDACD,CAAC;AAaF,MAAa,sBAEgD,MAAM,MAEhE,CAAC,KACF,MAAM,KAAK,aAAa,OAAO,QAA+B,YAAY,CAAC,EAC3E,MAAM,KAAK,aAAa,OAAO,QAA+B,YAAY,CAAC,EAC3E,MAAM,OAAO,OAAO,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,MACzD,OAAO,KACL,IAAI,QAAQ,YAAY;CACtB,UAAU;CACV,YAAY;CACZ,QAAQ,iDAAiD,EAAE;CAC5D,CAAC,CACH,CACF,EACD,MAAM,WACP;;;;;;;AAQD,MAAM,eAAe,UAA2B,MAAM,SAAS,OAAO;AAEtE,MAAa,iBACX,MAAM,MAA6B,CAAC,KAClC,MAAM,KACJ,oBACoB;CAAE,WAAW;CAAO,UAAU;CAAO,YAAY;CAAO,UAAU;CAAG,EAC1F,EACD,MAAM,KACJ,oBACoB;CAClB,WAAW;CACX,UAAU;CACV,YAAY;CACZ,UAAU;CACX,EACF,EACD,MAAM,WACP;;;;;;;;;AAcH,MAAM,oBAAoB,YACxB,OAAO,IACL;CACE,gBAAgB,QAAQ,QAAQ;EAC9B,UAAU;EACV,YAAY;EACZ,OAAO;EACP,QACE;EACH,CAAC;CACF,gBACE,QAAQ,aAAa,KAAA,IAAY,SAAS,SAAS,QAAQ,SAAS,GAAG,KAAA,GACvE;EACE,UAAU;EACV,YAAY;EACZ,OAAO;EACP,QACE;EACH,CACF;CACD,gBAAgB,QAAQ,MAAM;EAC5B,UAAU;EACV,YAAY;EACZ,OAAO;EACP,QACE;EACH,CAAC;CACH,EACD,EAAE,SAAS,MAAM,CAClB;AAMH,MAAM,aAAa,OAAO,OAAO;CAC/B,UAAU,OAAO,SAAS,OAAO,OAAO;CACxC,MAAM,OAAO;CACd,CAAC;AAEF,MAAM,OAAO,OAAO,OAAO;CACzB,MAAM,OAAO,SAAS,OAAO,OAAO;CACpC,YAAY,OAAO,SAAS,WAAW;CACxC,CAAC;AAEF,MAAM,UAAU,OAAO,OAAO,EAC5B,OAAO,OAAO,SAAS,OAAO,MAAM,KAAK,CAAC,EAC3C,CAAC;AAEF,MAAM,YAAY,OAAO,OAAO,EAC9B,SAAS,OAAO,SAAS,QAAQ,EAClC,CAAC;AAEF,MAAM,OAAO,OAAO,OAAO,EACzB,YAAY,OAAO,SAAS,OAAO,MAAM,UAAU,CAAC,EACrD,CAAC;AAEF,MAAM,aAAa,OAAO,oBAAoB,KAAK;AAMnD,MAAM,WAAW,QACf,IAAI,WAAW;AAEjB,MAAM,oBAAoB,UACxB,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAS,KAAK;CAAO,CAAC;AAE5D,MAAM,mBAAsE,QAAQ,SAClF,MAAM,MAAM,OAAO,CAAC,KAClB,MAAM,KACJ,WACuB,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAS,KAAK;CAAM,CAAC,CACjF,EACD,MAAM,OACJ,KACA,WACuB,IAAI,QAAQ,QAAQ;CAAE,UAAU;CAAS,KAAK;CAAM,CAAC,CAC7E,EACD,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAU;CAAS,SAAS;CAAQ,KAAK;CAAM,CAAC,CAC5E,EACD,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAU;CAAS,SAAS;CAAc,KAAK;CAAM,CAAC,CAClF,EACD,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAU;CAAS,SAAS;CAAW,KAAK;CAAM,CAAC,CAC/E,EACD,MAAM,MACH,MAAM,KAAK,MACX,MAAuB,IAAI,QAAQ,YAAY;CAAE,UAAU;CAAS,QAAQ;CAAG,KAAK;CAAM,CAAC,CAC7F,EACD,MAAM,aACmB,IAAI,QAAQ,eAAe;CAAE,UAAU;CAAS,KAAK;CAAM,CAAC,CACpF,CACF;AAEH,MAAM,uBAAuB,QAC3B,OAAO,MAAM,SAAS,aAAa,IAAI,EAAE;CACvC,WAAW,OAAO;CAClB,YAAY,UACV,OAAO,KAAK,IAAI,QAAQ,eAAe;EAAE,UAAU;EAAS,OAAO;EAAS,KAAK;EAAO,CAAC,CAAC;CAC7F,CAAC;;;;;AAMJ,MAAM,gBACJ,SACyE;CACzE,MAAM,YAAY,KAAK,cAAc,EAAE,EAAE,SAAS,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;CAC/E,MAAM,SAAS,SAAS,MAAM,MAAM,EAAE,eAAe,KAAA,EAAU,EAAE;CACjE,MAAM,YAAY,SAAS,SAAS,MAAO,EAAE,SAAS,KAAA,IAAY,CAAC,EAAE,KAAK,GAAG,EAAE,CAAE;CACjF,MAAM,OAAO,UAAU,WAAW,IAAI,KAAA,IAAY,UAAU,KAAK,KAAK;AACtE,QAAO;EACL,GAAI,WAAW,KAAA,KAAa,EAAE,QAAQ;EACtC,GAAI,SAAS,KAAA,KAAa,EAAE,MAAM;EACnC;;AAOH,MAAM,gBACH,SAEC,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO,WAAW;CAGjC,MAAM,gBAAgB,OAAO,oBAFD,QAAQ,cAAc,aAChD,MACkE;CAMpE,MAAM,WAAW,YAAY,QAAQ,MAAM,GAAI,cAAwB;AACvE,QAAO,iBAAiB,QAAQ;CAChC,MAAM,OAAO,YAAY,QAAQ,MAAM,GACnC,EAAE,UAAU,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,QAAQ,QAAQ,CAAC,EAAE,CAAC,EAAE,GACrD;EACE,UAAU,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,QAAQ,QAAQ,CAAC,EAAE,CAAC;EACjD,kBAAkB;GAChB,oBAAoB,CAAC,SAAS,OAAO;GACrC,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE;GACxC;EACF;CACL,MAAM,cAAc,kBAAkB,KACpC,GAAG,QAAQ,IAAI,CAAC,UAAU,QAAQ,MAAM,kBACzC,CAAC,KACA,kBAAkB,UAAU,kBAAkB,SAAS,MAAM,IAAI,OAAO,CAAC,EACzE,kBAAkB,eAAe,KAAK,CACvC;CACD,MAAM,WAAW,OAAO,OAAO,QAAQ,YAAY,CAAC,KAAK,OAAO,SAAS,iBAAiB,CAAC;AAC3F,KAAI,SAAS,UAAU,KAAK;EAC1B,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,oBAAoB,GAAG,CAAC;AACtE,SAAO,OAAO,gBAAgB,SAAS,QAAQ,KAAK;;CAEtD,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,SAAS,iBAAiB,CAAC;CAOzE,MAAM,EAAE,QAAQ,SAAS,aAAa,OANlB,WAAW,KAAK,CAAC,KACnC,OAAO,UACJ,UACC,IAAI,QAAQ,iBAAiB;EAAE,UAAU;EAAS,KAAK;EAAO,CAAC,CAClE,CACF,CAC0C;AAC3C,KAAI,WAAW,KAAA,EACb,QAAO,OAAO,IAAI,QAAQ,iBAAiB;EACzC,UAAU;EACV,KAAK;GACH,SAAS;GACT,MAAM;GACN,WAAW;GACX,UAAU;GACX;EACF,CAAC;CAEJ,MAAM,QAAQ,OAAO,oBAAoB,OAAO,KAAK;AAOrD,QAAO,cAAc;EALnB,OAAO;GAAE,QAAQ,eAAe,SAAS;GAAE;GAAO;EAClD,UAAU;EACV,GAAI,SAAS,KAAA,KAAa,EAAE,QAAQ,MAAM;EAC1C,WAAW;EAEc,CAAC;EAC5B;AAEN,MAAM,wBACH,SAEC,YAEA,OAAO,OACL,OAAO,IAAI,aAAa,IAAI,CAAC,QAAQ,GAAG,WACtC,OAAO,QAAoB,EAAE,OAAO,OAAO,QAAQ,MAAM,OAAO,CAAC,CAClE,CACF;;;;;;;;AASL,MAAM,mCACJ,QACA,aAC4D;AAS5D,QARsE,OAAO,KAC3E,IAAI,QAAQ,YAAY;EACtB,UAAU;EACV,YAAY;EACZ,QACE;EACH,CAAC,CAEO;;AAOb,MAAa,QACX,QAEA,OAAO,IAAI,WAAW,aAAa,YAAY;CAC7C,WAAW,YACT,aAAa,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,OAAO,CAAC;CACvF,mBAAmB,YACjB,qBAAqB,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,OAAO,CAAC;CAC/F,sBAAsB;CACvB,EAAE;;;;;;;;;;AAWL,MAAa,SACX,QAEA,MAAM,MACJ,MAAM,OAAO,gBAAgB,KAAK,IAAI,CAAC,EACvC,MAAM,OACJ,gBACA,OAAO,IACL,KAAK,IAAI,GACR,OAA8B;CAC7B,WAAW,QACT,EAAE,SAAS;EAAE,GAAG;EAAK,OAAO,IAAI;EAAqB,CAAC;CACxD,mBAAmB,QACjB,EAAE,iBAAiB;EAAE,GAAG;EAAK,OAAO,IAAI;EAAqB,CAAC;CAChE,sBAAsB,EAAE;CACzB,EACF,CACF,CACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as it, n as globalExpect, r as describe, t as import_dist } from "./dist-
|
|
1
|
+
import { i as it, n as globalExpect, r as describe, t as import_dist } from "./dist-CWoAuwS9.mjs";
|
|
2
2
|
import { LyriaGenerator, containerToMimeType, layer, realizedFormat } from "./LyriaGenerator.mjs";
|
|
3
3
|
import { Duration, Effect, Layer, Redacted, Stream } from "effect";
|
|
4
4
|
import { FetchHttpClient } from "effect/unstable/http";
|
package/dist/codec.d.mts
CHANGED
|
@@ -6,7 +6,7 @@ import { HistoryItem } from "@effect-uai/core/Items";
|
|
|
6
6
|
|
|
7
7
|
//#region src/codec.d.ts
|
|
8
8
|
declare namespace codec_d_exports {
|
|
9
|
-
export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, RequestBody, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, hasUrlImageSource, ingestChunk };
|
|
9
|
+
export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, RequestBody, RequestTool, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, hasUrlImageSource, ingestChunk };
|
|
10
10
|
}
|
|
11
11
|
declare const WireChunk: Schema.Struct<{
|
|
12
12
|
readonly candidates: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
@@ -26,6 +26,7 @@ declare const WireChunk: Schema.Struct<{
|
|
|
26
26
|
readonly name: Schema.String;
|
|
27
27
|
readonly args: Schema.optional<Schema.Unknown>;
|
|
28
28
|
}>;
|
|
29
|
+
readonly thoughtSignature: Schema.optional<Schema.String>;
|
|
29
30
|
}>]>>>;
|
|
30
31
|
}>>;
|
|
31
32
|
readonly finishReason: Schema.optional<Schema.String>;
|
|
@@ -49,9 +50,11 @@ type RequestPart = {
|
|
|
49
50
|
};
|
|
50
51
|
} | {
|
|
51
52
|
readonly functionCall: {
|
|
53
|
+
readonly id?: string;
|
|
52
54
|
readonly name: string;
|
|
53
55
|
readonly args: unknown;
|
|
54
56
|
};
|
|
57
|
+
readonly thoughtSignature?: string;
|
|
55
58
|
} | {
|
|
56
59
|
readonly functionResponse: {
|
|
57
60
|
readonly name: string;
|
|
@@ -67,13 +70,25 @@ type RequestSystemInstruction = {
|
|
|
67
70
|
readonly text: string;
|
|
68
71
|
}>;
|
|
69
72
|
};
|
|
70
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Gemini's tool declaration. A `tools[]` entry is either a function-declaration
|
|
75
|
+
* block (translated from `ToolDescriptor`s) or one of the native hosted-tool
|
|
76
|
+
* entries. The native entries are empty objects keyed camelCase per the REST
|
|
77
|
+
* v1beta `Tool` object, and sit as siblings of the declarations in the same
|
|
78
|
+
* `tools` array.
|
|
79
|
+
*/
|
|
71
80
|
type RequestTool = {
|
|
72
81
|
readonly functionDeclarations: ReadonlyArray<{
|
|
73
82
|
readonly name: string;
|
|
74
83
|
readonly description: string;
|
|
75
84
|
readonly parameters: Record<string, unknown>;
|
|
76
85
|
}>;
|
|
86
|
+
} | {
|
|
87
|
+
readonly googleSearch: {};
|
|
88
|
+
} | {
|
|
89
|
+
readonly urlContext: {};
|
|
90
|
+
} | {
|
|
91
|
+
readonly codeExecution: {};
|
|
77
92
|
};
|
|
78
93
|
type RequestToolConfig = {
|
|
79
94
|
readonly functionCallingConfig: {
|
|
@@ -106,11 +121,12 @@ type RequestBody = {
|
|
|
106
121
|
* dropping the image.
|
|
107
122
|
*/
|
|
108
123
|
declare const hasUrlImageSource: (history: ReadonlyArray<HistoryItem>) => boolean;
|
|
109
|
-
declare const buildRequestBody: (history: ReadonlyArray<HistoryItem>, generationConfig: Option.Option<GenerationConfig>, tools?: ReadonlyArray<ToolDescriptor>, toolChoice?: CommonRequest["toolChoice"]) => RequestBody;
|
|
124
|
+
declare const buildRequestBody: (history: ReadonlyArray<HistoryItem>, generationConfig: Option.Option<GenerationConfig>, tools?: ReadonlyArray<ToolDescriptor>, providerToolEntries?: ReadonlyArray<RequestTool>, toolChoice?: CommonRequest["toolChoice"]) => RequestBody;
|
|
110
125
|
type AccumulatedFunctionCall = {
|
|
111
126
|
/** Synthesized id-or-name we surface as `call_id` on the canonical item. */readonly callId: string;
|
|
112
127
|
readonly name: string; /** Wire id from Gemini 3, when present - echoed back on `functionResponse`. */
|
|
113
|
-
readonly providerId: Option.Option<string>; /**
|
|
128
|
+
readonly providerId: Option.Option<string>; /** Thought signature from Gemini 3, replayed on the next request's part. */
|
|
129
|
+
readonly signature: Option.Option<string>; /** Args as JSON-encoded string, mirroring `Items.ToolCall.arguments`. */
|
|
114
130
|
readonly arguments: string;
|
|
115
131
|
};
|
|
116
132
|
type Accumulator = {
|
|
@@ -145,6 +161,7 @@ type ChunkPart = {
|
|
|
145
161
|
} | {
|
|
146
162
|
readonly kind: "function_call";
|
|
147
163
|
readonly id: Option.Option<string>;
|
|
164
|
+
readonly signature: Option.Option<string>;
|
|
148
165
|
readonly name: string;
|
|
149
166
|
readonly args: unknown;
|
|
150
167
|
};
|
|
@@ -156,5 +173,5 @@ type ChunkResult = {
|
|
|
156
173
|
declare const ingestChunk: (acc: Accumulator, chunk: WireChunk) => ChunkResult;
|
|
157
174
|
declare const accumulatorToTurn: (acc: Accumulator) => Turn;
|
|
158
175
|
//#endregion
|
|
159
|
-
export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, RequestBody, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, hasUrlImageSource, ingestChunk, codec_d_exports as t };
|
|
176
|
+
export { AccumulatedFunctionCall, Accumulator, ChunkPart, ChunkResult, GenerationConfig, RequestBody, RequestTool, ThinkingConfig, WireChunk, accumulatorToTurn, buildRequestBody, emptyAccumulator, hasUrlImageSource, ingestChunk, codec_d_exports as t };
|
|
160
177
|
//# sourceMappingURL=codec.d.mts.map
|
package/dist/codec.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":"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"}
|