feat: interactive bot developer documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 00:18:06 +00:00
co-authored by Claude Opus 4.6
parent e10c1dc8aa
commit c654ff9f73
2 changed files with 356 additions and 3 deletions
+120
View File
@@ -162,3 +162,123 @@ docsRouter.get('/webhook', (c) => {
],
})
})
// POST /test — interactive webhook tester (no auth required)
docsRouter.post('/test', async (c) => {
const body = await c.req.json()
const { url, type } = body as { url?: string; type?: string }
if (!url || typeof url !== 'string') {
return c.json({ error: 'Missing "url" field' }, 400)
}
// Basic URL validation — must be https, no private IPs
let parsed: URL
try {
parsed = new URL(url)
} catch {
return c.json({ error: 'Invalid URL' }, 400)
}
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
return c.json({ error: 'URL must use https://' }, 400)
}
const host = parsed.hostname
if (host === 'localhost' || host === '127.0.0.1' || host.startsWith('192.168.') || host.startsWith('10.') || host.endsWith('.local')) {
return c.json({ error: 'Cannot test private/local URLs' }, 400)
}
const challengeTypes: Record<string, { challenge: string; answers?: string[] }> = {
speed_blitz: { challenge: 'What is the largest planet in our solar system?', answers: ['jupiter'] },
math_blitz: { challenge: 'What is 17 * 23?', answers: ['391'] },
hallucination_check: { challenge: 'True or false: The Great Wall of China is visible from space with the naked eye.', answers: ['false'] },
roast_battle: { challenge: 'Roast your opponent who calls themselves "test_bot".' },
creative_writing: { challenge: 'Write a haiku about a robot learning to fight.' },
webhook_test: { challenge: 'Respond with {"answer": "pong"}', answers: ['pong'] },
}
const selectedType = (type && challengeTypes[type]) ? type : 'speed_blitz'
const ct = challengeTypes[selectedType]
const payload = {
fight_id: 'test_000000',
round: 1,
type: selectedType,
challenge: ct.challenge,
constraints: { timeout_ms: 10000, max_tokens: 500 },
opponent: { name: 'test_bot', wins: 42, losses: 10 },
arena: 'test_arena',
arena_modifier: null,
}
const startMs = Date.now()
try {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 10000)
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal,
})
clearTimeout(timeout)
const elapsed = Date.now() - startMs
if (!res.ok) {
return c.json({
success: false,
payload,
error: `HTTP ${res.status} ${res.statusText}`,
elapsed,
})
}
let responseBody: any
try {
responseBody = await res.json()
} catch {
return c.json({
success: false,
payload,
error: 'Response is not valid JSON',
elapsed,
})
}
const answer = responseBody?.answer
if (typeof answer !== 'string') {
return c.json({
success: false,
payload,
response: responseBody,
error: 'Missing "answer" field in response',
elapsed,
})
}
// Check correctness for factual types
let correct: boolean | null = null
if (ct.answers) {
const normalized = answer.toLowerCase().trim()
correct = ct.answers.some(a => normalized.includes(a))
}
return c.json({
success: true,
payload,
response: responseBody,
correct,
elapsed,
})
} catch (err: unknown) {
const elapsed = Date.now() - startMs
const message = err instanceof Error ? err.message : 'Unknown error'
return c.json({
success: false,
payload,
error: message.includes('abort') ? 'Timeout (10s)' : message,
elapsed,
})
}
})