feat(backend): add claude runtime, anthropic OAuth auth kind, and profile→runtime derivation
This commit is contained in:
@@ -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'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
};
|
||||||
@@ -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']);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user