mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
feature/codex-cli
This commit is contained in:
@@ -33,9 +33,31 @@ export const DEFAULT_MODEL = 'claude-opus-4-5-20251101';
|
||||
* Formats a model name for display
|
||||
*/
|
||||
export function formatModelName(model: string): string {
|
||||
// Claude models
|
||||
if (model.includes('opus')) return 'Opus 4.5';
|
||||
if (model.includes('sonnet')) return 'Sonnet 4.5';
|
||||
if (model.includes('haiku')) return 'Haiku 4.5';
|
||||
|
||||
// Codex/GPT models
|
||||
if (model === 'gpt-5.2') return 'GPT-5.2';
|
||||
if (model === 'gpt-5.1-codex-max') return 'GPT-5.1 Max';
|
||||
if (model === 'gpt-5.1-codex') return 'GPT-5.1 Codex';
|
||||
if (model === 'gpt-5.1-codex-mini') return 'GPT-5.1 Mini';
|
||||
if (model === 'gpt-5.1') return 'GPT-5.1';
|
||||
if (model.startsWith('gpt-')) return model.toUpperCase();
|
||||
if (model.match(/^o\d/)) return model.toUpperCase(); // o1, o3, etc.
|
||||
|
||||
// Cursor models
|
||||
if (model === 'cursor-auto' || model === 'auto') return 'Cursor Auto';
|
||||
if (model === 'cursor-composer-1' || model === 'composer-1') return 'Composer 1';
|
||||
if (model.startsWith('cursor-sonnet')) return 'Cursor Sonnet';
|
||||
if (model.startsWith('cursor-opus')) return 'Cursor Opus';
|
||||
if (model.startsWith('cursor-gpt')) return model.replace('cursor-', '').replace('gpt-', 'GPT-');
|
||||
if (model.startsWith('cursor-gemini'))
|
||||
return model.replace('cursor-', 'Cursor ').replace('gemini', 'Gemini');
|
||||
if (model.startsWith('cursor-grok')) return 'Cursor Grok';
|
||||
|
||||
// Default: split by dash and capitalize
|
||||
return model.split('-').slice(1, 3).join(' ');
|
||||
}
|
||||
|
||||
|
||||
86
apps/ui/src/lib/codex-usage-format.ts
Normal file
86
apps/ui/src/lib/codex-usage-format.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { type CodexCreditsSnapshot, type CodexPlanType } from '@/store/app-store';
|
||||
|
||||
const WINDOW_DEFAULT_LABEL = 'Usage window';
|
||||
const RESET_LABEL = 'Resets';
|
||||
const UNKNOWN_LABEL = 'Unknown';
|
||||
const UNAVAILABLE_LABEL = 'Unavailable';
|
||||
const UNLIMITED_LABEL = 'Unlimited';
|
||||
const AVAILABLE_LABEL = 'Available';
|
||||
const NONE_LABEL = 'None';
|
||||
const DAY_UNIT = 'day';
|
||||
const HOUR_UNIT = 'hour';
|
||||
const MINUTE_UNIT = 'min';
|
||||
const WINDOW_SUFFIX = 'window';
|
||||
const MINUTES_PER_HOUR = 60;
|
||||
const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
|
||||
const MILLISECONDS_PER_SECOND = 1000;
|
||||
const SESSION_HOURS = 5;
|
||||
const DAYS_PER_WEEK = 7;
|
||||
const SESSION_WINDOW_MINS = SESSION_HOURS * MINUTES_PER_HOUR;
|
||||
const WEEKLY_WINDOW_MINS = DAYS_PER_WEEK * MINUTES_PER_DAY;
|
||||
const SESSION_TITLE = 'Session Usage';
|
||||
const SESSION_SUBTITLE = '5-hour rolling window';
|
||||
const WEEKLY_TITLE = 'Weekly';
|
||||
const WEEKLY_SUBTITLE = 'All models';
|
||||
const FALLBACK_TITLE = 'Usage Window';
|
||||
const PLAN_TYPE_LABELS: Record<CodexPlanType, string> = {
|
||||
free: 'Free',
|
||||
plus: 'Plus',
|
||||
pro: 'Pro',
|
||||
team: 'Team',
|
||||
business: 'Business',
|
||||
enterprise: 'Enterprise',
|
||||
edu: 'Education',
|
||||
unknown: UNKNOWN_LABEL,
|
||||
};
|
||||
|
||||
export function formatCodexWindowDuration(minutes: number | null): string {
|
||||
if (!minutes || minutes <= 0) return WINDOW_DEFAULT_LABEL;
|
||||
if (minutes % MINUTES_PER_DAY === 0) {
|
||||
const days = minutes / MINUTES_PER_DAY;
|
||||
return `${days} ${DAY_UNIT}${days === 1 ? '' : 's'} ${WINDOW_SUFFIX}`;
|
||||
}
|
||||
if (minutes % MINUTES_PER_HOUR === 0) {
|
||||
const hours = minutes / MINUTES_PER_HOUR;
|
||||
return `${hours} ${HOUR_UNIT}${hours === 1 ? '' : 's'} ${WINDOW_SUFFIX}`;
|
||||
}
|
||||
return `${minutes} ${MINUTE_UNIT} ${WINDOW_SUFFIX}`;
|
||||
}
|
||||
|
||||
export type CodexWindowLabel = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
isPrimary: boolean;
|
||||
};
|
||||
|
||||
export function getCodexWindowLabel(windowDurationMins: number | null): CodexWindowLabel {
|
||||
if (windowDurationMins === SESSION_WINDOW_MINS) {
|
||||
return { title: SESSION_TITLE, subtitle: SESSION_SUBTITLE, isPrimary: true };
|
||||
}
|
||||
if (windowDurationMins === WEEKLY_WINDOW_MINS) {
|
||||
return { title: WEEKLY_TITLE, subtitle: WEEKLY_SUBTITLE, isPrimary: false };
|
||||
}
|
||||
return {
|
||||
title: FALLBACK_TITLE,
|
||||
subtitle: formatCodexWindowDuration(windowDurationMins),
|
||||
isPrimary: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatCodexResetTime(resetsAt: number | null): string | null {
|
||||
if (!resetsAt) return null;
|
||||
const date = new Date(resetsAt * MILLISECONDS_PER_SECOND);
|
||||
return `${RESET_LABEL} ${date.toLocaleString()}`;
|
||||
}
|
||||
|
||||
export function formatCodexPlanType(plan: CodexPlanType | null): string {
|
||||
if (!plan) return UNKNOWN_LABEL;
|
||||
return PLAN_TYPE_LABELS[plan] ?? plan;
|
||||
}
|
||||
|
||||
export function formatCodexCredits(snapshot: CodexCreditsSnapshot | null): string {
|
||||
if (!snapshot) return UNAVAILABLE_LABEL;
|
||||
if (snapshot.unlimited) return UNLIMITED_LABEL;
|
||||
if (snapshot.balance) return snapshot.balance;
|
||||
return snapshot.hasCredits ? AVAILABLE_LABEL : NONE_LABEL;
|
||||
}
|
||||
@@ -682,6 +682,51 @@ export interface ElectronAPI {
|
||||
user: string | null;
|
||||
error?: string;
|
||||
}>;
|
||||
getCursorStatus: () => Promise<{
|
||||
success: boolean;
|
||||
installed: boolean;
|
||||
version: string | null;
|
||||
path: string | null;
|
||||
auth: {
|
||||
authenticated: boolean;
|
||||
method: string;
|
||||
};
|
||||
installCommand?: string;
|
||||
loginCommand?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
getCodexStatus: () => Promise<{
|
||||
success: boolean;
|
||||
installed: boolean;
|
||||
version: string | null;
|
||||
path: string | null;
|
||||
auth: {
|
||||
authenticated: boolean;
|
||||
method: string;
|
||||
hasApiKey: boolean;
|
||||
};
|
||||
installCommand?: string;
|
||||
loginCommand?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
installCodex: () => Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
authCodex: () => Promise<{
|
||||
success: boolean;
|
||||
requiresManualAuth?: boolean;
|
||||
command?: string;
|
||||
error?: string;
|
||||
message?: string;
|
||||
}>;
|
||||
verifyCodexAuth: (authMethod?: 'cli' | 'api_key') => Promise<{
|
||||
success: boolean;
|
||||
authenticated: boolean;
|
||||
error?: string;
|
||||
details?: string;
|
||||
}>;
|
||||
onInstallProgress?: (callback: (progress: any) => void) => () => void;
|
||||
onAuthProgress?: (callback: (progress: any) => void) => () => void;
|
||||
};
|
||||
|
||||
@@ -1180,6 +1180,51 @@ export class HttpApiClient implements ElectronAPI {
|
||||
`/api/setup/cursor-permissions/example${profileId ? `?profileId=${profileId}` : ''}`
|
||||
),
|
||||
|
||||
// Codex CLI methods
|
||||
getCodexStatus: (): Promise<{
|
||||
success: boolean;
|
||||
status?: string;
|
||||
installed?: boolean;
|
||||
method?: string;
|
||||
version?: string;
|
||||
path?: string;
|
||||
auth?: {
|
||||
authenticated: boolean;
|
||||
method: string;
|
||||
hasAuthFile?: boolean;
|
||||
hasOAuthToken?: boolean;
|
||||
hasApiKey?: boolean;
|
||||
hasStoredApiKey?: boolean;
|
||||
hasEnvApiKey?: boolean;
|
||||
};
|
||||
error?: string;
|
||||
}> => this.get('/api/setup/codex-status'),
|
||||
|
||||
installCodex: (): Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}> => this.post('/api/setup/install-codex'),
|
||||
|
||||
authCodex: (): Promise<{
|
||||
success: boolean;
|
||||
token?: string;
|
||||
requiresManualAuth?: boolean;
|
||||
terminalOpened?: boolean;
|
||||
command?: string;
|
||||
error?: string;
|
||||
message?: string;
|
||||
output?: string;
|
||||
}> => this.post('/api/setup/auth-codex'),
|
||||
|
||||
verifyCodexAuth: (
|
||||
authMethod?: 'cli' | 'api_key'
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
authenticated: boolean;
|
||||
error?: string;
|
||||
}> => this.post('/api/setup/verify-codex-auth', { authMethod }),
|
||||
|
||||
onInstallProgress: (callback: (progress: unknown) => void) => {
|
||||
return this.subscribeToEvent('agent:stream', callback);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import type { ModelAlias } from '@/store/app-store';
|
||||
import type { ModelAlias, ModelProvider } from '@/store/app-store';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
@@ -14,6 +14,33 @@ export function modelSupportsThinking(_model?: ModelAlias | string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the provider from a model string
|
||||
* Mirrors the logic in apps/server/src/providers/provider-factory.ts
|
||||
*/
|
||||
export function getProviderFromModel(model?: string): ModelProvider {
|
||||
if (!model) return 'claude';
|
||||
|
||||
// Check for Cursor models (cursor- prefix)
|
||||
if (model.startsWith('cursor-') || model.startsWith('cursor:')) {
|
||||
return 'cursor';
|
||||
}
|
||||
|
||||
// Check for Codex/OpenAI models (gpt- prefix or o-series)
|
||||
const CODEX_MODEL_PREFIXES = ['gpt-'];
|
||||
const OPENAI_O_SERIES_PATTERN = /^o\d/;
|
||||
if (
|
||||
CODEX_MODEL_PREFIXES.some((prefix) => model.startsWith(prefix)) ||
|
||||
OPENAI_O_SERIES_PATTERN.test(model) ||
|
||||
model.startsWith('codex:')
|
||||
) {
|
||||
return 'codex';
|
||||
}
|
||||
|
||||
// Default to Claude
|
||||
return 'claude';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display name for a model
|
||||
*/
|
||||
@@ -22,6 +49,15 @@ export function getModelDisplayName(model: ModelAlias | string): string {
|
||||
haiku: 'Claude Haiku',
|
||||
sonnet: 'Claude Sonnet',
|
||||
opus: 'Claude Opus',
|
||||
// Codex models
|
||||
'gpt-5.2': 'GPT-5.2',
|
||||
'gpt-5.1-codex-max': 'GPT-5.1 Codex Max',
|
||||
'gpt-5.1-codex': 'GPT-5.1 Codex',
|
||||
'gpt-5.1-codex-mini': 'GPT-5.1 Codex Mini',
|
||||
'gpt-5.1': 'GPT-5.1',
|
||||
// Cursor models (common ones)
|
||||
'cursor-auto': 'Cursor Auto',
|
||||
'cursor-composer-1': 'Composer 1',
|
||||
};
|
||||
return displayNames[model] || model;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user