Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -3008,6 +3011,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 @@ -225,6 +226,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedDbtConnectionSchema.options,
...SanitizedOpenRouterConnectionSchema.options,
...SanitizedAnthropicConnectionSchema.options,
...SanitizedDevinConnectionSchema.options,
...SanitizedAzureEntraIdConnectionSchema.options,
...SanitizedVenafiConnectionSchema.options,
...SanitizedExternalInfisicalConnectionSchema.options,
Expand Down Expand Up @@ -287,7 +289,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
VenafiConnectionListItemSchema,
ExternalInfisicalConnectionListItemSchema,
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 { registerExternalInfisicalConnectionRouter } from "./external-infisical-connection-router";
Expand Down Expand Up @@ -116,5 +117,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Venafi]: registerVenafiConnectionRouter,
[AppConnection.ExternalInfisical]: registerExternalInfisicalConnectionRouter,
[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 @@ -54,7 +54,8 @@ export enum AppConnection {
Venafi = "venafi",
ExternalInfisical = "external-infisical",
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 @@ -288,6 +289,7 @@ export const listAppConnectionOptions = (projectType?: ProjectType) => {
getSmbConnectionListItem(),
getOpenRouterConnectionListItem(),
getAnthropicConnectionListItem(),
getDevinConnectionListItem(),
getCircleCIConnectionListItem(),
getAzureEntraIdConnectionListItem(),
getVenafiConnectionListItem(),
Expand Down Expand Up @@ -435,6 +437,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 @@ -529,6 +532,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 @@ -643,6 +647,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 @@ -56,7 +56,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Venafi]: "Venafi TLS Protect Cloud",
[AppConnection.ExternalInfisical]: "Infisical",
[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 @@ -115,5 +116,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Venafi]: AppConnectionPlanType.Regular,
[AppConnection.ExternalInfisical]: 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 @@ -219,7 +220,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Venafi]: ValidateVenafiConnectionCredentialsSchema,
[AppConnection.ExternalInfisical]: ValidateExternalInfisicalConnectionCredentialsSchema,
[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 @@ -387,6 +393,7 @@ export type TAppConnection = { id: string } & (
| TExternalInfisicalConnection
| TNetScalerConnection
| TAnthropicConnection
| TDevinConnection
);

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

export type TSqlConnectionInput =
Expand Down Expand Up @@ -549,7 +557,8 @@ export type TAppConnectionConfig =
| TVenafiConnectionConfig
| TExternalInfisicalConnectionConfig
| TNetScalerConnectionConfig
| TAnthropicConnectionConfig;
| TAnthropicConnectionConfig
| TDevinConnectionConfig;

export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema
Expand Down Expand Up @@ -607,7 +616,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateVenafiConnectionCredentialsSchema
| TValidateExternalInfisicalConnectionCredentialsSchema
| 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