UNPKG

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
/// <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; }>; }