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
2 changes: 1 addition & 1 deletion api/services/ai/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { Models } from './types'
export const models: Record<Models, string> = {
openai: 'o4-mini-2025-04-16',
gemini: 'gemini-2.5-flash',
claude: 'claude-4-sonnet',
claude: 'claude-3-5-sonnet-20241022',
}
62 changes: 62 additions & 0 deletions api/steps/chess/03-retry-last-move-api.step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ApiRouteConfig, Handlers } from 'motia'
import { z } from 'zod'

export const config: ApiRouteConfig = {
type: 'api',
name: 'RetryLastMove',
description: 'Retry last move',
path: '/chess/game/:id/retry-last-move',
method: 'POST',
emits: ['chess-game-moved', 'chess-game-ended'],
flows: ['chess'],
bodySchema: z.object({}),
responseSchema: {
200: z.object({
message: z.string(),
}),
404: z.object({ message: z.string() }),
400: z.object({ message: z.string() }),
},
}

export const handler: Handlers['RetryLastMove'] = async (req, { logger, emit, streams, state }) => {
logger.info('Received retry last move request', { body: req.body })

const gameId = req.pathParams.id
const game = await streams.chessGame.get('game', gameId)

if (!game) {
return { status: 404, body: { message: 'Game not found' } }
} else if (game.status === 'completed') {
return { status: 400, body: { message: 'Game is finished' } }
} else if (!game.players[game.turn].ai) {
return { status: 400, body: { message: 'Cannot retry last unless AI is playing' } }
}

const messageId = crypto.randomUUID()

const player = game.turn === 'white' ? game.players.white : game.players.black
if (!!player.ai) {
await streams.chessGameMessage.set(gameId, messageId, {
message: 'Retrying move...',
sender: player.ai,
role: game.turn,
timestamp: Date.now(),
})
}

await streams.chessGame.set('game', game.id, {
...game,
status: 'pending',
})

await emit({
topic: 'chess-game-moved',
data: {
gameId,
fenBefore: game.fen,
},
})

return { status: 200, body: { message: 'Last move retried' } }
}
32 changes: 29 additions & 3 deletions api/steps/chess/05-ai-player.step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { evaluateBestMoves } from '../../services/chess/evaluate-best-moves'
import { ActionMove, move } from '../../services/chess/move'

const MAX_ATTEMPTS = 3
const MAX_AI_RETRY_MOVE_ATTEMPTS = 3;

export const config: EventConfig = {
type: 'event',
Expand Down Expand Up @@ -64,8 +65,6 @@ export const handler: Handlers['AI_Player'] = async (input, { logger, emit, stre
const validMoves = evaluateBestMoves(game)
let lastInvalidMove = undefined

logger.info('Valid moves', { validMoves })

while (true) {
const messageId = crypto.randomUUID()

Expand Down Expand Up @@ -95,10 +94,10 @@ export const handler: Handlers['AI_Player'] = async (input, { logger, emit, stre
let action: { thought: string; move: ActionMove } | undefined

try {
logger.info('Prompt', { prompt })
action = await makePrompt(prompt, responseSchema, player.ai, logger)

logger.info('Updating message', { messageId, gameId: input.gameId })

await streams.chessGameMessage.set(input.gameId, messageId, {
...message,
message: action.thought,
Expand All @@ -110,6 +109,33 @@ export const handler: Handlers['AI_Player'] = async (input, { logger, emit, stre
message: 'Error making prompt, I will need to try again soon',
})

const nextRetryMoveAttempts = (game.players[input.player].retryMoveAttempts ?? 0) + 1

if (nextRetryMoveAttempts > MAX_AI_RETRY_MOVE_ATTEMPTS) {
await streams.chessGame.set('game', game.id, {
...game,
status: 'completed',
endGameReason: 'Reached max retry attempts for AI player move',
})

await emit({
topic: 'chess-game-ended',
data: { gameId: game.id },
})
} else {
await streams.chessGame.set('game', game.id, {
...game,
status: 'requires-retry',
players: {
...game.players,
[input.player]: {
...game.players[input.player],
retryMoveAttempts: nextRetryMoveAttempts,
},
}
})
}

logger.error('Error making prompt', { err })
throw err
}
Expand Down
4 changes: 3 additions & 1 deletion api/steps/chess/streams/00-chess-game.stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const gameSchema = z.object({
id: z.string({ description: 'The ID of the game' }),
fen: z.string({ description: 'The FEN of the game' }),
turn: z.enum(['white', 'black'], { description: 'The color of the current turn' }),
status: z.enum(['pending', 'completed', 'draw'], { description: 'The status of the game' }),
status: z.enum(['pending', 'completed', 'draw', 'requires-retry'], { description: 'The status of the game' }),
lastMove: z.array(z.string({ description: 'The last move made' })).optional(),
winner: z.enum(['white', 'black']).optional(),
turns: z.number({ description: 'The number of turns' }).optional(),
Expand All @@ -52,6 +52,7 @@ export const gameSchema = z.object({
)
.optional(),
promotions: z.number({ description: 'The number of pawn promotions' }).optional(),
retryMoveAttempts: z.number({ description: 'The number of retry move attempts' }).optional(),
}),
black: z.object({
name: z.string({ description: 'The name of the player' }),
Expand All @@ -67,6 +68,7 @@ export const gameSchema = z.object({
)
.optional(),
promotions: z.number({ description: 'The number of pawn promotions' }).optional(),
retryMoveAttempts: z.number({ description: 'The number of retry move attempts' }).optional(),
}),
}),
check: z.boolean({ description: 'Whether the game is in check' }),
Expand Down
Loading