Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/src/lib/api-docs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2701,6 +2701,9 @@ export const AppConnections = {
FLYIO: {
accessToken: "The Access Token used to access fly.io."
},
DEVIN: {
apiKey: "The Devin service-user API key used to authenticate against the Devin v3 API."
},
GITLAB: {
instanceUrl: "The GitLab instance URL to connect with.",
accessToken: "The Access Token used to access GitLab.",
Expand Down Expand Up @@ -3010,6 +3013,9 @@ export const SecretSyncs = {
FLYIO: {
appId: "The ID of the Fly.io app to sync secrets to."
},
DEVIN: {
orgId: "The Devin organization ID to sync secrets to."
},
GITLAB: {
projectId: "The GitLab Project ID to sync secrets to.",
projectName: "The GitLab Project Name to sync secrets to.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
SanitizedDatabricksConnectionSchema
} from "@app/services/app-connection/databricks";
import { DbtConnectionListItemSchema, SanitizedDbtConnectionSchema } from "@app/services/app-connection/dbt";
import { DevinConnectionListItemSchema, SanitizedDevinConnectionSchema } from "@app/services/app-connection/devin";
import {
DigitalOceanConnectionListItemSchema,
SanitizedDigitalOceanConnectionSchema
Expand Down Expand Up @@ -229,6 +230,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedDbtConnectionSchema.options,
...SanitizedOpenRouterConnectionSchema.options,
...SanitizedAnthropicConnectionSchema.options,
...SanitizedDevinConnectionSchema.options,
...SanitizedAzureEntraIdConnectionSchema.options,
...SanitizedVenafiConnectionSchema.options,
...SanitizedExternalInfisicalConnectionSchema.options,
Expand Down Expand Up @@ -293,7 +295,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
ExternalInfisicalConnectionListItemSchema,
DopplerConnectionListItemSchema,
NetScalerConnectionListItemSchema,
AnthropicConnectionListItemSchema
AnthropicConnectionListItemSchema,
DevinConnectionListItemSchema
]);

export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateDevinConnectionSchema,
SanitizedDevinConnectionSchema,
UpdateDevinConnectionSchema
} from "@app/services/app-connection/devin";

import { registerAppConnectionEndpoints } from "./app-connection-endpoints";

export const registerDevinConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Devin,
server,
sanitizedResponseSchema: SanitizedDevinConnectionSchema,
createSchema: CreateDevinConnectionSchema,
updateSchema: UpdateDevinConnectionSchema
});
};
4 changes: 3 additions & 1 deletion backend/src/server/routes/v1/app-connection-routers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { registerCircleCIConnectionRouter } from "./circleci-connection-router";
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerDbtConnectionRouter } from "./dbt-connection-router";
import { registerDevinConnectionRouter } from "./devin-connection-router";
import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router";
import { registerDNSMadeEasyConnectionRouter } from "./dns-made-easy-connection-router";
import { registerDopplerConnectionRouter } from "./doppler-connection-router";
Expand Down Expand Up @@ -118,5 +119,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.ExternalInfisical]: registerExternalInfisicalConnectionRouter,
[AppConnection.Doppler]: registerDopplerConnectionRouter,
[AppConnection.NetScaler]: registerNetScalerConnectionRouter,
[AppConnection.Anthropic]: registerAnthropicConnectionRouter
[AppConnection.Anthropic]: registerAnthropicConnectionRouter,
[AppConnection.Devin]: registerDevinConnectionRouter
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CreateDevinSyncSchema, DevinSyncSchema, UpdateDevinSyncSchema } from "@app/services/secret-sync/devin";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";

import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";

export const registerDevinSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Devin,
server,
responseSchema: DevinSyncSchema,
createSchema: CreateDevinSyncSchema,
updateSchema: UpdateDevinSyncSchema
});
4 changes: 3 additions & 1 deletion backend/src/server/routes/v1/secret-sync-routers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { registerCircleCISyncRouter } from "./circleci-sync-router";
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
import { registerDevinSyncRouter } from "./devin-sync-router";
import { registerDigitalOceanAppPlatformSyncRouter } from "./digital-ocean-app-platform-sync-router";
import { registerExternalInfisicalSyncRouter } from "./external-infisical-sync-router";
import { registerFlyioSyncRouter } from "./flyio-sync-router";
Expand Down Expand Up @@ -77,5 +78,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.OctopusDeploy]: registerOctopusDeploySyncRouter,
[SecretSync.CircleCI]: registerCircleCISyncRouter,
[SecretSync.AzureEntraIdScim]: registerAzureEntraIdScimSyncRouter,
[SecretSync.ExternalInfisical]: registerExternalInfisicalSyncRouter
[SecretSync.ExternalInfisical]: registerExternalInfisicalSyncRouter,
[SecretSync.Devin]: registerDevinSyncRouter
};
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
CloudflareWorkersSyncSchema
} from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas";
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
import { DevinSyncListItemSchema, DevinSyncSchema } from "@app/services/secret-sync/devin";
import {
DigitalOceanAppPlatformSyncListItemSchema,
DigitalOceanAppPlatformSyncSchema
Expand Down Expand Up @@ -104,7 +105,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
OctopusDeploySyncSchema,
CircleCISyncSchema,
AzureEntraIdScimSyncSchema,
ExternalInfisicalSyncSchema
ExternalInfisicalSyncSchema,
DevinSyncSchema
]);

const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
Expand Down Expand Up @@ -144,7 +146,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
OctopusDeploySyncListItemSchema,
CircleCISyncListItemSchema,
AzureEntraIdScimSyncListItemSchema,
ExternalInfisicalSyncListItemSchema
ExternalInfisicalSyncListItemSchema,
DevinSyncListItemSchema
]);

export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
Expand Down
3 changes: 2 additions & 1 deletion backend/src/services/app-connection/app-connection-enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export enum AppConnection {
ExternalInfisical = "external-infisical",
Doppler = "doppler",
NetScaler = "netscaler",
Anthropic = "anthropic"
Anthropic = "anthropic",
Devin = "devin"
}

export enum AWSRegion {
Expand Down
5 changes: 5 additions & 0 deletions backend/src/services/app-connection/app-connection-fns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import {
validateDatabricksConnectionCredentials
} from "./databricks/databricks-connection-fns";
import { DbtConnectionMethod, getDbtConnectionListItem, validateDbtConnectionCredentials } from "./dbt";
import { DevinConnectionMethod, getDevinConnectionListItem, validateDevinConnectionCredentials } from "./devin";
import {
DigitalOceanConnectionMethod,
getDigitalOceanConnectionListItem,
Expand Down Expand Up @@ -289,6 +290,7 @@ export const listAppConnectionOptions = (projectType?: ProjectType) => {
getSmbConnectionListItem(),
getOpenRouterConnectionListItem(),
getAnthropicConnectionListItem(),
getDevinConnectionListItem(),
getCircleCIConnectionListItem(),
getAzureEntraIdConnectionListItem(),
getVenafiConnectionListItem(),
Expand Down Expand Up @@ -437,6 +439,7 @@ export const validateAppConnectionCredentials = async (
[AppConnection.SMB]: validateSmbConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OpenRouter]: validateOpenRouterConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Anthropic]: validateAnthropicConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Devin]: validateDevinConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.CircleCI]: validateCircleCIConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.AzureEntraId]: validateAzureEntraIdConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Venafi]: validateVenafiConnectionCredentials as TAppConnectionCredentialsValidator,
Expand Down Expand Up @@ -532,6 +535,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case OctopusDeployConnectionMethod.ApiKey:
case OpenRouterConnectionMethod.ApiKey:
case AnthropicConnectionMethod.ApiKey:
case DevinConnectionMethod.ApiKey:
return "API Key";
case ChefConnectionMethod.UserKey:
return "User Key";
Expand Down Expand Up @@ -648,6 +652,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.SMB]: platformManagedCredentialsNotSupported,
[AppConnection.OpenRouter]: platformManagedCredentialsNotSupported,
[AppConnection.Anthropic]: platformManagedCredentialsNotSupported,
[AppConnection.Devin]: platformManagedCredentialsNotSupported,
[AppConnection.CircleCI]: platformManagedCredentialsNotSupported,
[AppConnection.AzureEntraId]: platformManagedCredentialsNotSupported,
[AppConnection.Venafi]: platformManagedCredentialsNotSupported,
Expand Down
6 changes: 4 additions & 2 deletions backend/src/services/app-connection/app-connection-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.ExternalInfisical]: "Infisical",
[AppConnection.Doppler]: "Doppler",
[AppConnection.NetScaler]: "NetScaler",
[AppConnection.Anthropic]: "Anthropic"
[AppConnection.Anthropic]: "Anthropic",
[AppConnection.Devin]: "Devin"
};

export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
Expand Down Expand Up @@ -117,5 +118,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.ExternalInfisical]: AppConnectionPlanType.Regular,
[AppConnection.Doppler]: AppConnectionPlanType.Regular,
[AppConnection.NetScaler]: AppConnectionPlanType.Regular,
[AppConnection.Anthropic]: AppConnectionPlanType.Regular
[AppConnection.Anthropic]: AppConnectionPlanType.Regular,
[AppConnection.Devin]: AppConnectionPlanType.Regular
};
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
import { databricksConnectionService } from "./databricks/databricks-connection-service";
import { ValidateDbtConnectionCredentialsSchema } from "./dbt";
import { dbtConnectionService } from "./dbt/dbt-connection-service";
import { ValidateDevinConnectionCredentialsSchema } from "./devin";
import { ValidateDigitalOceanConnectionCredentialsSchema } from "./digital-ocean";
import { digitalOceanAppPlatformConnectionService } from "./digital-ocean/digital-ocean-connection-service";
import { ValidateDNSMadeEasyConnectionCredentialsSchema } from "./dns-made-easy/dns-made-easy-connection-schema";
Expand Down Expand Up @@ -222,7 +223,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.ExternalInfisical]: ValidateExternalInfisicalConnectionCredentialsSchema,
[AppConnection.Doppler]: ValidateDopplerConnectionCredentialsSchema,
[AppConnection.NetScaler]: ValidateNetScalerConnectionCredentialsSchema,
[AppConnection.Anthropic]: ValidateAnthropicConnectionCredentialsSchema
[AppConnection.Anthropic]: ValidateAnthropicConnectionCredentialsSchema,
[AppConnection.Devin]: ValidateDevinConnectionCredentialsSchema
};

export const appConnectionServiceFactory = ({
Expand Down
14 changes: 12 additions & 2 deletions backend/src/services/app-connection/app-connection-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ import {
TDbtConnectionInput,
TValidateDbtConnectionCredentialsSchema
} from "./dbt";
import {
TDevinConnection,
TDevinConnectionConfig,
TDevinConnectionInput,
TValidateDevinConnectionCredentialsSchema
} from "./devin";
import {
TDigitalOceanConnection,
TDigitalOceanConnectionConfig,
Expand Down Expand Up @@ -394,6 +400,7 @@ export type TAppConnection = { id: string } & (
| TDopplerConnection
| TNetScalerConnection
| TAnthropicConnection
| TDevinConnection
);

export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
Expand Down Expand Up @@ -462,6 +469,7 @@ export type TAppConnectionInput = { id: string } & (
| TDopplerConnectionInput
| TNetScalerConnectionInput
| TAnthropicConnectionInput
| TDevinConnectionInput
);

export type TSqlConnectionInput =
Expand Down Expand Up @@ -558,7 +566,8 @@ export type TAppConnectionConfig =
| TExternalInfisicalConnectionConfig
| TDopplerConnectionConfig
| TNetScalerConnectionConfig
| TAnthropicConnectionConfig;
| TAnthropicConnectionConfig
| TDevinConnectionConfig;

export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema
Expand Down Expand Up @@ -617,7 +626,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateExternalInfisicalConnectionCredentialsSchema
| TValidateDopplerConnectionCredentialsSchema
| TValidateNetScalerConnectionCredentialsSchema
| TValidateAnthropicConnectionCredentialsSchema;
| TValidateAnthropicConnectionCredentialsSchema
| TValidateDevinConnectionCredentialsSchema;

export type TListAwsConnectionKmsKeys = {
connectionId: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum DevinConnectionMethod {
ApiKey = "api-key"
}
41 changes: 41 additions & 0 deletions backend/src/services/app-connection/devin/devin-connection-fns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { AxiosError } from "axios";

import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";

import { DevinConnectionMethod } from "./devin-connection-enums";
import { TDevinConnectionConfig } from "./devin-connection-types";

export const getDevinConnectionListItem = () => {
return {
name: "Devin" as const,
app: AppConnection.Devin as const,
methods: Object.values(DevinConnectionMethod) as [DevinConnectionMethod.ApiKey]
};
};

export const validateDevinConnectionCredentials = async (config: TDevinConnectionConfig) => {
const { apiKey } = config.credentials;

try {
await request.get(`${IntegrationUrls.DEVIN_API_URL}/v3/self`, {
Comment thread
x032205 marked this conversation as resolved.
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json"
}
});
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
});
}

return config.credentials;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import z from "zod";

import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";

import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
import { DevinConnectionMethod } from "./devin-connection-enums";

export const DevinConnectionApiKeyCredentialsSchema = z.object({
apiKey: z
.string()
.trim()
.min(1, "API Key required")
.max(1000)
.startsWith("cog_", "API Key must start with 'cog_'")
.describe(AppConnections.CREDENTIALS.DEVIN.apiKey)
});

const BaseDevinConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Devin) });

export const DevinConnectionSchema = BaseDevinConnectionSchema.extend({
method: z.literal(DevinConnectionMethod.ApiKey),
credentials: DevinConnectionApiKeyCredentialsSchema
});

export const SanitizedDevinConnectionSchema = z.discriminatedUnion("method", [
BaseDevinConnectionSchema.extend({
method: z.literal(DevinConnectionMethod.ApiKey),
credentials: DevinConnectionApiKeyCredentialsSchema.pick({})
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Devin]} (API Key)` }))
]);

export const ValidateDevinConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z.literal(DevinConnectionMethod.ApiKey).describe(AppConnections.CREATE(AppConnection.Devin).method),
credentials: DevinConnectionApiKeyCredentialsSchema.describe(AppConnections.CREATE(AppConnection.Devin).credentials)
})
]);

export const CreateDevinConnectionSchema = ValidateDevinConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Devin)
);

export const UpdateDevinConnectionSchema = z
.object({
credentials: DevinConnectionApiKeyCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Devin).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Devin));

export const DevinConnectionListItemSchema = z
.object({
name: z.literal("Devin"),
app: z.literal(AppConnection.Devin),
methods: z.nativeEnum(DevinConnectionMethod).array()
})
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Devin] }));
Loading
Loading