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
3 changes: 3 additions & 0 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
- run: yarn install --frozen-lockfile --network-timeout 120000
- run: yarn run typecheck
- run: yarn run lint
# Windows checkouts get CRLF line endings, which prettier reports as unformatted.
- run: yarn run format:check
if: runner.os == 'Linux'
- run: yarn run build
# Sanity-check `npm pack` only once (Linux + Node 24) to avoid
# 6-way artifact-name collisions; lint/build coverage is what
Expand Down
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.scoria/
scoria.config.json
dist/
coverage/
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"printWidth": 160
}
24 changes: 4 additions & 20 deletions demo/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@
<div v-if="ViewComponent" class="bg-gray-800 rounded-lg p-5 mb-5 shadow-md">
<h2 class="text-gray-200 text-xl mb-4">View Component</h2>
<div class="border border-gray-600 rounded h-[500px]">
<component
:is="ViewComponent"
:selectedResult="result"
:sendTextMessage="handleSendMessage"
/>
<component :is="ViewComponent" :selectedResult="result" :sendTextMessage="handleSendMessage" />
</div>
</div>

Expand All @@ -44,20 +40,11 @@
<script setup lang="ts">
import { ref, computed } from "vue";
import { plugin, executeTicTacToe } from "../src/vue";
import type {
ToolResult,
ToolSample,
ToolPlugin,
SendTextMessageOptions,
} from "gui-chat-protocol/vue";
import type {
TicTacToeArgs,
TicTacToeState,
TicTacToeClickData,
} from "../src/core/types";
import type { ToolResult, ToolSample, ToolPlugin, SendTextMessageOptions } from "gui-chat-protocol/vue";
import type { TicTacToeArgs, TicTacToeState, TicTacToeClickData } from "../src/core/types";
import { playTicTacToe } from "../src/core/logic";

const currentPlugin = plugin as unknown as ToolPlugin;

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions

Check warning on line 47 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions

const pluginName = computed(() => currentPlugin.toolDefinition.name);
const samplesList = computed(() => currentPlugin.samples || []);
Expand All @@ -74,8 +61,8 @@
});

const executeSample = async (sample: ToolSample) => {
const args = sample.args as unknown as TicTacToeArgs;

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions

Check warning on line 64 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions
const execResult = await executeTicTacToe({} as any, args);

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Unexpected any. Specify a different type

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Unexpected any. Specify a different type

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Unexpected any. Specify a different type

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Unexpected any. Specify a different type

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Unexpected any. Specify a different type

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Unexpected any. Specify a different type

Check warning on line 65 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions
result.value = {
...result.value,
...execResult,
Expand All @@ -89,7 +76,7 @@

// Use data option if available (from View component click)
if (options?.data) {
const clickData = options.data as TicTacToeClickData;

Check warning on line 79 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 79 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 79 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 79 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 79 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 79 in demo/App.vue

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions
processUserMove(clickData.row, clickData.col, clickData.currentState);
return;
}
Expand Down Expand Up @@ -127,10 +114,7 @@
};

// If it's now computer's turn and game is not over, make computer move
if (
!newState.isTerminal &&
newState.playerNames[newState.currentSide] === "computer"
) {
if (!newState.isTerminal && newState.playerNames[newState.currentSide] === "computer") {
// Small delay for visual feedback
setTimeout(() => {
makeComputerMove(newState);
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
"build": "vite build && vue-tsc -p tsconfig.build.json --emitDeclarationOnly",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint src demo",
"quality": "scoria"
"quality": "scoria",
"format": "prettier --write \"{src,demo}/**/*.{ts,tsx,vue,json,css}\" \"*.{ts,js,json}\"",
"format:check": "prettier --check \"{src,demo}/**/*.{ts,tsx,vue,json,css}\" \"*.{ts,js,json}\""
},
"peerDependencies": {
"gui-chat-protocol": "^2.1.0",
Expand All @@ -48,6 +50,7 @@
"eslint-plugin-vue": "^10.11.1",
"globals": "^17.13.0",
"gui-chat-protocol": "^2.3.0",
"prettier": "^3.9.10",
"scoria": "^0.4.1",
"tailwindcss": "^4.3.3",
"typescript": "~6.0.3",
Expand Down
21 changes: 7 additions & 14 deletions src/core/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ export const TOOL_NAME = "playTicTacToe";
export const TOOL_DEFINITION: ToolDefinition = {
type: "function",
name: TOOL_NAME,
description:
"Play Tic-Tac-Toe (Noughts and Crosses) game with the user. You can start a new game or make moves on the 3x3 board.",
description: "Play Tic-Tac-Toe (Noughts and Crosses) game with the user. You can start a new game or make moves on the 3x3 board.",
parameters: {
type: "object",
properties: {
Expand All @@ -21,22 +20,19 @@ export const TOOL_DEFINITION: ToolDefinition = {
},
col: {
type: "number",
description:
"Column position for the move (0-2, required for 'move' action). Left=0, Center=1, Right=2",
description: "Column position for the move (0-2, required for 'move' action). Left=0, Center=1, Right=2",
minimum: 0,
maximum: 2,
},
row: {
type: "number",
description:
"Row position for the move (0-2, required for 'move' action). Top=0, Middle=1, Bottom=2",
description: "Row position for the move (0-2, required for 'move' action). Top=0, Middle=1, Bottom=2",
minimum: 0,
maximum: 2,
},
board: {
type: "array",
description:
"Current 3x3 board state BEFORE the move (required for 'move' action). Pass the current board state as-is.",
description: "Current 3x3 board state BEFORE the move (required for 'move' action). Pass the current board state as-is.",
items: {
type: "array",
items: {
Expand All @@ -48,13 +44,11 @@ export const TOOL_DEFINITION: ToolDefinition = {
currentSide: {
type: "string",
enum: ["X", "O"],
description:
"Current player's side (required for 'move' action). X always goes first.",
description: "Current player's side (required for 'move' action). X always goes first.",
},
playerNames: {
type: "object",
description:
"Player assignments (required for 'move' action)",
description: "Player assignments (required for 'move' action)",
properties: {
X: {
type: "string",
Expand All @@ -70,8 +64,7 @@ export const TOOL_DEFINITION: ToolDefinition = {
firstPlayer: {
type: "string",
enum: ["user", "computer"],
description:
"Optional: Which player should play as X (goes first) for 'new_game' action. If not specified, will be chosen randomly.",
description: "Optional: Which player should play as X (goes first) for 'new_game' action. If not specified, will be chosen randomly.",
},
},
required: ["action"],
Expand Down
22 changes: 2 additions & 20 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,10 @@
*/

// Export plugin-specific types
export type {
Cell,
Side,
TicTacToeBoard,
PlayerType,
TicTacToeArgs,
NewGameCommand,
MoveCommand,
Command,
TicTacToeState,
TicTacToeClickData,
} from "./types";
export type { Cell, Side, TicTacToeBoard, PlayerType, TicTacToeArgs, NewGameCommand, MoveCommand, Command, TicTacToeState, TicTacToeClickData } from "./types";

// Export plugin utilities
export {
TOOL_NAME,
TOOL_DEFINITION,
SYSTEM_PROMPT,
playTicTacToe,
executeTicTacToe,
pluginCore,
} from "./plugin";
export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT, playTicTacToe, executeTicTacToe, pluginCore } from "./plugin";

// Export samples
export { samples } from "./samples";
75 changes: 45 additions & 30 deletions src/core/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,59 @@
* TicTacToe Plugin - Game Logic
*/

import type {
Cell,
Side,
TicTacToeBoard,
Command,
TicTacToeState,
} from "./types";
import type { Cell, Side, TicTacToeBoard, Command, TicTacToeState } from "./types";

// All possible winning lines (rows, columns, diagonals)
const WINNING_LINES: { row: number; col: number }[][] = [
// Rows
[{ row: 0, col: 0 }, { row: 0, col: 1 }, { row: 0, col: 2 }],
[{ row: 1, col: 0 }, { row: 1, col: 1 }, { row: 1, col: 2 }],
[{ row: 2, col: 0 }, { row: 2, col: 1 }, { row: 2, col: 2 }],
[
{ row: 0, col: 0 },
{ row: 0, col: 1 },
{ row: 0, col: 2 },
],
[
{ row: 1, col: 0 },
{ row: 1, col: 1 },
{ row: 1, col: 2 },
],
[
{ row: 2, col: 0 },
{ row: 2, col: 1 },
{ row: 2, col: 2 },
],
// Columns
[{ row: 0, col: 0 }, { row: 1, col: 0 }, { row: 2, col: 0 }],
[{ row: 0, col: 1 }, { row: 1, col: 1 }, { row: 2, col: 1 }],
[{ row: 0, col: 2 }, { row: 1, col: 2 }, { row: 2, col: 2 }],
[
{ row: 0, col: 0 },
{ row: 1, col: 0 },
{ row: 2, col: 0 },
],
[
{ row: 0, col: 1 },
{ row: 1, col: 1 },
{ row: 2, col: 1 },
],
[
{ row: 0, col: 2 },
{ row: 1, col: 2 },
{ row: 2, col: 2 },
],
// Diagonals
[{ row: 0, col: 0 }, { row: 1, col: 1 }, { row: 2, col: 2 }],
[{ row: 0, col: 2 }, { row: 1, col: 1 }, { row: 2, col: 0 }],
[
{ row: 0, col: 0 },
{ row: 1, col: 1 },
{ row: 2, col: 2 },
],
[
{ row: 0, col: 2 },
{ row: 1, col: 1 },
{ row: 2, col: 0 },
],
];

function createEmptyBoard(): TicTacToeBoard {
const board: TicTacToeBoard = [];
for (let i = 0; i < 3; i++) {
board[i] = new Array(3).fill(".") as Cell[];

Check warning on line 57 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 57 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 57 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 57 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 57 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 57 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions
}
return board;
}
Expand All @@ -41,9 +67,7 @@
return side === "X" ? "O" : "X";
}

function getLegalMoves(
board: TicTacToeBoard,
): { row: number; col: number }[] {
function getLegalMoves(board: TicTacToeBoard): { row: number; col: number }[] {
const moves: { row: number; col: number }[] = [];

for (let row = 0; row < 3; row++) {
Expand All @@ -57,12 +81,7 @@
return moves;
}

function makeMove(
board: TicTacToeBoard,
row: number,
col: number,
side: Side,
): TicTacToeBoard {
function makeMove(board: TicTacToeBoard, row: number, col: number, side: Side): TicTacToeBoard {
const newBoard = copyBoard(board);
newBoard[row][col] = side;
return newBoard;
Expand All @@ -89,21 +108,17 @@
return { X, O, empty };
}

function checkWinner(
board: TicTacToeBoard,
): { winner: Side | null; winningLine: { row: number; col: number }[] | null } {
function checkWinner(board: TicTacToeBoard): { winner: Side | null; winningLine: { row: number; col: number }[] | null } {
for (const line of WINNING_LINES) {
const cells = line.map((pos) => board[pos.row][pos.col]);
if (cells[0] !== "." && cells[0] === cells[1] && cells[1] === cells[2]) {
return { winner: cells[0] as Side, winningLine: line };

Check warning on line 115 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 115 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 115 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 115 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 115 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 115 in src/core/logic.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions
}
}
return { winner: null, winningLine: null };
}

function determineGameEnd(
board: TicTacToeBoard,
): { isTerminal: boolean; winner: Side | "draw" | null; winningLine: { row: number; col: number }[] | null } {
function determineGameEnd(board: TicTacToeBoard): { isTerminal: boolean; winner: Side | "draw" | null; winningLine: { row: number; col: number }[] | null } {
const { winner, winningLine } = checkWinner(board);

if (winner) {
Expand Down
27 changes: 6 additions & 21 deletions src/core/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT } from "./definition";
export { playTicTacToe } from "./logic";

export const executeTicTacToe = async (
_context: ToolContext,
args: TicTacToeArgs,
): Promise<ToolResult<never, TicTacToeState>> => {
export const executeTicTacToe = async (_context: ToolContext, args: TicTacToeArgs): Promise<ToolResult<never, TicTacToeState>> => {
try {
let command: Command;

Expand All @@ -31,23 +28,15 @@
playerNames: { X: xPlayer, O: oPlayer },
};
} else if (args.action === "move") {
if (
typeof args.row !== "number" ||
typeof args.col !== "number" ||
!args.board ||
!args.currentSide ||
!args.playerNames
) {
throw new Error(
"Move action requires row, col, board, currentSide, and playerNames parameters",
);
if (typeof args.row !== "number" || typeof args.col !== "number" || !args.board || !args.currentSide || !args.playerNames) {
throw new Error("Move action requires row, col, board, currentSide, and playerNames parameters");
}
command = {
action: "move",
row: args.row,
col: args.col,
board: args.board,
currentSide: args.currentSide as Side,

Check warning on line 39 in src/core/plugin.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 39 in src/core/plugin.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, ubuntu-latest)

Do not use any type assertions

Check warning on line 39 in src/core/plugin.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, macos-latest)

Do not use any type assertions

Check warning on line 39 in src/core/plugin.ts

View workflow job for this annotation

GitHub Actions / lint_test (24.x, windows-latest)

Do not use any type assertions

Check warning on line 39 in src/core/plugin.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, windows-latest)

Do not use any type assertions

Check warning on line 39 in src/core/plugin.ts

View workflow job for this annotation

GitHub Actions / lint_test (26.x, macos-latest)

Do not use any type assertions
playerNames: args.playerNames,
};
} else {
Expand All @@ -58,11 +47,8 @@

// Handle invalid move
if (state.error) {
const isComputerTurn =
state.playerNames[state.currentSide] === "computer";
const legalMovesStr = state.legalMoves
.map((m) => `(row=${m.row}, col=${m.col})`)
.join(", ");
const isComputerTurn = state.playerNames[state.currentSide] === "computer";
const legalMovesStr = state.legalMoves.map((m) => `(row=${m.row}, col=${m.col})`).join(", ");

const instructions = isComputerTurn
? `Invalid move attempted. You must make a valid move. Legal moves are: ${legalMovesStr}. Choose one of these moves.`
Expand Down Expand Up @@ -110,8 +96,7 @@
console.error("ERR: TicTacToe game error", error);
return {
message: `TicTacToe game error: ${error instanceof Error ? error.message : "Unknown error"}`,
instructions:
"Acknowledge that there was an error with the TicTacToe game and suggest trying again.",
instructions: "Acknowledge that there was an error with the TicTacToe game and suggest trying again.",
};
}
};
Expand Down
5 changes: 1 addition & 4 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ export interface TicTacToeState {
isTerminal: boolean;
winner: Side | "draw" | null; // null until terminal
winningLine: { row: number; col: number }[] | null; // cells that form winning line
lastAction:
| { type: "new_game" }
| { type: "move"; row: number; col: number };
lastAction: { type: "new_game" } | { type: "move"; row: number; col: number };
error?: string;
}

Expand All @@ -57,4 +55,3 @@ export interface TicTacToeClickData {
col: number;
currentState: TicTacToeState;
}

Loading
Loading