@effect-uai/google 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/Gemini.d.mts +11 -2
  2. package/dist/Gemini.d.mts.map +1 -1
  3. package/dist/Gemini.mjs +18 -6
  4. package/dist/Gemini.mjs.map +1 -1
  5. package/dist/GeminiEmbedding.mjs +1 -1
  6. package/dist/GeminiEmbedding.mjs.map +1 -1
  7. package/dist/GeminiSynthesizer.d.mts.map +1 -1
  8. package/dist/GeminiSynthesizer.mjs +2 -2
  9. package/dist/GeminiSynthesizer.mjs.map +1 -1
  10. package/dist/GeminiSynthesizer.test.mjs +1 -1
  11. package/dist/GeminiTools.d.mts +48 -0
  12. package/dist/GeminiTools.d.mts.map +1 -0
  13. package/dist/GeminiTools.mjs +64 -0
  14. package/dist/GeminiTools.mjs.map +1 -0
  15. package/dist/GoogleDeepResearch.d.mts +122 -0
  16. package/dist/GoogleDeepResearch.d.mts.map +1 -0
  17. package/dist/GoogleDeepResearch.mjs +320 -0
  18. package/dist/GoogleDeepResearch.mjs.map +1 -0
  19. package/dist/GoogleDeepResearch.test.d.mts +1 -0
  20. package/dist/GoogleDeepResearch.test.mjs +66 -0
  21. package/dist/GoogleDeepResearch.test.mjs.map +1 -0
  22. package/dist/LyriaGenerator.mjs +1 -1
  23. package/dist/LyriaGenerator.mjs.map +1 -1
  24. package/dist/LyriaGenerator.test.mjs +1 -1
  25. package/dist/codec.d.mts +38 -6
  26. package/dist/codec.d.mts.map +1 -1
  27. package/dist/codec.mjs +30 -8
  28. package/dist/codec.mjs.map +1 -1
  29. package/dist/{dist-DMsg8YLb.mjs → dist-CWoAuwS9.mjs} +7 -7
  30. package/dist/{dist-DMsg8YLb.mjs.map → dist-CWoAuwS9.mjs.map} +1 -1
  31. package/dist/geminiSpeechCodec.test.mjs +1 -1
  32. package/dist/geminiTools.test.d.mts +1 -0
  33. package/dist/geminiTools.test.mjs +99 -0
  34. package/dist/geminiTools.test.mjs.map +1 -0
  35. package/dist/index.d.mts +4 -2
  36. package/dist/index.mjs +3 -1
  37. package/dist/models.d.mts +11 -1
  38. package/dist/models.d.mts.map +1 -1
  39. package/package.json +8 -4
  40. package/src/Gemini.ts +21 -3
  41. package/src/GeminiEmbedding.ts +1 -1
  42. package/src/GeminiSynthesizer.ts +4 -2
  43. package/src/GeminiTools.ts +95 -0
  44. package/src/GoogleDeepResearch.test.ts +80 -0
  45. package/src/GoogleDeepResearch.ts +545 -0
  46. package/src/LyriaGenerator.ts +1 -1
  47. package/src/codec.ts +114 -12
  48. package/src/geminiTools.test.ts +106 -0
  49. package/src/index.ts +2 -0
  50. package/src/models.ts +15 -0
@@ -0,0 +1,122 @@
1
+ import { GoogleDeepResearchModel } from "./models.mjs";
2
+ import { Context, Effect, Layer, Option, Redacted, Schema } from "effect";
3
+ import { HttpClient } from "effect/unstable/http";
4
+ import { DeepResearch, DeepResearchServiceShape } from "@effect-uai/core/DeepResearch";
5
+ import * as Items from "@effect-uai/core/Items";
6
+ import * as Job from "@effect-uai/core/Job";
7
+ import { ResearchRequest, ResearchState } from "@effect-uai/core/Research";
8
+
9
+ //#region src/GoogleDeepResearch.d.ts
10
+ declare namespace GoogleDeepResearch_d_exports {
11
+ export { Config, GeminiResearchData, GoogleDeepResearch, GoogleDeepResearchService, GoogleResearchRequest, WireInteraction, jobStateOf, layer, make, researchDataOf };
12
+ }
13
+ /**
14
+ * Gemini-typed research request. Narrows `model` to the deep-research agent ids.
15
+ * Provider-portable code uses the generic `DeepResearch` tag instead.
16
+ */
17
+ type GoogleResearchRequest = Omit<ResearchRequest, "model"> & {
18
+ readonly model?: GoogleDeepResearchModel;
19
+ };
20
+ type GoogleDeepResearchService = DeepResearchServiceShape<GoogleResearchRequest>;
21
+ declare const GoogleDeepResearch_base: Context.ServiceClass<GoogleDeepResearch, "@betalyra/effect-uai/providers/google/GoogleDeepResearch", GoogleDeepResearchService>;
22
+ /**
23
+ * Provider-typed tag for Gemini deep research over the Interactions API
24
+ * (`POST /v1beta/interactions`, `agent: deep-research-*`). Yield this for the
25
+ * agent-id knob; yield the generic `DeepResearch` tag for portable code.
26
+ *
27
+ * `research` / `submit` / `collect` drive a background job by polling;
28
+ * `researchStream` consumes the real Interactions SSE (`step.delta` text /
29
+ * thought, terminating in `interaction.completed`). Preview surface: the SSE
30
+ * event shapes and the completed-interaction citation shape are decoded
31
+ * defensively and best confirmed against a live response.
32
+ */
33
+ declare class GoogleDeepResearch extends GoogleDeepResearch_base {}
34
+ type Config = {
35
+ readonly apiKey: Redacted.Redacted;
36
+ readonly baseUrl?: string; /** Poll cadence / overall timeout for the derived poll loops. */
37
+ readonly job?: Job.JobConfig;
38
+ };
39
+ declare const WireInteraction: Schema.Struct<{
40
+ readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
41
+ readonly status: Schema.optional<Schema.NullOr<Schema.String>>;
42
+ readonly outputs: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
43
+ readonly type: Schema.optional<Schema.NullOr<Schema.String>>;
44
+ readonly text: Schema.optional<Schema.NullOr<Schema.String>>;
45
+ readonly groundingMetadata: Schema.optional<Schema.NullOr<Schema.Struct<{
46
+ readonly groundingChunks: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
47
+ readonly web: Schema.optional<Schema.NullOr<Schema.Struct<{
48
+ readonly uri: Schema.optional<Schema.NullOr<Schema.String>>;
49
+ readonly title: Schema.optional<Schema.NullOr<Schema.String>>;
50
+ }>>>;
51
+ }>>>>;
52
+ }>>>;
53
+ }>>>>;
54
+ readonly steps: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
55
+ readonly type: Schema.optional<Schema.NullOr<Schema.String>>;
56
+ readonly text: Schema.optional<Schema.NullOr<Schema.String>>;
57
+ readonly content: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
58
+ readonly type: Schema.optional<Schema.NullOr<Schema.String>>;
59
+ readonly text: Schema.optional<Schema.NullOr<Schema.String>>;
60
+ readonly groundingMetadata: Schema.optional<Schema.NullOr<Schema.Struct<{
61
+ readonly groundingChunks: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
62
+ readonly web: Schema.optional<Schema.NullOr<Schema.Struct<{
63
+ readonly uri: Schema.optional<Schema.NullOr<Schema.String>>;
64
+ readonly title: Schema.optional<Schema.NullOr<Schema.String>>;
65
+ }>>>;
66
+ }>>>>;
67
+ }>>>;
68
+ }>>>>;
69
+ readonly groundingMetadata: Schema.optional<Schema.NullOr<Schema.Struct<{
70
+ readonly groundingChunks: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
71
+ readonly web: Schema.optional<Schema.NullOr<Schema.Struct<{
72
+ readonly uri: Schema.optional<Schema.NullOr<Schema.String>>;
73
+ readonly title: Schema.optional<Schema.NullOr<Schema.String>>;
74
+ }>>>;
75
+ }>>>>;
76
+ }>>>;
77
+ }>>>>;
78
+ readonly groundingMetadata: Schema.optional<Schema.NullOr<Schema.Struct<{
79
+ readonly groundingChunks: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
80
+ readonly web: Schema.optional<Schema.NullOr<Schema.Struct<{
81
+ readonly uri: Schema.optional<Schema.NullOr<Schema.String>>;
82
+ readonly title: Schema.optional<Schema.NullOr<Schema.String>>;
83
+ }>>>;
84
+ }>>>>;
85
+ }>>>;
86
+ readonly error: Schema.optional<Schema.NullOr<Schema.Struct<{
87
+ readonly message: Schema.optional<Schema.NullOr<Schema.String>>;
88
+ readonly code: Schema.optional<Schema.NullOr<Schema.String>>;
89
+ }>>>;
90
+ }>;
91
+ type WireInteraction = typeof WireInteraction.Type;
92
+ /**
93
+ * The research trace: what the model did at each step and which sources it
94
+ * consulted there. The `Turn` keeps only the final report and the deduped
95
+ * union of all sources, so the per-step breakdown lives here or nowhere.
96
+ *
97
+ * Lands on `providerData.gemini`; read it with {@link researchDataOf}.
98
+ */
99
+ declare const GeminiResearchData: Schema.Struct<{
100
+ readonly steps: Schema.$Array<Schema.Struct<{
101
+ readonly text: Schema.String;
102
+ readonly sources: Schema.$Array<Schema.Struct<{
103
+ readonly url: Schema.String;
104
+ readonly title: Schema.optional<Schema.String>;
105
+ }>>;
106
+ }>>;
107
+ }>;
108
+ type GeminiResearchData = typeof GeminiResearchData.Type;
109
+ /** Read this provider's data off an item, if it is there and ours. */
110
+ declare const researchDataOf: (item: Items.HistoryItem) => Option.Option<GeminiResearchData>;
111
+ declare const jobStateOf: (wire: WireInteraction) => ResearchState;
112
+ /** Build a `GoogleDeepResearchService`. For Layer setup, prefer {@link layer}. */
113
+ declare const make: (cfg: Config) => Effect.Effect<GoogleDeepResearchService, never, HttpClient.HttpClient>;
114
+ /**
115
+ * Layer registering the provider-typed `GoogleDeepResearch` tag and the generic
116
+ * `DeepResearch` tag. No capability markers: the surface (poll, collect,
117
+ * synthesized stream) is universal.
118
+ */
119
+ declare const layer: (cfg: Config) => Layer.Layer<GoogleDeepResearch | DeepResearch, never, HttpClient.HttpClient>;
120
+ //#endregion
121
+ export { Config, GeminiResearchData, GoogleDeepResearch, GoogleDeepResearchService, GoogleResearchRequest, WireInteraction, jobStateOf, layer, make, researchDataOf, GoogleDeepResearch_d_exports as t };
122
+ //# sourceMappingURL=GoogleDeepResearch.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleDeepResearch.d.mts","names":[],"sources":["../src/GoogleDeepResearch.ts"],"mappings":";;;;;;;;;;;;;;;;KAwCY,qBAAA,GAAwB,IAAA,CAAK,eAAA;EAAA,SAC9B,KAAA,GAAQ,uBAAA;AAAA;AAAA,KAGP,yBAAA,GAA4B,wBAAA,CAAyB,qBAAA;AAAA,cAAsB,uBAAA;;;;;;;AAJvF;;;;;cAiBa,kBAAA,SAA2B,uBAAA;AAAA,KAK5B,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA,WAvBA;EAAA,SAyBA,GAAA,GAAM,GAAA,CAAI,SAAA;AAAA;AAAA,cA4BR,eAAA,EAAe,MAAA,CAAA,MAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAehB,eAAA,UAAyB,eAAA,CAAgB,IAAA;;;;;;;;cAkCxC,kBAAA,EAAkB,MAAA,CAAA,MAAA;EAAA;;;;;;;;KAQnB,kBAAA,UAA4B,kBAAA,CAAmB,IAAA;;cAK9C,cAAA,GAAkB,IAAA,EAAM,KAAA,CAAM,WAAA,KAAc,MAAA,CAAO,MAAA,CAAO,kBAAA;AAAA,cA4D1D,UAAA,GAAc,IAAA,EAAM,eAAA,KAAkB,aAAA;;cA4StC,IAAA,GACX,GAAA,EAAK,MAAA,KACJ,MAAA,CAAO,MAAA,CAAO,yBAAA,SAAkC,UAAA,CAAW,UAAA;;;;;;cAUjD,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,kBAAA,GAAqB,YAAA,SAAqB,UAAA,CAAW,UAAA"}
@@ -0,0 +1,320 @@
1
+ import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
2
+ import { GroundingMetadata, groundingToAnnotations } from "./codec.mjs";
3
+ import { Array, Context, Effect, Layer, Match, Option, Redacted, Ref, Result, Schema, Stream, pipe } from "effect";
4
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
5
+ import * as AiError from "@effect-uai/core/AiError";
6
+ import * as SSE from "@effect-uai/core/SSE";
7
+ import { TurnEvent } from "@effect-uai/core/Turn";
8
+ import { DeepResearch, fromJob } from "@effect-uai/core/DeepResearch";
9
+ import * as Items from "@effect-uai/core/Items";
10
+ import * as Job from "@effect-uai/core/Job";
11
+ //#region src/GoogleDeepResearch.ts
12
+ var GoogleDeepResearch_exports = /* @__PURE__ */ __exportAll({
13
+ GeminiResearchData: () => GeminiResearchData,
14
+ GoogleDeepResearch: () => GoogleDeepResearch,
15
+ WireInteraction: () => WireInteraction,
16
+ jobStateOf: () => jobStateOf,
17
+ layer: () => layer,
18
+ make: () => make,
19
+ researchDataOf: () => researchDataOf
20
+ });
21
+ /**
22
+ * Provider-typed tag for Gemini deep research over the Interactions API
23
+ * (`POST /v1beta/interactions`, `agent: deep-research-*`). Yield this for the
24
+ * agent-id knob; yield the generic `DeepResearch` tag for portable code.
25
+ *
26
+ * `research` / `submit` / `collect` drive a background job by polling;
27
+ * `researchStream` consumes the real Interactions SSE (`step.delta` text /
28
+ * thought, terminating in `interaction.completed`). Preview surface: the SSE
29
+ * event shapes and the completed-interaction citation shape are decoded
30
+ * defensively and best confirmed against a live response.
31
+ */
32
+ var GoogleDeepResearch = class extends Context.Service()("@betalyra/effect-uai/providers/google/GoogleDeepResearch") {};
33
+ const WireSubmit = Schema.Struct({ id: Schema.String });
34
+ const decodeSubmit = Schema.decodeUnknownEffect(WireSubmit);
35
+ const WireTextPart = Schema.Struct({
36
+ type: Schema.optional(Schema.NullOr(Schema.String)),
37
+ text: Schema.optional(Schema.NullOr(Schema.String)),
38
+ groundingMetadata: Schema.optional(Schema.NullOr(GroundingMetadata))
39
+ });
40
+ const WireStep = Schema.Struct({
41
+ type: Schema.optional(Schema.NullOr(Schema.String)),
42
+ text: Schema.optional(Schema.NullOr(Schema.String)),
43
+ content: Schema.optional(Schema.NullOr(Schema.Array(WireTextPart))),
44
+ groundingMetadata: Schema.optional(Schema.NullOr(GroundingMetadata))
45
+ });
46
+ const WireInteraction = Schema.Struct({
47
+ id: Schema.optional(Schema.NullOr(Schema.String)),
48
+ status: Schema.optional(Schema.NullOr(Schema.String)),
49
+ outputs: Schema.optional(Schema.NullOr(Schema.Array(WireTextPart))),
50
+ steps: Schema.optional(Schema.NullOr(Schema.Array(WireStep))),
51
+ groundingMetadata: Schema.optional(Schema.NullOr(GroundingMetadata)),
52
+ error: Schema.optional(Schema.NullOr(Schema.Struct({
53
+ message: Schema.optional(Schema.NullOr(Schema.String)),
54
+ code: Schema.optional(Schema.NullOr(Schema.String))
55
+ })))
56
+ });
57
+ const decodeInteraction = Schema.decodeUnknownEffect(WireInteraction);
58
+ const textOfParts = (parts) => (parts ?? []).filter((p) => (p.type ?? "text") === "text" && p.text != null).map((p) => p.text).join("");
59
+ const reportText = (wire) => {
60
+ const fromOutputs = textOfParts(wire.outputs);
61
+ if (fromOutputs.length > 0) return fromOutputs;
62
+ const lastStep = (wire.steps ?? []).at(-1);
63
+ const fromStep = textOfParts(lastStep?.content);
64
+ return fromStep.length > 0 ? fromStep : lastStep?.text ?? "";
65
+ };
66
+ /**
67
+ * The subset of the domain `Citation.Source` this provider can populate.
68
+ * Declared here rather than derived from the wire schema, so the exported
69
+ * shape is ours and a wire change cannot silently alter it.
70
+ */
71
+ const SourceSchema = Schema.Struct({
72
+ url: Schema.String,
73
+ title: Schema.optional(Schema.String)
74
+ });
75
+ /**
76
+ * The research trace: what the model did at each step and which sources it
77
+ * consulted there. The `Turn` keeps only the final report and the deduped
78
+ * union of all sources, so the per-step breakdown lives here or nowhere.
79
+ *
80
+ * Lands on `providerData.gemini`; read it with {@link researchDataOf}.
81
+ */
82
+ const GeminiResearchData = Schema.Struct({ steps: Schema.Array(Schema.Struct({
83
+ text: Schema.String,
84
+ sources: Schema.Array(SourceSchema)
85
+ })) });
86
+ const decodeResearchData = Schema.decodeUnknownResult(Schema.Struct({ gemini: GeminiResearchData }));
87
+ /** Read this provider's data off an item, if it is there and ours. */
88
+ const researchDataOf = (item) => pipe(decodeResearchData(item.providerData), Result.match({
89
+ onSuccess: (d) => Option.some(d.gemini),
90
+ onFailure: () => Option.none()
91
+ }));
92
+ const sourcesOf = (meta) => (meta?.groundingChunks ?? []).flatMap((chunk) => chunk.web?.uri == null ? [] : [{
93
+ url: chunk.web.uri,
94
+ ...chunk.web.title != null && { title: chunk.web.title }
95
+ }]);
96
+ const researchSteps = (wire) => (wire.steps ?? []).flatMap((step) => {
97
+ const text = textOfParts(step.content) || (step.text ?? "");
98
+ const sources = [...sourcesOf(step.groundingMetadata), ...(step.content ?? []).flatMap((p) => sourcesOf(p.groundingMetadata))];
99
+ return text.length === 0 && sources.length === 0 ? [] : [{
100
+ text,
101
+ sources
102
+ }];
103
+ });
104
+ const interactionAnnotations = (wire) => groundingToAnnotations(wire.groundingMetadata, ...Array.map(wire.outputs ?? [], (o) => o.groundingMetadata), ...Array.map(wire.steps ?? [], (s) => s.groundingMetadata));
105
+ const turnFromInteraction = (wire) => {
106
+ const annotations = interactionAnnotations(wire);
107
+ return {
108
+ items: [{
109
+ type: "message",
110
+ role: "assistant",
111
+ content: [{
112
+ type: "output_text",
113
+ text: reportText(wire),
114
+ ...annotations.length > 0 && { annotations }
115
+ }],
116
+ providerData: { gemini: { steps: researchSteps(wire) } }
117
+ }],
118
+ usage: {},
119
+ stop_reason: "stop"
120
+ };
121
+ };
122
+ 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", () => ({
123
+ _tag: "Succeeded",
124
+ result: turnFromInteraction(wire)
125
+ })), Match.whenOr("failed", "error", "cancelled", () => ({
126
+ _tag: "Failed",
127
+ ...wire.error?.message != null && { reason: wire.error.message },
128
+ raw: wire
129
+ })), Match.orElse(() => ({ _tag: "Running" })));
130
+ const baseUrl = (cfg) => cfg.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
131
+ const authHeader = (cfg) => HttpClientRequest.setHeader("x-goog-api-key", Redacted.value(cfg.apiKey));
132
+ const transportFailure = (cause) => new AiError.Unavailable({
133
+ provider: "google",
134
+ raw: cause
135
+ });
136
+ const httpStatusError = (status, body) => {
137
+ const provider = "google";
138
+ const raw = body;
139
+ if (status === 429) return new AiError.RateLimited({
140
+ provider,
141
+ raw
142
+ });
143
+ if (status === 408 || status === 504) return new AiError.Timeout({
144
+ provider,
145
+ raw
146
+ });
147
+ if (status === 401) return new AiError.AuthFailed({
148
+ provider,
149
+ subtype: "auth",
150
+ raw
151
+ });
152
+ if (status === 403) return new AiError.AuthFailed({
153
+ provider,
154
+ subtype: "permission",
155
+ raw
156
+ });
157
+ if (status === 402) return new AiError.AuthFailed({
158
+ provider,
159
+ subtype: "billing",
160
+ raw
161
+ });
162
+ if (status === 413) return new AiError.ContextLengthExceeded({
163
+ provider,
164
+ raw
165
+ });
166
+ if (status >= 500) return new AiError.Unavailable({
167
+ provider,
168
+ status,
169
+ raw
170
+ });
171
+ return new AiError.InvalidRequest({
172
+ provider,
173
+ raw
174
+ });
175
+ };
176
+ 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");
177
+ const submitBody = (request) => ({
178
+ agent: request.model ?? "deep-research-preview-04-2026",
179
+ input: inputText(request.history),
180
+ background: true,
181
+ store: true,
182
+ agent_config: {
183
+ type: "deep-research",
184
+ thinking_summaries: "auto"
185
+ },
186
+ tools: [{ type: "google_search" }]
187
+ });
188
+ const submitJob = (cfg, request) => Effect.gen(function* () {
189
+ const client = yield* HttpClient.HttpClient;
190
+ const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/interactions`).pipe(authHeader(cfg), HttpClientRequest.bodyJsonUnsafe(submitBody(request)));
191
+ const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
192
+ if (response.status >= 400) {
193
+ const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
194
+ return yield* httpStatusError(response.status, text);
195
+ }
196
+ const wire = yield* decodeSubmit(yield* response.json.pipe(Effect.mapError(transportFailure))).pipe(Effect.mapError(transportFailure));
197
+ return Job.jobRef("google", wire.id);
198
+ });
199
+ const pollJob = (cfg, ref) => Effect.gen(function* () {
200
+ const client = yield* HttpClient.HttpClient;
201
+ const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/interactions/${ref.id}`).pipe(authHeader(cfg));
202
+ const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
203
+ if (response.status >= 400) {
204
+ const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
205
+ return yield* httpStatusError(response.status, text);
206
+ }
207
+ const json = yield* response.json.pipe(Effect.mapError(transportFailure));
208
+ yield* Effect.logDebug("google interactions poll response", { interaction: json });
209
+ return jobStateOf(yield* decodeInteraction(json).pipe(Effect.mapError(transportFailure)));
210
+ });
211
+ const unsupported = (capability) => new AiError.Unsupported({
212
+ provider: "google",
213
+ capability,
214
+ reason: "Gemini deep research (preview) exposes no verified cancel endpoint."
215
+ });
216
+ const WireDelta = Schema.Struct({
217
+ type: Schema.optional(Schema.NullOr(Schema.String)),
218
+ text: Schema.optional(Schema.NullOr(Schema.String)),
219
+ content: Schema.optional(Schema.NullOr(Schema.Struct({
220
+ type: Schema.optional(Schema.NullOr(Schema.String)),
221
+ text: Schema.optional(Schema.NullOr(Schema.String))
222
+ })))
223
+ });
224
+ const WireStreamEvent = Schema.Struct({
225
+ event_type: Schema.optional(Schema.NullOr(Schema.String)),
226
+ delta: Schema.optional(Schema.NullOr(WireDelta)),
227
+ interaction: Schema.optional(Schema.NullOr(WireInteraction)),
228
+ error: Schema.optional(Schema.NullOr(Schema.Struct({ message: Schema.optional(Schema.NullOr(Schema.String)) })))
229
+ });
230
+ const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
231
+ const decodeStreamEvent = Schema.decodeUnknownEffect(WireStreamEvent);
232
+ const completedTurn = (wire, streamed) => wire != null && reportText(wire).length > 0 ? turnFromInteraction(wire) : {
233
+ items: [{
234
+ type: "message",
235
+ role: "assistant",
236
+ content: [{
237
+ type: "output_text",
238
+ text: streamed
239
+ }],
240
+ ...wire != null && { providerData: { gemini: { steps: researchSteps(wire) } } }
241
+ }],
242
+ usage: {},
243
+ stop_reason: "stop"
244
+ };
245
+ const streamEventToTurnEvents = (acc, ev) => {
246
+ if (ev.event_type === "interaction.error") return Effect.fail(new AiError.GenerationFailed({
247
+ provider: "google",
248
+ ...ev.error?.message != null && { message: ev.error.message },
249
+ raw: ev
250
+ }));
251
+ if (ev.event_type === "step.delta") {
252
+ const delta = ev.delta;
253
+ if (delta?.text != null) {
254
+ const text = delta.text;
255
+ return Effect.as(Ref.update(acc, (streamed) => streamed + text), [TurnEvent.TextDelta({ text })]);
256
+ }
257
+ if (delta?.content?.text != null) return Effect.succeed([TurnEvent.ReasoningDelta({
258
+ text: delta.content.text,
259
+ kind: "summary"
260
+ })]);
261
+ return Effect.succeed([]);
262
+ }
263
+ if (ev.event_type === "interaction.completed") return Effect.map(Ref.get(acc), (streamed) => [TurnEvent.TurnComplete({ turn: completedTurn(ev.interaction, streamed) })]);
264
+ return Effect.succeed([]);
265
+ };
266
+ const streamCreate = (cfg, request) => Stream.unwrap(Effect.gen(function* () {
267
+ const client = yield* HttpClient.HttpClient;
268
+ const acc = yield* Ref.make("");
269
+ const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/interactions?stream=true`).pipe(authHeader(cfg), HttpClientRequest.bodyJsonUnsafe({
270
+ ...submitBody(request),
271
+ stream: true
272
+ }), HttpClientRequest.accept("text/event-stream"));
273
+ const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
274
+ yield* Effect.logDebug("google interactions stream response", {
275
+ status: response.status,
276
+ contentType: response.headers["content-type"]
277
+ });
278
+ if (response.status >= 400) {
279
+ const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
280
+ return Stream.fail(httpStatusError(response.status, text));
281
+ }
282
+ return response.stream.pipe(Stream.mapError(transportFailure), SSE.fromBytes, Stream.tap((ev) => Effect.logDebug("google interactions sse frame", {
283
+ event: ev.event,
284
+ data: ev.data
285
+ })), 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));
286
+ }));
287
+ const jobOps = (cfg, client) => {
288
+ const withClient = (e) => Effect.provideService(e, HttpClient.HttpClient, client);
289
+ return {
290
+ submit: (request) => withClient(submitJob(cfg, request)),
291
+ poll: (ref) => withClient(pollJob(cfg, ref)),
292
+ cancel: () => Effect.fail(unsupported("cancel"))
293
+ };
294
+ };
295
+ const buildService = (cfg, client, ops, toGoogle) => ({
296
+ ...fromJob(ops, cfg.job),
297
+ researchStream: (request) => Stream.provideService(streamCreate(cfg, toGoogle(request)), HttpClient.HttpClient, client)
298
+ });
299
+ /** Build a `GoogleDeepResearchService`. For Layer setup, prefer {@link layer}. */
300
+ const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => buildService(cfg, client, jobOps(cfg, client), (request) => request));
301
+ /**
302
+ * Layer registering the provider-typed `GoogleDeepResearch` tag and the generic
303
+ * `DeepResearch` tag. No capability markers: the surface (poll, collect,
304
+ * synthesized stream) is universal.
305
+ */
306
+ const layer = (cfg) => {
307
+ const typed = Layer.effect(GoogleDeepResearch, make(cfg));
308
+ const generic = Layer.effect(DeepResearch, Effect.map(HttpClient.HttpClient, (client) => {
309
+ const ops = jobOps(cfg, client);
310
+ return buildService(cfg, client, {
311
+ ...ops,
312
+ submit: (request) => ops.submit(request)
313
+ }, (request) => request);
314
+ }));
315
+ return Layer.merge(typed, generic);
316
+ };
317
+ //#endregion
318
+ export { GeminiResearchData, GoogleDeepResearch, WireInteraction, jobStateOf, layer, make, researchDataOf, GoogleDeepResearch_exports as t };
319
+
320
+ //# sourceMappingURL=GoogleDeepResearch.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleDeepResearch.mjs","names":["Arr"],"sources":["../src/GoogleDeepResearch.ts"],"sourcesContent":["import {\n Array as Arr,\n Context,\n Effect,\n Layer,\n Match,\n Option,\n Redacted,\n Ref,\n Result,\n Schema,\n Stream,\n pipe,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { Source } from \"@effect-uai/core/Citation\"\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 { GroundingMetadata, groundingToAnnotations } from \"./codec.js\"\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. Grounding sources ride Google's\n// `groundingMetadata.groundingChunks[]`; this preview surface is undocumented,\n// so decode it at every plausible level (interaction / output / step) and map\n// whichever is populated onto `url_citation` annotations. The report text also\n// carries the sources as an inline `[n]` / `**Sources:**` list.\nconst WireTextPart = Schema.Struct({\n type: Schema.optional(Schema.NullOr(Schema.String)),\n text: Schema.optional(Schema.NullOr(Schema.String)),\n groundingMetadata: Schema.optional(Schema.NullOr(GroundingMetadata)),\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 groundingMetadata: Schema.optional(Schema.NullOr(GroundingMetadata)),\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 groundingMetadata: Schema.optional(Schema.NullOr(GroundingMetadata)),\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\n/**\n * The subset of the domain `Citation.Source` this provider can populate.\n * Declared here rather than derived from the wire schema, so the exported\n * shape is ours and a wire change cannot silently alter it.\n */\nconst SourceSchema = Schema.Struct({\n url: Schema.String,\n title: Schema.optional(Schema.String),\n})\n\n/**\n * The research trace: what the model did at each step and which sources it\n * consulted there. The `Turn` keeps only the final report and the deduped\n * union of all sources, so the per-step breakdown lives here or nowhere.\n *\n * Lands on `providerData.gemini`; read it with {@link researchDataOf}.\n */\nexport const GeminiResearchData = Schema.Struct({\n steps: Schema.Array(\n Schema.Struct({\n text: Schema.String,\n sources: Schema.Array(SourceSchema),\n }),\n ),\n})\nexport type GeminiResearchData = typeof GeminiResearchData.Type\n\nconst decodeResearchData = Schema.decodeUnknownResult(Schema.Struct({ gemini: GeminiResearchData }))\n\n/** Read this provider's data off an item, if it is there and ours. */\nexport const researchDataOf = (item: Items.HistoryItem): Option.Option<GeminiResearchData> =>\n pipe(\n decodeResearchData(item.providerData),\n Result.match({\n onSuccess: (d) => Option.some(d.gemini),\n onFailure: () => Option.none<GeminiResearchData>(),\n }),\n )\n\nconst sourcesOf = (meta: GroundingMetadata | null | undefined): ReadonlyArray<Source> =>\n (meta?.groundingChunks ?? []).flatMap((chunk) =>\n chunk.web?.uri == null\n ? []\n : [{ url: chunk.web.uri, ...(chunk.web.title != null && { title: chunk.web.title }) }],\n )\n\n// A step contributes to the trace if it said something or consulted something.\nconst researchSteps = (wire: WireInteraction): GeminiResearchData[\"steps\"] =>\n (wire.steps ?? []).flatMap((step) => {\n const text = textOfParts(step.content) || (step.text ?? \"\")\n const sources = [\n ...sourcesOf(step.groundingMetadata),\n ...(step.content ?? []).flatMap((p) => sourcesOf(p.groundingMetadata)),\n ]\n return text.length === 0 && sources.length === 0 ? [] : [{ text, sources }]\n })\n\n// Grounding can live at any level of the interaction envelope; gather it from\n// all three (interaction / output part / step) and let `groundingToAnnotations`\n// de-dupe by url.\nconst interactionAnnotations = (wire: WireInteraction): ReadonlyArray<Items.Annotation> =>\n groundingToAnnotations(\n wire.groundingMetadata,\n ...Arr.map(wire.outputs ?? [], (o) => o.groundingMetadata),\n ...Arr.map(wire.steps ?? [], (s) => s.groundingMetadata),\n )\n\nconst turnFromInteraction = (wire: WireInteraction): Turn => {\n const annotations = interactionAnnotations(wire)\n return {\n items: [\n {\n type: \"message\",\n role: \"assistant\",\n content: [\n {\n type: \"output_text\",\n text: reportText(wire),\n ...(annotations.length > 0 && { annotations }),\n },\n ],\n providerData: { gemini: { steps: researchSteps(wire) } },\n },\n ],\n usage: {},\n stop_reason: \"stop\",\n }\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 // Preview surface: log the raw interaction so the (undocumented) grounding\n // path can be confirmed against a live completed job at LOG_LEVEL=Debug.\n yield* Effect.logDebug(\"google interactions poll response\", { interaction: json })\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: { gemini: { steps: researchSteps(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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"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,66 @@
1
+ import { i as it, n as globalExpect, r as describe } from "./dist-CWoAuwS9.mjs";
2
+ import { WireInteraction, jobStateOf } from "./GoogleDeepResearch.mjs";
3
+ import { Schema } from "effect";
4
+ import * as Turn$1 from "@effect-uai/core/Turn";
5
+ //#region src/GoogleDeepResearch.test.ts
6
+ const decode = Schema.decodeUnknownSync(WireInteraction);
7
+ const turnOf = (raw) => {
8
+ const state = jobStateOf(decode(raw));
9
+ if (state._tag !== "Succeeded") throw new Error(`expected Succeeded, got ${state._tag}`);
10
+ return state.result;
11
+ };
12
+ describe("GoogleDeepResearch grounding", () => {
13
+ it("maps grounding chunks on a completed interaction to url_citation annotations", () => {
14
+ const turn = turnOf({
15
+ id: "int_1",
16
+ status: "completed",
17
+ outputs: [{
18
+ type: "text",
19
+ text: "Report body [1][2].",
20
+ groundingMetadata: { groundingChunks: [{ web: {
21
+ uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/abc",
22
+ title: "example.com"
23
+ } }, { web: { uri: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/def" } }] }
24
+ }]
25
+ });
26
+ globalExpect(Turn$1.assistantText(turn)).toBe("Report body [1][2].");
27
+ globalExpect(Turn$1.citations(turn)).toEqual([{
28
+ type: "url_citation",
29
+ url: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/abc",
30
+ title: "example.com"
31
+ }, {
32
+ type: "url_citation",
33
+ url: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/def",
34
+ title: "https://vertexaisearch.cloud.google.com/grounding-api-redirect/def"
35
+ }]);
36
+ });
37
+ it("gathers and de-dupes grounding across interaction / output / step levels", () => {
38
+ const turn = turnOf({
39
+ id: "int_2",
40
+ status: "completed",
41
+ groundingMetadata: { groundingChunks: [{ web: {
42
+ uri: "https://a.example",
43
+ title: "a"
44
+ } }] },
45
+ outputs: [{
46
+ type: "text",
47
+ text: "Body."
48
+ }],
49
+ steps: [{
50
+ type: "research",
51
+ groundingMetadata: { groundingChunks: [{ web: {
52
+ uri: "https://a.example",
53
+ title: "a"
54
+ } }, { web: {
55
+ uri: "https://b.example",
56
+ title: "b"
57
+ } }] }
58
+ }]
59
+ });
60
+ globalExpect(Turn$1.citations(turn).map((c) => c.type === "url_citation" && c.url)).toEqual(["https://a.example", "https://b.example"]);
61
+ });
62
+ });
63
+ //#endregion
64
+ export {};
65
+
66
+ //# sourceMappingURL=GoogleDeepResearch.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleDeepResearch.test.mjs","names":["Turn"],"sources":["../src/GoogleDeepResearch.test.ts"],"sourcesContent":["import { Schema } from \"effect\"\nimport * as Turn from \"@effect-uai/core/Turn\"\nimport { describe, expect, it } from \"vitest\"\nimport { WireInteraction, jobStateOf } from \"./GoogleDeepResearch.js\"\n\nconst decode = Schema.decodeUnknownSync(WireInteraction)\n\n// Extract the `Turn` from a completed interaction the way the service does.\nconst turnOf = (raw: unknown): Turn.Turn => {\n const state = jobStateOf(decode(raw))\n if (state._tag !== \"Succeeded\") throw new Error(`expected Succeeded, got ${state._tag}`)\n return state.result\n}\n\ndescribe(\"GoogleDeepResearch grounding\", () => {\n it(\"maps grounding chunks on a completed interaction to url_citation annotations\", () => {\n const turn = turnOf({\n id: \"int_1\",\n status: \"completed\",\n outputs: [\n {\n type: \"text\",\n text: \"Report body [1][2].\",\n groundingMetadata: {\n groundingChunks: [\n {\n web: {\n uri: \"https://vertexaisearch.cloud.google.com/grounding-api-redirect/abc\",\n title: \"example.com\",\n },\n },\n {\n web: { uri: \"https://vertexaisearch.cloud.google.com/grounding-api-redirect/def\" },\n },\n ],\n },\n },\n ],\n })\n expect(Turn.assistantText(turn)).toBe(\"Report body [1][2].\")\n expect(Turn.citations(turn)).toEqual([\n {\n type: \"url_citation\",\n url: \"https://vertexaisearch.cloud.google.com/grounding-api-redirect/abc\",\n title: \"example.com\",\n },\n {\n type: \"url_citation\",\n url: \"https://vertexaisearch.cloud.google.com/grounding-api-redirect/def\",\n title: \"https://vertexaisearch.cloud.google.com/grounding-api-redirect/def\",\n },\n ])\n })\n\n it(\"gathers and de-dupes grounding across interaction / output / step levels\", () => {\n const turn = turnOf({\n id: \"int_2\",\n status: \"completed\",\n groundingMetadata: {\n groundingChunks: [{ web: { uri: \"https://a.example\", title: \"a\" } }],\n },\n outputs: [{ type: \"text\", text: \"Body.\" }],\n steps: [\n {\n type: \"research\",\n groundingMetadata: {\n groundingChunks: [\n { web: { uri: \"https://a.example\", title: \"a\" } },\n { web: { uri: \"https://b.example\", title: \"b\" } },\n ],\n },\n },\n ],\n })\n expect(Turn.citations(turn).map((c) => c.type === \"url_citation\" && c.url)).toEqual([\n \"https://a.example\",\n \"https://b.example\",\n ])\n })\n})\n"],"mappings":";;;;;AAKA,MAAM,SAAS,OAAO,kBAAkB,gBAAgB;AAGxD,MAAM,UAAU,QAA4B;CAC1C,MAAM,QAAQ,WAAW,OAAO,IAAI,CAAC;AACrC,KAAI,MAAM,SAAS,YAAa,OAAM,IAAI,MAAM,2BAA2B,MAAM,OAAO;AACxF,QAAO,MAAM;;AAGf,SAAS,sCAAsC;AAC7C,IAAG,sFAAsF;EACvF,MAAM,OAAO,OAAO;GAClB,IAAI;GACJ,QAAQ;GACR,SAAS,CACP;IACE,MAAM;IACN,MAAM;IACN,mBAAmB,EACjB,iBAAiB,CACf,EACE,KAAK;KACH,KAAK;KACL,OAAO;KACR,EACF,EACD,EACE,KAAK,EAAE,KAAK,sEAAsE,EACnF,CACF,EACF;IACF,CACF;GACF,CAAC;AACF,eAAOA,OAAK,cAAc,KAAK,CAAC,CAAC,KAAK,sBAAsB;AAC5D,eAAOA,OAAK,UAAU,KAAK,CAAC,CAAC,QAAQ,CACnC;GACE,MAAM;GACN,KAAK;GACL,OAAO;GACR,EACD;GACE,MAAM;GACN,KAAK;GACL,OAAO;GACR,CACF,CAAC;GACF;AAEF,IAAG,kFAAkF;EACnF,MAAM,OAAO,OAAO;GAClB,IAAI;GACJ,QAAQ;GACR,mBAAmB,EACjB,iBAAiB,CAAC,EAAE,KAAK;IAAE,KAAK;IAAqB,OAAO;IAAK,EAAE,CAAC,EACrE;GACD,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAAS,CAAC;GAC1C,OAAO,CACL;IACE,MAAM;IACN,mBAAmB,EACjB,iBAAiB,CACf,EAAE,KAAK;KAAE,KAAK;KAAqB,OAAO;KAAK,EAAE,EACjD,EAAE,KAAK;KAAE,KAAK;KAAqB,OAAO;KAAK,EAAE,CAClD,EACF;IACF,CACF;GACF,CAAC;AACF,eAAOA,OAAK,UAAU,KAAK,CAAC,KAAK,MAAM,EAAE,SAAS,kBAAkB,EAAE,IAAI,CAAC,CAAC,QAAQ,CAClF,qBACA,oBACD,CAAC;GACF;EACF"}
@@ -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.asEffect(), (client) => ({
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