feat(backend): add claude runtime, anthropic OAuth auth kind, and profile→runtime derivation

This commit is contained in:
Gabriel Brown
2026-07-11 10:42:48 -04:00
parent 76886621b5
commit 70396feccc
5 changed files with 81 additions and 4 deletions
@@ -24,6 +24,7 @@ const provider = v.union(
const authType = v.union( const authType = v.union(
v.literal('api_key'), v.literal('api_key'),
v.literal('opencode_auth_json'), v.literal('opencode_auth_json'),
v.literal('anthropic_oauth_json'),
v.literal('none'), v.literal('none'),
); );
@@ -24,6 +24,7 @@ const provider = v.union(
const authType = v.union( const authType = v.union(
v.literal('api_key'), v.literal('api_key'),
v.literal('opencode_auth_json'), v.literal('opencode_auth_json'),
v.literal('anthropic_oauth_json'),
v.literal('none'), v.literal('none'),
); );
+23
View File
@@ -0,0 +1,23 @@
import type { Doc } from './_generated/dataModel';
export type AgentRuntimeName = 'codex' | 'opencode' | 'claude';
// Which runtimes a provider profile can drive.
export const runtimesForProfile = (
profile: Pick<Doc<'aiProviderProfiles'>, 'provider' | 'authType'>,
): AgentRuntimeName[] => {
if (
profile.provider === 'opencode_openai_login' ||
profile.authType === 'opencode_auth_json'
) {
return ['codex']; // ChatGPT-login snapshot → Codex CLI only
}
if (profile.provider === 'anthropic') {
return profile.authType === 'anthropic_oauth_json'
? ['claude']
: ['claude', 'opencode'];
}
if (profile.provider === 'openai') return ['opencode', 'codex'];
// Every remaining API-key provider is OpenAI-compatible → OpenCode.
return ['opencode'];
};
+15 -4
View File
@@ -389,6 +389,7 @@ const applicationTables = {
authType: v.union( authType: v.union(
v.literal('api_key'), v.literal('api_key'),
v.literal('opencode_auth_json'), v.literal('opencode_auth_json'),
v.literal('anthropic_oauth_json'),
v.literal('none'), v.literal('none'),
), ),
encryptedSecret: v.optional(v.string()), encryptedSecret: v.optional(v.string()),
@@ -468,9 +469,14 @@ const applicationTables = {
spoonId: v.id('spoons'), spoonId: v.id('spoons'),
ownerId: v.id('users'), ownerId: v.id('users'),
enabled: v.boolean(), enabled: v.boolean(),
// Legacy records may contain openai_direct. New writes use opencode only. // Legacy records may contain openai_direct (deprecated, never written).
runtime: v.optional( runtime: v.optional(
v.union(v.literal('opencode'), v.literal('openai_direct')), v.union(
v.literal('openai_direct'),
v.literal('opencode'),
v.literal('codex'),
v.literal('claude'),
),
), ),
defaultBaseBranch: v.optional(v.string()), defaultBaseBranch: v.optional(v.string()),
branchPrefix: v.string(), branchPrefix: v.string(),
@@ -532,9 +538,14 @@ const applicationTables = {
v.literal('timed_out'), v.literal('timed_out'),
), ),
prompt: v.string(), prompt: v.string(),
// Legacy jobs may contain openai_direct. New jobs use opencode only. // Legacy jobs may contain openai_direct (deprecated, never written).
runtime: v.optional( runtime: v.optional(
v.union(v.literal('openai_direct'), v.literal('opencode')), v.union(
v.literal('openai_direct'),
v.literal('opencode'),
v.literal('codex'),
v.literal('claude'),
),
), ),
workspaceStatus: v.optional( workspaceStatus: v.optional(
v.union( v.union(
@@ -0,0 +1,41 @@
import { describe, expect, test } from 'vitest';
import { runtimesForProfile } from '../../convex/runtimeSupport';
describe('runtimesForProfile', () => {
test('anthropic + api_key drives claude and opencode', () => {
expect(
runtimesForProfile({ provider: 'anthropic', authType: 'api_key' }),
).toEqual(['claude', 'opencode']);
});
test('opencode_openai_login snapshot drives codex only', () => {
expect(
runtimesForProfile({
provider: 'opencode_openai_login',
authType: 'opencode_auth_json',
}),
).toEqual(['codex']);
});
test('anthropic + anthropic_oauth_json drives claude only', () => {
expect(
runtimesForProfile({
provider: 'anthropic',
authType: 'anthropic_oauth_json',
}),
).toEqual(['claude']);
});
test('openrouter + api_key drives opencode only', () => {
expect(
runtimesForProfile({ provider: 'openrouter', authType: 'api_key' }),
).toEqual(['opencode']);
});
test('openai + api_key drives opencode and codex', () => {
expect(
runtimesForProfile({ provider: 'openai', authType: 'api_key' }),
).toEqual(['opencode', 'codex']);
});
});