@rafads/plugin-openapi 1.5.38

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 (122) hide show
  1. package/README.md +83 -0
  2. package/dist/AddOpenApiIntegration-JPAEXE56.js +512 -0
  3. package/dist/AddOpenApiIntegration-JPAEXE56.js.map +1 -0
  4. package/dist/OpenApiAccountsPanel-GZFDSTAU.js +106 -0
  5. package/dist/OpenApiAccountsPanel-GZFDSTAU.js.map +1 -0
  6. package/dist/UpdateSpecSection-KUSNI44Q.js +106 -0
  7. package/dist/UpdateSpecSection-KUSNI44Q.js.map +1 -0
  8. package/dist/api/group.d.ts +239 -0
  9. package/dist/api/handlers.d.ts +7 -0
  10. package/dist/api/index.d.ts +237 -0
  11. package/dist/chunk-6V45G2XQ.js +199 -0
  12. package/dist/chunk-6V45G2XQ.js.map +1 -0
  13. package/dist/chunk-CURJJXB3.js +132 -0
  14. package/dist/chunk-CURJJXB3.js.map +1 -0
  15. package/dist/chunk-CXFO6TLJ.js +39 -0
  16. package/dist/chunk-CXFO6TLJ.js.map +1 -0
  17. package/dist/chunk-HTHEUUJA.js +2873 -0
  18. package/dist/chunk-HTHEUUJA.js.map +1 -0
  19. package/dist/chunk-IG6QTTZB.js +79 -0
  20. package/dist/chunk-IG6QTTZB.js.map +1 -0
  21. package/dist/chunk-O2LMSZ2Z.js +120 -0
  22. package/dist/chunk-O2LMSZ2Z.js.map +1 -0
  23. package/dist/chunk-OR27BCXQ.js +956 -0
  24. package/dist/chunk-OR27BCXQ.js.map +1 -0
  25. package/dist/chunk-R7TVM3XD.js +275 -0
  26. package/dist/chunk-R7TVM3XD.js.map +1 -0
  27. package/dist/client.js +38 -0
  28. package/dist/client.js.map +1 -0
  29. package/dist/core.js +331 -0
  30. package/dist/core.js.map +1 -0
  31. package/dist/index.js +19 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/providers/google/config.d.ts +34 -0
  34. package/dist/providers/google/discovery.d.ts +49 -0
  35. package/dist/providers/google/discovery.test.d.ts +1 -0
  36. package/dist/providers/google/index.d.ts +5 -0
  37. package/dist/providers/google/oauth-scopes.d.ts +3 -0
  38. package/dist/providers/google/oauth-scopes.test.d.ts +1 -0
  39. package/dist/providers/google/openapi-ownership-migration.d.ts +7 -0
  40. package/dist/providers/google/openapi-ownership-migration.test.d.ts +1 -0
  41. package/dist/providers/google/presets.d.ts +31 -0
  42. package/dist/providers/google/presets.test.d.ts +1 -0
  43. package/dist/providers/google/spec-format-adapter.d.ts +3 -0
  44. package/dist/providers/google/spec-format-adapter.test.d.ts +1 -0
  45. package/dist/providers/google.js +1446 -0
  46. package/dist/providers/google.js.map +1 -0
  47. package/dist/providers/microsoft/graph.d.ts +76 -0
  48. package/dist/providers/microsoft/graph.test.d.ts +1 -0
  49. package/dist/providers/microsoft/index.d.ts +3 -0
  50. package/dist/providers/microsoft/presets.d.ts +46 -0
  51. package/dist/providers/microsoft/presets.test.d.ts +1 -0
  52. package/dist/providers/microsoft/spec-format-adapter.d.ts +2 -0
  53. package/dist/providers/microsoft/spec-format-adapter.test.d.ts +1 -0
  54. package/dist/providers/microsoft.js +1056 -0
  55. package/dist/providers/microsoft.js.map +1 -0
  56. package/dist/react/AddOpenApiIntegration.d.ts +22 -0
  57. package/dist/react/AddOpenApiIntegration.test.d.ts +1 -0
  58. package/dist/react/OpenApiAccountsPanel.d.ts +6 -0
  59. package/dist/react/OpenApiIntegrationDetailsFields.d.ts +25 -0
  60. package/dist/react/UpdateSpecSection.d.ts +2 -0
  61. package/dist/react/atoms.d.ts +317 -0
  62. package/dist/react/auth-method-config.d.ts +18 -0
  63. package/dist/react/auth-method-config.test.d.ts +1 -0
  64. package/dist/react/client.d.ts +233 -0
  65. package/dist/react/index.d.ts +5 -0
  66. package/dist/react/integration-plugin.d.ts +6 -0
  67. package/dist/react/plugin-client.d.ts +2 -0
  68. package/dist/sdk/backing.d.ts +188 -0
  69. package/dist/sdk/client-credentials-oauth.test.d.ts +1 -0
  70. package/dist/sdk/compiled-spec-cache.test.d.ts +1 -0
  71. package/dist/sdk/config.d.ts +81 -0
  72. package/dist/sdk/configure.test.d.ts +1 -0
  73. package/dist/sdk/definitions.d.ts +57 -0
  74. package/dist/sdk/derive-auth.d.ts +11 -0
  75. package/dist/sdk/describe-auth-methods.test.d.ts +1 -0
  76. package/dist/sdk/errors.d.ts +45 -0
  77. package/dist/sdk/extract.d.ts +157 -0
  78. package/dist/sdk/extract.test.d.ts +1 -0
  79. package/dist/sdk/form-urlencoded-body.test.d.ts +1 -0
  80. package/dist/sdk/index.d.ts +19 -0
  81. package/dist/sdk/index.test.d.ts +1 -0
  82. package/dist/sdk/invoke.d.ts +150 -0
  83. package/dist/sdk/migrate-config.d.ts +4 -0
  84. package/dist/sdk/migrate-config.test.d.ts +1 -0
  85. package/dist/sdk/multi-scope-bearer.test.d.ts +1 -0
  86. package/dist/sdk/multi-scope-oauth.test.d.ts +1 -0
  87. package/dist/sdk/ndjson-output-migration.d.ts +13 -0
  88. package/dist/sdk/ndjson-output-migration.test.d.ts +1 -0
  89. package/dist/sdk/non-json-body.test.d.ts +1 -0
  90. package/dist/sdk/oauth-refresh.test.d.ts +1 -0
  91. package/dist/sdk/openapi-utils.d.ts +65 -0
  92. package/dist/sdk/output-schema-migration.d.ts +21 -0
  93. package/dist/sdk/output-schema-migration.test.d.ts +1 -0
  94. package/dist/sdk/parse.d.ts +33 -0
  95. package/dist/sdk/parse.test.d.ts +1 -0
  96. package/dist/sdk/plugin.d.ts +109 -0
  97. package/dist/sdk/plugin.test.d.ts +1 -0
  98. package/dist/sdk/presets.d.ts +15 -0
  99. package/dist/sdk/preview-oauth2.test.d.ts +1 -0
  100. package/dist/sdk/preview.d.ts +514 -0
  101. package/dist/sdk/query-serialization.test.d.ts +1 -0
  102. package/dist/sdk/real-specs.test.d.ts +1 -0
  103. package/dist/sdk/request-user-agent.test.d.ts +1 -0
  104. package/dist/sdk/response-headers-timeout.test.d.ts +1 -0
  105. package/dist/sdk/server-url-resolution.test.d.ts +1 -0
  106. package/dist/sdk/spec-blob-migration.d.ts +7 -0
  107. package/dist/sdk/spec-blob.test.d.ts +1 -0
  108. package/dist/sdk/spec-format.d.ts +36 -0
  109. package/dist/sdk/spec-format.test.d.ts +1 -0
  110. package/dist/sdk/split.d.ts +84 -0
  111. package/dist/sdk/store.d.ts +49 -0
  112. package/dist/sdk/store.test.d.ts +1 -0
  113. package/dist/sdk/streaming-response.test.d.ts +1 -0
  114. package/dist/sdk/tool-row-projection.test.d.ts +1 -0
  115. package/dist/sdk/types.d.ts +352 -0
  116. package/dist/sdk/upstream-failures.test.d.ts +1 -0
  117. package/dist/sdk/usage-scope-isolation.test.d.ts +1 -0
  118. package/dist/testing/index.d.ts +177 -0
  119. package/dist/testing.js +454 -0
  120. package/dist/testing.js.map +1 -0
  121. package/dist/testing.test.d.ts +1 -0
  122. package/package.json +103 -0
@@ -0,0 +1,1446 @@
1
+ import {
2
+ OpenApiParseError
3
+ } from "../chunk-CXFO6TLJ.js";
4
+
5
+ // src/providers/google/discovery.ts
6
+ import { Effect, Option, Predicate, Schema, SchemaGetter } from "effect";
7
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
8
+
9
+ // src/providers/google/oauth-scopes.ts
10
+ var googleUserConsentBlockedScopes = /* @__PURE__ */ new Set([
11
+ "https://www.googleapis.com/auth/chat.bot",
12
+ "https://www.googleapis.com/auth/chat.import",
13
+ "https://www.googleapis.com/auth/keep",
14
+ "https://www.googleapis.com/auth/keep.readonly"
15
+ ]);
16
+ var googleUserConsentBlockedScopePrefixes = ["https://www.googleapis.com/auth/chat.app."];
17
+ var googleBroadScopeGroups = [
18
+ {
19
+ broad: "https://mail.google.com/",
20
+ prefixes: ["https://www.googleapis.com/auth/gmail."]
21
+ },
22
+ {
23
+ broad: "https://www.googleapis.com/auth/calendar",
24
+ prefixes: ["https://www.googleapis.com/auth/calendar."]
25
+ },
26
+ {
27
+ broad: "https://www.googleapis.com/auth/drive",
28
+ prefixes: ["https://www.googleapis.com/auth/drive."]
29
+ }
30
+ ];
31
+ var normalizeGoogleIdentityScope = (scope) => scope === "https://www.googleapis.com/auth/userinfo.email" ? "email" : scope === "https://www.googleapis.com/auth/userinfo.profile" ? "profile" : scope;
32
+ var orderedUniqueScopes = (scopes) => {
33
+ const ordered = [];
34
+ const seen = /* @__PURE__ */ new Set();
35
+ for (const scope of scopes) {
36
+ const trimmed = scope.trim();
37
+ if (!trimmed || seen.has(trimmed)) continue;
38
+ seen.add(trimmed);
39
+ ordered.push(trimmed);
40
+ }
41
+ return ordered;
42
+ };
43
+ var isGoogleUserConsentOAuthScope = (scope) => !googleUserConsentBlockedScopes.has(scope) && !googleUserConsentBlockedScopePrefixes.some((prefix) => scope.startsWith(prefix));
44
+ var filterGoogleUserConsentOAuthScopes = (scopes) => orderedUniqueScopes(scopes).filter(isGoogleUserConsentOAuthScope);
45
+ var compactGoogleOAuthScopes = (scopes) => {
46
+ const ordered = filterGoogleUserConsentOAuthScopes([...scopes].map(normalizeGoogleIdentityScope));
47
+ const present = new Set(ordered);
48
+ return ordered.filter(
49
+ (scope) => !googleBroadScopeGroups.some(
50
+ (group) => scope !== group.broad && present.has(group.broad) && group.prefixes.some((prefix) => scope.startsWith(prefix))
51
+ )
52
+ );
53
+ };
54
+
55
+ // src/providers/google/discovery.ts
56
+ import { AuthTemplateSlug } from "@rafads/sdk/shared";
57
+ var DISCOVERY_SERVICE_HOST = "https://www.googleapis.com/discovery/v1/apis";
58
+ var GOOGLE_BUNDLE_BASE_URL = "https://www.googleapis.com/";
59
+ var GOOGLE_OAUTH_AUTHORIZATION_URL = "https://accounts.google.com/o/oauth2/v2/auth";
60
+ var GOOGLE_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
61
+ var GOOGLE_PHOTOS_PICKER_SERVICE = "photospicker";
62
+ var GOOGLE_PHOTOS_PICKER_SCOPE = "https://www.googleapis.com/auth/photospicker.mediaitems.readonly";
63
+ var GOOGLE_PHOTOS_PICKER_SCOPE_DESCRIPTION = "Read selected Google Photos media";
64
+ var OPENAPI_SCHEMA_TYPES = /* @__PURE__ */ new Set([
65
+ "array",
66
+ "boolean",
67
+ "integer",
68
+ "null",
69
+ "number",
70
+ "object",
71
+ "string"
72
+ ]);
73
+ var GOOGLE_DISCOVERY_SERVICE_OVERRIDES = {
74
+ forms: { preserveServiceHostedUrl: true },
75
+ keep: { preserveServiceHostedUrl: true },
76
+ [GOOGLE_PHOTOS_PICKER_SERVICE]: {
77
+ preserveServiceHostedUrl: true,
78
+ scopes: {
79
+ [GOOGLE_PHOTOS_PICKER_SCOPE]: GOOGLE_PHOTOS_PICKER_SCOPE_DESCRIPTION
80
+ },
81
+ fallbackMethodScopes: [GOOGLE_PHOTOS_PICKER_SCOPE]
82
+ }
83
+ };
84
+ var googleDiscoveryUrlForService = (service, version, host = `${service}.googleapis.com`) => {
85
+ const override = GOOGLE_DISCOVERY_SERVICE_OVERRIDES[service];
86
+ return override?.preserveServiceHostedUrl === true ? `https://${host}/$discovery/rest?version=${version}` : `${DISCOVERY_SERVICE_HOST}/${service}/${version}/rest`;
87
+ };
88
+ var TextOption = Schema.OptionFromOptional(Schema.Trim).pipe(
89
+ Schema.decode({
90
+ decode: SchemaGetter.transform((value) => Option.filter(value, (text) => text.length > 0)),
91
+ encode: SchemaGetter.transform((value) => value)
92
+ }),
93
+ Schema.withDecodingDefaultType(Effect.succeed(Option.none()))
94
+ );
95
+ var TextArray = Schema.optional(Schema.Array(Schema.String)).pipe(
96
+ Schema.withDecodingDefaultType(Effect.succeed([]))
97
+ );
98
+ var UnknownRecord = Schema.Record(Schema.String, Schema.Unknown);
99
+ var UnknownRecordWithDefault = Schema.optional(UnknownRecord).pipe(
100
+ Schema.withDecodingDefaultType(Effect.succeed({}))
101
+ );
102
+ var DiscoveryParameter = Schema.Struct({
103
+ type: Schema.optional(Schema.String),
104
+ description: TextOption,
105
+ properties: UnknownRecordWithDefault,
106
+ items: Schema.optional(Schema.Unknown),
107
+ additionalProperties: Schema.optional(Schema.Union([Schema.Boolean, Schema.Unknown])),
108
+ enum: TextArray,
109
+ format: Schema.optional(Schema.String),
110
+ readOnly: Schema.optional(Schema.Boolean),
111
+ default: Schema.optional(Schema.Union([Schema.String, Schema.Number, Schema.Boolean])),
112
+ $ref: Schema.optional(Schema.String),
113
+ location: Schema.optional(Schema.Literals(["path", "query", "header"])),
114
+ required: Schema.optional(Schema.Boolean),
115
+ repeated: Schema.optional(Schema.Boolean)
116
+ });
117
+ var DiscoveryRef = Schema.Struct({
118
+ $ref: Schema.optional(Schema.String)
119
+ });
120
+ var DiscoveryMediaUploadProtocol = Schema.Struct({
121
+ path: TextOption,
122
+ multipart: Schema.optional(Schema.Boolean)
123
+ });
124
+ var DiscoveryMediaUploadProtocols = Schema.Struct({
125
+ simple: Schema.optional(DiscoveryMediaUploadProtocol),
126
+ resumable: Schema.optional(DiscoveryMediaUploadProtocol)
127
+ });
128
+ var DiscoveryMediaUpload = Schema.Struct({
129
+ accept: Schema.optional(TextArray),
130
+ maxSize: Schema.optional(Schema.String),
131
+ protocols: Schema.optional(DiscoveryMediaUploadProtocols)
132
+ });
133
+ var DiscoveryMethod = Schema.Struct({
134
+ id: TextOption,
135
+ description: TextOption,
136
+ httpMethod: Schema.optional(Schema.String),
137
+ path: TextOption,
138
+ parameters: UnknownRecordWithDefault,
139
+ request: Schema.optional(DiscoveryRef),
140
+ response: Schema.optional(DiscoveryRef),
141
+ scopes: TextArray,
142
+ supportsMediaUpload: Schema.optional(Schema.Boolean),
143
+ mediaUpload: Schema.optional(DiscoveryMediaUpload),
144
+ supportsMediaDownload: Schema.optional(Schema.Boolean),
145
+ useMediaDownloadService: Schema.optional(Schema.Boolean)
146
+ });
147
+ var DiscoveryResource = Schema.Struct({
148
+ methods: UnknownRecordWithDefault,
149
+ resources: UnknownRecordWithDefault
150
+ });
151
+ var DiscoveryDocument = Schema.Struct({
152
+ name: TextOption,
153
+ version: TextOption,
154
+ title: TextOption,
155
+ rootUrl: TextOption,
156
+ servicePath: Schema.optional(Schema.Trim).pipe(
157
+ Schema.withDecodingDefaultType(Effect.succeed(""))
158
+ ),
159
+ parameters: UnknownRecordWithDefault,
160
+ methods: UnknownRecordWithDefault,
161
+ resources: UnknownRecordWithDefault,
162
+ schemas: UnknownRecordWithDefault,
163
+ auth: Schema.optional(
164
+ Schema.Struct({
165
+ oauth2: Schema.optional(
166
+ Schema.Struct({
167
+ scopes: Schema.optional(
168
+ Schema.Record(
169
+ Schema.String,
170
+ Schema.Struct({
171
+ description: TextOption
172
+ })
173
+ )
174
+ ).pipe(Schema.withDecodingDefaultType(Effect.succeed({})))
175
+ })
176
+ )
177
+ })
178
+ )
179
+ });
180
+ var decodeDiscoveryDocument = Schema.decodeUnknownSync(DiscoveryDocument);
181
+ var decodeDiscoveryParameter = Schema.decodeUnknownSync(DiscoveryParameter);
182
+ var decodeDiscoveryMethod = Schema.decodeUnknownSync(DiscoveryMethod);
183
+ var decodeDiscoveryResource = Schema.decodeUnknownSync(DiscoveryResource);
184
+ var parseJson = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
185
+ var DISCOVERY_SERVICE_PATH_RE = /^\/discovery\/v1\/apis\/([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)\/rest\/?$/;
186
+ var DISCOVERY_VERSION_RE = /^[A-Za-z0-9._-]+$/;
187
+ var serviceFromGoogleApisHost = (host) => {
188
+ if (!host.endsWith(".googleapis.com")) return null;
189
+ const rawService = host.slice(0, -".googleapis.com".length);
190
+ if (!rawService || rawService.includes(".")) return null;
191
+ const service = rawService === "calendar-json" ? "calendar" : rawService.endsWith("-json") ? rawService.slice(0, -5) : rawService;
192
+ return /^[a-z0-9][a-z0-9-]*$/.test(service) ? service : null;
193
+ };
194
+ var normalizeGoogleDiscoveryUrl = (discoveryUrl) => {
195
+ const trimmed = discoveryUrl.trim();
196
+ if (!URL.canParse(trimmed)) return null;
197
+ const parsed = new URL(trimmed);
198
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.hash) {
199
+ return null;
200
+ }
201
+ const host = parsed.hostname.toLowerCase();
202
+ if (host === "www.googleapis.com") {
203
+ if (parsed.search) return null;
204
+ const match = parsed.pathname.match(DISCOVERY_SERVICE_PATH_RE);
205
+ const service2 = match?.[1];
206
+ const version2 = match?.[2];
207
+ return service2 && version2 ? googleDiscoveryUrlForService(service2, version2) : null;
208
+ }
209
+ const service = serviceFromGoogleApisHost(host);
210
+ if (!service || !["/$discovery/rest", "/$discovery/rest/"].includes(parsed.pathname)) {
211
+ return null;
212
+ }
213
+ const keys = [...parsed.searchParams.keys()];
214
+ const version = parsed.searchParams.get("version")?.trim();
215
+ if (keys.length !== 1 || keys[0] !== "version" || !version || !DISCOVERY_VERSION_RE.test(version)) {
216
+ return null;
217
+ }
218
+ return googleDiscoveryUrlForService(service, version, host);
219
+ };
220
+ var normalizeDiscoveryUrl = (discoveryUrl) => {
221
+ return normalizeGoogleDiscoveryUrl(discoveryUrl) ?? discoveryUrl.trim();
222
+ };
223
+ var isGoogleDiscoveryUrl = (url) => {
224
+ return normalizeGoogleDiscoveryUrl(url) !== null;
225
+ };
226
+ var fetchGoogleDiscoveryDocument = Effect.fn("OpenApi.fetchGoogleDiscoveryDocument")(
227
+ function* (discoveryUrl, credentials) {
228
+ const normalizedDiscoveryUrl = normalizeGoogleDiscoveryUrl(discoveryUrl);
229
+ if (!normalizedDiscoveryUrl) {
230
+ return yield* new OpenApiParseError({
231
+ message: "Google Discovery document URL must be a supported googleapis.com HTTPS Discovery endpoint"
232
+ });
233
+ }
234
+ const client = yield* HttpClient.HttpClient;
235
+ const requestUrl = new URL(normalizedDiscoveryUrl);
236
+ for (const [name, value] of Object.entries(credentials?.queryParams ?? {})) {
237
+ requestUrl.searchParams.set(name, value);
238
+ }
239
+ let request = HttpClientRequest.get(requestUrl.toString()).pipe(
240
+ HttpClientRequest.setHeader("Accept", "application/json, */*")
241
+ );
242
+ for (const [name, value] of Object.entries(credentials?.headers ?? {})) {
243
+ request = HttpClientRequest.setHeader(request, name, value);
244
+ }
245
+ const response = yield* client.execute(request).pipe(
246
+ Effect.mapError(
247
+ () => new OpenApiParseError({
248
+ message: "Failed to fetch Google Discovery document"
249
+ })
250
+ )
251
+ );
252
+ if (response.status < 200 || response.status >= 300) {
253
+ return yield* new OpenApiParseError({
254
+ message: `Failed to fetch Google Discovery document: HTTP ${response.status}`
255
+ });
256
+ }
257
+ return yield* response.text.pipe(
258
+ Effect.mapError(
259
+ () => new OpenApiParseError({
260
+ message: "Failed to read Google Discovery document body"
261
+ })
262
+ )
263
+ );
264
+ }
265
+ );
266
+ var schemaRef = (name) => `#/components/schemas/${name}`;
267
+ var identitySchemaName = (name) => name;
268
+ var schemaComponentPart = (value) => value.trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "") || "schema";
269
+ var normalizeDiscoveryPathTemplate = (pathTemplate) => pathTemplate.replaceAll(/\{\+([^{}]+)\}/g, "{$1}");
270
+ var pathUsesReservedExpansion = (pathTemplate, parameterName) => pathTemplate.includes(`{+${parameterName}}`);
271
+ var uniquePathKey = (paths, preferredPath, method, toolPath) => {
272
+ if (!paths[preferredPath]?.[method]) return preferredPath;
273
+ const disambiguated = `/${toolPath.replace(/[^A-Za-z0-9._~-]+/g, "/")}`;
274
+ if (!paths[disambiguated]?.[method]) return disambiguated;
275
+ let index = 2;
276
+ while (paths[`${disambiguated}/${index}`]?.[method]) index += 1;
277
+ return `${disambiguated}/${index}`;
278
+ };
279
+ var discoveryDescription = (value) => typeof value === "string" ? value : Option.isOption(value) && Option.isSome(value) ? typeof value.value === "string" ? value.value : void 0 : void 0;
280
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
281
+ var jsonValue = (value) => {
282
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
283
+ return value;
284
+ }
285
+ if (Array.isArray(value)) {
286
+ const values = value.map(jsonValue);
287
+ return values.every(Predicate.isNotUndefined) ? values : void 0;
288
+ }
289
+ if (!isRecord(value)) return void 0;
290
+ const entries = Object.entries(value).flatMap(([key, item]) => {
291
+ const converted = jsonValue(item);
292
+ return converted === void 0 ? [] : [[key, converted]];
293
+ });
294
+ return Object.fromEntries(entries);
295
+ };
296
+ var stringArray = (value) => {
297
+ if (!Array.isArray(value)) return void 0;
298
+ const strings = value.filter((item) => typeof item === "string");
299
+ return strings.length === value.length ? strings : void 0;
300
+ };
301
+ var schemaType = (value) => typeof value === "string" && OPENAPI_SCHEMA_TYPES.has(value) ? value : void 0;
302
+ var discoverySchemaToOpenApiSchema = (raw, schemaNameForRef = identitySchemaName) => {
303
+ if (!isRecord(raw)) return {};
304
+ const schema = raw;
305
+ if (typeof schema.$ref === "string") return { $ref: schemaRef(schemaNameForRef(schema.$ref)) };
306
+ const description = discoveryDescription(schema.description);
307
+ const title = discoveryDescription(schema.title);
308
+ const defaultValue = jsonValue(schema.default);
309
+ const enumValues = Array.isArray(schema.enum) ? schema.enum.map(jsonValue).filter(Predicate.isNotUndefined) : [];
310
+ const format = typeof schema.format === "string" ? schema.format : void 0;
311
+ const readOnly = typeof schema.readOnly === "boolean" ? schema.readOnly : void 0;
312
+ const type = schemaType(schema.type);
313
+ const base = {
314
+ ...description !== void 0 ? { description } : {},
315
+ ...title !== void 0 ? { title } : {},
316
+ ...format !== void 0 ? { format } : {},
317
+ ...readOnly !== void 0 ? { readOnly } : {},
318
+ ...defaultValue !== void 0 ? { default: defaultValue } : {},
319
+ ...enumValues.length > 0 ? { enum: enumValues } : {}
320
+ };
321
+ if (type === "array") {
322
+ return {
323
+ ...base,
324
+ type: "array",
325
+ items: discoverySchemaToOpenApiSchema(schema.items, schemaNameForRef)
326
+ };
327
+ }
328
+ const properties = schema.properties;
329
+ if (type === "object" || isRecord(properties) && Object.keys(properties).length > 0 || schema.additionalProperties !== void 0) {
330
+ const convertedProperties = isRecord(properties) ? Object.fromEntries(
331
+ Object.entries(properties).map(([name, value]) => [
332
+ name,
333
+ discoverySchemaToOpenApiSchema(value, schemaNameForRef)
334
+ ])
335
+ ) : void 0;
336
+ const required = stringArray(schema.required);
337
+ const additionalProperties = schema.additionalProperties === void 0 ? void 0 : typeof schema.additionalProperties === "boolean" ? schema.additionalProperties : discoverySchemaToOpenApiSchema(schema.additionalProperties, schemaNameForRef);
338
+ return {
339
+ ...base,
340
+ type: "object",
341
+ ...convertedProperties && Object.keys(convertedProperties).length > 0 ? { properties: convertedProperties } : {},
342
+ ...required && required.length > 0 ? { required } : {},
343
+ ...additionalProperties !== void 0 ? { additionalProperties } : {}
344
+ };
345
+ }
346
+ return type !== void 0 ? { ...base, type } : base;
347
+ };
348
+ var parameterSchema = (parameter, schemaNameForRef = identitySchemaName) => {
349
+ const base = discoverySchemaToOpenApiSchema(parameter, schemaNameForRef);
350
+ return parameter.repeated ? {
351
+ type: "array",
352
+ items: base
353
+ } : base;
354
+ };
355
+ var methodToolPath = (service, methodId) => methodId.startsWith(`${service}.`) ? methodId.slice(service.length + 1) : methodId;
356
+ var collectMethods = (resource) => {
357
+ const decoded = decodeDiscoveryResource(resource);
358
+ const direct = Object.values(decoded.methods ?? {}).map((raw) => decodeDiscoveryMethod(raw));
359
+ const nested = Object.values(decoded.resources ?? {}).flatMap(collectMethods);
360
+ return [...direct, ...nested];
361
+ };
362
+ var discoveryScopes = (document) => Object.fromEntries(
363
+ Object.entries(document.auth?.oauth2?.scopes ?? {}).map(([scope, value]) => [
364
+ scope,
365
+ Option.getOrElse(value.description, () => "")
366
+ ])
367
+ );
368
+ var compactDiscoveryScopeMap = (raw) => {
369
+ const descriptionFor = (scope) => {
370
+ if (raw[scope] !== void 0) return raw[scope];
371
+ if (scope === "email") return raw["https://www.googleapis.com/auth/userinfo.email"] ?? "";
372
+ if (scope === "profile") return raw["https://www.googleapis.com/auth/userinfo.profile"] ?? "";
373
+ return "";
374
+ };
375
+ return Object.fromEntries(
376
+ compactGoogleOAuthScopes(Object.keys(raw)).map((scope) => [scope, descriptionFor(scope)])
377
+ );
378
+ };
379
+ var allDiscoveryMethods = (document) => [
380
+ ...Object.values(document.methods ?? {}).map((raw) => decodeDiscoveryMethod(raw)),
381
+ ...Object.values(document.resources ?? {}).flatMap(collectMethods)
382
+ ];
383
+ var discoveryDocumentInfo = (document, discoveryUrl) => Effect.gen(function* () {
384
+ const service = Option.getOrUndefined(document.name);
385
+ const version = Option.getOrUndefined(document.version);
386
+ const rootUrl = Option.getOrUndefined(document.rootUrl);
387
+ if (!service || !version || !rootUrl) {
388
+ return yield* new OpenApiParseError({
389
+ message: "Google Discovery document is missing one of: name, version, rootUrl"
390
+ });
391
+ }
392
+ return {
393
+ discoveryUrl,
394
+ document,
395
+ service,
396
+ version,
397
+ rootUrl,
398
+ baseUrl: new URL(document.servicePath || "", rootUrl).toString(),
399
+ title: Option.getOrElse(document.title, () => `${service} ${version}`)
400
+ };
401
+ });
402
+ var googleDiscoveryResponseContent = (method, schemaNameForRef) => {
403
+ if ((method.supportsMediaDownload === true || method.useMediaDownloadService === true) && method.response === void 0) {
404
+ return {
405
+ "application/octet-stream": {
406
+ schema: {
407
+ type: "string",
408
+ format: "binary"
409
+ }
410
+ }
411
+ };
412
+ }
413
+ return {
414
+ "application/json": {
415
+ schema: method.response?.$ref ? { $ref: schemaRef(schemaNameForRef(method.response.$ref)) } : {}
416
+ }
417
+ };
418
+ };
419
+ var buildDiscoveryOperation = (input) => {
420
+ const mergedParameters = /* @__PURE__ */ new Map();
421
+ for (const [name, raw] of Object.entries(input.document.parameters ?? {})) {
422
+ const parameter = decodeDiscoveryParameter(raw);
423
+ if (parameter.location) mergedParameters.set(name, parameter);
424
+ }
425
+ for (const [name, raw] of Object.entries(input.method.parameters ?? {})) {
426
+ const parameter = decodeDiscoveryParameter(raw);
427
+ if (parameter.location) mergedParameters.set(name, parameter);
428
+ }
429
+ const methodScopes = input.oauthScopes ?? input.method.scopes ?? [];
430
+ const methodDescription = Option.getOrUndefined(input.method.description);
431
+ const schemaNameForRef = input.schemaNameForRef ?? identitySchemaName;
432
+ return {
433
+ operationId: input.toolPath,
434
+ "x-executor-toolPath": input.toolPath,
435
+ "x-executor-pathTemplate": input.pathTemplate,
436
+ ...input.tags && input.tags.length > 0 ? { tags: input.tags } : {},
437
+ ...methodDescription !== void 0 ? { description: methodDescription } : {},
438
+ ...input.serverUrl ? { servers: [{ url: input.serverUrl }] } : {},
439
+ parameters: [...mergedParameters.entries()].flatMap(([name, parameter]) => {
440
+ const location = parameter.location;
441
+ if (!location) return [];
442
+ const description = Option.getOrUndefined(parameter.description);
443
+ const allowReserved = location === "path" && pathUsesReservedExpansion(input.pathTemplate, name);
444
+ return [
445
+ {
446
+ name,
447
+ in: location,
448
+ required: location === "path" ? true : parameter.required === true,
449
+ ...description !== void 0 ? { description } : {},
450
+ schema: parameterSchema(parameter, schemaNameForRef),
451
+ ...location === "query" ? { style: "form", explode: parameter.repeated === true } : {},
452
+ ...allowReserved ? { allowReserved: true } : {}
453
+ }
454
+ ];
455
+ }),
456
+ ...input.method.request?.$ref ? {
457
+ requestBody: {
458
+ required: false,
459
+ content: {
460
+ "application/json": {
461
+ schema: { $ref: schemaRef(schemaNameForRef(input.method.request.$ref)) }
462
+ }
463
+ }
464
+ }
465
+ } : {},
466
+ responses: {
467
+ "200": {
468
+ description: "Successful response",
469
+ content: googleDiscoveryResponseContent(input.method, schemaNameForRef)
470
+ }
471
+ },
472
+ ...methodScopes.length > 0 ? { security: [{ googleOAuth2: methodScopes }] } : {},
473
+ "x-google-scopes": methodScopes
474
+ };
475
+ };
476
+ var buildDiscoveryMediaUploadOperation = (input) => {
477
+ if (input.method.supportsMediaUpload !== true) return void 0;
478
+ const mediaUpload = input.method.mediaUpload;
479
+ if (mediaUpload === void 0) return void 0;
480
+ const simpleProtocol = mediaUpload.protocols?.simple;
481
+ const uploadPath = simpleProtocol ? Option.getOrUndefined(simpleProtocol.path) : void 0;
482
+ if (!uploadPath) return void 0;
483
+ const mergedParameters = /* @__PURE__ */ new Map();
484
+ for (const [name, raw] of Object.entries(input.document.parameters ?? {})) {
485
+ const parameter = decodeDiscoveryParameter(raw);
486
+ if (parameter.location) mergedParameters.set(name, parameter);
487
+ }
488
+ for (const [name, raw] of Object.entries(input.method.parameters ?? {})) {
489
+ const parameter = decodeDiscoveryParameter(raw);
490
+ if (parameter.location) mergedParameters.set(name, parameter);
491
+ }
492
+ mergedParameters.set("uploadType", {
493
+ type: "string",
494
+ location: "query",
495
+ required: true,
496
+ description: Option.some("The upload type for the media upload."),
497
+ properties: {},
498
+ items: void 0,
499
+ additionalProperties: void 0,
500
+ enum: ["media"],
501
+ format: void 0,
502
+ readOnly: void 0,
503
+ default: "media",
504
+ $ref: void 0,
505
+ repeated: void 0
506
+ });
507
+ const methodScopes = input.oauthScopes ?? input.method.scopes ?? [];
508
+ const schemaNameForRef = input.schemaNameForRef ?? identitySchemaName;
509
+ const methodDescription = Option.getOrUndefined(input.method.description);
510
+ return {
511
+ operationId: `${input.toolPath}Media`,
512
+ "x-executor-toolPath": `${input.toolPath}Media`,
513
+ "x-executor-pathTemplate": uploadPath,
514
+ ...input.tags && input.tags.length > 0 ? { tags: input.tags } : {},
515
+ ...methodDescription !== void 0 ? { description: `${methodDescription} (media upload)` } : {},
516
+ ...input.serverUrl ? { servers: [{ url: input.serverUrl }] } : {},
517
+ parameters: [...mergedParameters.entries()].flatMap(([name, parameter]) => {
518
+ const location = parameter.location;
519
+ if (!location) return [];
520
+ const description = Option.getOrUndefined(parameter.description);
521
+ const allowReserved = location === "path" && pathUsesReservedExpansion(uploadPath, name);
522
+ return [
523
+ {
524
+ name,
525
+ in: location,
526
+ required: location === "path" ? true : parameter.required === true,
527
+ ...description !== void 0 ? { description } : {},
528
+ schema: parameterSchema(parameter, schemaNameForRef),
529
+ ...location === "query" ? { style: "form", explode: parameter.repeated === true } : {},
530
+ ...allowReserved ? { allowReserved: true } : {}
531
+ }
532
+ ];
533
+ }),
534
+ requestBody: {
535
+ required: true,
536
+ content: {
537
+ "application/octet-stream": {
538
+ schema: { type: "string", format: "binary" }
539
+ }
540
+ }
541
+ },
542
+ responses: {
543
+ "200": {
544
+ description: "Successful response",
545
+ content: {
546
+ "application/json": {
547
+ schema: input.method.response?.$ref ? { $ref: schemaRef(schemaNameForRef(input.method.response.$ref)) } : {}
548
+ }
549
+ }
550
+ }
551
+ },
552
+ ...methodScopes.length > 0 ? { security: [{ googleOAuth2: methodScopes }] } : {},
553
+ "x-google-scopes": methodScopes
554
+ };
555
+ };
556
+ var GOOGLE_OAUTH_SECURITY_SCHEME = "googleOAuth2";
557
+ var GOOGLE_PHOTOS_LIBRARY_SERVICE = "photoslibrary";
558
+ var GOOGLE_PHOTOS_APPENDONLY_SCOPE = "https://www.googleapis.com/auth/photoslibrary.appendonly";
559
+ var GOOGLE_PHOTOS_UPLOAD_TOOL_PATH = "photoslibrary.mediaItems.upload";
560
+ var GOOGLE_PHOTOS_UPLOAD_PATH = "/uploads";
561
+ var isGooglePhotosService = (service) => service === GOOGLE_PHOTOS_LIBRARY_SERVICE || service === GOOGLE_PHOTOS_PICKER_SERVICE;
562
+ var discoveryScopesForService = (service, document) => {
563
+ const scopes = discoveryScopes(document);
564
+ const overrideScopes = GOOGLE_DISCOVERY_SERVICE_OVERRIDES[service]?.scopes;
565
+ if (!overrideScopes) {
566
+ return scopes;
567
+ }
568
+ const missingScopes = Object.fromEntries(
569
+ Object.entries(overrideScopes).filter(([scope]) => scopes[scope] === void 0)
570
+ );
571
+ return Object.keys(missingScopes).length === 0 ? scopes : { ...scopes, ...missingScopes };
572
+ };
573
+ var discoveryMethodScopesForService = (service, method) => {
574
+ const scopes = method.scopes ?? [];
575
+ return scopes.length === 0 ? GOOGLE_DISCOVERY_SERVICE_OVERRIDES[service]?.fallbackMethodScopes ?? scopes : scopes;
576
+ };
577
+ var googleOauthTemplate = (scopes) => (
578
+ // A Google-discovery integration is ALWAYS OAuth, so it ALWAYS declares its
579
+ // oauth method - even when the compacted scope set is empty (e.g. a bundle of
580
+ // only limited-consent APIs like Google Keep, whose scopes Google won't grant
581
+ // through standard consent). Without this the integration declares no auth
582
+ // method and the "Add account" / "Add a connection" actions are disabled, so
583
+ // you can't even start the flow. Empty `scopes` just requests no scope.
584
+ [
585
+ {
586
+ slug: AuthTemplateSlug.make(GOOGLE_OAUTH_SECURITY_SCHEME),
587
+ kind: "oauth2",
588
+ authorizationUrl: GOOGLE_OAUTH_AUTHORIZATION_URL,
589
+ tokenUrl: GOOGLE_OAUTH_TOKEN_URL,
590
+ scopes: Object.keys(scopes)
591
+ }
592
+ ]
593
+ );
594
+ var googlePhotosUploadOperation = (input) => ({
595
+ operationId: input.toolPath,
596
+ "x-executor-toolPath": input.toolPath,
597
+ "x-executor-pathTemplate": GOOGLE_PHOTOS_UPLOAD_PATH,
598
+ ...input.tags && input.tags.length > 0 ? { tags: input.tags } : {},
599
+ description: "Uploads raw photo or video bytes to Google Photos and returns a plain-text upload token. Call mediaItems.batchCreate with the returned token to create the media item.",
600
+ servers: [{ url: input.serverUrl }],
601
+ parameters: [
602
+ {
603
+ name: "X-Goog-Upload-File-Name",
604
+ in: "header",
605
+ required: true,
606
+ description: "File name Google Photos should associate with the uploaded bytes.",
607
+ schema: { type: "string" }
608
+ },
609
+ {
610
+ name: "X-Goog-Upload-Protocol",
611
+ in: "header",
612
+ required: true,
613
+ description: "Google Photos raw upload protocol. Set to raw.",
614
+ schema: { type: "string", enum: ["raw"], default: "raw" }
615
+ },
616
+ {
617
+ name: "X-Goog-Upload-Content-Type",
618
+ in: "header",
619
+ required: false,
620
+ description: "MIME type of the uploaded media, for example image/jpeg or video/mp4.",
621
+ schema: { type: "string" }
622
+ }
623
+ ],
624
+ requestBody: {
625
+ required: true,
626
+ content: {
627
+ "application/octet-stream": {
628
+ schema: { type: "string", format: "binary" }
629
+ }
630
+ }
631
+ },
632
+ responses: {
633
+ "200": {
634
+ description: "Successful response",
635
+ content: {
636
+ "text/plain": {
637
+ schema: { type: "string" }
638
+ }
639
+ }
640
+ }
641
+ },
642
+ ...input.oauthScopes.length > 0 ? { security: [{ googleOAuth2: input.oauthScopes }] } : {},
643
+ "x-google-scopes": input.oauthScopes
644
+ });
645
+ var hasOperation = (paths, operationId) => Object.values(paths).some(
646
+ (pathItem) => Object.values(pathItem).some((operation) => operation.operationId === operationId)
647
+ );
648
+ var convertGoogleDiscoveryToOpenApi = Effect.fn("OpenApi.convertGoogleDiscovery")(
649
+ function* (input) {
650
+ const parsed = yield* parseJson(input.documentText).pipe(
651
+ Effect.mapError(
652
+ () => new OpenApiParseError({
653
+ message: "Failed to parse Google Discovery document"
654
+ })
655
+ )
656
+ );
657
+ const document = yield* Effect.try({
658
+ try: () => decodeDiscoveryDocument(parsed),
659
+ catch: () => new OpenApiParseError({
660
+ message: "Failed to decode Google Discovery document"
661
+ })
662
+ });
663
+ const info = yield* discoveryDocumentInfo(document, input.discoveryUrl);
664
+ const { service, version, rootUrl, baseUrl, title } = info;
665
+ const paths = {};
666
+ for (const method of allDiscoveryMethods(document)) {
667
+ const methodId = Option.getOrUndefined(method.id);
668
+ const pathTemplate = Option.getOrUndefined(method.path);
669
+ if (!methodId || !pathTemplate || !method.httpMethod) continue;
670
+ const toolPath = methodToolPath(service, methodId);
671
+ const path = normalizeDiscoveryPathTemplate(
672
+ pathTemplate.startsWith("/") ? pathTemplate : `/${pathTemplate}`
673
+ );
674
+ const methodKey = method.httpMethod.toLowerCase();
675
+ const pathKey = uniquePathKey(paths, path, methodKey, toolPath);
676
+ paths[pathKey] ??= {};
677
+ paths[pathKey][methodKey] = buildDiscoveryOperation({
678
+ document,
679
+ method,
680
+ toolPath,
681
+ pathTemplate: pathTemplate.startsWith("/") ? pathTemplate : `/${pathTemplate}`,
682
+ oauthScopes: discoveryMethodScopesForService(service, method)
683
+ });
684
+ const mediaUploadOperation = buildDiscoveryMediaUploadOperation({
685
+ document,
686
+ method,
687
+ toolPath,
688
+ oauthScopes: discoveryMethodScopesForService(service, method),
689
+ serverUrl: rootUrl
690
+ });
691
+ if (mediaUploadOperation) {
692
+ const mediaUploadPathTemplate = mediaUploadOperation["x-executor-pathTemplate"] ?? "";
693
+ const uploadPath = normalizeDiscoveryPathTemplate(
694
+ mediaUploadPathTemplate.startsWith("/") ? mediaUploadPathTemplate : `/${mediaUploadPathTemplate}`
695
+ );
696
+ const uploadMethodKey = method.httpMethod.toLowerCase();
697
+ const uploadPathKey = uniquePathKey(
698
+ paths,
699
+ uploadPath,
700
+ uploadMethodKey,
701
+ mediaUploadOperation.operationId
702
+ );
703
+ paths[uploadPathKey] ??= {};
704
+ paths[uploadPathKey][uploadMethodKey] = mediaUploadOperation;
705
+ }
706
+ }
707
+ if (service === GOOGLE_PHOTOS_LIBRARY_SERVICE && !hasOperation(paths, GOOGLE_PHOTOS_UPLOAD_TOOL_PATH)) {
708
+ paths[GOOGLE_PHOTOS_UPLOAD_PATH] ??= {};
709
+ paths[GOOGLE_PHOTOS_UPLOAD_PATH].post = googlePhotosUploadOperation({
710
+ toolPath: GOOGLE_PHOTOS_UPLOAD_TOOL_PATH,
711
+ oauthScopes: [GOOGLE_PHOTOS_APPENDONLY_SCOPE],
712
+ serverUrl: baseUrl
713
+ });
714
+ }
715
+ const scopes = compactDiscoveryScopeMap(discoveryScopesForService(service, document));
716
+ const authenticationTemplate = googleOauthTemplate(scopes);
717
+ const spec = {
718
+ openapi: "3.1.0",
719
+ info: {
720
+ title,
721
+ version
722
+ },
723
+ servers: [{ url: baseUrl }],
724
+ paths,
725
+ components: {
726
+ schemas: Object.fromEntries(
727
+ Object.entries(document.schemas ?? {}).map(([name, schema]) => [
728
+ name,
729
+ discoverySchemaToOpenApiSchema(schema)
730
+ ])
731
+ ),
732
+ ...authenticationTemplate ? {
733
+ securitySchemes: {
734
+ googleOAuth2: {
735
+ type: "oauth2",
736
+ flows: {
737
+ authorizationCode: {
738
+ authorizationUrl: GOOGLE_OAUTH_AUTHORIZATION_URL,
739
+ tokenUrl: GOOGLE_OAUTH_TOKEN_URL,
740
+ scopes
741
+ }
742
+ }
743
+ }
744
+ }
745
+ } : {}
746
+ },
747
+ ...authenticationTemplate ? { security: [{ googleOAuth2: Object.keys(scopes) }] } : {},
748
+ "x-executor-origin": {
749
+ kind: "googleDiscovery",
750
+ discoveryUrl: normalizeDiscoveryUrl(input.discoveryUrl),
751
+ service,
752
+ version
753
+ }
754
+ };
755
+ return {
756
+ // @effect-diagnostics-next-line preferSchemaOverJson:off
757
+ specText: JSON.stringify(spec),
758
+ baseUrl,
759
+ title,
760
+ service,
761
+ version,
762
+ ...authenticationTemplate ? { authenticationTemplate } : {}
763
+ };
764
+ }
765
+ );
766
+ var convertGoogleDiscoveryBundleToOpenApi = Effect.fn(
767
+ "OpenApi.convertGoogleDiscoveryBundle"
768
+ )(function* (input) {
769
+ if (input.documents.length === 0) {
770
+ return yield* new OpenApiParseError({
771
+ message: "Google Discovery bundle requires at least one document"
772
+ });
773
+ }
774
+ const infos = yield* Effect.forEach(
775
+ input.documents,
776
+ ({ discoveryUrl, documentText }) => Effect.gen(function* () {
777
+ const parsed = yield* parseJson(documentText).pipe(
778
+ Effect.mapError(
779
+ () => new OpenApiParseError({
780
+ message: "Failed to parse Google Discovery document"
781
+ })
782
+ )
783
+ );
784
+ const document = yield* Effect.try({
785
+ try: () => decodeDiscoveryDocument(parsed),
786
+ catch: () => new OpenApiParseError({
787
+ message: "Failed to decode Google Discovery document"
788
+ })
789
+ });
790
+ return yield* discoveryDocumentInfo(document, discoveryUrl);
791
+ })
792
+ );
793
+ const paths = {};
794
+ const schemas = {};
795
+ const rawScopes = {};
796
+ const consentScopeSet = input.consentScopes ? new Set(input.consentScopes) : null;
797
+ for (const info of infos) {
798
+ const schemaPrefix = schemaComponentPart(`${info.service}_${info.version}`);
799
+ const schemaNameForRef = (name) => `${schemaPrefix}_${schemaComponentPart(name)}`;
800
+ const scopeDescriptions = discoveryScopesForService(info.service, info.document);
801
+ const filterPhotosScopes = consentScopeSet !== null && isGooglePhotosService(info.service);
802
+ for (const [scope, description] of Object.entries(scopeDescriptions)) {
803
+ if (filterPhotosScopes && !consentScopeSet.has(scope)) continue;
804
+ rawScopes[scope] ??= description;
805
+ }
806
+ for (const [name, schema] of Object.entries(info.document.schemas ?? {})) {
807
+ schemas[schemaNameForRef(name)] = discoverySchemaToOpenApiSchema(schema, schemaNameForRef);
808
+ }
809
+ for (const method of allDiscoveryMethods(info.document)) {
810
+ const methodId = Option.getOrUndefined(method.id);
811
+ const rawPathTemplate = Option.getOrUndefined(method.path);
812
+ if (!methodId || !rawPathTemplate || !method.httpMethod) continue;
813
+ const methodScopes = discoveryMethodScopesForService(info.service, method);
814
+ const oauthScopes = filterPhotosScopes ? methodScopes.filter((scope) => consentScopeSet.has(scope)) : methodScopes;
815
+ if (filterPhotosScopes && methodScopes.length > 0 && oauthScopes.length === 0) continue;
816
+ const toolPath = methodId;
817
+ const wirePath = rawPathTemplate.startsWith("/") ? rawPathTemplate : `/${rawPathTemplate}`;
818
+ const openApiPath = normalizeDiscoveryPathTemplate(wirePath);
819
+ const methodKey = method.httpMethod.toLowerCase();
820
+ const pathKey = uniquePathKey(paths, openApiPath, methodKey, toolPath);
821
+ paths[pathKey] ??= {};
822
+ paths[pathKey][methodKey] = buildDiscoveryOperation({
823
+ document: info.document,
824
+ method,
825
+ toolPath,
826
+ pathTemplate: wirePath,
827
+ oauthScopes,
828
+ schemaNameForRef,
829
+ serverUrl: info.baseUrl,
830
+ tags: [info.title]
831
+ });
832
+ const mediaUploadOperation = buildDiscoveryMediaUploadOperation({
833
+ document: info.document,
834
+ method,
835
+ toolPath,
836
+ oauthScopes,
837
+ schemaNameForRef,
838
+ serverUrl: info.rootUrl,
839
+ tags: [info.title]
840
+ });
841
+ if (mediaUploadOperation) {
842
+ const mediaUploadPathTemplate = mediaUploadOperation["x-executor-pathTemplate"] ?? "";
843
+ const uploadPath = normalizeDiscoveryPathTemplate(
844
+ mediaUploadPathTemplate.startsWith("/") ? mediaUploadPathTemplate : `/${mediaUploadPathTemplate}`
845
+ );
846
+ const uploadMethodKey = method.httpMethod.toLowerCase();
847
+ const uploadPathKey = uniquePathKey(
848
+ paths,
849
+ uploadPath,
850
+ uploadMethodKey,
851
+ mediaUploadOperation.operationId
852
+ );
853
+ paths[uploadPathKey] ??= {};
854
+ paths[uploadPathKey][uploadMethodKey] = mediaUploadOperation;
855
+ }
856
+ }
857
+ if (info.service === GOOGLE_PHOTOS_LIBRARY_SERVICE && (!consentScopeSet || consentScopeSet.has(GOOGLE_PHOTOS_APPENDONLY_SCOPE)) && !hasOperation(paths, GOOGLE_PHOTOS_UPLOAD_TOOL_PATH)) {
858
+ paths[GOOGLE_PHOTOS_UPLOAD_PATH] ??= {};
859
+ paths[GOOGLE_PHOTOS_UPLOAD_PATH].post = googlePhotosUploadOperation({
860
+ toolPath: GOOGLE_PHOTOS_UPLOAD_TOOL_PATH,
861
+ oauthScopes: [GOOGLE_PHOTOS_APPENDONLY_SCOPE],
862
+ serverUrl: info.baseUrl,
863
+ tags: [info.title]
864
+ });
865
+ }
866
+ }
867
+ const scopes = compactDiscoveryScopeMap(rawScopes);
868
+ const authenticationTemplate = googleOauthTemplate(scopes);
869
+ const spec = {
870
+ openapi: "3.1.0",
871
+ info: {
872
+ title: "Google",
873
+ version: "google-discovery-bundle"
874
+ },
875
+ servers: [{ url: GOOGLE_BUNDLE_BASE_URL }],
876
+ paths,
877
+ components: {
878
+ schemas,
879
+ ...authenticationTemplate ? {
880
+ securitySchemes: {
881
+ googleOAuth2: {
882
+ type: "oauth2",
883
+ flows: {
884
+ authorizationCode: {
885
+ authorizationUrl: GOOGLE_OAUTH_AUTHORIZATION_URL,
886
+ tokenUrl: GOOGLE_OAUTH_TOKEN_URL,
887
+ scopes
888
+ }
889
+ }
890
+ }
891
+ }
892
+ } : {}
893
+ },
894
+ ...authenticationTemplate ? { security: [{ googleOAuth2: Object.keys(scopes) }] } : {},
895
+ "x-executor-origin": {
896
+ kind: "googleDiscoveryBundle",
897
+ services: infos.map((info) => ({
898
+ discoveryUrl: normalizeDiscoveryUrl(info.discoveryUrl),
899
+ service: info.service,
900
+ version: info.version
901
+ }))
902
+ }
903
+ };
904
+ return {
905
+ // @effect-diagnostics-next-line preferSchemaOverJson:off
906
+ specText: JSON.stringify(spec),
907
+ baseUrl: GOOGLE_BUNDLE_BASE_URL,
908
+ title: "Google",
909
+ service: "google",
910
+ version: "google-discovery-bundle",
911
+ discoveryUrls: infos.map((info) => normalizeDiscoveryUrl(info.discoveryUrl)),
912
+ ...authenticationTemplate ? { authenticationTemplate } : {}
913
+ };
914
+ });
915
+
916
+ // src/providers/google/presets.ts
917
+ var gd = (service, version) => `https://www.googleapis.com/discovery/v1/apis/${service}/${version}/rest`;
918
+ var GOOGLE_G = "https://fonts.gstatic.com/s/i/productlogos/googleg/v6/192px.svg";
919
+ var GOOGLE_BUNDLE_PRESET_ID = "google";
920
+ var GOOGLE_PHOTOS_PRESET_ID = "google-photos";
921
+ var GOOGLE_PHOTOS_ICON = "https://www.gstatic.com/images/branding/product/2x/photos_96dp.png";
922
+ var googleOpenApiBundlePreset = {
923
+ id: GOOGLE_BUNDLE_PRESET_ID,
924
+ name: "Google",
925
+ summary: "Bundle Gmail, Calendar, Drive, Docs, and other Google APIs into one integration.",
926
+ icon: GOOGLE_G,
927
+ featured: true
928
+ };
929
+ var googlePhotosOpenApiBundlePreset = {
930
+ id: GOOGLE_PHOTOS_PRESET_ID,
931
+ name: "Google Photos",
932
+ summary: "Albums, uploads, app-created media, and user-selected picker media.",
933
+ icon: GOOGLE_PHOTOS_ICON,
934
+ featured: true
935
+ };
936
+ var googleOpenApiPresets = [
937
+ {
938
+ id: "google-calendar",
939
+ name: "Google Calendar",
940
+ summary: "Calendars, events, ACLs, and scheduling.",
941
+ url: gd("calendar", "v3"),
942
+ icon: "https://fonts.gstatic.com/s/i/productlogos/calendar_2020q4/v8/192px.svg",
943
+ featured: true,
944
+ oauthAudience: "standard-user"
945
+ },
946
+ {
947
+ id: "google-gmail",
948
+ name: "Gmail",
949
+ summary: "Messages, threads, labels, and drafts.",
950
+ url: gd("gmail", "v1"),
951
+ icon: "https://fonts.gstatic.com/s/i/productlogos/gmail_2020q4/v8/web-96dp/logo_gmail_2020q4_color_2x_web_96dp.png",
952
+ featured: true,
953
+ oauthAudience: "standard-user"
954
+ },
955
+ {
956
+ id: "google-sheets",
957
+ name: "Google Sheets",
958
+ summary: "Spreadsheets, values, ranges, and formatting.",
959
+ url: gd("sheets", "v4"),
960
+ icon: "https://fonts.gstatic.com/s/i/productlogos/sheets_2020q4/v8/192px.svg",
961
+ featured: true,
962
+ oauthAudience: "standard-user"
963
+ },
964
+ {
965
+ id: "google-drive",
966
+ name: "Google Drive",
967
+ summary: "Files, folders, permissions, and shared drives.",
968
+ url: gd("drive", "v3"),
969
+ icon: "https://fonts.gstatic.com/s/i/productlogos/drive_2020q4/v8/192px.svg",
970
+ featured: true,
971
+ oauthAudience: "standard-user"
972
+ },
973
+ {
974
+ id: "google-docs",
975
+ name: "Google Docs",
976
+ summary: "Documents, structural edits, and formatting.",
977
+ url: gd("docs", "v1"),
978
+ icon: "https://fonts.gstatic.com/s/i/productlogos/docs_2020q4/v12/192px.svg",
979
+ featured: true,
980
+ oauthAudience: "standard-user"
981
+ },
982
+ {
983
+ id: "google-slides",
984
+ name: "Google Slides",
985
+ summary: "Presentations, slides, page elements, and deck updates.",
986
+ url: gd("slides", "v1"),
987
+ icon: "https://fonts.gstatic.com/s/i/productlogos/slides_2020q4/v12/192px.svg",
988
+ oauthAudience: "standard-user"
989
+ },
990
+ {
991
+ id: "google-forms",
992
+ name: "Google Forms",
993
+ summary: "Forms, questions, responses, and quizzes.",
994
+ url: "https://forms.googleapis.com/$discovery/rest?version=v1",
995
+ icon: "https://fonts.gstatic.com/s/i/productlogos/forms_2020q4/v6/192px.svg",
996
+ oauthAudience: "standard-user"
997
+ },
998
+ {
999
+ id: "google-tasks",
1000
+ name: "Google Tasks",
1001
+ summary: "Task lists, task items, notes, and due dates.",
1002
+ url: gd("tasks", "v1"),
1003
+ icon: "https://fonts.gstatic.com/s/i/productlogos/tasks/v5/192px.svg",
1004
+ oauthAudience: "standard-user"
1005
+ },
1006
+ {
1007
+ id: "google-people",
1008
+ name: "Google People",
1009
+ summary: "Contacts, profiles, directory people, and contact groups.",
1010
+ url: gd("people", "v1"),
1011
+ icon: "https://fonts.gstatic.com/s/i/productlogos/contacts_2022/v2/192px.svg",
1012
+ oauthAudience: "standard-user"
1013
+ },
1014
+ {
1015
+ id: "google-photos-library",
1016
+ name: "Google Photos Library",
1017
+ summary: "Albums, uploads, and app-created media through Google Photos.",
1018
+ url: gd("photoslibrary", "v1"),
1019
+ icon: GOOGLE_PHOTOS_ICON,
1020
+ oauthAudience: "advanced-user"
1021
+ },
1022
+ {
1023
+ id: "google-photos-picker",
1024
+ name: "Google Photos Picker",
1025
+ summary: "Picker sessions and user-selected Google Photos media items.",
1026
+ url: "https://photospicker.googleapis.com/$discovery/rest?version=v1",
1027
+ icon: GOOGLE_PHOTOS_ICON,
1028
+ oauthAudience: "advanced-user"
1029
+ },
1030
+ {
1031
+ id: "google-chat",
1032
+ name: "Google Chat",
1033
+ summary: "Spaces, messages, members, reactions, and chat workflows.",
1034
+ url: gd("chat", "v1"),
1035
+ icon: "https://fonts.gstatic.com/s/i/productlogos/chat_2020q4/v8/192px.svg",
1036
+ oauthAudience: "workspace-admin"
1037
+ },
1038
+ {
1039
+ id: "google-keep",
1040
+ name: "Google Keep",
1041
+ summary: "Notes, lists, attachments, and annotations.",
1042
+ url: "https://keep.googleapis.com/$discovery/rest?version=v1",
1043
+ icon: "https://fonts.gstatic.com/s/i/productlogos/keep_2020q4/v8/192px.svg",
1044
+ oauthAudience: "unsupported-user"
1045
+ },
1046
+ {
1047
+ id: "google-youtube-data",
1048
+ name: "YouTube Data",
1049
+ summary: "Channels, playlists, videos, comments, and uploads.",
1050
+ url: gd("youtube", "v3"),
1051
+ icon: "https://fonts.gstatic.com/s/i/productlogos/youtube/v9/192px.svg",
1052
+ oauthAudience: "advanced-user"
1053
+ },
1054
+ {
1055
+ id: "google-search-console",
1056
+ name: "Google Search Console",
1057
+ summary: "Sites, sitemaps, URL inspection, and search performance.",
1058
+ url: gd("searchconsole", "v1"),
1059
+ icon: GOOGLE_G,
1060
+ oauthAudience: "standard-user"
1061
+ },
1062
+ {
1063
+ id: "google-classroom",
1064
+ name: "Google Classroom",
1065
+ summary: "Courses, rosters, coursework, and grading.",
1066
+ url: gd("classroom", "v1"),
1067
+ icon: "https://fonts.gstatic.com/s/i/productlogos/classroom/v7/192px.svg",
1068
+ oauthAudience: "advanced-user"
1069
+ },
1070
+ {
1071
+ id: "google-admin-directory",
1072
+ name: "Google Admin Directory",
1073
+ summary: "Users, groups, org units, roles, and domain resources.",
1074
+ url: "https://admin.googleapis.com/$discovery/rest?version=directory_v1",
1075
+ icon: "https://fonts.gstatic.com/s/i/productlogos/admin_2020q4/v6/192px.svg",
1076
+ oauthAudience: "workspace-admin"
1077
+ },
1078
+ {
1079
+ id: "google-admin-reports",
1080
+ name: "Google Admin Reports",
1081
+ summary: "Audit events, usage reports, and admin activity logs.",
1082
+ url: "https://admin.googleapis.com/$discovery/rest?version=reports_v1",
1083
+ icon: "https://fonts.gstatic.com/s/i/productlogos/admin_2020q4/v6/192px.svg",
1084
+ oauthAudience: "workspace-admin"
1085
+ },
1086
+ {
1087
+ id: "google-apps-script",
1088
+ name: "Google Apps Script",
1089
+ summary: "Projects, deployments, and script execution.",
1090
+ url: gd("script", "v1"),
1091
+ icon: "https://fonts.gstatic.com/s/i/productlogos/apps_script/v10/192px.svg",
1092
+ oauthAudience: "advanced-user"
1093
+ },
1094
+ {
1095
+ id: "google-bigquery",
1096
+ name: "Google BigQuery",
1097
+ summary: "Datasets, tables, jobs, and analytical queries.",
1098
+ url: gd("bigquery", "v2"),
1099
+ icon: "https://fonts.gstatic.com/s/i/productlogos/google_cloud/v6/192px.svg",
1100
+ oauthAudience: "advanced-user"
1101
+ },
1102
+ {
1103
+ id: "google-cloud-resource-manager",
1104
+ name: "Google Cloud Resource Manager",
1105
+ summary: "Projects, folders, organizations, and IAM hierarchy.",
1106
+ url: "https://cloudresourcemanager.googleapis.com/$discovery/rest?version=v3",
1107
+ icon: "https://fonts.gstatic.com/s/i/productlogos/google_cloud/v6/192px.svg",
1108
+ oauthAudience: "advanced-user"
1109
+ }
1110
+ ];
1111
+ var googleStandardUserOAuthPresets = googleOpenApiPresets.filter(
1112
+ (preset) => preset.oauthAudience === "standard-user"
1113
+ );
1114
+ var googlePhotosPresetIds = [
1115
+ "google-photos-library",
1116
+ "google-photos-picker"
1117
+ ];
1118
+ var googlePhotosOpenApiPresets = googleOpenApiPresets.filter((preset) => googlePhotosPresetIds.includes(preset.id));
1119
+ var googleOAuthConsentScopes = {
1120
+ "google-calendar": ["https://www.googleapis.com/auth/calendar"],
1121
+ "google-gmail": ["https://mail.google.com/"],
1122
+ "google-sheets": ["https://www.googleapis.com/auth/spreadsheets"],
1123
+ "google-drive": ["https://www.googleapis.com/auth/drive"],
1124
+ "google-docs": ["https://www.googleapis.com/auth/documents"],
1125
+ "google-slides": ["https://www.googleapis.com/auth/presentations"],
1126
+ "google-forms": ["https://www.googleapis.com/auth/forms.body"],
1127
+ "google-tasks": ["https://www.googleapis.com/auth/tasks"],
1128
+ "google-people": ["https://www.googleapis.com/auth/contacts"],
1129
+ "google-photos-library": [
1130
+ "https://www.googleapis.com/auth/photoslibrary.appendonly",
1131
+ "https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
1132
+ ],
1133
+ "google-photos-picker": ["https://www.googleapis.com/auth/photospicker.mediaitems.readonly"],
1134
+ "google-chat": ["https://www.googleapis.com/auth/chat.spaces"],
1135
+ "google-keep": ["https://www.googleapis.com/auth/keep"],
1136
+ "google-youtube-data": ["https://www.googleapis.com/auth/youtube"],
1137
+ "google-search-console": ["https://www.googleapis.com/auth/webmasters"],
1138
+ "google-classroom": ["https://www.googleapis.com/auth/classroom.courses"],
1139
+ "google-admin-directory": ["https://www.googleapis.com/auth/admin.directory.user"],
1140
+ "google-admin-reports": ["https://www.googleapis.com/auth/admin.reports.audit.readonly"],
1141
+ "google-apps-script": ["https://www.googleapis.com/auth/script.projects"],
1142
+ "google-bigquery": ["https://www.googleapis.com/auth/bigquery"],
1143
+ "google-cloud-resource-manager": ["https://www.googleapis.com/auth/cloud-platform"]
1144
+ };
1145
+ var googleOAuthConsentScopesForPreset = (presetId) => googleOAuthConsentScopes[presetId] ?? [];
1146
+ var googleServiceSlug = (presetId) => presetId.replaceAll("-", "_");
1147
+ var GOOGLE_OAUTH_AUTHORIZATION_URL2 = "https://accounts.google.com/o/oauth2/v2/auth";
1148
+ var GOOGLE_OAUTH_TOKEN_URL2 = "https://oauth2.googleapis.com/token";
1149
+ var GOOGLE_OAUTH_SECURITY_SCHEME2 = "googleOAuth2";
1150
+ var GOOGLE_IDENTITY_SCOPES = ["openid", "email", "profile"];
1151
+ var GOOGLE_HEALTH_CHECKS = {
1152
+ "google-calendar": { operation: "calendar.calendarList.list" },
1153
+ "google-gmail": {
1154
+ operation: "gmail.users.labels.list",
1155
+ args: { userId: "me" }
1156
+ },
1157
+ "google-drive": {
1158
+ operation: "drive.about.get",
1159
+ args: { fields: "user" }
1160
+ },
1161
+ "google-tasks": { operation: "tasks.tasklists.list" },
1162
+ "google-people": {
1163
+ operation: "people.people.get",
1164
+ args: { resourceName: "people/me", personFields: "emailAddresses" }
1165
+ },
1166
+ "google-photos-library": { operation: "photoslibrary.albums.list" },
1167
+ "google-chat": { operation: "chat.spaces.list" },
1168
+ "google-keep": { operation: "keep.notes.list" },
1169
+ "google-youtube-data": {
1170
+ operation: "youtube.channels.list",
1171
+ args: { part: "id", mine: true }
1172
+ },
1173
+ "google-search-console": { operation: "webmasters.sites.list" },
1174
+ "google-classroom": { operation: "classroom.courses.list" },
1175
+ "google-admin-directory": {
1176
+ operation: "directory.users.list",
1177
+ args: { customer: "my_customer", maxResults: 1 }
1178
+ },
1179
+ "google-apps-script": { operation: "script.processes.list" },
1180
+ "google-bigquery": { operation: "bigquery.projects.list" },
1181
+ "google-cloud-resource-manager": { operation: "cloudresourcemanager.projects.list" }
1182
+ };
1183
+ var googleCatalogAuthTemplate = (presetId) => [
1184
+ {
1185
+ slug: GOOGLE_OAUTH_SECURITY_SCHEME2,
1186
+ kind: "oauth2",
1187
+ authorizationUrl: GOOGLE_OAUTH_AUTHORIZATION_URL2,
1188
+ tokenUrl: GOOGLE_OAUTH_TOKEN_URL2,
1189
+ scopes: compactGoogleOAuthScopes([
1190
+ ...GOOGLE_IDENTITY_SCOPES,
1191
+ ...googleOAuthConsentScopesForPreset(presetId)
1192
+ ])
1193
+ }
1194
+ ];
1195
+ var googleCatalog = googleOpenApiPresets.map((preset) => ({
1196
+ id: preset.id,
1197
+ name: preset.name,
1198
+ summary: preset.summary,
1199
+ ...preset.url ? { url: preset.url } : {},
1200
+ ...preset.icon ? { icon: preset.icon } : {},
1201
+ ...preset.featured ? { featured: preset.featured } : {},
1202
+ family: "google",
1203
+ specFormat: "google-discovery",
1204
+ defaultSlug: googleServiceSlug(preset.id),
1205
+ authTemplate: googleCatalogAuthTemplate(preset.id),
1206
+ ...GOOGLE_HEALTH_CHECKS[preset.id] ? { healthCheck: GOOGLE_HEALTH_CHECKS[preset.id] } : {}
1207
+ }));
1208
+ var normalizeGooglePresetUrl = (url) => {
1209
+ const discoveryUrl = normalizeGoogleDiscoveryUrl(url);
1210
+ if (discoveryUrl) return discoveryUrl;
1211
+ const trimmed = url.trim();
1212
+ if (!URL.canParse(trimmed)) return trimmed.replace(/\/$/, "");
1213
+ const parsed = new URL(trimmed);
1214
+ parsed.hash = "";
1215
+ parsed.searchParams.sort();
1216
+ return parsed.toString().replace(/\/$/, "");
1217
+ };
1218
+ var googlePresetsByNormalizedUrl = new Map(
1219
+ googleOpenApiPresets.flatMap(
1220
+ (preset) => preset.url ? [[normalizeGooglePresetUrl(preset.url), preset]] : []
1221
+ )
1222
+ );
1223
+ var googlePresetForDiscoveryUrl = (url) => googlePresetsByNormalizedUrl.get(normalizeGooglePresetUrl(url));
1224
+ var googleAudienceWarningsForUrls = (urls) => {
1225
+ const seen = /* @__PURE__ */ new Set();
1226
+ for (const url of urls) {
1227
+ const audience = googlePresetForDiscoveryUrl(url)?.oauthAudience;
1228
+ if (audience === "workspace-admin" || audience === "unsupported-user") seen.add(audience);
1229
+ }
1230
+ return [...seen];
1231
+ };
1232
+
1233
+ // src/providers/google/spec-format-adapter.ts
1234
+ import { Effect as Effect2, Schema as Schema2 } from "effect";
1235
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1236
+ var parseJson2 = (text) => Schema2.decodeUnknownEffect(Schema2.fromJsonString(Schema2.Unknown))(text).pipe(
1237
+ Effect2.mapError(
1238
+ () => new OpenApiParseError({
1239
+ message: "Failed to parse Google Discovery document"
1240
+ })
1241
+ )
1242
+ );
1243
+ var googleDiscoverySlug = (service) => `google_${service.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
1244
+ var deriveGoogleDiscoveryIdentity = (doc) => {
1245
+ if (!isRecord2(doc)) return null;
1246
+ const rawName = typeof doc.name === "string" ? doc.name.trim() : "";
1247
+ if (!rawName) return null;
1248
+ const title = typeof doc.title === "string" && doc.title.trim() ? doc.title.trim() : rawName;
1249
+ const description = typeof doc.description === "string" && doc.description.trim() ? doc.description.trim() : void 0;
1250
+ return {
1251
+ slug: googleDiscoverySlug(rawName),
1252
+ name: title,
1253
+ ...description ? { description } : {}
1254
+ };
1255
+ };
1256
+ var googleDiscoveryAdapter = {
1257
+ id: "google-discovery",
1258
+ fetch: (input) => Effect2.gen(function* () {
1259
+ const documents = yield* Effect2.forEach(
1260
+ input.urls,
1261
+ (url) => fetchGoogleDiscoveryDocument(url, input.credentials).pipe(
1262
+ Effect2.provide(input.httpClientLayer),
1263
+ Effect2.map((documentText) => ({
1264
+ discoveryUrl: normalizeGoogleDiscoveryUrl(url) ?? url,
1265
+ documentText
1266
+ }))
1267
+ ),
1268
+ { concurrency: 4 }
1269
+ );
1270
+ const conversion = yield* convertGoogleDiscoveryBundleToOpenApi({ documents });
1271
+ const document = documents.length === 1 ? yield* parseJson2(documents[0].documentText) : yield* Effect2.forEach(
1272
+ documents,
1273
+ (item) => parseJson2(item.documentText).pipe(
1274
+ Effect2.map((parsed) => ({
1275
+ discoveryUrl: item.discoveryUrl,
1276
+ document: parsed
1277
+ }))
1278
+ )
1279
+ );
1280
+ return {
1281
+ specText: conversion.specText,
1282
+ specUrl: documents[0]?.discoveryUrl,
1283
+ baseUrl: conversion.baseUrl,
1284
+ authenticationTemplate: conversion.authenticationTemplate,
1285
+ document
1286
+ };
1287
+ }),
1288
+ deriveIdentity: deriveGoogleDiscoveryIdentity
1289
+ };
1290
+
1291
+ // src/providers/google/openapi-ownership-migration.ts
1292
+ import { Effect as Effect3 } from "effect";
1293
+ import { DataMigrationError } from "@rafads/sdk/core";
1294
+ var MIGRATION_NAME = "2026-06-20-google-openapi-ownership";
1295
+ var googleOpenApiCandidate = (alias) => {
1296
+ const column = (name) => alias ? `${alias}.${name}` : name;
1297
+ return `${column("plugin_id")} = 'openapi' AND ${column("config")} IS NOT NULL AND json_valid(${column("config")}) AND json_type(${column("config")}, '$.googleDiscoveryUrls') = 'array'`;
1298
+ };
1299
+ var execute = (client, stmt) => Effect3.tryPromise({
1300
+ try: () => client.execute(stmt),
1301
+ catch: (cause) => new DataMigrationError({ migration: MIGRATION_NAME, cause })
1302
+ });
1303
+ var runSqliteGoogleOpenApiOwnershipMigration = (client) => Effect3.gen(function* () {
1304
+ const exists = yield* execute(
1305
+ client,
1306
+ "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'integration'"
1307
+ );
1308
+ if (exists.rows.length === 0) return 0;
1309
+ const count = yield* execute(
1310
+ client,
1311
+ `SELECT COUNT(*) AS count FROM integration WHERE ${googleOpenApiCandidate()}`
1312
+ );
1313
+ const moved = Number(count.rows[0]?.count ?? 0);
1314
+ if (moved === 0) return 0;
1315
+ const applyAll = Effect3.gen(function* () {
1316
+ yield* execute(
1317
+ client,
1318
+ `INSERT OR IGNORE INTO blob (namespace, key, value, row_id, id)
1319
+ SELECT
1320
+ 'o:' || g.tenant || '/google',
1321
+ b.key,
1322
+ b.value,
1323
+ lower(hex(randomblob(16))),
1324
+ json_array('o:' || g.tenant || '/google', b.key)
1325
+ FROM integration g
1326
+ JOIN blob b
1327
+ ON b.namespace = 'o:' || g.tenant || '/openapi'
1328
+ AND b.key = 'spec/' || json_extract(g.config, '$.specHash')
1329
+ WHERE ${googleOpenApiCandidate("g")}
1330
+ AND json_extract(g.config, '$.specHash') IS NOT NULL
1331
+ AND json_extract(g.config, '$.specHash') <> ''`
1332
+ );
1333
+ yield* execute(
1334
+ client,
1335
+ `INSERT OR IGNORE INTO plugin_storage
1336
+ (tenant, owner, subject, plugin_id, collection, key, data, created_at, updated_at, row_id)
1337
+ SELECT
1338
+ ps.tenant,
1339
+ ps.owner,
1340
+ ps.subject,
1341
+ 'google',
1342
+ ps.collection,
1343
+ ps.key,
1344
+ ps.data,
1345
+ ps.created_at,
1346
+ ps.updated_at,
1347
+ lower(hex(randomblob(16)))
1348
+ FROM plugin_storage ps
1349
+ JOIN integration g
1350
+ ON g.tenant = ps.tenant
1351
+ WHERE ps.plugin_id = 'openapi'
1352
+ AND ${googleOpenApiCandidate("g")}
1353
+ AND ps.collection = 'operation'
1354
+ AND (
1355
+ json_extract(ps.data, '$.integration') = g.slug
1356
+ OR ps.key LIKE g.slug || '.%'
1357
+ )`
1358
+ );
1359
+ yield* execute(
1360
+ client,
1361
+ `DELETE FROM plugin_storage
1362
+ WHERE plugin_id = 'openapi'
1363
+ AND collection = 'operation'
1364
+ AND EXISTS (
1365
+ SELECT 1
1366
+ FROM integration g
1367
+ WHERE g.tenant = plugin_storage.tenant
1368
+ AND ${googleOpenApiCandidate("g")}
1369
+ AND (
1370
+ json_extract(plugin_storage.data, '$.integration') = g.slug
1371
+ OR plugin_storage.key LIKE g.slug || '.%'
1372
+ )
1373
+ )`
1374
+ );
1375
+ yield* execute(
1376
+ client,
1377
+ `UPDATE tool
1378
+ SET plugin_id = 'google'
1379
+ WHERE plugin_id = 'openapi'
1380
+ AND EXISTS (
1381
+ SELECT 1
1382
+ FROM integration g
1383
+ WHERE g.tenant = tool.tenant
1384
+ AND ${googleOpenApiCandidate("g")}
1385
+ AND g.slug = tool.integration
1386
+ )`
1387
+ );
1388
+ yield* execute(
1389
+ client,
1390
+ `UPDATE definition
1391
+ SET plugin_id = 'google'
1392
+ WHERE plugin_id = 'openapi'
1393
+ AND EXISTS (
1394
+ SELECT 1
1395
+ FROM integration g
1396
+ WHERE g.tenant = definition.tenant
1397
+ AND ${googleOpenApiCandidate("g")}
1398
+ AND g.slug = definition.integration
1399
+ )`
1400
+ );
1401
+ yield* execute(
1402
+ client,
1403
+ `UPDATE integration
1404
+ SET plugin_id = 'google'
1405
+ WHERE ${googleOpenApiCandidate()}`
1406
+ );
1407
+ yield* execute(client, "COMMIT");
1408
+ return moved;
1409
+ });
1410
+ yield* execute(client, "BEGIN");
1411
+ return yield* applyAll.pipe(
1412
+ Effect3.tapError(() => execute(client, "ROLLBACK").pipe(Effect3.ignore))
1413
+ );
1414
+ });
1415
+ var googleOpenApiOwnershipDataMigration = {
1416
+ name: MIGRATION_NAME,
1417
+ run: (client) => runSqliteGoogleOpenApiOwnershipMigration(client).pipe(Effect3.asVoid)
1418
+ };
1419
+ export {
1420
+ GOOGLE_PHOTOS_ICON,
1421
+ GOOGLE_PHOTOS_PRESET_ID,
1422
+ compactGoogleOAuthScopes,
1423
+ convertGoogleDiscoveryBundleToOpenApi,
1424
+ convertGoogleDiscoveryToOpenApi,
1425
+ deriveGoogleDiscoveryIdentity,
1426
+ fetchGoogleDiscoveryDocument,
1427
+ filterGoogleUserConsentOAuthScopes,
1428
+ googleAudienceWarningsForUrls,
1429
+ googleCatalog,
1430
+ googleDiscoveryAdapter,
1431
+ googleOAuthConsentScopes,
1432
+ googleOAuthConsentScopesForPreset,
1433
+ googleOpenApiBundlePreset,
1434
+ googleOpenApiOwnershipDataMigration,
1435
+ googleOpenApiPresets,
1436
+ googlePhotosOpenApiBundlePreset,
1437
+ googlePhotosOpenApiPresets,
1438
+ googlePhotosPresetIds,
1439
+ googlePresetForDiscoveryUrl,
1440
+ googleServiceSlug,
1441
+ googleStandardUserOAuthPresets,
1442
+ isGoogleDiscoveryUrl,
1443
+ isGoogleUserConsentOAuthScope,
1444
+ runSqliteGoogleOpenApiOwnershipMigration
1445
+ };
1446
+ //# sourceMappingURL=google.js.map