@shipfox/api-integration-core 2.0.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 (275) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +264 -0
  5. package/LICENSE +21 -0
  6. package/dist/config.d.ts +9 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +29 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/agent-tool-selection.d.ts +15 -0
  11. package/dist/core/agent-tool-selection.d.ts.map +1 -0
  12. package/dist/core/agent-tool-selection.js +48 -0
  13. package/dist/core/agent-tool-selection.js.map +1 -0
  14. package/dist/core/entities/connection.d.ts +2 -0
  15. package/dist/core/entities/connection.d.ts.map +1 -0
  16. package/dist/core/entities/connection.js +3 -0
  17. package/dist/core/entities/connection.js.map +1 -0
  18. package/dist/core/entities/provider.d.ts +6 -0
  19. package/dist/core/entities/provider.d.ts.map +1 -0
  20. package/dist/core/entities/provider.js +3 -0
  21. package/dist/core/entities/provider.js.map +1 -0
  22. package/dist/core/errors.d.ts +29 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +40 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/providers/agent-tools.d.ts +2 -0
  27. package/dist/core/providers/agent-tools.d.ts.map +1 -0
  28. package/dist/core/providers/agent-tools.js +3 -0
  29. package/dist/core/providers/agent-tools.js.map +1 -0
  30. package/dist/core/providers/redact-checkout-spec.d.ts +9 -0
  31. package/dist/core/providers/redact-checkout-spec.d.ts.map +1 -0
  32. package/dist/core/providers/redact-checkout-spec.js +25 -0
  33. package/dist/core/providers/redact-checkout-spec.js.map +1 -0
  34. package/dist/core/providers/registry.d.ts +9 -0
  35. package/dist/core/providers/registry.d.ts.map +1 -0
  36. package/dist/core/providers/registry.js +52 -0
  37. package/dist/core/providers/registry.js.map +1 -0
  38. package/dist/core/providers/source-control.d.ts +2 -0
  39. package/dist/core/providers/source-control.d.ts.map +1 -0
  40. package/dist/core/providers/source-control.js +3 -0
  41. package/dist/core/providers/source-control.js.map +1 -0
  42. package/dist/core/source-control-service.d.ts +46 -0
  43. package/dist/core/source-control-service.d.ts.map +1 -0
  44. package/dist/core/source-control-service.js +84 -0
  45. package/dist/core/source-control-service.js.map +1 -0
  46. package/dist/db/connections.d.ts +67 -0
  47. package/dist/db/connections.d.ts.map +1 -0
  48. package/dist/db/connections.js +130 -0
  49. package/dist/db/connections.js.map +1 -0
  50. package/dist/db/db.d.ts +865 -0
  51. package/dist/db/db.d.ts.map +1 -0
  52. package/dist/db/db.js +22 -0
  53. package/dist/db/db.js.map +1 -0
  54. package/dist/db/migrations.d.ts +2 -0
  55. package/dist/db/migrations.d.ts.map +1 -0
  56. package/dist/db/migrations.js +5 -0
  57. package/dist/db/migrations.js.map +1 -0
  58. package/dist/db/schema/common.d.ts +2 -0
  59. package/dist/db/schema/common.d.ts.map +1 -0
  60. package/dist/db/schema/common.js +4 -0
  61. package/dist/db/schema/common.js.map +1 -0
  62. package/dist/db/schema/connections.d.ts +165 -0
  63. package/dist/db/schema/connections.d.ts.map +1 -0
  64. package/dist/db/schema/connections.js +37 -0
  65. package/dist/db/schema/connections.js.map +1 -0
  66. package/dist/db/schema/outbox.d.ts +195 -0
  67. package/dist/db/schema/outbox.d.ts.map +1 -0
  68. package/dist/db/schema/outbox.js +5 -0
  69. package/dist/db/schema/outbox.js.map +1 -0
  70. package/dist/db/schema/webhook-deliveries.d.ts +76 -0
  71. package/dist/db/schema/webhook-deliveries.d.ts.map +1 -0
  72. package/dist/db/schema/webhook-deliveries.js +22 -0
  73. package/dist/db/schema/webhook-deliveries.js.map +1 -0
  74. package/dist/db/webhook-deliveries.d.ts +55 -0
  75. package/dist/db/webhook-deliveries.d.ts.map +1 -0
  76. package/dist/db/webhook-deliveries.js +132 -0
  77. package/dist/db/webhook-deliveries.js.map +1 -0
  78. package/dist/index.d.ts +54 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +105 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/metrics/index.d.ts +2 -0
  83. package/dist/metrics/index.d.ts.map +1 -0
  84. package/dist/metrics/index.js +3 -0
  85. package/dist/metrics/index.js.map +1 -0
  86. package/dist/metrics/instance.d.ts +8 -0
  87. package/dist/metrics/instance.d.ts.map +1 -0
  88. package/dist/metrics/instance.js +17 -0
  89. package/dist/metrics/instance.js.map +1 -0
  90. package/dist/presentation/dto/integrations.d.ts +36 -0
  91. package/dist/presentation/dto/integrations.d.ts.map +1 -0
  92. package/dist/presentation/dto/integrations.js +26 -0
  93. package/dist/presentation/dto/integrations.js.map +1 -0
  94. package/dist/presentation/routes/agent-tools-gateway/audit.d.ts +24 -0
  95. package/dist/presentation/routes/agent-tools-gateway/audit.d.ts.map +1 -0
  96. package/dist/presentation/routes/agent-tools-gateway/audit.js +52 -0
  97. package/dist/presentation/routes/agent-tools-gateway/audit.js.map +1 -0
  98. package/dist/presentation/routes/agent-tools-gateway/dispatch.d.ts +7 -0
  99. package/dist/presentation/routes/agent-tools-gateway/dispatch.d.ts.map +1 -0
  100. package/dist/presentation/routes/agent-tools-gateway/dispatch.js +108 -0
  101. package/dist/presentation/routes/agent-tools-gateway/dispatch.js.map +1 -0
  102. package/dist/presentation/routes/agent-tools-gateway/index.d.ts +12 -0
  103. package/dist/presentation/routes/agent-tools-gateway/index.d.ts.map +1 -0
  104. package/dist/presentation/routes/agent-tools-gateway/index.js +46 -0
  105. package/dist/presentation/routes/agent-tools-gateway/index.js.map +1 -0
  106. package/dist/presentation/routes/agent-tools-gateway/mcp-server.d.ts +17 -0
  107. package/dist/presentation/routes/agent-tools-gateway/mcp-server.d.ts.map +1 -0
  108. package/dist/presentation/routes/agent-tools-gateway/mcp-server.js +126 -0
  109. package/dist/presentation/routes/agent-tools-gateway/mcp-server.js.map +1 -0
  110. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts +37 -0
  111. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts.map +1 -0
  112. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js +145 -0
  113. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js.map +1 -0
  114. package/dist/presentation/routes/errors.d.ts +2 -0
  115. package/dist/presentation/routes/errors.d.ts.map +1 -0
  116. package/dist/presentation/routes/errors.js +53 -0
  117. package/dist/presentation/routes/errors.js.map +1 -0
  118. package/dist/presentation/routes/index.d.ts +14 -0
  119. package/dist/presentation/routes/index.d.ts.map +1 -0
  120. package/dist/presentation/routes/index.js +26 -0
  121. package/dist/presentation/routes/index.js.map +1 -0
  122. package/dist/presentation/routes/list-connections.d.ts +3 -0
  123. package/dist/presentation/routes/list-connections.d.ts.map +1 -0
  124. package/dist/presentation/routes/list-connections.js +58 -0
  125. package/dist/presentation/routes/list-connections.js.map +1 -0
  126. package/dist/presentation/routes/list-providers.d.ts +3 -0
  127. package/dist/presentation/routes/list-providers.d.ts.map +1 -0
  128. package/dist/presentation/routes/list-providers.js +27 -0
  129. package/dist/presentation/routes/list-providers.js.map +1 -0
  130. package/dist/presentation/routes/list-repositories.d.ts +3 -0
  131. package/dist/presentation/routes/list-repositories.d.ts.map +1 -0
  132. package/dist/presentation/routes/list-repositories.js +41 -0
  133. package/dist/presentation/routes/list-repositories.js.map +1 -0
  134. package/dist/presentation/routes/manage-connections.d.ts +4 -0
  135. package/dist/presentation/routes/manage-connections.d.ts.map +1 -0
  136. package/dist/presentation/routes/manage-connections.js +81 -0
  137. package/dist/presentation/routes/manage-connections.js.map +1 -0
  138. package/dist/providers/connection-slug.d.ts +2 -0
  139. package/dist/providers/connection-slug.d.ts.map +1 -0
  140. package/dist/providers/connection-slug.js +19 -0
  141. package/dist/providers/connection-slug.js.map +1 -0
  142. package/dist/providers/cron.d.ts +3 -0
  143. package/dist/providers/cron.d.ts.map +1 -0
  144. package/dist/providers/cron.js +13 -0
  145. package/dist/providers/cron.js.map +1 -0
  146. package/dist/providers/gitea.d.ts +3 -0
  147. package/dist/providers/gitea.d.ts.map +1 -0
  148. package/dist/providers/gitea.js +75 -0
  149. package/dist/providers/gitea.js.map +1 -0
  150. package/dist/providers/github.d.ts +3 -0
  151. package/dist/providers/github.d.ts.map +1 -0
  152. package/dist/providers/github.js +109 -0
  153. package/dist/providers/github.js.map +1 -0
  154. package/dist/providers/linear.d.ts +3 -0
  155. package/dist/providers/linear.d.ts.map +1 -0
  156. package/dist/providers/linear.js +134 -0
  157. package/dist/providers/linear.js.map +1 -0
  158. package/dist/providers/modules.d.ts +3 -0
  159. package/dist/providers/modules.d.ts.map +1 -0
  160. package/dist/providers/modules.js +26 -0
  161. package/dist/providers/modules.js.map +1 -0
  162. package/dist/providers/sentry.d.ts +3 -0
  163. package/dist/providers/sentry.d.ts.map +1 -0
  164. package/dist/providers/sentry.js +83 -0
  165. package/dist/providers/sentry.js.map +1 -0
  166. package/dist/providers/types.d.ts +58 -0
  167. package/dist/providers/types.d.ts.map +1 -0
  168. package/dist/providers/types.js +3 -0
  169. package/dist/providers/types.js.map +1 -0
  170. package/dist/providers/webhook.d.ts +3 -0
  171. package/dist/providers/webhook.d.ts.map +1 -0
  172. package/dist/providers/webhook.js +24 -0
  173. package/dist/providers/webhook.js.map +1 -0
  174. package/dist/temporal/activities/index.d.ts +5 -0
  175. package/dist/temporal/activities/index.d.ts.map +1 -0
  176. package/dist/temporal/activities/index.js +8 -0
  177. package/dist/temporal/activities/index.js.map +1 -0
  178. package/dist/temporal/activities/prune-webhook-deliveries.d.ts +4 -0
  179. package/dist/temporal/activities/prune-webhook-deliveries.d.ts.map +1 -0
  180. package/dist/temporal/activities/prune-webhook-deliveries.js +11 -0
  181. package/dist/temporal/activities/prune-webhook-deliveries.js.map +1 -0
  182. package/dist/temporal/constants.d.ts +3 -0
  183. package/dist/temporal/constants.d.ts.map +1 -0
  184. package/dist/temporal/constants.js +4 -0
  185. package/dist/temporal/constants.js.map +1 -0
  186. package/dist/temporal/workflows/index.d.ts +2 -0
  187. package/dist/temporal/workflows/index.d.ts.map +1 -0
  188. package/dist/temporal/workflows/index.js +3 -0
  189. package/dist/temporal/workflows/index.js.map +1 -0
  190. package/dist/temporal/workflows/prune-webhook-deliveries-cron.d.ts +2 -0
  191. package/dist/temporal/workflows/prune-webhook-deliveries-cron.d.ts.map +1 -0
  192. package/dist/temporal/workflows/prune-webhook-deliveries-cron.js +14 -0
  193. package/dist/temporal/workflows/prune-webhook-deliveries-cron.js.map +1 -0
  194. package/dist/tsconfig.test.tsbuildinfo +1 -0
  195. package/drizzle/0000_initial.sql +41 -0
  196. package/drizzle/meta/0001_snapshot.json +341 -0
  197. package/drizzle/meta/_journal.json +13 -0
  198. package/drizzle.config.ts +7 -0
  199. package/package.json +85 -0
  200. package/src/config.test.ts +32 -0
  201. package/src/config.ts +28 -0
  202. package/src/core/agent-tool-selection.test.ts +137 -0
  203. package/src/core/agent-tool-selection.ts +80 -0
  204. package/src/core/entities/connection.ts +4 -0
  205. package/src/core/entities/provider.ts +21 -0
  206. package/src/core/errors.ts +55 -0
  207. package/src/core/providers/agent-tools.ts +10 -0
  208. package/src/core/providers/redact-checkout-spec.test.ts +78 -0
  209. package/src/core/providers/redact-checkout-spec.ts +20 -0
  210. package/src/core/providers/registry.test.ts +138 -0
  211. package/src/core/providers/registry.ts +90 -0
  212. package/src/core/providers/source-control.ts +17 -0
  213. package/src/core/source-control-service.test.ts +230 -0
  214. package/src/core/source-control-service.ts +161 -0
  215. package/src/db/connections.test.ts +296 -0
  216. package/src/db/connections.ts +262 -0
  217. package/src/db/db.ts +22 -0
  218. package/src/db/migrations.ts +4 -0
  219. package/src/db/schema/common.ts +3 -0
  220. package/src/db/schema/connections.ts +49 -0
  221. package/src/db/schema/outbox.ts +4 -0
  222. package/src/db/schema/webhook-deliveries.ts +19 -0
  223. package/src/db/webhook-deliveries.test.ts +267 -0
  224. package/src/db/webhook-deliveries.ts +207 -0
  225. package/src/index.ts +212 -0
  226. package/src/metrics/index.ts +1 -0
  227. package/src/metrics/instance.ts +35 -0
  228. package/src/presentation/dto/integrations.ts +39 -0
  229. package/src/presentation/routes/agent-tools-gateway/agent-tools-gateway.route.test.ts +371 -0
  230. package/src/presentation/routes/agent-tools-gateway/audit.test.ts +86 -0
  231. package/src/presentation/routes/agent-tools-gateway/audit.ts +90 -0
  232. package/src/presentation/routes/agent-tools-gateway/dispatch.ts +139 -0
  233. package/src/presentation/routes/agent-tools-gateway/index.ts +62 -0
  234. package/src/presentation/routes/agent-tools-gateway/mcp-server.test.ts +308 -0
  235. package/src/presentation/routes/agent-tools-gateway/mcp-server.ts +155 -0
  236. package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.test.ts +181 -0
  237. package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.ts +245 -0
  238. package/src/presentation/routes/errors.ts +63 -0
  239. package/src/presentation/routes/index.ts +53 -0
  240. package/src/presentation/routes/list-connections.test.ts +227 -0
  241. package/src/presentation/routes/list-connections.ts +56 -0
  242. package/src/presentation/routes/list-providers.test.ts +113 -0
  243. package/src/presentation/routes/list-providers.ts +28 -0
  244. package/src/presentation/routes/list-repositories.test.ts +150 -0
  245. package/src/presentation/routes/list-repositories.ts +47 -0
  246. package/src/presentation/routes/manage-connections.test.ts +125 -0
  247. package/src/presentation/routes/manage-connections.ts +77 -0
  248. package/src/providers/connection-slug.ts +19 -0
  249. package/src/providers/cron.ts +13 -0
  250. package/src/providers/gitea.ts +103 -0
  251. package/src/providers/github.ts +150 -0
  252. package/src/providers/linear.ts +179 -0
  253. package/src/providers/modules.test.ts +40 -0
  254. package/src/providers/modules.ts +32 -0
  255. package/src/providers/sentry.ts +113 -0
  256. package/src/providers/types.ts +52 -0
  257. package/src/providers/webhook.ts +30 -0
  258. package/src/temporal/activities/index.ts +7 -0
  259. package/src/temporal/activities/prune-webhook-deliveries.test.ts +39 -0
  260. package/src/temporal/activities/prune-webhook-deliveries.ts +9 -0
  261. package/src/temporal/constants.ts +3 -0
  262. package/src/temporal/workflows/index.ts +1 -0
  263. package/src/temporal/workflows/prune-webhook-deliveries-cron.ts +15 -0
  264. package/test/agent-tools-gateway-helpers.ts +210 -0
  265. package/test/env.ts +23 -0
  266. package/test/factories/connection.ts +21 -0
  267. package/test/globalSetup.ts +22 -0
  268. package/test/index.ts +1 -0
  269. package/test/route-utils.ts +104 -0
  270. package/test/setup.ts +19 -0
  271. package/tsconfig.build.json +9 -0
  272. package/tsconfig.build.tsbuildinfo +1 -0
  273. package/tsconfig.json +3 -0
  274. package/tsconfig.test.json +8 -0
  275. package/vitest.config.ts +13 -0
@@ -0,0 +1,80 @@
1
+ import type {
2
+ AgentToolCatalogEntry,
3
+ AgentToolSelectionCatalog,
4
+ IntegrationCapability,
5
+ IntegrationProviderKind,
6
+ } from '@shipfox/api-integration-core-dto';
7
+ import type {IntegrationProviderRegistry} from '#core/providers/registry.js';
8
+ import {listIntegrationConnections} from '#db/connections.js';
9
+
10
+ export type AgentToolSelectionCatalogs = ReadonlyMap<
11
+ IntegrationProviderKind,
12
+ AgentToolSelectionCatalog
13
+ >;
14
+ export type AgentToolCatalogs = ReadonlyMap<
15
+ IntegrationProviderKind,
16
+ readonly AgentToolCatalogEntry[]
17
+ >;
18
+
19
+ export interface WorkspaceConnectionSnapshotEntry {
20
+ readonly id: string;
21
+ readonly provider: IntegrationProviderKind;
22
+ readonly capabilities: readonly IntegrationCapability[];
23
+ }
24
+
25
+ export type WorkspaceConnectionSnapshot = ReadonlyMap<string, WorkspaceConnectionSnapshotEntry>;
26
+ export type LoadWorkspaceConnectionSnapshot = (
27
+ workspaceId: string,
28
+ ) => Promise<WorkspaceConnectionSnapshot>;
29
+
30
+ export async function buildAgentToolSelectionCatalogs(
31
+ registry: IntegrationProviderRegistry,
32
+ ): Promise<AgentToolSelectionCatalogs> {
33
+ const entries = await Promise.all(
34
+ registry.list('agent_tools').map(async (provider) => {
35
+ const adapter = provider.adapters.agent_tools;
36
+ if (adapter === undefined) {
37
+ throw new Error(`Integration provider "${provider.provider}" has no agent tools adapter`);
38
+ }
39
+ return [provider.provider, await adapter.selectionCatalog()] as const;
40
+ }),
41
+ );
42
+ return new Map(entries);
43
+ }
44
+
45
+ export async function buildAgentToolCatalogs(
46
+ registry: IntegrationProviderRegistry,
47
+ ): Promise<AgentToolCatalogs> {
48
+ const entries = await Promise.all(
49
+ registry.list('agent_tools').map(async (provider) => {
50
+ const adapter = provider.adapters.agent_tools;
51
+ if (adapter === undefined) {
52
+ throw new Error(`Integration provider "${provider.provider}" has no agent tools adapter`);
53
+ }
54
+ return [provider.provider, await adapter.catalog()] as const;
55
+ }),
56
+ );
57
+ return new Map(entries);
58
+ }
59
+
60
+ export function createWorkspaceConnectionSnapshotLoader(
61
+ registry: IntegrationProviderRegistry,
62
+ ): LoadWorkspaceConnectionSnapshot {
63
+ const capabilitiesByProvider = new Map(
64
+ registry.list().map((provider) => [provider.provider, provider.capabilities]),
65
+ );
66
+
67
+ return async (workspaceId) => {
68
+ const connections = await listIntegrationConnections({workspaceId});
69
+ return new Map(
70
+ connections.map((connection) => [
71
+ connection.slug,
72
+ {
73
+ id: connection.id,
74
+ provider: connection.provider,
75
+ capabilities: capabilitiesByProvider.get(connection.provider) ?? [],
76
+ },
77
+ ]),
78
+ );
79
+ };
80
+ }
@@ -0,0 +1,4 @@
1
+ export type {
2
+ IntegrationConnection,
3
+ IntegrationConnectionLifecycleStatus,
4
+ } from '@shipfox/api-integration-core-dto';
@@ -0,0 +1,21 @@
1
+ import type {
2
+ IntegrationProvider as CoreIntegrationProvider,
3
+ RegisteredIntegrationProvider as CoreRegisteredIntegrationProvider,
4
+ } from '@shipfox/api-integration-core-dto';
5
+ import type {RouteExport} from '@shipfox/node-fastify';
6
+
7
+ export type {
8
+ AgentToolCallInput,
9
+ AgentToolCatalogEntry,
10
+ AgentToolJsonSchema,
11
+ AgentToolSensitivity,
12
+ AgentToolSession,
13
+ AgentToolsProvider,
14
+ IntegrationCapability,
15
+ IntegrationProviderAdapters,
16
+ IntegrationProviderKind,
17
+ OpenAgentToolsSessionInput,
18
+ } from '@shipfox/api-integration-core-dto';
19
+
20
+ export type IntegrationProvider = CoreIntegrationProvider<string, RouteExport>;
21
+ export type RegisteredIntegrationProvider = CoreRegisteredIntegrationProvider<string, RouteExport>;
@@ -0,0 +1,55 @@
1
+ import {
2
+ ConnectionSlugConflictError,
3
+ IntegrationProviderError,
4
+ type IntegrationProviderErrorReason,
5
+ } from '@shipfox/api-integration-core-dto';
6
+ import type {IntegrationCapability, IntegrationProviderKind} from '#core/entities/provider.js';
7
+
8
+ export class IntegrationConnectionNotFoundError extends Error {
9
+ constructor(connectionId: string) {
10
+ super(`Integration connection not found: ${connectionId}`);
11
+ }
12
+ }
13
+
14
+ export class IntegrationConnectionInactiveError extends Error {
15
+ constructor(connectionId: string) {
16
+ super(`Integration connection is not active: ${connectionId}`);
17
+ }
18
+ }
19
+
20
+ export class IntegrationConnectionWorkspaceMismatchError extends Error {
21
+ constructor(connectionId: string) {
22
+ super(`Integration connection does not belong to the requested workspace: ${connectionId}`);
23
+ }
24
+ }
25
+
26
+ export class IntegrationConnectionAlreadyExistsError extends Error {
27
+ constructor(
28
+ public readonly workspaceId: string,
29
+ public readonly provider: IntegrationProviderKind,
30
+ public readonly externalAccountId: string,
31
+ ) {
32
+ super(`Integration connection already exists: ${workspaceId}/${provider}/${externalAccountId}`);
33
+ this.name = 'IntegrationConnectionAlreadyExistsError';
34
+ }
35
+ }
36
+
37
+ export class IntegrationCapabilityUnavailableError extends Error {
38
+ constructor(capability: IntegrationCapability, provider: IntegrationProviderKind) {
39
+ super(`Integration provider ${provider} does not expose ${capability}`);
40
+ }
41
+ }
42
+
43
+ export class IntegrationCheckoutUnsupportedError extends Error {
44
+ constructor(public readonly provider: IntegrationProviderKind) {
45
+ super(`Integration provider ${provider} cannot create a checkout spec`);
46
+ }
47
+ }
48
+
49
+ export class IntegrationProviderUnavailableError extends Error {
50
+ constructor(provider: IntegrationProviderKind) {
51
+ super(`No integration provider registered for ${provider}`);
52
+ }
53
+ }
54
+
55
+ export {ConnectionSlugConflictError, IntegrationProviderError, type IntegrationProviderErrorReason};
@@ -0,0 +1,10 @@
1
+ export type {
2
+ AgentToolCallInput,
3
+ AgentToolCatalogEntry,
4
+ AgentToolCatalogMethod,
5
+ AgentToolJsonSchema,
6
+ AgentToolSensitivity,
7
+ AgentToolSession,
8
+ AgentToolsProvider,
9
+ OpenAgentToolsSessionInput,
10
+ } from '@shipfox/api-integration-core-dto';
@@ -0,0 +1,78 @@
1
+ import type {CheckoutSpec} from '@shipfox/api-integration-core-dto';
2
+ import {redactCheckoutSpec} from './redact-checkout-spec.js';
3
+
4
+ describe('redactCheckoutSpec', () => {
5
+ it('masks the credential token while keeping the other fields intact', () => {
6
+ const expiresAt = new Date('2026-06-10T12:00:00.000Z');
7
+ const spec: CheckoutSpec = {
8
+ repositoryUrl: 'https://github.com/shipfox/platform.git',
9
+ ref: 'main',
10
+ credentials: {username: 'x-access-token', token: 'ghs_supersecret', expiresAt},
11
+ };
12
+
13
+ const redacted = redactCheckoutSpec(spec);
14
+
15
+ expect(redacted).toEqual({
16
+ repositoryUrl: 'https://github.com/shipfox/platform.git',
17
+ ref: 'main',
18
+ credentials: {username: 'x-access-token', token: '***', expiresAt},
19
+ });
20
+ });
21
+
22
+ it('does not mutate the original spec', () => {
23
+ const spec: CheckoutSpec = {
24
+ repositoryUrl: 'https://github.com/shipfox/platform.git',
25
+ ref: 'main',
26
+ credentials: {
27
+ username: 'x-access-token',
28
+ token: 'ghs_supersecret',
29
+ expiresAt: new Date('2026-06-10T12:00:00.000Z'),
30
+ },
31
+ };
32
+
33
+ redactCheckoutSpec(spec);
34
+
35
+ expect(spec.credentials?.token).toBe('ghs_supersecret');
36
+ });
37
+
38
+ it('returns the spec unchanged when there are no credentials and the url is clean', () => {
39
+ const spec: CheckoutSpec = {
40
+ repositoryUrl: 'https://gitea.local/gitea-owner/platform.git',
41
+ ref: 'main',
42
+ };
43
+
44
+ const redacted = redactCheckoutSpec(spec);
45
+
46
+ expect(redacted).toBe(spec);
47
+ });
48
+
49
+ it('strips credentials embedded in repositoryUrl as a defense in depth', () => {
50
+ const spec: CheckoutSpec = {
51
+ repositoryUrl: 'https://x-access-token:ghs_supersecret@github.com/shipfox/platform.git',
52
+ ref: 'main',
53
+ credentials: {
54
+ username: 'x-access-token',
55
+ token: 'ghs_supersecret',
56
+ expiresAt: new Date('2026-06-10T12:00:00.000Z'),
57
+ },
58
+ };
59
+
60
+ const redacted = redactCheckoutSpec(spec);
61
+
62
+ expect(redacted.repositoryUrl).toBe('https://github.com/shipfox/platform.git');
63
+ expect(redacted.repositoryUrl).not.toContain('ghs_supersecret');
64
+ expect(redacted.credentials?.token).toBe('***');
65
+ });
66
+
67
+ it('strips embedded credentials even when the spec has no credentials field', () => {
68
+ const spec: CheckoutSpec = {
69
+ repositoryUrl: 'https://x-access-token:ghs_supersecret@github.com/shipfox/platform.git',
70
+ ref: 'main',
71
+ };
72
+
73
+ const redacted = redactCheckoutSpec(spec);
74
+
75
+ expect(redacted.repositoryUrl).toBe('https://github.com/shipfox/platform.git');
76
+ expect(redacted.repositoryUrl).not.toContain('ghs_supersecret');
77
+ });
78
+ });
@@ -0,0 +1,20 @@
1
+ import type {CheckoutSpec} from '@shipfox/api-integration-core-dto';
2
+ import {REDACTION_PLACEHOLDER, stripUrlCredentials} from '@shipfox/redact';
3
+
4
+ /**
5
+ * Returns a copy of `spec` that is safe to log: the credential token is masked
6
+ * and any userinfo a provider mistakenly left in `repositoryUrl` is stripped.
7
+ * Returns the original spec unchanged when it has no credentials and the URL is
8
+ * already clean.
9
+ */
10
+ export function redactCheckoutSpec(spec: CheckoutSpec): CheckoutSpec {
11
+ const repositoryUrl = stripUrlCredentials(spec.repositoryUrl);
12
+ if (!spec.credentials) {
13
+ return repositoryUrl === spec.repositoryUrl ? spec : {...spec, repositoryUrl};
14
+ }
15
+ return {
16
+ ...spec,
17
+ repositoryUrl,
18
+ credentials: {...spec.credentials, token: REDACTION_PLACEHOLDER},
19
+ };
20
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ IntegrationCapabilityUnavailableError,
3
+ IntegrationProviderUnavailableError,
4
+ } from '#core/errors.js';
5
+ import {createIntegrationProviderRegistry} from './registry.js';
6
+
7
+ function sourceControlAdapter() {
8
+ return {
9
+ listRepositories: async () => {
10
+ await Promise.resolve();
11
+ return {repositories: [], nextCursor: null};
12
+ },
13
+ resolveRepository: async () => {
14
+ await Promise.resolve();
15
+ throw new Error('not used');
16
+ },
17
+ listFiles: async () => {
18
+ await Promise.resolve();
19
+ return {files: [], nextCursor: null};
20
+ },
21
+ fetchFile: async () => {
22
+ await Promise.resolve();
23
+ throw new Error('not used');
24
+ },
25
+ };
26
+ }
27
+
28
+ function agentToolsAdapter() {
29
+ return {
30
+ catalog: () => [],
31
+ selectionCatalog: () => ({selectors: []}),
32
+ openSession: async () => {
33
+ await Promise.resolve();
34
+ return {
35
+ call: async () => {
36
+ await Promise.resolve();
37
+ return {};
38
+ },
39
+ };
40
+ },
41
+ };
42
+ }
43
+
44
+ describe('integration provider registry', () => {
45
+ it('lists providers by capability', () => {
46
+ const registry = createIntegrationProviderRegistry([
47
+ {
48
+ provider: 'gitea',
49
+ displayName: 'Gitea',
50
+ adapters: {
51
+ source_control: sourceControlAdapter(),
52
+ },
53
+ },
54
+ {
55
+ provider: 'github',
56
+ displayName: 'GitHub',
57
+ },
58
+ ]);
59
+
60
+ const result = registry.list('source_control');
61
+
62
+ expect(result.map((provider) => provider.provider)).toEqual(['gitea']);
63
+ });
64
+
65
+ it('computes the agent tools capability from the adapter', () => {
66
+ const registry = createIntegrationProviderRegistry([
67
+ {
68
+ provider: 'github',
69
+ displayName: 'GitHub',
70
+ adapters: {
71
+ agent_tools: agentToolsAdapter(),
72
+ },
73
+ },
74
+ ]);
75
+
76
+ const result = registry.list('agent_tools');
77
+
78
+ expect(result.map((provider) => provider.provider)).toEqual(['github']);
79
+ expect(registry.get('github').capabilities).toEqual(['agent_tools']);
80
+ });
81
+
82
+ it('keeps provider sets isolated per registry instance', () => {
83
+ const emptyRegistry = createIntegrationProviderRegistry([]);
84
+ const giteaRegistry = createIntegrationProviderRegistry([
85
+ {
86
+ provider: 'gitea',
87
+ displayName: 'Gitea',
88
+ adapters: {
89
+ source_control: sourceControlAdapter(),
90
+ },
91
+ },
92
+ ]);
93
+
94
+ expect(giteaRegistry.get('gitea').provider).toBe('gitea');
95
+ expect(() => emptyRegistry.get('gitea')).toThrow(IntegrationProviderUnavailableError);
96
+ });
97
+
98
+ it('rejects source-control access for providers without the capability implementation', () => {
99
+ const registry = createIntegrationProviderRegistry([
100
+ {
101
+ provider: 'gitea',
102
+ displayName: 'Gitea',
103
+ },
104
+ ]);
105
+
106
+ const result = () => registry.getSourceControl('gitea');
107
+
108
+ expect(result).toThrow(IntegrationCapabilityUnavailableError);
109
+ });
110
+
111
+ it('rejects duplicate provider registrations', () => {
112
+ const result = () =>
113
+ createIntegrationProviderRegistry([
114
+ {
115
+ provider: 'gitea',
116
+ displayName: 'Gitea',
117
+ },
118
+ {
119
+ provider: 'gitea',
120
+ displayName: 'Gitea Duplicate',
121
+ },
122
+ ]);
123
+
124
+ expect(result).toThrow('Duplicate integration provider registered');
125
+ });
126
+
127
+ it('rejects invalid provider ids', () => {
128
+ const result = () =>
129
+ createIntegrationProviderRegistry([
130
+ {
131
+ provider: 'GitHub',
132
+ displayName: 'GitHub',
133
+ },
134
+ ]);
135
+
136
+ expect(result).toThrow('Invalid integration provider id');
137
+ });
138
+ });
@@ -0,0 +1,90 @@
1
+ import {isLowercaseAlphaSlug} from '@shipfox/regex';
2
+ import type {
3
+ IntegrationCapability,
4
+ IntegrationProvider,
5
+ IntegrationProviderAdapters,
6
+ IntegrationProviderKind,
7
+ RegisteredIntegrationProvider,
8
+ } from '#core/entities/provider.js';
9
+ import {
10
+ IntegrationCapabilityUnavailableError,
11
+ IntegrationProviderUnavailableError,
12
+ } from '#core/errors.js';
13
+
14
+ export interface IntegrationProviderRegistry {
15
+ list(capability?: IntegrationCapability | undefined): RegisteredIntegrationProvider[];
16
+ get(provider: IntegrationProviderKind): RegisteredIntegrationProvider;
17
+ getAdapter<Capability extends IntegrationCapability>(
18
+ provider: IntegrationProviderKind,
19
+ capability: Capability,
20
+ ): NonNullable<IntegrationProviderAdapters[Capability]>;
21
+ getSourceControl(
22
+ provider: IntegrationProviderKind,
23
+ ): NonNullable<IntegrationProviderAdapters['source_control']>;
24
+ }
25
+
26
+ export function createIntegrationProviderRegistry(
27
+ providers: IntegrationProvider[],
28
+ ): IntegrationProviderRegistry {
29
+ return new MapIntegrationProviderRegistry(providers);
30
+ }
31
+
32
+ class MapIntegrationProviderRegistry implements IntegrationProviderRegistry {
33
+ private readonly providers: Map<IntegrationProviderKind, RegisteredIntegrationProvider>;
34
+
35
+ constructor(providers: IntegrationProvider[]) {
36
+ this.providers = new Map();
37
+
38
+ for (const provider of providers) {
39
+ if (!isLowercaseAlphaSlug(provider.provider)) {
40
+ throw new Error(`Invalid integration provider id: ${provider.provider}`);
41
+ }
42
+ if (this.providers.has(provider.provider)) {
43
+ throw new Error(`Duplicate integration provider registered: ${provider.provider}`);
44
+ }
45
+
46
+ this.providers.set(provider.provider, normalizeProvider(provider));
47
+ }
48
+ }
49
+
50
+ list(capability?: IntegrationCapability | undefined): RegisteredIntegrationProvider[] {
51
+ const providers = [...this.providers.values()];
52
+ if (!capability) return providers;
53
+ return providers.filter((provider) => provider.capabilities.includes(capability));
54
+ }
55
+
56
+ get(provider: IntegrationProviderKind): RegisteredIntegrationProvider {
57
+ const resolved = this.providers.get(provider);
58
+ if (!resolved) throw new IntegrationProviderUnavailableError(provider);
59
+ return resolved;
60
+ }
61
+
62
+ getAdapter<Capability extends IntegrationCapability>(
63
+ provider: IntegrationProviderKind,
64
+ capability: Capability,
65
+ ): NonNullable<IntegrationProviderAdapters[Capability]> {
66
+ const resolved = this.get(provider);
67
+ const adapter = resolved.adapters[capability];
68
+ if (!adapter) {
69
+ throw new IntegrationCapabilityUnavailableError(capability, provider);
70
+ }
71
+ return adapter as NonNullable<IntegrationProviderAdapters[Capability]>;
72
+ }
73
+
74
+ getSourceControl(
75
+ provider: IntegrationProviderKind,
76
+ ): NonNullable<IntegrationProviderAdapters['source_control']> {
77
+ return this.getAdapter(provider, 'source_control');
78
+ }
79
+ }
80
+
81
+ function normalizeProvider(provider: IntegrationProvider): RegisteredIntegrationProvider {
82
+ const adapters = provider.adapters ?? {};
83
+ return {
84
+ ...provider,
85
+ adapters,
86
+ capabilities: Object.entries(adapters)
87
+ .filter(([, adapter]) => Boolean(adapter))
88
+ .map(([capability]) => capability as IntegrationCapability),
89
+ };
90
+ }
@@ -0,0 +1,17 @@
1
+ export type {
2
+ CheckoutCredentials,
3
+ CheckoutPermissions,
4
+ CheckoutSpec,
5
+ CreateCheckoutSpecInput,
6
+ FetchFileInput,
7
+ FileEntry,
8
+ FilePage,
9
+ FileSnapshot,
10
+ ListFilesInput,
11
+ ListRepositoriesInput,
12
+ RepositoryPage,
13
+ RepositorySnapshot,
14
+ RepositoryVisibility,
15
+ ResolveRepositoryInput,
16
+ SourceControlProvider,
17
+ } from '@shipfox/api-integration-core-dto';