(
integrationRow: IntegrationRow,
ref: ConnectionRef,
)
| 2003 | // ------------------------------------------------------------------ |
| 2004 | |
| 2005 | const produceConnectionTools = ( |
| 2006 | integrationRow: IntegrationRow, |
| 2007 | ref: ConnectionRef, |
| 2008 | ): Effect.Effect<readonly Tool[], IntegrationNotFoundError | StorageFailure> => |
| 2009 | Effect.gen(function* () { |
| 2010 | const runtime = runtimes.get(integrationRow.plugin_id); |
| 2011 | const keys = yield* Effect.try({ |
| 2012 | try: () => ownedKeys(ref.owner), |
| 2013 | catch: (cause) => storageFailureFromUnknown("invalid owner", cause), |
| 2014 | }); |
| 2015 | const owner = ref.owner; |
| 2016 | const where = (b: AnyCb) => |
| 2017 | b.and( |
| 2018 | byOwner(owner)(b), |
| 2019 | b("integration", "=", String(ref.integration)), |
| 2020 | b("connection", "=", String(ref.name)), |
| 2021 | ); |
| 2022 | // Every exit stamps the sync time — including the cleanup paths that |
| 2023 | // produce zero tools — so the stale-catalog check (`config_revised_at` |
| 2024 | // vs `tools_synced_at`) doesn't re-attempt this connection per read. |
| 2025 | const stampSynced = core.updateMany("connection", { |
| 2026 | where: (b: AnyCb) => |
| 2027 | b.and( |
| 2028 | byOwner(owner)(b), |
| 2029 | b("integration", "=", String(ref.integration)), |
| 2030 | b("name", "=", String(ref.name)), |
| 2031 | ), |
| 2032 | set: { tools_synced_at: Date.now() }, |
| 2033 | }); |
| 2034 | |
| 2035 | // Defense in depth (and cleanup for rows created before the create-time |
| 2036 | // guard, or emptied by an external edit): a credentialed non-OAuth |
| 2037 | // connection with no bound credential inputs can never resolve a value, |
| 2038 | // so never advertise tools for it — every call would fail with |
| 2039 | // `connection_value_missing`. OAuth connections resolve via refresh and |
| 2040 | // carry their token outside `item_ids`; no-auth (`"none"` template) |
| 2041 | // connections legitimately bind nothing (an empty `item_ids` is their |
| 2042 | // canonical shape) — both are exempt. |
| 2043 | const existingRow = yield* findConnectionRow(ref); |
| 2044 | if ( |
| 2045 | existingRow && |
| 2046 | existingRow.oauth_client == null && |
| 2047 | existingRow.template !== String(NO_AUTH_TEMPLATE) && |
| 2048 | Object.keys(connectionItemIds(existingRow)).length === 0 |
| 2049 | ) { |
| 2050 | yield* transaction( |
| 2051 | Effect.gen(function* () { |
| 2052 | yield* core.deleteMany("tool", { where }); |
| 2053 | yield* core.deleteMany("definition", { where }); |
| 2054 | yield* stampSynced; |
| 2055 | }), |
| 2056 | ); |
| 2057 | return []; |
| 2058 | } |
| 2059 | |
| 2060 | if (!runtime?.plugin.resolveTools) { |
| 2061 | // No dynamic tools — clear any existing rows and return empty. |
| 2062 | yield* transaction( |
no test coverage detected