From 72e83f39619f6bdc57a856b0e0bebc42f9e1f41d Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Tue, 5 May 2026 22:39:14 -0700 Subject: [PATCH] Use typed Google Discovery invocation boundaries --- .../google-discovery/src/sdk/invoke.ts | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/packages/plugins/google-discovery/src/sdk/invoke.ts b/packages/plugins/google-discovery/src/sdk/invoke.ts index cac95144d..a3d1aa16c 100644 --- a/packages/plugins/google-discovery/src/sdk/invoke.ts +++ b/packages/plugins/google-discovery/src/sdk/invoke.ts @@ -1,4 +1,4 @@ -import { Effect, Layer, Option } from "effect"; +import { Effect, Layer, Option, Schema } from "effect"; import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"; import type { PluginCtx, StorageFailure } from "@executor-js/sdk/core"; @@ -13,6 +13,16 @@ import { const SAFE_METHODS = new Set(["get", "head", "options"]); +const UnknownErrorMessage = Schema.Struct({ message: Schema.String }); +const decodeUnknownErrorMessage = Schema.decodeUnknownOption(UnknownErrorMessage); + +const errorMessageFromUnknown = (cause: unknown): string => { + const decoded = decodeUnknownErrorMessage(cause); + if (Option.isSome(decoded)) return decoded.value.message; + // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: preserves existing fallback text for HTTP client errors + return String(cause); +}; + export const annotationsForOperation = ( method: string, pathTemplate: string, @@ -46,19 +56,27 @@ const replacePathParameters = (input: { pathTemplate: string; args: Record; parameters: readonly GoogleDiscoveryParameter[]; -}): string => - input.pathTemplate.replaceAll(/\{([^}]+)\}/g, (_, name: string) => { - const parameter = input.parameters.find( - (entry) => entry.location === "path" && entry.name === name, - ); - const values = stringValuesFromParameter(input.args[name], false); - if (values.length === 0) { - if (parameter?.required) { - throw new Error(`Missing required path parameter: ${name}`); +}): Effect.Effect => + Effect.gen(function* () { + let failure: GoogleDiscoveryInvocationError | undefined; + const resolved = input.pathTemplate.replaceAll(/\{([^}]+)\}/g, (_, name: string) => { + const parameter = input.parameters.find( + (entry) => entry.location === "path" && entry.name === name, + ); + const values = stringValuesFromParameter(input.args[name], false); + if (values.length === 0) { + if (parameter?.required) { + failure = new GoogleDiscoveryInvocationError({ + message: `Missing required path parameter: ${name}`, + statusCode: Option.none(), + }); + } + return ""; } - return ""; - } - return encodeURIComponent(values[0]!); + return encodeURIComponent(values[0]!); + }); + if (failure) return yield* failure; + return resolved; }); const resolveBaseUrl = (source: GoogleDiscoveryStoredSourceData): string => @@ -87,7 +105,7 @@ const performRequest = Effect.fn("GoogleDiscovery.invoke")(function* (input: { }) { const client = yield* HttpClient.HttpClient; - const resolvedPath = replacePathParameters({ + const resolvedPath = yield* replacePathParameters({ pathTemplate: input.pathTemplate, args: input.args, parameters: input.parameters, @@ -138,7 +156,7 @@ const performRequest = Effect.fn("GoogleDiscovery.invoke")(function* (input: { Effect.mapError( (err) => new GoogleDiscoveryInvocationError({ - message: `HTTP request failed: ${err.message}`, + message: `HTTP request failed: ${errorMessageFromUnknown(err)}`, statusCode: Option.none(), cause: err, }), @@ -147,9 +165,9 @@ const performRequest = Effect.fn("GoogleDiscovery.invoke")(function* (input: { const contentType = response.headers["content-type"] ?? null; const mapBodyError = Effect.mapError( - (err: { readonly message?: string }) => + (err: unknown) => new GoogleDiscoveryInvocationError({ - message: `Failed to read response body: ${err.message ?? String(err)}`, + message: `Failed to read response body: ${errorMessageFromUnknown(err)}`, statusCode: Option.some(response.status), cause: err, }), @@ -191,21 +209,17 @@ export const invokeGoogleDiscoveryTool = (input: { Effect.gen(function* () { const entry = yield* input.ctx.storage.getBinding(input.toolId, input.toolScope); if (!entry) { - return yield* Effect.fail( - new GoogleDiscoveryInvocationError({ - message: `No Google Discovery operation found for tool "${input.toolId}"`, - statusCode: Option.none(), - }), - ); + return yield* new GoogleDiscoveryInvocationError({ + message: `No Google Discovery operation found for tool "${input.toolId}"`, + statusCode: Option.none(), + }); } const stored = yield* input.ctx.storage.getSource(entry.namespace, input.toolScope); if (!stored) { - return yield* Effect.fail( - new GoogleDiscoveryInvocationError({ - message: `No Google Discovery source found for "${entry.namespace}"`, - statusCode: Option.none(), - }), - ); + return yield* new GoogleDiscoveryInvocationError({ + message: `No Google Discovery source found for "${entry.namespace}"`, + statusCode: Option.none(), + }); } const source = stored.config; @@ -215,7 +229,7 @@ export const invokeGoogleDiscoveryTool = (input: { Effect.mapError( (err) => new GoogleDiscoveryOAuthError({ - message: "message" in err ? (err as { message: string }).message : String(err), + message: errorMessageFromUnknown(err), }), ), )}`