dimensions-ai
Version:
A generalized AI Competition framework that allows you to create any competition you want in any language you want with no hassle.
404 lines (403 loc) • 15.2 kB
TypeScript
/// <reference types="node" />
import { ChildProcess } from 'child_process';
import { WriteStream } from 'fs';
import { Logger } from '../Logger';
import { Tournament } from '../Tournament';
import { MatchEngine } from '../MatchEngine';
import { DeepPartial } from '../utils/DeepPartial';
import { Writable, Readable, Duplex } from 'stream';
import { EventEmitter } from 'events';
import Dockerode from 'dockerode';
/**
* @class Agent
* @classdesc The agent is what participates in a match and contains details on the files powering the agent, the
* process associated and many other details.
*
* Reads in a file source for the code and copies the bot folder to a temporary directory in secure modes
* and creates an `Agent` for use in the {@link MatchEngine} and {@link Match}
*
* This is a class that should not be broken. If something goes wrong, this should always throw a error. It is
* expected that agents are used knowing beforehand that the file given is validated
*/
export declare class Agent extends EventEmitter {
/**
* This agent's ID in a match. It is always a non-negative integer and agents in a match are always numbered
* `0, 1, 2, ...n` where there are `n` agents.
*/
id: Agent.ID;
/**
* A tournmanet ID if Agent is generated from within a {@link Tournament}
*/
tournamentID: Tournament.ID;
/**
* Name of the agent
* @default `agent_[agent.id]`
*/
name: string;
/** The source path to the file that runs the agent */
src: string;
/** The extension of the file */
ext: string;
/** file without extension */
srcNoExt: string;
/**
* The current working directory of the source file. If in insecure mode, this is always a temporary directory that
* will get deleted later.
*/
cwd: string;
/** The command used to run the file */
cmd: string;
/**
* The original file path provided
*/
file: string;
/**
* The agent's options
*/
options: Agent.Options;
/**
* Creation date of the agent
*/
creationDate: Date;
/** internal buffer to store stdout from an agent that has yet to be delimited / used */
_buffer: Array<string>;
/** Interval that periodically watches the memory usage of the process associated with this agent */
memoryWatchInterval: any;
/**
* The associated process running the Agent
*/
private process;
/**
* Associated docker container running the agent
*/
private container;
/**
* Streams associated with the agent
*/
streams: Agent.Streams;
/**
* Current status of the agent
*/
status: Agent.Status;
/** The commands collected so far for the current move */
currentMoveCommands: Array<string>;
/** a promise that resolves when the Agent's current move in the {@link Match} is finished */
_currentMovePromise: Promise<void>;
_currentMoveResolve: Function;
_currentMoveReject: Function;
/** A number that counts the number of times the agent has essentially interacted with the {@link MatchEngine} */
agentTimeStep: number;
/** Clears out the timer associated with the agent during a match */
_clearTimer: Function;
errorLogWriteStream: WriteStream;
private log;
/**
* Key used to retrieve the error logs of this agent
*/
logkey: string;
/** whether agent is allowed to send commands. Used to help ignore extra output from agents */
private allowedToSendCommands;
/** Agent version, used by tournament */
version: number;
/** Size of agent's logs so far */
_logsize: number;
_trimmed: boolean;
/** List of all messages written to this agent are directly pushed to here when in detached mode */
messages: Array<string>;
constructor(file: string, options: Partial<Agent.Options>, languageSpecificOptions?: Agent.LanguageSpecificOptions);
setupContainer(name: string, docker: Dockerode, engineOptions: MatchEngine.EngineOptions): Promise<void>;
/**
* Install whatever is needed through a `install.sh` file in the root of the bot folder
*/
_install(stderrWritestream: Writable, stdoutWritestream: Writable, engineOptions: MatchEngine.EngineOptions): Promise<void>;
/**
* Compile whatever is needed and validate files. Called by {@link MatchEngine} and has a timer set by the
* maxCompileTime option in {@link Agent.Options}
*/
_compile(stderrWritestream: Writable, stdoutWritestream: Writable, engineOptions: MatchEngine.EngineOptions): Promise<void>;
/**
* Spawns the compilation process
* @param command - command to compile with
* @param args - argument for the compilation
*/
_spawnCompileProcess(command: string, args: Array<string>): Promise<ChildProcess | Agent.ContainerExecData>;
/**
* Executes the given command string in the agent's container and attaches stdin, stdout, and stderr accordingly
* @param command - the command to execute in the container
*/
containerSpawn(command: string, workingDir?: string): Promise<Agent.ContainerExecData>;
/**
* Spawn the process and return the process
*/
_spawn(): Promise<ChildProcess | Agent.ContainerExecData>;
/**
* Spawns process in this.cwd accordingly and uses the configs accordingly.
* Resolves with the process if spawned succesfully
*
* Note, we are spawning detached so we can kill off all sub processes if they are made. See {@link _terminate} for
* explanation
*/
_spawnProcess(command: string, args: Array<string>): Promise<ChildProcess | Agent.ContainerExecData>;
/**
* Stop an agent provided it is not terminated. To terminate it, see {@link _terminate};
*/
stop(): Promise<void>;
/**
* Resume an agent as long it is not terminated already
*/
resume(): Promise<void>;
/**
* timeout the agent
*/
timeout(): void;
/**
* call out agent for exceeding memory limit
*/
overMemory(): void;
/**
* Whether or not input is destroyed
*/
inputDestroyed(): boolean;
/**
* Write to stdin of the process associated with the agent
* @param message - the message
* @param callback - callback function
*
* returns true if written, false if highWaterMark reached
*/
write(message: string, callback: (error: Error) => void): boolean;
writeToErrorLog(message: string): void;
/**
* Get process of agent
*/
_getProcess(): ChildProcess;
/**
* Store process for agent
* @param p - process to store
*/
_storeProcess(p: ChildProcess): void;
/**
* Returns true if this agent was terminated and no longer send or receive emssages
*/
isTerminated(): boolean;
/**
* Terminates this agent by stopping all related processes and remove any temporary directory. this is the only function allowed to
* set the status value to killed.
*/
_terminate(): Promise<void>;
/**
* Disallow an agent from sending more commands
*/
_disallowCommands(): void;
/**
* Allow agent to send commands again
*/
_allowCommands(): void;
/**
* Check if agent is set to be allowed to send commands. The {@link EngineOptions} affect when this is flipped
*/
isAllowedToSendCommands(): boolean;
/**
* Setup the agent timer clear out method
*/
_setTimeout(fn: Function, delay: number, ...args: any[]): void;
/**
* Stop this agent from more outputs and mark it as done for now and awaiting for updates. Effectively force agent to sync with match
*/
_finishMove(): Promise<void>;
_setupMove(): void;
/**
* Used by {@link MatchEngine} only. Setups the memory watcher if docker is not used.
* @param engineOptions - engine options to configure the agent with
*/
_setupMemoryWatcher(engineOptions: MatchEngine.EngineOptions): void;
/**
* Generates a list of agents for use
* @param files List of files to use to make agents or a list of objects with a file key for the file path to the bot
* and a name key for the name of the agent
* @param options - Options to first override with for all agents
* @param languageSpecificOptions - Options to second overrided with for agents depending on language
* @param agentSpecificOptions - Options to lastly override with depending on agent's index
*/
static generateAgents(files: Agent.GenerationMetaData, options: DeepPartial<Agent.Options>, languageSpecificOptions?: Agent.LanguageSpecificOptions, agentSpecificOptions?: Array<DeepPartial<Agent.Options>>): Array<Agent>;
getAgentErrorLogFilename(): string;
}
export declare namespace Agent {
/**
* Stream data for any process
*/
interface Streams {
in: Writable;
out: Readable;
err: Readable;
}
/**
* data related to executing a command in a container, with stream data, and the exec object
*/
interface ContainerExecData extends Streams {
exec: Dockerode.Exec;
stream: Duplex;
}
/**
* Status enums for an Agent
*/
enum Status {
/** When agent is just created */
UNINITIALIZED = "uninitialized",
/** Agent is ready too be used by the {@link MatchEngine} in a {@link Match} */
READY = "ready",
/** Agent is currently running */
RUNNING = "running",
/** Agent crashed somehow */
CRASHED = "crashed",
/** Agent is finished and no longer in use after {@link Match} ended or was prematurely killed */
KILLED = "killed",
/** Agent is currently not running */
STOPPED = "stopped"
}
/**
* Agent ID. Always a non-negative integer and all agents in a match have IDs that are strictly increasing from `0`
*
* For example, in a 4 agent match, the ids are `0, 1, 2, 3`.
*/
type ID = number;
/**
* Agent options interface
*/
interface Options {
/** Name of agent */
name: string;
/**
* Whether or not to spawn agent securely and avoid malicious activity
*
* When set to true, the agent's file and the directory containing the file are copied over to a temporary directory
* of which there is restricted access. By default this is false
*
* @default `false` (always inherited from the match configs, see {@link Match.Configs})
*/
secureMode: boolean;
/** A specified ID to use for the agent */
id: ID;
/** A specified tournament ID linking an agent to the {@link Tournament} and {@link Player} it belongs to */
tournamentID: Tournament.ID;
/** Logging level of this agent */
loggingLevel: Logger.LEVEL;
/**
* Maximium time allowed for an agent to spend on the installing step
* @default 5 minutes (300,000 ms)
*/
maxInstallTime: number;
/**
* Maximum time allowed to be spent compiling
* @default 1 minute (60,000 ms)
*/
maxCompileTime: number;
/**
* Map from extension type to set of commands used to compile this agent instead of the defaults. When there is no
* mapping default commands are used
*
* @default `null`
*/
compileCommands: {
[x in string]: Array<string>;
};
/**
* Map from extension type to set of commands used to run this agent instead of the defaults. When there is no
* mapping default commands are used
*
* @default `null`
*/
runCommands: {
[x in string]: Array<string>;
};
/**
* Image to use for docker container if Agent is being run in secureMode. The default is a standard image provided
* by Dimensions that has all supported languages built in so agents can use them. The requirement for these images
* is that they have bash installed.
*
* It is highly recommended
* to use the {@link Match.Configs.languageSpecificAgentOptions} field and set specific images for each programming
* language in a production environment to reduce overhead caused by docker.
*
* @default `docker.io/stonezt2000/dimensions_langs`
*/
image: string;
/**
* Whether or not to try and use a cached bot file. This is only relevant if a storage service is used as it helps
* reduce the number of times a bot file is downloaded.
*
* @default `false`
*/
useCachedBotFile: boolean;
/**
* Limit of agent logs in bytes.
*
* @default `1e5 - 100 kb`
*/
logLimit: number;
/**
* Whether agent is to be run in detached mode. In detached mode, the agent simply stores messages that is written to it and clears the message queue
* when a turn is finished
*
* @default `false`
*/
detached: boolean;
}
/**
* Language specic options mapping programming language to the agent options to use for that programing language.
* Used to customize options such as docker image, compile time limits etc. on a per language basis
*/
type LanguageSpecificOptions = {
[x in string]?: DeepPartial<Agent.Options>;
};
/**
* Agent events
*/
enum AGENT_EVENTS {
/**
* Event emitted by process of {@link Agent} when memory limit is exceeded
*/
EXCEED_MEMORY_LIMIT = "exceedMemoryLimit",
/**
* Event emitted by process of {@link Agent} when it times out.
*/
TIMEOUT = "timeout",
/**
* event emitted when associated process or container for agent closes and agent effectively is terminated
*/
CLOSE = "close"
}
/**
* Default Agent options
*/
const OptionDefaults: Agent.Options;
type GenerationMetaData = GenerationMetaData_CreateMatch | GenerationMetaData_FilesOnly | GenerationMetaData_Tournament;
/**
* Agent Generation meta data with paths to files only
*/
type GenerationMetaData_FilesOnly = Array<string>;
/**
* Agent generation meta data used by {@link Dimension.createMatch}
*/
type GenerationMetaData_CreateMatch = Array<{
/** file path */
file: string;
/** name of bot */
name: string;
/** botkey for bots in storage */
botkey?: string;
}>;
/**
* Agent generation meta data used by tournaments
*/
type GenerationMetaData_Tournament = Array<{
/** file path */
file: string;
/** tournament ID containing playerId, username, and bot name */
tournamentID: Tournament.ID;
/** botkey for bots in storage */
botkey?: string;
/** track version # of the bot used */
version: number;
}>;
}