@oomol-lab/open-connector 1.7.0 → 1.8.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 (288) hide show
  1. package/README.md +4 -0
  2. package/assets/open-connector/catalog/apps/airtable.json +134 -0
  3. package/assets/open-connector/catalog/apps/aisa.json +916 -76
  4. package/assets/open-connector/catalog/apps/cal.json +24 -6
  5. package/assets/open-connector/catalog/apps/discordbot.json +2 -2
  6. package/assets/open-connector/catalog/apps/enormail.json +411 -0
  7. package/assets/open-connector/catalog/apps/feishu_app_bot.json +158 -0
  8. package/assets/open-connector/catalog/apps/github.json +275 -0
  9. package/assets/open-connector/catalog/apps/gitlab.json +125 -0
  10. package/assets/open-connector/catalog/apps/gmail.json +151 -0
  11. package/assets/open-connector/catalog/apps/googlecalendar.json +206 -0
  12. package/assets/open-connector/catalog/apps/googledrive.json +479 -0
  13. package/assets/open-connector/catalog/apps/googlesheets.json +144 -0
  14. package/assets/open-connector/catalog/apps/hithink_finance.json +6 -3
  15. package/assets/open-connector/catalog/apps/indexed.json +1115 -0
  16. package/assets/open-connector/catalog/apps/linear.json +116 -0
  17. package/assets/open-connector/catalog/apps/notion.json +142 -0
  18. package/assets/open-connector/catalog/apps/one_drive.json +185 -0
  19. package/assets/open-connector/catalog/apps/outlook.json +182 -0
  20. package/assets/open-connector/catalog/apps/shopify.json +282 -0
  21. package/assets/open-connector/catalog/apps/slack.json +182 -0
  22. package/assets/open-connector/catalog/apps/spotify.json +46 -43
  23. package/assets/open-connector/catalog/apps/stripe.json +96 -0
  24. package/assets/open-connector/catalog/apps/telegram.json +138 -0
  25. package/assets/open-connector/catalog/apps/weixin_store.json +543 -2
  26. package/assets/open-connector/catalog/apps/woocommerce.json +127 -0
  27. package/assets/open-connector/catalog/apps/zendesk.json +152 -0
  28. package/assets/open-connector/catalog/apps/zerotier.json +9 -9
  29. package/assets/open-connector/catalog/apps-index.json +1 -1
  30. package/assets/open-connector/migrations/0014_saas_project.sql +55 -0
  31. package/assets/open-connector/migrations/0015_saas_cleanup_runtime.sql +8 -0
  32. package/assets/open-connector/migrations/0016_trigger_policy.sql +1 -0
  33. package/assets/open-connector/migrations/0017_trigger_subscriptions.sql +17 -0
  34. package/assets/open-connector/migrations/postgresql/0014_saas_project.sql +55 -0
  35. package/assets/open-connector/migrations/postgresql/0015_saas_cleanup_runtime.sql +8 -0
  36. package/assets/open-connector/migrations/postgresql/0016_trigger_policy.sql +1 -0
  37. package/assets/open-connector/migrations/postgresql/0017_trigger_subscriptions.sql +17 -0
  38. package/package.json +2 -2
  39. package/src/connection-service.d.ts +40 -7
  40. package/src/connection-service.js +60 -18
  41. package/src/core/action-policy.d.ts +8 -1
  42. package/src/core/action-policy.js +51 -0
  43. package/src/core/cast.d.ts +3 -0
  44. package/src/core/cast.js +4 -1
  45. package/src/core/guarded-fetch.d.ts +5 -0
  46. package/src/core/guarded-fetch.js +13 -4
  47. package/src/core/remote-http.d.ts +33 -0
  48. package/src/core/remote-http.js +104 -0
  49. package/src/core/request.d.ts +1 -0
  50. package/src/core/request.js +7 -0
  51. package/src/core/types.d.ts +4 -0
  52. package/src/marketplace/marketplace-service.d.ts +0 -1
  53. package/src/marketplace/marketplace-service.js +80 -92
  54. package/src/mcp.js +19 -5
  55. package/src/oauth/oauth-flow-service.d.ts +8 -1
  56. package/src/oauth/oauth-flow-service.js +10 -4
  57. package/src/providers/action-contracts.generated.d.ts +4 -2
  58. package/src/providers/airtable/executors.d.ts +3 -0
  59. package/src/providers/airtable/executors.js +2 -0
  60. package/src/providers/airtable/trigger-on-record-changed.d.ts +2 -0
  61. package/src/providers/airtable/trigger-on-record-changed.definition.d.ts +4 -0
  62. package/src/providers/airtable/trigger-on-record-changed.definition.js +54 -0
  63. package/src/providers/airtable/trigger-on-record-changed.js +235 -0
  64. package/src/providers/aisa/executors.js +196 -22
  65. package/src/providers/cal/actions.js +5 -1
  66. package/src/providers/enormail/executors.d.ts +4 -0
  67. package/src/providers/enormail/executors.js +97 -0
  68. package/src/providers/feishu_app_bot/executors.d.ts +3 -0
  69. package/src/providers/feishu_app_bot/executors.js +2 -0
  70. package/src/providers/feishu_app_bot/trigger-config.d.ts +1 -0
  71. package/src/providers/feishu_app_bot/trigger-config.js +10 -0
  72. package/src/providers/feishu_app_bot/trigger-on-event.d.ts +2 -0
  73. package/src/providers/feishu_app_bot/trigger-on-event.definition.d.ts +4 -0
  74. package/src/providers/feishu_app_bot/trigger-on-event.definition.js +63 -0
  75. package/src/providers/feishu_app_bot/trigger-on-event.js +29 -0
  76. package/src/providers/feishu_app_bot/trigger-subscriptions.d.ts +5 -0
  77. package/src/providers/feishu_app_bot/trigger-subscriptions.js +70 -0
  78. package/src/providers/github/executors.d.ts +3 -0
  79. package/src/providers/github/executors.js +6 -0
  80. package/src/providers/github/trigger-on-repo-event.d.ts +2 -0
  81. package/src/providers/github/trigger-on-repo-event.definition.d.ts +4 -0
  82. package/src/providers/github/trigger-on-repo-event.definition.js +94 -0
  83. package/src/providers/github/trigger-on-repo-event.js +168 -0
  84. package/src/providers/github/trigger-watch-pull-request.d.ts +2 -0
  85. package/src/providers/github/trigger-watch-pull-request.definition.d.ts +4 -0
  86. package/src/providers/github/trigger-watch-pull-request.definition.js +49 -0
  87. package/src/providers/github/trigger-watch-pull-request.js +106 -0
  88. package/src/providers/gitlab/executors.d.ts +3 -0
  89. package/src/providers/gitlab/executors.js +2 -0
  90. package/src/providers/gitlab/trigger-on-project-event.d.ts +2 -0
  91. package/src/providers/gitlab/trigger-on-project-event.definition.d.ts +5 -0
  92. package/src/providers/gitlab/trigger-on-project-event.definition.js +63 -0
  93. package/src/providers/gitlab/trigger-on-project-event.js +186 -0
  94. package/src/providers/gmail/executors.d.ts +3 -0
  95. package/src/providers/gmail/executors.js +2 -0
  96. package/src/providers/gmail/trigger-on-message-received.d.ts +2 -0
  97. package/src/providers/gmail/trigger-on-message-received.definition.d.ts +4 -0
  98. package/src/providers/gmail/trigger-on-message-received.definition.js +74 -0
  99. package/src/providers/gmail/trigger-on-message-received.js +232 -0
  100. package/src/providers/googlecalendar/executors.d.ts +3 -0
  101. package/src/providers/googlecalendar/executors.js +2 -0
  102. package/src/providers/googlecalendar/trigger-on-event-changed.d.ts +2 -0
  103. package/src/providers/googlecalendar/trigger-on-event-changed.definition.d.ts +4 -0
  104. package/src/providers/googlecalendar/trigger-on-event-changed.definition.js +74 -0
  105. package/src/providers/googlecalendar/trigger-on-event-changed.js +233 -0
  106. package/src/providers/googledrive/executors.d.ts +3 -0
  107. package/src/providers/googledrive/executors.js +7 -0
  108. package/src/providers/googledrive/trigger-changes.d.ts +12 -0
  109. package/src/providers/googledrive/trigger-changes.definition.d.ts +7 -0
  110. package/src/providers/googledrive/trigger-changes.definition.js +101 -0
  111. package/src/providers/googledrive/trigger-changes.js +452 -0
  112. package/src/providers/googledrive/trigger-on-file-change.d.ts +2 -0
  113. package/src/providers/googledrive/trigger-on-file-change.definition.d.ts +4 -0
  114. package/src/providers/googledrive/trigger-on-file-change.definition.js +77 -0
  115. package/src/providers/googledrive/trigger-on-file-change.js +258 -0
  116. package/src/providers/googlesheets/executors.d.ts +3 -0
  117. package/src/providers/googlesheets/executors.js +2 -0
  118. package/src/providers/googlesheets/trigger-on-row-added.d.ts +2 -0
  119. package/src/providers/googlesheets/trigger-on-row-added.definition.d.ts +4 -0
  120. package/src/providers/googlesheets/trigger-on-row-added.definition.js +54 -0
  121. package/src/providers/googlesheets/trigger-on-row-added.js +234 -0
  122. package/src/providers/hithink_finance/runtime.js +1 -1
  123. package/src/providers/indexed/executors.d.ts +4 -0
  124. package/src/providers/indexed/executors.js +16 -0
  125. package/src/providers/indexed/runtime.d.ts +26 -0
  126. package/src/providers/indexed/runtime.js +129 -0
  127. package/src/providers/linear/executors.d.ts +3 -0
  128. package/src/providers/linear/executors.js +2 -0
  129. package/src/providers/linear/trigger-on-issue-changed.d.ts +2 -0
  130. package/src/providers/linear/trigger-on-issue-changed.definition.d.ts +5 -0
  131. package/src/providers/linear/trigger-on-issue-changed.definition.js +43 -0
  132. package/src/providers/linear/trigger-on-issue-changed.js +215 -0
  133. package/src/providers/notion/executors.d.ts +3 -0
  134. package/src/providers/notion/executors.js +2 -0
  135. package/src/providers/notion/trigger-on-database-page-event.d.ts +2 -0
  136. package/src/providers/notion/trigger-on-database-page-event.definition.d.ts +4 -0
  137. package/src/providers/notion/trigger-on-database-page-event.definition.js +63 -0
  138. package/src/providers/notion/trigger-on-database-page-event.js +293 -0
  139. package/src/providers/one_drive/executors.d.ts +3 -0
  140. package/src/providers/one_drive/executors.js +2 -0
  141. package/src/providers/one_drive/trigger-on-item-changed.d.ts +2 -0
  142. package/src/providers/one_drive/trigger-on-item-changed.definition.d.ts +4 -0
  143. package/src/providers/one_drive/trigger-on-item-changed.definition.js +63 -0
  144. package/src/providers/one_drive/trigger-on-item-changed.js +214 -0
  145. package/src/providers/outlook/executors.d.ts +3 -0
  146. package/src/providers/outlook/executors.js +2 -0
  147. package/src/providers/outlook/trigger-on-message-received.d.ts +2 -0
  148. package/src/providers/outlook/trigger-on-message-received.definition.d.ts +4 -0
  149. package/src/providers/outlook/trigger-on-message-received.definition.js +93 -0
  150. package/src/providers/outlook/trigger-on-message-received.js +226 -0
  151. package/src/providers/outlook_calendar/executors.js +6 -0
  152. package/src/providers/provider-loader.d.ts +5 -0
  153. package/src/providers/provider-loader.js +4 -0
  154. package/src/providers/registry.generated.js +2 -0
  155. package/src/providers/shopify/executors.d.ts +3 -0
  156. package/src/providers/shopify/executors.js +2 -0
  157. package/src/providers/shopify/trigger-on-shop-event.d.ts +2 -0
  158. package/src/providers/shopify/trigger-on-shop-event.definition.d.ts +4 -0
  159. package/src/providers/shopify/trigger-on-shop-event.definition.js +214 -0
  160. package/src/providers/shopify/trigger-on-shop-event.js +165 -0
  161. package/src/providers/slack/executors.d.ts +3 -0
  162. package/src/providers/slack/executors.js +2 -0
  163. package/src/providers/slack/trigger-on-message-posted.d.ts +2 -0
  164. package/src/providers/slack/trigger-on-message-posted.definition.d.ts +4 -0
  165. package/src/providers/slack/trigger-on-message-posted.definition.js +80 -0
  166. package/src/providers/slack/trigger-on-message-posted.js +176 -0
  167. package/src/providers/spotify/runtime.js +15 -4
  168. package/src/providers/stripe/executors.d.ts +3 -0
  169. package/src/providers/stripe/executors.js +2 -0
  170. package/src/providers/stripe/trigger-on-event.d.ts +2 -0
  171. package/src/providers/stripe/trigger-on-event.definition.d.ts +4 -0
  172. package/src/providers/stripe/trigger-on-event.definition.js +43 -0
  173. package/src/providers/stripe/trigger-on-event.js +212 -0
  174. package/src/providers/telegram/executors.d.ts +3 -0
  175. package/src/providers/telegram/executors.js +2 -0
  176. package/src/providers/telegram/trigger-on-update.d.ts +2 -0
  177. package/src/providers/telegram/trigger-on-update.definition.d.ts +5 -0
  178. package/src/providers/telegram/trigger-on-update.definition.js +78 -0
  179. package/src/providers/telegram/trigger-on-update.js +141 -0
  180. package/src/providers/weixin_store/access-token.d.ts +2 -1
  181. package/src/providers/weixin_store/access-token.js +15 -4
  182. package/src/providers/weixin_store/executors.js +97 -12
  183. package/src/providers/woocommerce/executors.d.ts +3 -0
  184. package/src/providers/woocommerce/executors.js +2 -0
  185. package/src/providers/woocommerce/trigger-on-store-event.d.ts +2 -0
  186. package/src/providers/woocommerce/trigger-on-store-event.definition.d.ts +4 -0
  187. package/src/providers/woocommerce/trigger-on-store-event.definition.js +55 -0
  188. package/src/providers/woocommerce/trigger-on-store-event.js +179 -0
  189. package/src/providers/zendesk/executors.d.ts +3 -0
  190. package/src/providers/zendesk/executors.js +2 -0
  191. package/src/providers/zendesk/trigger-on-event.d.ts +2 -0
  192. package/src/providers/zendesk/trigger-on-event.definition.d.ts +5 -0
  193. package/src/providers/zendesk/trigger-on-event.definition.js +101 -0
  194. package/src/providers/zendesk/trigger-on-event.js +188 -0
  195. package/src/saas/saas-cleanup-service.d.ts +26 -0
  196. package/src/saas/saas-cleanup-service.js +124 -0
  197. package/src/saas/saas-client.d.ts +86 -0
  198. package/src/saas/saas-client.js +369 -0
  199. package/src/saas/saas-execution-service.d.ts +15 -0
  200. package/src/saas/saas-execution-service.js +21 -0
  201. package/src/saas/saas-oauth-service.d.ts +25 -0
  202. package/src/saas/saas-oauth-service.js +197 -0
  203. package/src/saas/saas-project-service.d.ts +61 -0
  204. package/src/saas/saas-project-service.js +211 -0
  205. package/src/server/actions/action-idempotency.d.ts +1 -0
  206. package/src/server/actions/action-idempotency.js +1 -0
  207. package/src/server/actions/action-runner.d.ts +7 -0
  208. package/src/server/actions/action-runner.js +43 -7
  209. package/src/server/actions/run-log-summary.js +9 -0
  210. package/src/server/api/auth.d.ts +1 -0
  211. package/src/server/api/auth.js +13 -2
  212. package/src/server/api/cache-policy.js +4 -1
  213. package/src/server/api/connection-input.d.ts +7 -1
  214. package/src/server/api/connection-input.js +7 -1
  215. package/src/server/api/connection-routes.d.ts +3 -1
  216. package/src/server/api/connection-routes.js +21 -3
  217. package/src/server/api/http-utils.d.ts +4 -3
  218. package/src/server/api/oauth-completion-page.d.ts +124 -0
  219. package/src/server/api/oauth-completion-page.js +113 -18
  220. package/src/server/api/openapi.js +112 -14
  221. package/src/server/api/policy-input.js +6 -0
  222. package/src/server/api/runtime-api.d.ts +10 -2
  223. package/src/server/api/runtime-api.js +17 -4
  224. package/src/server/api/saas-completion-page.d.ts +2 -0
  225. package/src/server/api/saas-completion-page.js +103 -0
  226. package/src/server/connect-app.d.ts +5 -0
  227. package/src/server/connect-app.js +50 -0
  228. package/src/server/connect-server.d.ts +12 -0
  229. package/src/server/connect-server.js +147 -13
  230. package/src/server/connector-runtime.d.ts +3 -1
  231. package/src/server/connector-runtime.js +5 -2
  232. package/src/server/files/transit-files.js +16 -7
  233. package/src/server/proxy/proxy-runner.d.ts +5 -1
  234. package/src/server/proxy/proxy-runner.js +72 -26
  235. package/src/server/storage/connection-request-store.d.ts +41 -1
  236. package/src/server/storage/connection-request-store.js +245 -20
  237. package/src/server/storage/connection-store.d.ts +16 -0
  238. package/src/server/storage/connection-store.js +119 -0
  239. package/src/server/storage/node-runtime-database.js +3 -3
  240. package/src/server/storage/{postgres-migrations.d.ts → postgres/migrations.d.ts} +2 -2
  241. package/src/server/storage/{postgres-migrations.js → postgres/migrations.js} +1 -1
  242. package/src/server/storage/postgres/runtime-store.d.ts +43 -0
  243. package/src/server/storage/{postgres-runtime-store.js → postgres/runtime-store.js} +59 -94
  244. package/src/server/storage/runtime-database.d.ts +4 -0
  245. package/src/server/storage/runtime-sql.d.ts +1 -1
  246. package/src/server/storage/runtime-sql.js +2 -1
  247. package/src/server/storage/runtime-store.d.ts +1 -0
  248. package/src/server/storage/runtime-token-service.d.ts +2 -0
  249. package/src/server/storage/runtime-token-service.js +3 -0
  250. package/src/server/storage/saas-project-store.d.ts +77 -0
  251. package/src/server/storage/saas-project-store.js +273 -0
  252. package/src/server/storage/{sqlite-runtime-store.d.ts → sqlite/runtime-store.d.ts} +21 -25
  253. package/src/server/storage/{sqlite-runtime-store.js → sqlite/runtime-store.js} +100 -115
  254. package/src/server/storage/trigger-store.d.ts +16 -0
  255. package/src/server/storage/trigger-store.js +96 -0
  256. package/src/triggers/common/config.d.ts +2 -0
  257. package/src/triggers/common/config.js +17 -0
  258. package/src/triggers/common/configOptions.d.ts +13 -0
  259. package/src/triggers/common/configOptions.js +1 -0
  260. package/src/triggers/common/encoding.d.ts +3 -0
  261. package/src/triggers/common/encoding.js +19 -0
  262. package/src/triggers/common/integration.d.ts +115 -0
  263. package/src/triggers/common/integration.js +31 -0
  264. package/src/triggers/common/json.d.ts +6 -0
  265. package/src/triggers/common/json.js +91 -0
  266. package/src/triggers/common/poll.d.ts +41 -0
  267. package/src/triggers/common/poll.js +15 -0
  268. package/src/triggers/common/proxy.d.ts +14 -0
  269. package/src/triggers/common/proxy.js +1 -0
  270. package/src/triggers/common/types.d.ts +42 -0
  271. package/src/triggers/common/types.js +1 -0
  272. package/src/triggers/maintenance.d.ts +40 -0
  273. package/src/triggers/maintenance.js +120 -0
  274. package/src/triggers/metadata.d.ts +8 -0
  275. package/src/triggers/metadata.js +1 -0
  276. package/src/triggers/request.d.ts +33 -0
  277. package/src/triggers/request.js +52 -0
  278. package/src/triggers/resources.d.ts +12 -0
  279. package/src/triggers/resources.js +121 -0
  280. package/src/triggers/signature.d.ts +4 -0
  281. package/src/triggers/signature.js +48 -0
  282. package/src/triggers/store.d.ts +29 -0
  283. package/src/triggers/store.js +1 -0
  284. package/src/triggers/subscriptions.d.ts +22 -0
  285. package/src/triggers/subscriptions.js +220 -0
  286. package/src/triggers/trigger-runner.d.ts +34 -0
  287. package/src/triggers/trigger-runner.js +169 -0
  288. package/src/server/storage/postgres-runtime-store.d.ts +0 -39
@@ -8,6 +8,7 @@ import { PromiseCache } from "../core/promise-cache.js";
8
8
  import { MarketplaceError } from "../marketplace/marketplace-service.js";
9
9
  import { OAuthClientConfigError, OAuthClientConfigService } from "../oauth/oauth-client-config-service.js";
10
10
  import { OAuthCallbackError, OAuthFlowError, OAuthFlowService } from "../oauth/oauth-flow-service.js";
11
+ import { SaasError } from "../saas/saas-client.js";
11
12
  import { ActionInputDepthError, createIdempotencyExpiry, hashActionRequest, hashIdempotencyKey, readIdempotencyKey, } from "./actions/action-idempotency.js";
12
13
  import { ActionRunner } from "./actions/action-runner.js";
13
14
  import { renderActionMarkdown } from "./api/action-markdown.js";
@@ -17,7 +18,9 @@ import { createConnectionRoutes } from "./api/connection-routes.js";
17
18
  import { HttpRequestError, internalError, jsonError, notFound, readJsonBody } from "./api/http-utils.js";
18
19
  import { renderOAuthCompletionPage } from "./api/oauth-completion-page.js";
19
20
  import { policyRequestMaxBytes, readRuntimePolicyRules, readTokenPolicy } from "./api/policy-input.js";
21
+ import { serializeRuntimeTriggerPermissions } from "./api/runtime-api.js";
20
22
  import { mapConnectionErrorStatus, serializeRuntimeAction, serializeRuntimeActionResult, serializeRuntimeActionService, serializeRuntimeConnectedApp, serializeRuntimeFailure, serializeRuntimeProvider, serializeRuntimeProviderSetup, unknownActionFailure, unknownServiceFailure, writeRuntimeActionHttpResult, writeRuntimeFailure, writeRuntimeSuccess, } from "./api/runtime-api.js";
23
+ import { renderSaasCompletionPage } from "./api/saas-completion-page.js";
21
24
  import { TransitFileError } from "./files/transit-file-store.js";
22
25
  import { ProxyRunner } from "./proxy/proxy-runner.js";
23
26
  import { decodeRunLogCursor } from "./storage/runtime-store.js";
@@ -78,6 +81,7 @@ export class ConnectServer {
78
81
  providerLoader: options.providerLoader,
79
82
  connections: options.connections,
80
83
  logger: options.logger,
84
+ saas: options.saas,
81
85
  });
82
86
  }
83
87
  createApp() {
@@ -115,6 +119,21 @@ export class ConnectServer {
115
119
  }
116
120
  app.get("/v1/health", (context) => writeRuntimeSuccess(context, { ok: true, runtime: "oomol-connect" }));
117
121
  app.get("/v1/providers/:service/setup", (context) => this.getRuntimeProviderSetup(context, context.req.param("service")));
122
+ app.get("/v1/providers/:service/trigger-permissions", (context) => this.getRuntimeTriggerPermissions(context, context.req.param("service")));
123
+ app.post("/v1/providers/:service/triggers/:triggerId/execute", (context) => this.executeRuntimeTrigger(context));
124
+ app.get("/api/trigger-subscriptions", async (context) => context.json((await this.options.triggerMaintenance?.list()) ?? []));
125
+ app.post("/api/trigger-subscriptions/:id/cancel", async (context) => {
126
+ if (!this.options.triggerMaintenance)
127
+ throw new HttpRequestError("trigger_not_supported", "Trigger subscriptions are unavailable.", 501);
128
+ await this.options.triggerMaintenance.cancel(context.req.param("id"), context.req.raw.signal);
129
+ return context.json({ ok: true });
130
+ });
131
+ app.post("/api/trigger-subscriptions/:id/abandon", async (context) => {
132
+ if (!this.options.triggerMaintenance)
133
+ throw new HttpRequestError("trigger_not_supported", "Trigger subscriptions are unavailable.", 501);
134
+ await this.options.triggerMaintenance.abandon(context.req.param("id"), context.req.raw.signal);
135
+ return context.json({ ok: true });
136
+ });
118
137
  app.get("/v1/providers", (context) => this.listRuntimeProviders(context));
119
138
  app.get("/v1/actions", (context) => this.listRuntimeActions(context));
120
139
  app.get("/v1/actions/search", (context) => this.searchRuntimeActions(context));
@@ -166,9 +185,66 @@ export class ConnectServer {
166
185
  app.delete("/api/runtime-tokens/:id", (context) => this.revokeRuntimeToken(context, context.req.param("id")));
167
186
  app.get("/api/runtime-policy", (context) => this.getRuntimePolicy(context));
168
187
  app.put("/api/runtime-policy", (context) => this.updateRuntimePolicy(context));
188
+ const saas = this.options.saasProject;
189
+ if (saas) {
190
+ app.get("/api/oauth/managed-project", async (context) => context.json(await saas.getState(context.req.raw.signal)));
191
+ app.put("/api/oauth/managed-project", async (context) => context.json(await saas.configure(await readJsonBody(context, 16_384), context.req.raw.signal)));
192
+ app.delete("/api/oauth/managed-project", async (context) => {
193
+ await saas.remove();
194
+ return context.json(await saas.getState(context.req.raw.signal));
195
+ });
196
+ app.get("/api/oauth/managed-project/provider-configs", async (context) => context.json(await saas.listProviderConfigs(context.req.raw.signal)));
197
+ app.get("/api/oauth/sources/:service", async (context) => context.json(await saas.getSource(context.req.param("service"))));
198
+ app.put("/api/oauth/sources/:service", async (context) => context.json(await saas.setSource(context.req.param("service"), await readJsonBody(context, 16_384), context.req.raw.signal)));
199
+ }
200
+ if (saas && this.options.saasOAuth) {
201
+ app.get("/oauth/saas/complete", (context) => {
202
+ context.header("Content-Security-Policy", "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'");
203
+ context.header("Referrer-Policy", "no-referrer");
204
+ return context.html(renderSaasCompletionPage());
205
+ });
206
+ app.post("/api/oauth/connection-requests/:id/sync", async (context) => {
207
+ const expectedOrigin = saas.requireOrigin();
208
+ if (context.req.header("origin") !== expectedOrigin)
209
+ throw new SaasError("oauth_source_origin_mismatch", `Open Console at ${expectedOrigin}, or set OOMOL_CONNECT_ORIGIN to the exact Console address in your browser and restart Connect. localhost and 127.0.0.1 are different origins.`, 403);
210
+ if (context.req.header("x-openconnector-request") !== "sync" ||
211
+ context.req.header("content-type")?.split(";")[0].trim().toLowerCase() !== "application/json")
212
+ return context.json({ error: { code: "forbidden", message: "OAuth synchronization requires a same-origin JSON request." } }, 403);
213
+ const result = await this.options.saasOAuth.syncForBrowser(context.req.param("id"), "local-admin", context.req.raw.signal);
214
+ if (!result)
215
+ return jsonError(context, 404, "connection_request_not_found", "Connection request not found.");
216
+ context.header("Cache-Control", "private, no-store");
217
+ return context.json(result);
218
+ });
219
+ }
169
220
  app.get("/api/oauth/configs", (context) => this.listOAuthConfigs(context));
170
221
  app.put("/api/oauth/configs/:service", (context) => this.upsertOAuthConfig(context, context.req.param("service")));
171
222
  app.delete("/api/oauth/configs/:service", (context) => this.deleteOAuthConfig(context, context.req.param("service")));
223
+ app.post("/api/oauth/connection-requests", async (context) => {
224
+ try {
225
+ const { consoleOAuthConnectionInput } = await import("./api/connection-input.js");
226
+ const parsed = consoleOAuthConnectionInput.safeParse(await readJsonBody(context));
227
+ if (!parsed.success)
228
+ return jsonError(context, 400, "invalid_input", "Invalid OAuth connection request.");
229
+ const { appId, ...input } = parsed.data;
230
+ const target = appId ? await this.options.connections.getStoredConnection(appId) : undefined;
231
+ if (target && (target.service !== input.service || target.connectionName !== input.connectionName))
232
+ return jsonError(context, 400, "invalid_input", "The selected connection no longer matches.");
233
+ return context.json(await this.options.oauthFlow.startConnectionRequest({
234
+ ...input,
235
+ target,
236
+ owner: "local-admin",
237
+ signal: context.req.raw.signal,
238
+ }));
239
+ }
240
+ catch (error) {
241
+ if (error instanceof OAuthClientConfigError ||
242
+ error instanceof OAuthFlowError ||
243
+ error instanceof ConnectionError)
244
+ return jsonError(context, error.code === "unknown_service" || error.code === "app_not_found" ? 404 : 400, error.code, error.message);
245
+ throw error;
246
+ }
247
+ });
172
248
  app.post("/api/oauth/authorizations", (context) => this.createOAuthAuthorization(context));
173
249
  app.get("/oauth/callback", (context) => this.completeOAuth(context));
174
250
  app.post("/mcp", (context) => this.handleMcp(context));
@@ -181,6 +257,20 @@ export class ConnectServer {
181
257
  else
182
258
  app.notFound(notFound);
183
259
  app.onError((error, context) => {
260
+ if (error instanceof SaasError) {
261
+ this.options.logger?.warn({
262
+ method: context.req.method,
263
+ path: context.req.path,
264
+ code: error.code,
265
+ status: error.status,
266
+ reason: error.message,
267
+ }, "SaaS request failed");
268
+ if (error.retryAfter)
269
+ context.header("Retry-After", error.retryAfter);
270
+ if (context.req.path.startsWith("/v1/"))
271
+ return writeRuntimeFailure(context, { status: error.status, errorCode: error.code, message: error.message });
272
+ return context.json({ error: { code: error.code, message: error.message, reason: error.reason } }, error.status);
273
+ }
184
274
  if (error instanceof HttpRequestError) {
185
275
  if (context.req.path.startsWith("/v1/")) {
186
276
  return writeRuntimeFailure(context, {
@@ -227,7 +317,8 @@ export class ConnectServer {
227
317
  }
228
318
  catch (error) {
229
319
  if (error instanceof MarketplaceError) {
230
- return jsonError(context, error.status === 404 ? 404 : 400, error.code, error.message);
320
+ const status = error.status;
321
+ return jsonError(context, status === 401 || status === 403 || status === 404 || status === 502 || status === 504 ? status : 400, error.code, error.message);
231
322
  }
232
323
  throw error;
233
324
  }
@@ -254,9 +345,20 @@ export class ConnectServer {
254
345
  const provider = this.options.catalog.providers.find((provider) => provider.service === service);
255
346
  if (!provider)
256
347
  return writeRuntimeFailure(context, unknownServiceFailure(service));
257
- const oauth = provider.auth.some((auth) => auth.type === "oauth2")
348
+ let oauth = provider.auth.some((auth) => auth.type === "oauth2")
258
349
  ? await this.options.oauthClientConfigs.getSummary(service)
259
350
  : undefined;
351
+ const source = oauth ? await this.options.saasProject?.getSource(service) : undefined;
352
+ if (oauth && source?.mode === "saas") {
353
+ const { config } = await this.options.saasProject.resolveConfig(service, source.providerConfigId, source.managedProjectId, context.req.raw.signal);
354
+ oauth = {
355
+ ...oauth,
356
+ configured: true,
357
+ customClientAvailable: false,
358
+ expectedRedirectUri: config.callbackUrl,
359
+ missingFields: [],
360
+ };
361
+ }
260
362
  return writeRuntimeSuccess(context, serializeRuntimeProviderSetup(provider, oauth));
261
363
  }
262
364
  getProvider(context, service) {
@@ -353,11 +455,6 @@ export class ConnectServer {
353
455
  catch (error) {
354
456
  if (error instanceof ConnectionError) {
355
457
  const status = mapConnectionErrorStatus(error);
356
- // agent.md uses the admin JSON error envelope; mapConnectionErrorStatus may
357
- // return 409 for OAuth refresh failures, which jsonError does not accept.
358
- if (status === 409) {
359
- return context.json({ error: { code: error.code, message: error.message } }, 409);
360
- }
361
458
  return jsonError(context, status, error.code, error.message);
362
459
  }
363
460
  throw error;
@@ -428,6 +525,30 @@ export class ConnectServer {
428
525
  }
429
526
  return writeRuntimeSuccess(context, serializeRuntimeAction(action));
430
527
  }
528
+ async getRuntimeTriggerPermissions(context, service) {
529
+ const provider = this.options.catalog.providers.find((item) => item.service === service);
530
+ if (!provider)
531
+ throw new HttpRequestError("provider_not_found", "Provider not found.", 404);
532
+ return writeRuntimeSuccess(context, serializeRuntimeTriggerPermissions(provider));
533
+ }
534
+ async executeRuntimeTrigger(context) {
535
+ if (!this.options.triggers)
536
+ throw new HttpRequestError("trigger_not_supported", "Trigger execution is unavailable.", 501);
537
+ const { readTriggerRequest } = await import("../triggers/request.js");
538
+ const request = readTriggerRequest(await readJsonBody(context, 160 * 1024));
539
+ const signal = AbortSignal.any([context.req.raw.signal, AbortSignal.timeout(45_000)]);
540
+ const result = await this.options.triggers.run({
541
+ service: context.req.param("service"),
542
+ triggerId: context.req.param("triggerId"),
543
+ request,
544
+ connectionName: readConnectionName(context),
545
+ connectionId: optionalString(context.req.header("x-oo-connector-app-id")),
546
+ policy: await this.getPolicySnapshot(context),
547
+ grant: readRuntimeGrant(context),
548
+ signal,
549
+ });
550
+ return writeRuntimeSuccess(context, result);
551
+ }
431
552
  async createRuntimeActionRun(context, actionId) {
432
553
  const action = this.options.catalog.actionsById.get(actionId);
433
554
  if (!action) {
@@ -436,6 +557,7 @@ export class ConnectServer {
436
557
  const body = await readJsonBody(context);
437
558
  const input = body.input ?? {};
438
559
  const connectionName = readConnectionName(context, body);
560
+ const connectionId = optionalString(context.req.header("x-oo-connector-app-id"));
439
561
  const runtimeGrant = readRuntimeGrant(context);
440
562
  let policy;
441
563
  try {
@@ -450,7 +572,7 @@ export class ConnectServer {
450
572
  });
451
573
  }
452
574
  if (!policy.evaluate(action).allowed) {
453
- return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal));
575
+ return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId));
454
576
  }
455
577
  const idempotencyKey = readIdempotencyKey(context.req.header("idempotency-key"));
456
578
  if (!idempotencyKey.ok) {
@@ -462,7 +584,7 @@ export class ConnectServer {
462
584
  });
463
585
  }
464
586
  if (!idempotencyKey.key) {
465
- return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal));
587
+ return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId));
466
588
  }
467
589
  const now = new Date();
468
590
  const keyHash = hashIdempotencyKey(idempotencyKey.key);
@@ -471,6 +593,7 @@ export class ConnectServer {
471
593
  requestHash = hashActionRequest({
472
594
  actionId,
473
595
  connectionName: connectionName ?? defaultConnectionName,
596
+ connectionId,
474
597
  input,
475
598
  runtimeTokenId: runtimeGrant?.tokenId,
476
599
  });
@@ -513,7 +636,7 @@ export class ConnectServer {
513
636
  if (claim.kind === "completed") {
514
637
  return writeRuntimeActionHttpResult(context, claim.response);
515
638
  }
516
- const result = await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal);
639
+ const result = await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId);
517
640
  const completed = await this.options.idempotency.complete({
518
641
  keyHash,
519
642
  requestHash,
@@ -526,13 +649,14 @@ export class ConnectServer {
526
649
  }
527
650
  return writeRuntimeActionHttpResult(context, result);
528
651
  }
529
- async executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, signal) {
652
+ async executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, signal, connectionId) {
530
653
  try {
531
654
  const run = await this.options.actions.run({
532
655
  actionId,
533
656
  input,
534
657
  caller: "http",
535
658
  connectionName,
659
+ connectionId,
536
660
  policy,
537
661
  runtimeTokenId: runtimeGrant?.tokenId,
538
662
  signal,
@@ -543,6 +667,9 @@ export class ConnectServer {
543
667
  return serializeRuntimeActionResult({
544
668
  actionId,
545
669
  executionId: run.executionId,
670
+ remoteExecutionId: run.remoteExecutionId,
671
+ failureStatus: run.failureStatus,
672
+ retryAfter: run.retryAfter,
546
673
  auditPersisted: run.auditPersisted,
547
674
  result: run.result,
548
675
  });
@@ -591,11 +718,12 @@ export class ConnectServer {
591
718
  service,
592
719
  input: body,
593
720
  connectionName: readConnectionName(context, body),
721
+ connectionId: optionalString(context.req.header("x-oo-connector-app-id")),
594
722
  policy,
595
723
  signal: context.req.raw.signal,
596
724
  });
597
725
  if (result.ok) {
598
- return writeRuntimeSuccess(context, result.response);
726
+ return writeRuntimeSuccess(context, result.response, result.meta);
599
727
  }
600
728
  return writeRuntimeFailure(context, {
601
729
  status: result.status,
@@ -836,7 +964,13 @@ export class ConnectServer {
836
964
  });
837
965
  }
838
966
  async listOAuthConfigs(context) {
839
- return context.json(await this.options.oauthClientConfigs.listConfigs());
967
+ const configs = await this.options.oauthClientConfigs.listConfigs();
968
+ const sources = await this.options.saasProject?.listSources();
969
+ return context.json(configs.map((config) => ({
970
+ ...config,
971
+ oauthSource: sources?.get(config.service) ?? { mode: "local" },
972
+ customClientAvailable: sources?.has(config.service) ? false : config.customClientAvailable,
973
+ })));
840
974
  }
841
975
  async upsertOAuthConfig(context, service) {
842
976
  const body = await readJsonBody(context);
@@ -44,6 +44,8 @@ export interface ConnectorRuntimeOptions {
44
44
  dataDir: string;
45
45
  /** External HTTP(S) URL, optionally including a mount path such as /connector. */
46
46
  publicOrigin: string;
47
+ /** False when the host supplied a development fallback instead of an explicit public origin. */
48
+ publicOriginConfigured?: boolean;
47
49
  /** Encrypts stored credentials, OAuth client configuration, pending OAuth state and replayed action responses. Omit to store them in plain text. */
48
50
  encryptionKey?: string;
49
51
  /** Bearer token required for management requests such as connections, OAuth clients and policies. Omit to leave them open. */
@@ -54,7 +56,7 @@ export interface ConnectorRuntimeOptions {
54
56
  jwt?: RuntimeJwtConfig;
55
57
  postgres?: ConnectorPostgresOptions;
56
58
  network?: ConnectorNetworkOptions;
57
- /** Allow or block actions and proxies by name. */
59
+ /** Allow or block actions, proxies and Triggers by name. */
58
60
  actionPolicy?: ActionPolicyConfig;
59
61
  /** Services, or `*`, whose connections may carry their own OAuth client instead of the configured one. */
60
62
  allowedCustomOAuth?: string[];
@@ -104,13 +104,14 @@ async function openRuntime(options) {
104
104
  const tempDir = join(dataDir, "tmp/transit-files");
105
105
  await transitFiles.cleanupExpired();
106
106
  await cleanupStagedTransitFiles(tempDir, ttlSeconds * 1000);
107
- const { app, runtimeAuthConfigured } = await createConnectApp({
107
+ const { app, runtimeAuthConfigured, saasCleanup, triggerMaintenance } = await createConnectApp({
108
108
  catalog,
109
109
  providerLoader: new ProviderLoader(executorModules),
110
110
  runtimeDatabase: database,
111
111
  transitFiles,
112
112
  uploadTransitFile: createNodeTransitFileUpload({ transitFiles, tempDir }),
113
113
  publicOrigin,
114
+ configuredOrigin: options.publicOriginConfigured === false ? undefined : publicOrigin,
114
115
  secretCodec,
115
116
  adminToken: options.adminToken,
116
117
  runtimeToken: options.runtimeToken,
@@ -120,6 +121,8 @@ async function openRuntime(options) {
120
121
  logger: options.logger,
121
122
  serveDocumentation: options.apiReference ?? false,
122
123
  });
124
+ saasCleanup.start();
125
+ triggerMaintenance.start();
123
126
  const shutdown = new AbortController();
124
127
  const pending = new Set();
125
128
  let closing;
@@ -142,7 +145,7 @@ async function openRuntime(options) {
142
145
  if (!closing) {
143
146
  closing = Promise.resolve().then(async () => {
144
147
  shutdown.abort(new Error("Open Connector runtime is closing."));
145
- await Promise.allSettled([...pending]);
148
+ await Promise.allSettled([...pending, saasCleanup.close(), triggerMaintenance.close()]);
146
149
  try {
147
150
  await database.close();
148
151
  }
@@ -130,13 +130,22 @@ export class TransitFileService {
130
130
  async writeStream(file, tempPath) {
131
131
  let sizeBytes = 0;
132
132
  const maxBytes = this.maxBytes;
133
- await pipeline(Readable.fromWeb(file.body), async function* (source) {
134
- for await (const chunk of source) {
135
- sizeBytes += chunk.byteLength;
136
- assertFileSize(sizeBytes, maxBytes);
137
- yield chunk;
138
- }
139
- }, createWriteStream(tempPath, { flags: "wx" }), { signal: file.signal });
133
+ const source = Readable.fromWeb(file.body);
134
+ const destination = createWriteStream(tempPath, { flags: "wx" });
135
+ // Pipeline can reject while the file is still opening. Wait for close before unlinking it.
136
+ const closed = new Promise((resolve) => destination.once("close", resolve));
137
+ try {
138
+ await pipeline(source, async function* (source) {
139
+ for await (const chunk of source) {
140
+ sizeBytes += chunk.byteLength;
141
+ assertFileSize(sizeBytes, maxBytes);
142
+ yield chunk;
143
+ }
144
+ }, destination, { signal: file.signal });
145
+ }
146
+ finally {
147
+ await closed;
148
+ }
140
149
  return sizeBytes;
141
150
  }
142
151
  }
@@ -3,17 +3,20 @@ import type { ConnectionService } from "../../connection-service.ts";
3
3
  import type { ActionPolicySnapshot } from "../../core/action-policy.ts";
4
4
  import type { RuntimeLogger, ProxyResponse } from "../../core/types.ts";
5
5
  import type { IProviderLoader } from "../../providers/provider-loader.ts";
6
- export type ProxyFailureStatus = 400 | 402 | 403 | 404 | 409 | 413 | 429 | 500 | 501;
6
+ import type { SaasExecutionService } from "../../saas/saas-execution-service.ts";
7
+ export type ProxyFailureStatus = 400 | 402 | 403 | 404 | 409 | 413 | 429 | 500 | 501 | 502 | 503 | 504;
7
8
  export interface ProxyRunnerOptions {
8
9
  catalog: CatalogStore;
9
10
  providerLoader: IProviderLoader;
10
11
  connections: ConnectionService;
11
12
  logger?: RuntimeLogger;
13
+ saas?: SaasExecutionService;
12
14
  }
13
15
  export interface RunProxyInput {
14
16
  service: string;
15
17
  input: unknown;
16
18
  connectionName?: string;
19
+ connectionId?: string;
17
20
  policy: ActionPolicySnapshot;
18
21
  /** Cancellation signal from the HTTP request, handed to the provider proxy executor. */
19
22
  signal?: AbortSignal;
@@ -21,6 +24,7 @@ export interface RunProxyInput {
21
24
  export type ProxyRunResult = {
22
25
  ok: true;
23
26
  response: ProxyResponse;
27
+ meta?: Record<string, unknown>;
24
28
  } | ProxyRunFailure;
25
29
  export interface ProxyRunFailure {
26
30
  ok: false;
@@ -1,5 +1,6 @@
1
1
  import { ConnectionError } from "../../connection-service.js";
2
2
  import { optionalInteger, optionalRecord, requiredRecord, requiredString } from "../../core/cast.js";
3
+ import { SaasError } from "../../saas/saas-client.js";
3
4
  import { mapConnectionErrorStatus } from "../api/runtime-api.js";
4
5
  class ProxyInputError extends Error {
5
6
  }
@@ -30,29 +31,6 @@ export class ProxyRunner {
30
31
  meta: { service: provider.service },
31
32
  };
32
33
  }
33
- let executor;
34
- try {
35
- executor = await this.options.providerLoader.loadProxyExecutor(provider.service, provider.displayName);
36
- }
37
- catch {
38
- this.options.logger?.warn({ service: provider.service, errorCode: "internal_error" }, "proxy request failed");
39
- return {
40
- ok: false,
41
- status: 500,
42
- errorCode: "internal_error",
43
- message: "Proxy request failed unexpectedly.",
44
- meta: { service: provider.service },
45
- };
46
- }
47
- if (!executor) {
48
- return {
49
- ok: false,
50
- status: 501,
51
- errorCode: "proxy_not_supported",
52
- message: `Proxy execution is not supported for ${provider.service}.`,
53
- meta: { service: provider.service },
54
- };
55
- }
56
34
  const request = this.readProxyRequestInput(input.input);
57
35
  if (!request.ok) {
58
36
  return request;
@@ -64,8 +42,9 @@ export class ProxyRunner {
64
42
  connectionName: input.connectionName,
65
43
  };
66
44
  const startedAtMs = Date.now();
45
+ let executionId;
67
46
  try {
68
- const connection = await this.options.connections.getConnectionSummary(provider.service, input.connectionName);
47
+ const connection = await this.options.connections.getConnectionSummary(provider.service, input.connectionName, input.connectionId);
69
48
  const connectionDecision = connection?.authType === "no_auth" ? undefined : input.policy.evaluateConnection(connection?.id);
70
49
  if (connectionDecision && !connectionDecision.allowed) {
71
50
  return {
@@ -76,10 +55,58 @@ export class ProxyRunner {
76
55
  meta: { service: provider.service },
77
56
  };
78
57
  }
58
+ const target = await this.options.connections.resolveForExecution(provider.service, input.connectionName, input.connectionId);
59
+ const targetDecision = target.summary?.authType === "no_auth" ? undefined : input.policy.evaluateConnection(target.summary?.id);
60
+ if (targetDecision && !targetDecision.allowed)
61
+ return {
62
+ ok: false,
63
+ status: 403,
64
+ errorCode: targetDecision.code,
65
+ message: targetDecision.message,
66
+ meta: { service: provider.service },
67
+ };
79
68
  this.options.logger?.info(logContext, "proxy request started");
80
- const credentials = this.options.connections.forConnection(input.connectionName);
69
+ if (target.kind === "saas") {
70
+ executionId = crypto.randomUUID();
71
+ input.signal?.throwIfAborted();
72
+ if (!this.options.saas)
73
+ throw new SaasError("oauth_source_unavailable", "SaaS execution is unavailable.", 503);
74
+ const { alias: _alias, connectionName: _connectionName, ...remoteRequest } = optionalRecord(input.input);
75
+ remoteRequest.method = request.input.method;
76
+ const remote = await this.options.saas.executeProxy(target.reference, provider.service, remoteRequest, input.signal);
77
+ input.signal?.throwIfAborted();
78
+ this.options.logger?.info({
79
+ ...logContext,
80
+ executionId,
81
+ remoteExecutionId: remote.executionId,
82
+ durationMs: Date.now() - startedAtMs,
83
+ status: remote.response.status,
84
+ }, "proxy request completed");
85
+ return {
86
+ ok: true,
87
+ response: remote.response,
88
+ meta: { service: provider.service, executionId, remoteExecutionId: remote.executionId },
89
+ };
90
+ }
91
+ if (target.kind === "marketplace")
92
+ return {
93
+ ok: false,
94
+ status: 501,
95
+ errorCode: "proxy_not_supported",
96
+ message: "Marketplace connections do not support proxy execution.",
97
+ meta: { service: provider.service },
98
+ };
99
+ const executor = await this.options.providerLoader.loadProxyExecutor(provider.service, provider.displayName);
100
+ if (!executor)
101
+ return {
102
+ ok: false,
103
+ status: 501,
104
+ errorCode: "proxy_not_supported",
105
+ message: `Proxy execution is not supported for ${provider.service}.`,
106
+ meta: { service: provider.service },
107
+ };
81
108
  const result = await executor(request.input, {
82
- getCredential: credentials.getCredential,
109
+ getCredential: target.getCredential,
83
110
  signal: input.signal,
84
111
  });
85
112
  const durationMs = Date.now() - startedAtMs;
@@ -103,6 +130,25 @@ export class ProxyRunner {
103
130
  }
104
131
  catch (error) {
105
132
  const durationMs = Date.now() - startedAtMs;
133
+ if (executionId && input.signal?.aborted)
134
+ return {
135
+ ok: false,
136
+ status: 400,
137
+ errorCode: "execution_cancelled",
138
+ message: "Proxy execution was cancelled.",
139
+ meta: { service: provider.service, executionId },
140
+ };
141
+ if (error instanceof SaasError) {
142
+ this.options.logger?.warn({ ...logContext, durationMs, executionId, errorCode: error.code, remoteExecutionId: error.remoteExecutionId }, "proxy request failed");
143
+ return {
144
+ ok: false,
145
+ status: error.status,
146
+ errorCode: error.code,
147
+ message: error.message,
148
+ data: error.retryAfter ? { details: { retryAfter: error.retryAfter } } : undefined,
149
+ meta: { service: provider.service, executionId, remoteExecutionId: error.remoteExecutionId },
150
+ };
151
+ }
106
152
  if (error instanceof ConnectionError) {
107
153
  const missingConnectionDecision = error.code === "connection_not_found" ? input.policy.evaluateConnection() : undefined;
108
154
  if (missingConnectionDecision && !missingConnectionDecision.allowed) {
@@ -1,4 +1,4 @@
1
- import type { StoredConnection } from "../../connection-service.ts";
1
+ import type { StoredConnection, StoredSaasConnection } from "../../connection-service.ts";
2
2
  import type { ResolvedCredential } from "../../core/types.ts";
3
3
  import type { OAuthAuthorizationState } from "../../oauth/oauth-flow-service.ts";
4
4
  import type { ISecretCodec } from "../secrets/secret-codec-core.ts";
@@ -26,6 +26,35 @@ export interface RequestStatement {
26
26
  sql: string;
27
27
  values: (string | number | null)[];
28
28
  }
29
+ export interface PendingSaasRequest {
30
+ connectionRequestId: string;
31
+ connectionId: string;
32
+ owner: string;
33
+ service: string;
34
+ connectionName: string;
35
+ managedProjectId: string;
36
+ providerConfigId: string;
37
+ externalUserId: string;
38
+ createdAt: string;
39
+ expiresAt: string;
40
+ returnUri?: string;
41
+ target?: Pick<StoredConnection, "id" | "revision">;
42
+ comment?: string | null;
43
+ }
44
+ export interface SaasRequestLease {
45
+ pending: PendingSaasRequest;
46
+ leaseId: string;
47
+ phase: "creating" | "pending" | "candidate";
48
+ remoteRequestId: string | null;
49
+ candidate?: SaasCandidate;
50
+ pollAttempts: number;
51
+ }
52
+ export interface SaasCandidate {
53
+ connectedAccountId: string;
54
+ profile: StoredSaasConnection["profile"];
55
+ status: StoredSaasConnection["status"];
56
+ comment: string | null;
57
+ }
29
58
  /** Executes all statements atomically; each result contains its RETURNING/SELECT rows. */
30
59
  export type RequestTransaction = (statements: RequestStatement[]) => Promise<RuntimeRow[][]>;
31
60
  /** Shared SQL lifecycle for SQLite, PostgreSQL and D1. Secrets use the runtime's codec. */
@@ -38,4 +67,15 @@ export declare class ConnectionRequestStore {
38
67
  claim(state: string): Promise<PendingConnectionRequest | undefined>;
39
68
  fail(id: string, errorCode: string, errorMessage: string): Promise<void>;
40
69
  complete(pending: PendingConnectionRequest, credential: ResolvedCredential, signal?: AbortSignal): Promise<string | undefined>;
70
+ private retireRequests;
71
+ createSaas(pending: PendingSaasRequest): Promise<SaasRequestLease>;
72
+ claimSaas(id: string, owner: string, now?: number): Promise<SaasRequestLease | undefined>;
73
+ saveSaasRequest(lease: SaasRequestLease, remoteRequestId: string, expiresAt?: string): Promise<boolean>;
74
+ saveSaasCandidate(lease: SaasRequestLease, candidate: SaasCandidate): Promise<boolean>;
75
+ releaseSaas(lease: SaasRequestLease, nextPollAt: number, failed?: boolean): Promise<boolean>;
76
+ failSaas(lease: SaasRequestLease, code: string, message: string): Promise<boolean>;
77
+ getSaasReturnUri(id: string, owner: string): Promise<string | undefined>;
78
+ cancelSaas(id: string, owner: string): Promise<void>;
79
+ expireSaas(now?: number): Promise<void>;
80
+ completeSaas(lease: SaasRequestLease): Promise<"connected" | "conflict" | "lease_lost">;
41
81
  }