Skip to content
Open
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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@

## Getting Started

1. **Set Keys:**
- Add `OPENAI_API_KEY` or `TOGETHER_API_KEY` in `www/config.env`.
1. **Connect OpenAI (ChatGPT subscription):**
- Start the app and click "Connect OpenAI" in the UI to authorize via OAuth.
- This uses the same Codex OAuth flow as OpenCode.
- Make sure port 1455 is available for the local OAuth callback.
2. **Problems Setup:**

- Put your Hacker Cup-format problems into `./PROBLEMS/` (see examples)
Expand All @@ -54,7 +56,7 @@

5. **Models & Config:**

- Default model: GPT-4o-mini
- Default model: Codex (ChatGPT subscription)
- See `www/app/config.ts` to:
- Switch between different LLM models
- Adjust agent settings and parameters
Expand Down
3 changes: 2 additions & 1 deletion www/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.next/
config.env
node_modules
node_modules
.stackfish/
12 changes: 12 additions & 0 deletions www/app/api/openai/authorize/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NextResponse } from 'next/server';
import { startOpenAIAuthorize } from '../../../services/openaiOAuth';

export async function POST() {
try {
const authorization = await startOpenAIAuthorize();
return NextResponse.json(authorization);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to start OpenAI authorization';
return NextResponse.json({ error: message }, { status: 500 });
}
}
7 changes: 7 additions & 0 deletions www/app/api/openai/logout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NextResponse } from 'next/server';
import { clearOpenAIAuth } from '../../../services/openaiOAuth';

export async function POST() {
await clearOpenAIAuth();
return NextResponse.json({ success: true });
}
9 changes: 9 additions & 0 deletions www/app/api/openai/status/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NextResponse } from 'next/server';
import { getOpenAIAuthStatus } from '../../../services/openaiOAuth';

export const dynamic = 'force-dynamic';

export async function GET() {
const status = await getOpenAIAuthStatus();
return NextResponse.json(status);
}
59 changes: 34 additions & 25 deletions www/app/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,66 @@ import { Model } from '../types/models';
// RECOMMENDED:specify yours CLOUD EXECUTE URL after deploying ./cloud-run-worker to google cloud run
export const CLOUD_EXECUTE_URL = "https://cloud-run-worker-313568160682.us-central1.run.app/compute";

// If use OpenAI models, please specify OPENAI_API_KEY= in config.env file
// OpenAI models use ChatGPT subscription OAuth (Codex)
// If use qwq or llama models, please specify together.ai TOGETHER_API_KEY= in config.env file

// Defines how many parallel LLM calls to launch to generate synthetic tests for a problem
export const syntheticTestCallsPerModel: Record<Model, number> = {
'o1-preview': 0,
'o1-mini': 0,
'gpt-4o': 0,
'gpt-4o-mini': 1,
'gpt-5.3-codex': 1,
'gpt-5.2-codex': 0,
'gpt-5.2': 0,
'gpt-5.1-codex': 0,
'gpt-5.1-codex-mini': 0,
'gpt-5.1-codex-max': 0,
'qwq-32b-preview': 0,
'llama-3.3-70b': 0,
}

export const postSyntheticTestCallsPerModel: Record<Model, number> = {
'o1-preview': 0,
'o1-mini': 0,
'gpt-4o': 0,
'gpt-4o-mini': 1,
'gpt-5.3-codex': 1,
'gpt-5.2-codex': 0,
'gpt-5.2': 0,
'gpt-5.1-codex': 0,
'gpt-5.1-codex-mini': 0,
'gpt-5.1-codex-max': 0,
'qwq-32b-preview': 0,
'llama-3.3-70b': 0,
}

// Defines how many parallel LLM calls to launch to generate a hypothesis (aka. attack vector)
export const attackVectorCallsPerModel: Record<Model, number> = {
'o1-preview': 0,
'o1-mini': 0,
'gpt-4o': 0,
'gpt-4o-mini': 1,
'gpt-5.3-codex': 1,
'gpt-5.2-codex': 0,
'gpt-5.2': 0,
'gpt-5.1-codex': 0,
'gpt-5.1-codex-mini': 0,
'gpt-5.1-codex-max': 0,
'qwq-32b-preview': 0,
'llama-3.3-70b': 0,
}

// Defines how many parallel agents to launch to:
// After hypothesis is generated, how many agents should write a solution code
export const postAttackVectorSolutionCallsPerModel: Record<Model, number> = {
'o1-preview': 0,
'o1-mini': 0,
'gpt-4o': 0,
'gpt-4o-mini': 1,
'gpt-5.3-codex': 1,
'gpt-5.2-codex': 0,
'gpt-5.2': 0,
'gpt-5.1-codex': 0,
'gpt-5.1-codex-mini': 0,
'gpt-5.1-codex-max': 0,
'qwq-32b-preview': 0,
'llama-3.3-70b': 0,
}

// Defines how many parallel agents to launch to:
// Generate a solution directly, without a hypothesis and tests steps
export const directSolutionCallsPerModel: Record<Model, number> = {
'o1-preview': 0,
'o1-mini': 0,
'gpt-4o': 0,
'gpt-4o-mini': 0,
'gpt-5.3-codex': 0,
'gpt-5.2-codex': 0,
'gpt-5.2': 0,
'gpt-5.1-codex': 0,
'gpt-5.1-codex-mini': 0,
'gpt-5.1-codex-max': 0,
'qwq-32b-preview': 0,
'llama-3.3-70b': 0,
}
Expand All @@ -64,14 +74,14 @@ export const directSolutionCallsPerModel: Record<Model, number> = {
// If only one output is possible, we can verify test cases by simply comparing the strings
// If more than one output is possible, we need to use LLM to verify if our solution is correct
// The IS_ONLY_ONE_OUTPUT_VALID_MODEL model reads the problem statement and determines if only one output is possible
export const IS_ONLY_ONE_OUTPUT_VALID_MODEL: Model = 'gpt-4o';
export const IS_ONLY_ONE_OUTPUT_VALID_MODEL: Model = 'gpt-5.3-codex';

// In case if multiple outputs are possible, we use LLM to guess if the provided output seems correct
export const IS_VALID_OUTPUT_MODEL: Model = 'gpt-4o';
export const IS_VALID_OUTPUT_MODEL: Model = 'gpt-5.3-codex';

// After a hypothesis is generated, we use LLM to extract knowledge tags - advanced algorithms and data structures,
// which are mentioned in the hypothesis
export const EXTRACT_KNOWLEDGE_TAGS_MODEL: Model = 'gpt-4o';
export const EXTRACT_KNOWLEDGE_TAGS_MODEL: Model = 'gpt-5.3-codex';

// A dir with problem statements in hackercup format. Each problem is in a separate dir.
// Files required in each problem dir: statement.txt, sample_in.txt, sample_out.txt, full_in.txt
Expand All @@ -80,4 +90,3 @@ export const PROBLEMS_PATH = path.join(process.cwd(), '..', 'PROBLEMS');
// A dir where the model will place verified solutions
// For every problem a separate dir is created, and both source code and full test cases are placed there
export const SOLUTIONS_PATH = path.join(process.cwd(), '..', 'SOLUTIONS');

80 changes: 77 additions & 3 deletions www/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
'use client';

import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import ProblemColumn from '@/components/ProblemColumn';
import Image from 'next/image';
import { ProblemService } from './services/problemService';

export default function Home() {
const [problems, setProblems] = useState<string[]>([]);
const [problemRequests, setProblemRequests] = useState<Record<string, { llm: number; compute: number }>>({});
const [authStatus, setAuthStatus] = useState<{ status: string; connected: boolean; error?: string; accountId?: string }>({
status: 'idle',
connected: false,
});
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);

useEffect(() => {
const fetchProblems = async () => {
Expand All @@ -32,6 +36,56 @@ export default function Home() {
};
}, []);

const refreshAuth = async () => {
try {
const response = await fetch('/api/openai/status');
const data = await response.json();
setAuthStatus(data);
if (data.status !== 'pending' && pollRef.current) {
clearInterval(pollRef.current);
pollRef.current = null;
}
} catch {
setAuthStatus((prev) => ({ ...prev, status: 'error', error: 'Failed to read auth status' }));
}
};

useEffect(() => {
refreshAuth();
return () => {
if (pollRef.current) {
clearInterval(pollRef.current);
}
};
}, []);

const startAuth = async () => {
setAuthStatus((prev) => ({ ...prev, status: 'pending', error: undefined }));
try {
const response = await fetch('/api/openai/authorize', { method: 'POST' });
const data = await response.json();
if (data?.error) {
setAuthStatus((prev) => ({ ...prev, status: 'error', error: data.error }));
return;
}
if (data?.url) {
window.open(data.url, '_blank', 'noopener,noreferrer');
}
if (!pollRef.current) {
pollRef.current = setInterval(() => {
refreshAuth();
}, 1000);
}
} catch {
setAuthStatus((prev) => ({ ...prev, status: 'error', error: 'Failed to start OAuth' }));
}
};

const disconnect = async () => {
await fetch('/api/openai/logout', { method: 'POST' });
await refreshAuth();
};

// Calculate totals
const totalLLM = Object.values(problemRequests).reduce((sum, curr) => sum + curr.llm, 0);
const totalCompute = Object.values(problemRequests).reduce((sum, curr) => sum + curr.compute, 0);
Expand All @@ -45,7 +99,27 @@ export default function Home() {
🐟 STACKFISH
</span>
</div>
<div className="flex gap-2">
<div className="flex gap-2 items-center">
<div className="flex items-center gap-2 px-4 py-2 rounded-lg border border-white/10">
<span className="text-xs font-medium">
OpenAI: {authStatus.connected ? 'Connected' : authStatus.status === 'pending' ? 'Connecting' : 'Not connected'}
</span>
{authStatus.connected ? (
<button
className="text-xs px-3 py-1 rounded-md bg-white/10 hover:bg-white/20 transition"
onClick={disconnect}
>
Disconnect
</button>
) : (
<button
className="text-xs px-3 py-1 rounded-md bg-white/10 hover:bg-white/20 transition"
onClick={startAuth}
>
Connect OpenAI
</button>
)}
</div>
<div className="flex items-center gap-2 px-4 py-2 rounded-lg">
<div className={`w-2 h-2 rounded-full ${totalLLM > 0 ? 'bg-green-500 animate-pulse' : 'bg-gray-500'}`} />
<span className="text-xs font-medium">
Expand Down
Loading