Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/adapters/opencode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ describe('opencode adapter', () => {
expect(models.every(m => m.startsWith('opencode-go/'))).toBe(true);
});

it('exposes direct OpenAI models and defaults', () => {
const openAiSection = opencodeAdapter.defaultSection('openai');
const models = opencodeAdapter.modelsForProvider('openai');

expect(openAiSection.provider).toBe('openai');
expect(openAiSection.build_model).toBe('openai/gpt-5.6-terra');
expect(openAiSection.plan_model).toBe('openai/gpt-5.6-sol');
expect(openAiSection.agents).toEqual([
{ template: 'recon', name: 'recon', model: 'openai/gpt-5.6-luna' },
{ template: 'oracle', name: 'oracle', model: 'openai/gpt-5.6-sol' },
{ template: 'architect', name: 'architect', model: 'openai/gpt-5.6-sol' },
]);
expect(models.every(model => model.startsWith('openai/'))).toBe(true);
});

it('mergeHarnessConfig writes opencode.json', () => {
opencodeAdapter.mergeHarnessConfig(section(), basePath);
const path = join(basePath, 'opencode.json');
Expand Down
18 changes: 18 additions & 0 deletions src/adapters/opencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const OPENCODE_PROVIDERS: { name: string; value: OpenCodeProvider }[] = [
{ name: 'OpenCode Zen', value: 'opencode-zen' },
{ name: 'OpenCode Go', value: 'opencode-go' },
{ name: 'Amazon Bedrock', value: 'amazon-bedrock' },
{ name: 'OpenAI', value: 'openai' },
];

const PROVIDER_MODELS: Record<OpenCodeProvider, readonly string[]> = {
Expand Down Expand Up @@ -55,6 +56,16 @@ const PROVIDER_MODELS: Record<OpenCodeProvider, readonly string[]> = {
'amazon-bedrock/anthropic-claude-haiku-4-5',
'amazon-bedrock/anthropic-claude-opus-4-7',
],
openai: [
'openai/gpt-5.6-sol',
'openai/gpt-5.6-terra',
'openai/gpt-5.6-luna',
'openai/gpt-5.5',
'openai/gpt-5.4',
'openai/gpt-5.4-mini',
'openai/gpt-5.4-nano',
'openai/gpt-5.3-codex-spark',
],
};

const DEFAULT_MODELS: Record<OpenCodeProvider, { build: string; plan: string; recon: string; oracle: string; architect: string }> = {
Expand All @@ -79,6 +90,13 @@ const DEFAULT_MODELS: Record<OpenCodeProvider, { build: string; plan: string; re
build: 'amazon-bedrock/anthropic-claude-sonnet-4-5',
plan: 'amazon-bedrock/anthropic-claude-haiku-4-5',
},
openai: {
recon: 'openai/gpt-5.6-luna',
oracle: 'openai/gpt-5.6-sol',
architect: 'openai/gpt-5.6-sol',
build: 'openai/gpt-5.6-terra',
plan: 'openai/gpt-5.6-sol',
},
};

export const opencodeAdapter: HarnessAdapter = {
Expand Down
23 changes: 23 additions & 0 deletions src/commands/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,29 @@ describe('init', () => {
expect(existsSync(join(opencodeDir, 'agent/recon.md'))).toBe(true);
});

test('prints the OpenAI authentication command after initialization', async () => {
inquirerAnswers = {
provider: 'openai',
build_model: 'openai/gpt-5.6-terra',
plan_model: 'openai/gpt-5.6-sol',
recon: 'openai/gpt-5.6-luna',
oracle: 'openai/gpt-5.6-sol',
architect: 'openai/gpt-5.6-sol',
confirm: true,
};
const logs: string[] = [];
const originalLog = console.log;
console.log = (...args: unknown[]) => logs.push(args.join(' '));

try {
await init({ harness: 'opencode' });
} finally {
console.log = originalLog;
}

expect(logs).toContain(' opencode auth login --provider openai # connect your OpenAI account');
});

test('throws when --yes is used without --harness', async () => {
await expect(init({ yes: true })).rejects.toThrow(
'`--yes` requires `--harness` (claude, opencode, or codex).'
Expand Down
3 changes: 3 additions & 0 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,8 @@ export async function init(options: InitOptions): Promise<void> {

console.log(`\nAtelier initialized for ${harness}.`);
console.log('\nNext step:');
if (harness === 'opencode' && section.provider === 'openai') {
console.log(' opencode auth login --provider openai # connect your OpenAI account');
}
console.log(' npx skills add martinffx/atelier # install skills');
}
4 changes: 4 additions & 0 deletions src/services/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ describe('prompt', () => {
expect(() => guardProvider('claude' as unknown as import('../types.js').Provider)).toThrow();
});

it('accepts OpenAI as an opencode provider', () => {
expect(guardProvider('openai')).toBe('openai');
});

it('formatFileList renders exists marker', () => {
const files = [
{ path: '~/.claude/settings.json', exists: true },
Expand Down
2 changes: 1 addition & 1 deletion src/services/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function formatFileList(files: { path: string; exists: boolean }[]): stri
}

export function guardProvider(provider: Provider | undefined): OpenCodeConfig['provider'] {
if (!provider || !['opencode-zen', 'opencode-go', 'amazon-bedrock'].includes(provider)) {
if (!provider || !['opencode-zen', 'opencode-go', 'amazon-bedrock', 'openai'].includes(provider)) {
throw new InvalidConfigError('A valid provider must be selected for OpenCode');
}
return provider as OpenCodeConfig['provider'];
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type Harness = typeof HARNESS_NAMES[number];
export type AgentName = typeof AGENT_NAMES[number];

export type Provider = 'anthropic' | 'opencode-zen' | 'opencode-go' | 'amazon-bedrock' | 'openai';
export type OpenCodeProvider = 'opencode-zen' | 'opencode-go' | 'amazon-bedrock';
export type OpenCodeProvider = 'opencode-zen' | 'opencode-go' | 'amazon-bedrock' | 'openai';

export interface ProviderChoice {
name: string;
Expand Down
12 changes: 12 additions & 0 deletions src/utils/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ describe('config', () => {
expect(architect?.model).toBe('opencode-go/deepseek-v4-pro');
});

test('opencode config accepts the OpenAI provider', async () => {
const { validateConfig } = await import('./config.js');
const opencode = opencodeAdapter.defaultSection('openai');

expect(validateConfig({
version: '1.0.0',
skills_source: 'martinffx/atelier',
skills_path: '~/.agents/skills',
opencode,
}).opencode?.provider).toBe('openai');
});

test('opencode adapter defaultSection defaults to opencode-zen when no provider given', () => {
const section = opencodeAdapter.defaultSection();

Expand Down
2 changes: 1 addition & 1 deletion src/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const SimpleConfigSchema = z.object({
agents: z.array(AgentSchema).min(1),
});

export const OPENCODE_PROVIDERS = ['opencode-zen', 'opencode-go', 'amazon-bedrock'] as const;
export const OPENCODE_PROVIDERS = ['opencode-zen', 'opencode-go', 'amazon-bedrock', 'openai'] as const;

export const OpenCodeConfigSchema = z.object({
provider: z.enum(OPENCODE_PROVIDERS),
Expand Down