From e10bb41663c6fb8e21ee67e523c820f9c37263ec Mon Sep 17 00:00:00 2001 From: Tim Field Date: Wed, 11 Feb 2026 12:18:07 +1300 Subject: [PATCH 1/3] Support triggering via automation Simplify attachment fetching --- package-lock.json | 11 +- package.json | 6 +- src/events/createDevinSession.ts | 204 ++++--------------------------- src/lib/buildSessionPrompt.ts | 106 ++++------------ src/lib/devin.ts | 186 ++++++++++++++++++++++++++++ src/lib/records.ts | 6 +- src/views/SendToDevinButton.tsx | 18 +-- 7 files changed, 248 insertions(+), 289 deletions(-) create mode 100644 src/lib/devin.ts diff --git a/package-lock.json b/package-lock.json index bd1163d..87b0259 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,14 @@ { "name": "aha-develop.assign-devin-ai", - "version": "1.0.2", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aha-develop.assign-devin-ai", - "version": "1.0.2", + "version": "1.1.0", "license": "MIT", "dependencies": { - "base-64": "^1.0.0", "zod": "^4.3.5" }, "devDependencies": { @@ -582,12 +581,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "license": "MIT" - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", diff --git a/package.json b/package.json index 54ee352..d5b4530 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "aha-develop.assign-devin-ai", "description": "Devin.ai", - "version": "1.0.2", + "version": "1.1.0", "author": "Aha! (support@aha.io)", "repository": { "type": "git", @@ -32,7 +32,8 @@ "entryPoint": "src/events/createDevinSession.ts", "handles": [ "aha-develop.assign-devin-ai.createDevinSession" - ] + ], + "automatable": true } }, "settings": { @@ -104,7 +105,6 @@ } }, "dependencies": { - "base-64": "^1.0.0", "zod": "^4.3.5" } } diff --git a/src/events/createDevinSession.ts b/src/events/createDevinSession.ts index 04ae1fc..81bbd1a 100644 --- a/src/events/createDevinSession.ts +++ b/src/events/createDevinSession.ts @@ -1,30 +1,16 @@ import * as z from "zod/mini"; -import base64 from "base-64"; -import { DEVIN_API_URL, EXTENSION_ID, EXTENSION_NAME } from "../lib/constants"; +import { buildSessionPrompt } from "../lib/buildSessionPrompt"; +import { EXTENSION_ID, EXTENSION_NAME, SESSION_FIELD } from "../lib/constants"; +import type { DevinSessionData } from "../lib/devin"; +import { createSession, uploadAttachments } from "../lib/devin"; import { callEventHandler, registerEventHandler } from "../lib/events"; -import type { RecordAttachment } from "../lib/buildSessionPrompt"; import { ExtensionSettingsSchema, parseTags } from "../lib/settings"; -const AttachmentSchema = z.object({ - fileName: z.string(), - contentType: z.string(), - downloadUrl: z.string(), - base64Data: z.optional(z.string()), -}); - -const DEVIN_SESSIONS_URL = `${DEVIN_API_URL}sessions`; -const DEVIN_ATTACHMENTS_URL = `${DEVIN_API_URL}attachments`; - const CreateSessionSchema = z.object({ - title: z.string(), - prompt: z.string(), - attachments: z.optional(z.array(AttachmentSchema)), -}); - -const DevinResponseSchema = z.object({ - session_id: z.string(), - url: z.string(), - is_new_session: z.nullable(z.boolean()), + record: z.object({ + typename: z.enum(["Feature", "Requirement"]), + id: z.string(), + }), }); export const DevinSessionDataSchema = z.object({ @@ -37,160 +23,6 @@ export type DevinSessionData = z.infer; export type CreateSession = z.infer; -// Converts a string to bytes (replacement for TextEncoder which isn't available) -function stringToBytes(str: string): Uint8Array { - const bytes = new Uint8Array(str.length); - for (let i = 0; i < str.length; i++) { - bytes[i] = str.charCodeAt(i); - } - return bytes; -} - -// We need to resort to this manual multipart/form-data construction -// as the lambda environment doesn't support Blob or FormData APIs. -function buildMultipartBody( - fileBytes: Uint8Array, - fileName: string, - contentType: string, - boundary: string, -): Uint8Array { - const header = - `--${boundary}\r\n` + - `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` + - `Content-Type: ${contentType}\r\n\r\n`; - const footer = `\r\n--${boundary}--\r\n`; - - const headerBytes = stringToBytes(header); - const footerBytes = stringToBytes(footer); - - const body = new Uint8Array( - headerBytes.length + fileBytes.length + footerBytes.length, - ); - body.set(headerBytes, 0); - body.set(fileBytes, headerBytes.length); - body.set(footerBytes, headerBytes.length + fileBytes.length); - - return body; -} - -async function uploadAttachment( - attachment: RecordAttachment, - apiKey: string, -): Promise { - if (!attachment.base64Data) { - throw new Error(`Attachment ${attachment.fileName} is missing base64 data`); - } - - const fileBytes = stringToBytes(base64.decode(attachment.base64Data)); - const boundary = `----FormBoundary${Date.now()}`; - const body = buildMultipartBody( - fileBytes, - attachment.fileName, - attachment.contentType, - boundary, - ); - - const uploadResponse = await fetch(DEVIN_ATTACHMENTS_URL, { - method: "POST", - headers: { - Authorization: `Bearer ${apiKey}`, - "Content-Type": `multipart/form-data; boundary=${boundary}`, - }, - body: body as unknown as BodyInit, - }); - - const bodyText = await uploadResponse.text().catch(() => ""); - - if (!uploadResponse.ok) { - const message = bodyText - ? `${EXTENSION_NAME} attachment upload failed: ${bodyText}` - : `${EXTENSION_NAME} attachment upload failed (${uploadResponse.status})`; - throw new Error(message); - } - - const trimmed = bodyText.trim(); - if (!trimmed) { - throw new Error(`${EXTENSION_NAME} attachment upload returned no URL`); - } - - return trimmed; -} - -async function uploadAttachments( - attachments: RecordAttachment[], - apiKey: string, -): Promise { - if (!attachments.length) { - return []; - } - - const uploads = attachments.map((attachment) => - uploadAttachment(attachment, apiKey), - ); - - return Promise.all(uploads); -} - -async function createSession({ - prompt, - title, - tags, - playbookId, - apiKey, -}: { - prompt: string; - title: string; - tags?: string[]; - playbookId?: string; - apiKey: string; -}): Promise { - const sessionPayload: Record = { - title, - prompt, - idempotent: true, - }; - - if (tags?.length) { - sessionPayload.tags = tags; - } - if (playbookId) { - sessionPayload.playbook_id = playbookId; - } - - const response = await fetch(DEVIN_SESSIONS_URL, { - method: "POST", - headers: { - Authorization: `Bearer ${apiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(sessionPayload), - }); - - const data = await response.json().catch(() => ({})); - - if (!response.ok) { - const message = - typeof data === "object" && data && "detail" in data - ? String((data as { detail: unknown }).detail) - : `${EXTENSION_NAME} API error (${response.status})`; - throw new Error(message); - } - - const result = DevinResponseSchema.safeParse(data); - if (!result.success) { - console.error( - `Invalid Devin API response ${JSON.stringify(data)} ${result.error}`, - ); - throw new Error(`Invalid response from ${EXTENSION_NAME} API`); - } - - return { - sessionId: result.data.session_id, - sessionUrl: result.data.url, - assignedAt: new Date().toISOString(), - }; -} - export async function createDevinSession( args: CreateSession, ): Promise { @@ -207,7 +39,11 @@ registerEventHandler({ schema: CreateSessionSchema, resultSchema: DevinSessionDataSchema, handler: async (args, { settings: rawSettings }) => { - const { prompt, title, attachments = [] } = args; + const { record } = args; + + console.log( + `Received createDevinSession event for record ${record.typename} with ID ${record.id}`, + ); const parsedSettings = ExtensionSettingsSchema.safeParse(rawSettings); if (!parsedSettings.success) { @@ -228,6 +64,18 @@ registerEventHandler({ throw new Error(`${EXTENSION_NAME} API key is not configured`); } + const repository = settings.repository?.trim(); + const baseBranch = settings.baseBranch?.trim() || "main"; + + const { title, prompt, attachments, model } = await buildSessionPrompt( + record, + { + repository, + baseBranch, + customInstructions: settings.customInstructions, + }, + ); + const attachmentUrls = await uploadAttachments(attachments, apiKey); const attachmentLines = attachmentUrls .map((url) => `ATTACHMENT:"${url}"`) @@ -245,6 +93,8 @@ registerEventHandler({ apiKey, }); + await model.setExtensionField(EXTENSION_ID, SESSION_FIELD, result); + return result; }, }); diff --git a/src/lib/buildSessionPrompt.ts b/src/lib/buildSessionPrompt.ts index 49de55d..99659e2 100644 --- a/src/lib/buildSessionPrompt.ts +++ b/src/lib/buildSessionPrompt.ts @@ -1,11 +1,7 @@ -import base64 from "base-64"; -import { RecordType } from "./records"; - export interface RecordAttachment { fileName: string; contentType: string; downloadUrl: string; - base64Data?: string; } interface RawAttachment { @@ -14,56 +10,11 @@ interface RawAttachment { downloadUrl: string; } -async function fetchImageAsBase64( - attachment: RawAttachment, -): Promise { - try { - const response = await fetch(attachment.downloadUrl); - if (!response.ok) { - console.warn( - `Failed to fetch attachment ${attachment.fileName}: ${response.status}`, - ); - return null; - } - - const arrayBuffer = await response.arrayBuffer(); - const base64Data = base64.encode( - new Uint8Array(arrayBuffer).reduce( - (data, byte) => data + String.fromCharCode(byte), - "", - ), - ); - - return { - fileName: attachment.fileName, - contentType: attachment.contentType, - downloadUrl: attachment.downloadUrl, - base64Data, - }; - } catch (error) { - console.warn(`Error fetching attachment ${attachment.fileName}: ${error}`); - return null; - } -} - -async function fetchAttachmentsAsBase64( - attachments: RawAttachment[] | undefined, -): Promise { - if (!attachments?.length) { - return []; - } - - const results = await Promise.all( - attachments.map((att) => fetchImageAsBase64(att)), - ); - - return results.filter((att): att is RecordAttachment => att !== null); -} - export interface DevinSessionPayload { title: string; prompt: string; attachments: RecordAttachment[]; + model: FetchedFeature | FetchedRequirement; } export interface BuildSessionOptions { @@ -109,9 +60,7 @@ type FetchedRequirement = Aha.Requirement & { }>; }; -function dedupeRawAttachments( - attachments: RawAttachment[] = [], -): RawAttachment[] { +function dedupeAttachments(attachments: RawAttachment[] = []): RawAttachment[] { const byUrl = new Map(); for (const attachment of attachments) { if (!byUrl.has(attachment.downloadUrl)) { @@ -126,7 +75,7 @@ function dedupeRawAttachments( return Array.from(byUrl.values()); } -async function describeFeature(record: RecordType) { +export async function describeFeature(id: string) { const feature = (await aha.models.Feature.select( "id", "name", @@ -135,16 +84,14 @@ async function describeFeature(record: RecordType) { ) .merge({ description: aha.models.Note.select("markdownBody").merge({ - attachments: aha.models.Attachment.select( - "fileName", - "contentType", - "downloadUrl", - ), + attachments: aha.models.Attachment.select("fileName", "contentType", { + downloadUrl: { withToken: true }, + }), }), tasks: aha.models.Task.select("name", "body"), requirements: aha.models.Requirement.select("name", "referenceNum"), }) - .find(record.referenceNum)) as FetchedFeature | null; + .find(id)) as FetchedFeature | null; if (!feature) { throw new Error("Failed to load feature details"); @@ -168,21 +115,21 @@ async function describeFeature(record: RecordType) { const context = `### Description\n\n${ feature.description?.markdownBody || "No description provided." }\n\n${requirementsBlock}\n\n${todosBlock}\n\n**Aha! Reference:** [${ - record.referenceNum + feature.referenceNum }](${feature.path})\n`; - const rawAttachments = dedupeRawAttachments(feature.description?.attachments); - const attachments = await fetchAttachmentsAsBase64(rawAttachments); + const attachments = dedupeAttachments(feature.description?.attachments); return { context, title: feature.name, referenceNum: feature.referenceNum, attachments, + model: feature, }; } -async function describeRequirement(record: RecordType) { +export async function describeRequirement(id: string) { const requirement = (await aha.models.Requirement.select( "id", "name", @@ -191,24 +138,20 @@ async function describeRequirement(record: RecordType) { ) .merge({ description: aha.models.Note.select("markdownBody").merge({ - attachments: aha.models.Attachment.select( - "fileName", - "contentType", - "downloadUrl", - ), + attachments: aha.models.Attachment.select("fileName", "contentType", { + downloadUrl: { withToken: true }, + }), }), tasks: aha.models.Task.select("name", "body"), feature: aha.models.Feature.select("name", "referenceNum").merge({ description: aha.models.Note.select("markdownBody").merge({ - attachments: aha.models.Attachment.select( - "fileName", - "contentType", - "downloadUrl", - ), + attachments: aha.models.Attachment.select("fileName", "contentType", { + downloadUrl: { withToken: true }, + }), }), }), }) - .find(record.referenceNum)) as FetchedRequirement | null; + .find(id)) as FetchedRequirement | null; if (!requirement) { throw new Error("Failed to load requirement details"); @@ -225,34 +168,34 @@ async function describeRequirement(record: RecordType) { }\n\n## Feature ${requirement.feature?.referenceNum}\n\n${ requirement.feature?.description?.markdownBody || "No feature description provided." - }\n\n${todosBlock}\n\n**Aha! Reference:** [${record.referenceNum}](${ + }\n\n${todosBlock}\n\n**Aha! Reference:** [${requirement.referenceNum}](${ requirement.path })\n`; - const rawAttachments = dedupeRawAttachments([ + const attachments = dedupeAttachments([ ...(requirement.description?.attachments || []), ...(requirement.feature?.description?.attachments || []), ]); - const attachments = await fetchAttachmentsAsBase64(rawAttachments); return { context, title: requirement.name, referenceNum: requirement.referenceNum, attachments, + model: requirement, }; } export async function buildSessionPrompt( - record: RecordType, + record: { id: string; typename: "Feature" | "Requirement" }, options: BuildSessionOptions, ): Promise { const { customInstructions, repository, baseBranch = "main" } = options; const describe = record.typename === "Feature" - ? await describeFeature(record) - : await describeRequirement(record); + ? await describeFeature(record.id) + : await describeRequirement(record.id); const header = `You are being assigned the Aha! ${record.typename.toLowerCase()} ${ describe.referenceNum @@ -285,5 +228,6 @@ export async function buildSessionPrompt( title, prompt, attachments: describe.attachments, + model: describe.model, }; } diff --git a/src/lib/devin.ts b/src/lib/devin.ts new file mode 100644 index 0000000..2c7a953 --- /dev/null +++ b/src/lib/devin.ts @@ -0,0 +1,186 @@ +import * as z from "zod/mini"; +import { RecordAttachment } from "./buildSessionPrompt"; +import { DEVIN_API_URL, EXTENSION_NAME } from "./constants"; + +const DEVIN_SESSIONS_URL = `${DEVIN_API_URL}sessions`; +const DEVIN_ATTACHMENTS_URL = `${DEVIN_API_URL}attachments`; + +// Converts a string to bytes (replacement for TextEncoder which isn't available) +function stringToBytes(str: string): Uint8Array { + const bytes = new Uint8Array(str.length); + for (let i = 0; i < str.length; i++) { + bytes[i] = str.charCodeAt(i); + } + return bytes; +} + +// We need to resort to this manual multipart/form-data construction +// as the lambda environment doesn't support Blob or FormData APIs. +function buildMultipartBody( + fileBytes: Uint8Array, + fileName: string, + contentType: string, + boundary: string, +): Uint8Array { + const header = + `--${boundary}\r\n` + + `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` + + `Content-Type: ${contentType}\r\n\r\n`; + const footer = `\r\n--${boundary}--\r\n`; + + const headerBytes = stringToBytes(header); + const footerBytes = stringToBytes(footer); + + const body = new Uint8Array( + headerBytes.length + fileBytes.length + footerBytes.length, + ); + body.set(headerBytes, 0); + body.set(fileBytes, headerBytes.length); + body.set(footerBytes, headerBytes.length + fileBytes.length); + + return body; +} + +export async function uploadAttachment( + attachment: RecordAttachment, + apiKey: string, +): Promise { + const response = await fetch(attachment.downloadUrl); + + if (!response.ok) { + console.warn( + `Failed to fetch attachment ${attachment.fileName}: ${response.status} ${response.statusText}`, + ); + throw new Error( + `Failed to fetch attachment ${attachment.fileName}: ${response.status}`, + ); + } + + const arrayBuffer = await response.arrayBuffer(); + const fileBytes = new Uint8Array(arrayBuffer); + + const boundary = `----FormBoundary${Date.now()}`; + const body = buildMultipartBody( + fileBytes, + attachment.fileName, + attachment.contentType, + boundary, + ); + + const uploadResponse = await fetch(DEVIN_ATTACHMENTS_URL, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": `multipart/form-data; boundary=${boundary}`, + }, + body: body as unknown as BodyInit, + }); + + const bodyText = await uploadResponse.text().catch(() => ""); + + if (!uploadResponse.ok) { + const message = bodyText + ? `${EXTENSION_NAME} attachment upload failed: ${bodyText}` + : `${EXTENSION_NAME} attachment upload failed (${uploadResponse.status})`; + throw new Error(message); + } + + const trimmed = bodyText.trim(); + if (!trimmed) { + throw new Error(`${EXTENSION_NAME} attachment upload returned no URL`); + } + + return trimmed; +} + +export async function uploadAttachments( + attachments: RecordAttachment[], + apiKey: string, +): Promise { + if (!attachments.length) { + return []; + } + + const uploads = attachments.map((attachment) => + uploadAttachment(attachment, apiKey), + ); + + return Promise.all(uploads); +} + +export interface DevinSessionData { + sessionId: string; + sessionUrl: string; + assignedAt: string; +} + +export async function createSession({ + prompt, + title, + tags, + playbookId, + apiKey, +}: { + prompt: string; + title: string; + tags?: string[]; + playbookId?: string; + apiKey: string; +}): Promise { + const DevinResponseSchema = z.object({ + session_id: z.string(), + url: z.string(), + is_new_session: z.nullable(z.boolean()), + }); + + const sessionPayload: Record = { + title, + prompt, + idempotent: true, + }; + + if (tags?.length) { + sessionPayload.tags = tags; + } + if (playbookId) { + sessionPayload.playbook_id = playbookId; + } + + console.log( + `Creating Devin session with payload: ${JSON.stringify(sessionPayload, null, 2)}`, + ); + // throw new Error("WIP Testing"); + + const response = await fetch(DEVIN_SESSIONS_URL, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(sessionPayload), + }); + + const data = await response.json().catch(() => ({})); + + if (!response.ok) { + const message = + typeof data === "object" && data && "detail" in data + ? String((data as { detail: unknown }).detail) + : `${EXTENSION_NAME} API error (${response.status})`; + throw new Error(message); + } + + const result = DevinResponseSchema.safeParse(data); + if (!result.success) { + console.error( + `Invalid Devin API response ${JSON.stringify(data)} ${result.error}`, + ); + throw new Error(`Invalid response from ${EXTENSION_NAME} API`); + } + + return { + sessionId: result.data.session_id, + sessionUrl: result.data.url, + assignedAt: new Date().toISOString(), + }; +} diff --git a/src/lib/records.ts b/src/lib/records.ts index 375fa90..65dd152 100644 --- a/src/lib/records.ts +++ b/src/lib/records.ts @@ -1,13 +1,13 @@ export type FeatureRecord = Pick< Aha.Feature, - "typename" | "referenceNum" | "setExtensionField" | "getExtensionField" + "typename" | "id" | "referenceNum" | "setExtensionField" | "getExtensionField" > & { typename: "Feature"; }; export type RequirementRecord = Pick< Aha.Requirement, - "typename" | "referenceNum" | "setExtensionField" | "getExtensionField" + "typename" | "id" | "referenceNum" | "setExtensionField" | "getExtensionField" > & { typename: "Requirement"; }; @@ -19,7 +19,7 @@ export function isAssignableRecord(record: unknown): record is RecordType { !!record && (record as { typename?: string }).typename !== undefined && (["Feature", "Requirement"] as string[]).includes( - (record as { typename: string }).typename + (record as { typename: string }).typename, ) && typeof (record as FeatureRecord).referenceNum === "string" ); diff --git a/src/views/SendToDevinButton.tsx b/src/views/SendToDevinButton.tsx index 33f1300..3f843b0 100644 --- a/src/views/SendToDevinButton.tsx +++ b/src/views/SendToDevinButton.tsx @@ -4,8 +4,7 @@ import { DevinSessionData, DevinSessionDataSchema, } from "../events/createDevinSession"; -import { buildSessionPrompt } from "../lib/buildSessionPrompt"; -import { EXTENSION_ID, SESSION_FIELD } from "../lib/constants"; +import { SESSION_FIELD } from "../lib/constants"; import { isAssignableRecord, RecordType } from "../lib/records"; import { ExtensionSettings, ExtensionSettingsSchema } from "../lib/settings"; import { Icon } from "./Icon"; @@ -41,29 +40,16 @@ const SendToDevinButton: React.FC = ({ ); const handleClick = async () => { - const repository = settings.repository?.trim(); - const baseBranch = settings.baseBranch?.trim() || "main"; - setStatus("loading"); setMessage("Gathering context..."); try { - const { title, prompt, attachments } = await buildSessionPrompt(record, { - customInstructions: settings.customInstructions, - repository, - baseBranch, - }); - setMessage(`Creating Devin session...`); const session = await createDevinSession({ - title, - prompt, - attachments, + record, }); - await record.setExtensionField(EXTENSION_ID, SESSION_FIELD, session); - setStatus("success"); setMessage( `Success. Devin has started work on this ${record.typename.toLowerCase()}.`, From d032fd90a87bd664fa39c99b4fc73905021e715d Mon Sep 17 00:00:00 2001 From: Tim Field Date: Wed, 11 Feb 2026 12:22:04 +1300 Subject: [PATCH 2/3] remove debug logs --- src/lib/devin.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/devin.ts b/src/lib/devin.ts index 2c7a953..ea87bad 100644 --- a/src/lib/devin.ts +++ b/src/lib/devin.ts @@ -146,11 +146,6 @@ export async function createSession({ sessionPayload.playbook_id = playbookId; } - console.log( - `Creating Devin session with payload: ${JSON.stringify(sessionPayload, null, 2)}`, - ); - // throw new Error("WIP Testing"); - const response = await fetch(DEVIN_SESSIONS_URL, { method: "POST", headers: { From 31be65bca982be82f0cd3d851f37d05ba8e470cd Mon Sep 17 00:00:00 2001 From: Tim Field Date: Wed, 11 Feb 2026 15:31:36 +1300 Subject: [PATCH 3/3] Remove duplicate type --- src/events/createDevinSession.ts | 1 - src/lib/devin.ts | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/events/createDevinSession.ts b/src/events/createDevinSession.ts index 81bbd1a..e271119 100644 --- a/src/events/createDevinSession.ts +++ b/src/events/createDevinSession.ts @@ -1,7 +1,6 @@ import * as z from "zod/mini"; import { buildSessionPrompt } from "../lib/buildSessionPrompt"; import { EXTENSION_ID, EXTENSION_NAME, SESSION_FIELD } from "../lib/constants"; -import type { DevinSessionData } from "../lib/devin"; import { createSession, uploadAttachments } from "../lib/devin"; import { callEventHandler, registerEventHandler } from "../lib/events"; import { ExtensionSettingsSchema, parseTags } from "../lib/settings"; diff --git a/src/lib/devin.ts b/src/lib/devin.ts index ea87bad..64550a0 100644 --- a/src/lib/devin.ts +++ b/src/lib/devin.ts @@ -1,6 +1,7 @@ import * as z from "zod/mini"; import { RecordAttachment } from "./buildSessionPrompt"; import { DEVIN_API_URL, EXTENSION_NAME } from "./constants"; +import { DevinSessionData } from "../events/createDevinSession"; const DEVIN_SESSIONS_URL = `${DEVIN_API_URL}sessions`; const DEVIN_ATTACHMENTS_URL = `${DEVIN_API_URL}attachments`; @@ -108,12 +109,6 @@ export async function uploadAttachments( return Promise.all(uploads); } -export interface DevinSessionData { - sessionId: string; - sessionUrl: string; - assignedAt: string; -} - export async function createSession({ prompt, title,