112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
'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;
|
|
aiProviderProfile: Doc<'aiProviderProfiles'> | 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'];
|
|
};
|
|
aiProviderProfile?: {
|
|
id: string;
|
|
name: string;
|
|
provider: Doc<'aiProviderProfiles'>['provider'];
|
|
authType: Doc<'aiProviderProfiles'>['authType'];
|
|
secret?: string;
|
|
baseUrl?: string;
|
|
model: string;
|
|
reasoningEffort: Doc<'aiProviderProfiles'>['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<WorkerClaim | null> => {
|
|
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.aiProviderProfile) {
|
|
throw new ConvexError(
|
|
'AI is not configured for this user. Add an AI provider in settings.',
|
|
);
|
|
}
|
|
if (
|
|
claimed.aiProviderProfile.authType !== 'none' &&
|
|
!claimed.aiProviderProfile.encryptedSecret
|
|
) {
|
|
throw new ConvexError('Selected AI provider is missing credentials.');
|
|
}
|
|
const profile = claimed.aiProviderProfile;
|
|
return {
|
|
job: claimed.job,
|
|
spoon: claimed.spoon,
|
|
openai: {
|
|
model: profile.defaultModel,
|
|
reasoningEffort: profile.reasoningEffort,
|
|
},
|
|
aiProviderProfile: {
|
|
id: profile._id,
|
|
name: profile.name,
|
|
provider: profile.provider,
|
|
authType: profile.authType,
|
|
secret: profile.encryptedSecret
|
|
? decryptSecret(profile.encryptedSecret)
|
|
: undefined,
|
|
baseUrl: profile.baseUrl,
|
|
model: profile.defaultModel,
|
|
reasoningEffort: profile.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),
|
|
})),
|
|
};
|
|
},
|
|
});
|