'use node'; import { ConvexError, v } from 'convex/values'; import type { Doc } from './_generated/dataModel'; import { internal } from './_generated/api'; import { action } from './_generated/server'; import { decryptSecret } from './secretCrypto'; type ClaimedJob = { job: Doc<'agentJobs'>; spoon: Doc<'spoons'> | null; aiSettings: Doc<'userAiSettings'> | null; agentSettings: Doc<'spoonAgentSettings'> | null; secrets: Doc<'spoonSecrets'>[]; }; type WorkerClaim = { job: Doc<'agentJobs'>; spoon: Doc<'spoons'>; openai: { apiKey: string; model: string; reasoningEffort: Doc<'agentJobs'>['reasoningEffort']; }; agentSettings: Doc<'spoonAgentSettings'> | null; github: { installationId?: string; }; secrets: { name: string; value: string }[]; }; const requireWorkerToken = (workerToken: string) => { const expected = process.env.SPOON_WORKER_TOKEN?.trim(); if (!expected) throw new ConvexError('SPOON_WORKER_TOKEN is not configured.'); if (workerToken !== expected) throw new ConvexError('Invalid worker token.'); }; export const claimNextForWorker = action({ args: { workerId: v.string(), workerToken: v.string(), }, handler: async (ctx, args): Promise => { requireWorkerToken(args.workerToken); const claimed = (await ctx.runMutation( internal.agentJobs.claimNextInternal, { workerId: args.workerId, }, )) as ClaimedJob | null; if (!claimed) return null; if (!claimed.spoon) { throw new ConvexError('Claimed job points at a missing Spoon.'); } if (!claimed.aiSettings?.encryptedApiKey) { throw new ConvexError( 'OpenAI is not configured for this user. Add an OpenAI API key in settings.', ); } return { job: claimed.job, spoon: claimed.spoon, openai: { apiKey: decryptSecret(claimed.aiSettings.encryptedApiKey), model: claimed.job.model, reasoningEffort: claimed.job.reasoningEffort, }, agentSettings: claimed.agentSettings, github: { installationId: claimed.job.githubInstallationId ?? claimed.spoon.githubInstallationId ?? process.env.GITHUB_APP_INSTALLATION_ID, }, secrets: claimed.secrets.map((secret: Doc<'spoonSecrets'>) => ({ name: secret.name, value: decryptSecret(secret.encryptedValue), })), }; }, });