@ankorcs/protocol 0.0.0-stage → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,160 @@
1
+ /**
2
+ * The messages of the agent's local plugin socket: sequenced events and status from the plugin, and
3
+ * commands and acknowledgements from the agent.
4
+ */
5
+ import { z } from 'zod';
6
+ import { AGENT_ERRORS, AgentBackup, AgentPhaseChange, AgentScore, ServerState, } from './index.js';
7
+ import { EventBatchFrame, EventSeq, RoundNumber } from './index.js';
8
+ /**
9
+ * One sequenced event, which the plugin sends and the agent spools without its `type`, then uploads
10
+ * as an {@link EventBatchFrame} of an {@link EventBatch | event_batch}. `seq` runs from 0 without gaps.
11
+ * The frame names no server, because the connection's key already proves which one it is.
12
+ *
13
+ * A frame carries at most {@link MAX_FRAME_BYTES}, which is no more than
14
+ * {@link EVENT_BATCH_MAX_BYTES}, so every event the agent accepts fits an upload batch once stored.
15
+ * The plugin drops a larger event before sending it, and the agent ends a socket that sends one.
16
+ *
17
+ * After a gap past the events it stored, or with more than {@link RELAY_QUEUE_MAX_FRAMES} events or
18
+ * {@link RELAY_QUEUE_MAX_BYTES} of them waiting to be stored, the agent accepts no more history and
19
+ * ends the socket with code `1000` once the rest is stored. The plugin reconnects and resends from its last
20
+ * {@link EventAck | ack}.
21
+ */
22
+ export const PluginEventFrame = EventBatchFrame.extend({
23
+ type: z.literal('event'),
24
+ });
25
+ /**
26
+ * Acknowledges that every frame with `seq <= to_seq` is stored on the node, so the plugin may
27
+ * discard them.
28
+ *
29
+ * The agent sends it as frames are stored, and on every connect once the server has events on the
30
+ * node, so a reconnecting plugin resends only what follows. An `ack` past the last frame the plugin
31
+ * sent breaks the stream, and the plugin drops the connection.
32
+ */
33
+ export const EventAck = z.object({
34
+ type: z.literal('ack'),
35
+ to_seq: EventSeq,
36
+ });
37
+ /**
38
+ * A game server's state, which the plugin sends in answer to every
39
+ * {@link PluginGetServerStatus | get_server_status}. The plugin also sends it unasked once when it
40
+ * connects and once when it turns `ready`.
41
+ */
42
+ export const ServerStatus = ServerState.extend({
43
+ type: z.literal('server_status'),
44
+ /**
45
+ * The `request_id` of the {@link PluginGetServerStatus | get_server_status} this answers. Null
46
+ * when the request had none or the status was sent unasked.
47
+ */
48
+ request_id: z.string().max(64).nullable(),
49
+ });
50
+ /**
51
+ * Tells the agent that the plugin is about to run `quit` after its match, with every event
52
+ * acknowledged and the demo finished. The agent stops a server that is still running
53
+ * {@link QUIT_TIMEOUT_SECONDS} later. Like {@link ServerStatus | server_status}, it is not resent,
54
+ * so one sent without a connection is lost.
55
+ */
56
+ export const Quitting = z.object({
57
+ type: z.literal('quitting'),
58
+ });
59
+ /**
60
+ * The match moved to another phase, which the plugin sends whether or not the match sends
61
+ * statistics, and the agent forwards as {@link AgentPhaseChange | phase_change}. Like
62
+ * {@link ServerStatus | server_status}, it is sent once and lost without a connection.
63
+ */
64
+ export const PluginPhaseChange = AgentPhaseChange.omit({ allocation_id: true });
65
+ /**
66
+ * The match's score changed, which the plugin sends like {@link PluginPhaseChange | phase_change},
67
+ * and the agent forwards as {@link AgentScore | score}.
68
+ */
69
+ export const PluginScore = AgentScore.omit({ allocation_id: true });
70
+ /**
71
+ * The `round_backup` of a match that sends no statistics, which the plugin
72
+ * sends like {@link PluginPhaseChange | phase_change}, and the agent forwards as
73
+ * {@link AgentBackup | backup}. `backup` holds the complete event, unchanged, as the
74
+ * event stream would carry it.
75
+ */
76
+ export const PluginBackup = AgentBackup.omit({ allocation_id: true });
77
+ /** Why a plugin did not carry out a command, as an {@link AgentErrorCode}. */
78
+ export const CommandRefusal = z.enum([
79
+ AGENT_ERRORS.noMatch, AGENT_ERRORS.notInWarmup, AGENT_ERRORS.teamEmpty,
80
+ AGENT_ERRORS.noBackup, AGENT_ERRORS.notLive, AGENT_ERRORS.atHalftime,
81
+ AGENT_ERRORS.unsupportedRequest,
82
+ ]);
83
+ /** The plugin's answer to a command that carries a `request_id`. */
84
+ export const CommandResult = z.object({
85
+ type: z.literal('command_result'),
86
+ /** The `request_id` of the command this answers. */
87
+ request_id: z.string().max(64),
88
+ /** Why the plugin refused the command. Null when the plugin carried it out. */
89
+ error: CommandRefusal.nullable(),
90
+ });
91
+ /**
92
+ * What a plugin sends its agent: sequenced {@link PluginEventFrame | event} frames, its
93
+ * {@link ServerStatus | server_status}, {@link PluginPhaseChange | phase_change},
94
+ * {@link PluginScore | score}, {@link PluginBackup | backup},
95
+ * {@link CommandResult | command_result} and {@link Quitting | quitting}.
96
+ */
97
+ export const PluginToAgentMessage = z.discriminatedUnion('type', [PluginEventFrame, ServerStatus, PluginPhaseChange, PluginScore, PluginBackup, CommandResult, Quitting]);
98
+ /**
99
+ * Asks the plugin for its server's state, which the plugin answers with a
100
+ * {@link ServerStatus | server_status}.
101
+ */
102
+ export const PluginGetServerStatus = z.object({
103
+ type: z.literal('get_server_status'),
104
+ /**
105
+ * An id that the answering {@link ServerStatus | server_status} carries back, to match it to this
106
+ * request. Null for none.
107
+ */
108
+ request_id: z.string().max(64).nullable(),
109
+ });
110
+ /**
111
+ * Runs a server console command. The plugin answers with a {@link CommandResult | command_result}
112
+ * once it has handed the command to the engine.
113
+ */
114
+ export const ExecCommand = z.object({
115
+ type: z.literal('exec'),
116
+ /** The console command line. */
117
+ command: z.string(),
118
+ /** An id that the answering {@link CommandResult | command_result} carries back. */
119
+ request_id: z.string().max(64),
120
+ });
121
+ /**
122
+ * Loads the server's backup of a round, as {@link ControllerRestoreRound | restore_round} from the controller
123
+ * asks. The plugin runs `mp_backup_restore_load_file` on the file the engine wrote for it
124
+ * and answers with a {@link CommandResult | command_result}.
125
+ */
126
+ export const PluginRestoreRound = z.object({
127
+ type: z.literal('restore_round'),
128
+ /** The round to play on from, one-based. */
129
+ round: RoundNumber,
130
+ /** An id that the answering {@link CommandResult | command_result} carries back. */
131
+ request_id: z.string().max(64),
132
+ });
133
+ /**
134
+ * Starts a match in warmup whatever its `ready_mode`, as {@link ControllerStartMatch | start_match} from
135
+ * the controller asks. The plugin answers with a {@link CommandResult | command_result}.
136
+ */
137
+ export const PluginStartMatch = z.object({
138
+ type: z.literal('start_match'),
139
+ /** An id that the answering {@link CommandResult | command_result} carries back. */
140
+ request_id: z.string().max(64),
141
+ });
142
+ /**
143
+ * What the agent asks of a game server.
144
+ *
145
+ * - `exec`: relays {@link ControllerConsoleCommand | console_command} from the controller.
146
+ * - `restore_round`: relays {@link ControllerRestoreRound | restore_round} from the controller.
147
+ * - `start_match`: relays {@link ControllerStartMatch | start_match} from the controller.
148
+ * - `get_server_status`: the agent's own request.
149
+ */
150
+ export const PluginCommand = z.discriminatedUnion('type', [ExecCommand, PluginRestoreRound, PluginStartMatch, PluginGetServerStatus]);
151
+ /**
152
+ * What the agent sends a plugin: commands, and an {@link EventAck | ack} once events are stored on
153
+ * the node.
154
+ *
155
+ * When the agent ends a plugin's socket, it sends a close frame whose reason says why: `1001` when
156
+ * the agent shuts down, and `1000` when the plugin must replay its history, when a newer connection
157
+ * of the same server takes its place, or when the server's launch has ended. The plugin reconnects
158
+ * after each and resends from its last {@link EventAck | ack}.
159
+ */
160
+ export const AgentToPluginMessage = z.discriminatedUnion('type', [...PluginCommand.options, EventAck]);
@@ -0,0 +1,127 @@
1
+ /**
2
+ * One connected agent, from the controller's side of the agent socket. The controller hands the
3
+ * session each frame that arrives and a function that writes one, so any WebSocket server works.
4
+ */
5
+ import type { z } from 'zod';
6
+ import { AgentToControllerMessage, ControllerToAgentMessage, type AgentError, type ReplyData, type RequestType } from './index.js';
7
+ /** Milliseconds a request waits for its reply unless the session or the call sets another wait. */
8
+ export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
9
+ /**
10
+ * How long `request` waits for its reply, when other requests wait `wait`. A
11
+ * {@link ControllerStopServer | stop_server} replies once its container stopped, so it waits
12
+ * {@link STOP_TIMEOUT_SECONDS} and {@link STOP_REPLY_MARGIN_SECONDS}, whatever `wait` is. Null waits until
13
+ * the socket closes. A controller that relays requests through something with its own deadline
14
+ * gives that deadline this wait too.
15
+ */
16
+ export declare function replyWait(request: {
17
+ type: string;
18
+ }, wait: number): number;
19
+ export declare function replyWait(request: {
20
+ type: string;
21
+ }, wait: number | null): number | null;
22
+ /**
23
+ * A request as the controller builds it: its `type` and fields, without the `id` that the session
24
+ * picks. Fields with a default may be left out, since the session sends what the schema accepts.
25
+ */
26
+ export type SessionRequest<T extends RequestType = RequestType> = Extract<z.input<typeof ControllerToAgentMessage>, {
27
+ type: T;
28
+ }> extends infer M ? M extends unknown ? Omit<M, 'id'> : never : never;
29
+ /**
30
+ * A frame the controller sends that gets no reply: {@link ControllerWelcome | welcome},
31
+ * {@link ControllerHelloRefused | hello_refused}, the watches {@link ControllerWatchResources | watch_resources}
32
+ * and {@link ControllerWatchLogs | watch_logs}, and {@link ControllerCheckCs2Build | check_cs2_build}.
33
+ */
34
+ export type ControllerNotice = Exclude<ControllerToAgentMessage, {
35
+ id: string;
36
+ }>;
37
+ /** A frame the agent sends that is not a reply. */
38
+ export type AgentReport = Exclude<AgentToControllerMessage, {
39
+ type: 'reply';
40
+ }>;
41
+ /** A {@link AgentReply | reply} that the agent sent. */
42
+ export type AgentReplyMessage = Extract<AgentToControllerMessage, {
43
+ type: 'reply';
44
+ }>;
45
+ /** One way a frame failed the contract, as zod reports it. */
46
+ export type ContractIssue = NonNullable<ReturnType<typeof AgentToControllerMessage.safeParse>['error']>['issues'][number];
47
+ /**
48
+ * How a request came out.
49
+ *
50
+ * - `ok`: the agent replied that the request succeeded.
51
+ * - `error`: the agent replied that it refused or failed the request.
52
+ * - `timeout`: no reply came before the request's wait ran out.
53
+ * - `closed`: the socket closed before a reply came.
54
+ *
55
+ * After `timeout` or `closed`, the agent may still have done the work, and its reports say whether
56
+ * it did.
57
+ */
58
+ export type RequestResult<T extends RequestType> = {
59
+ status: 'ok';
60
+ data: ReplyData<T>;
61
+ } | {
62
+ status: 'error';
63
+ error: AgentError;
64
+ } | {
65
+ status: 'timeout';
66
+ } | {
67
+ status: 'closed';
68
+ };
69
+ /**
70
+ * What one received frame was.
71
+ *
72
+ * - `report`: an {@link AgentReport}.
73
+ * - `reply`: a reply, which the frame carries. `answered` is `true` when the reply settled the
74
+ * request it answers, and `false` when it answered no open request, as one past its timeout does.
75
+ * - `unsupported`: a message whose `type` this version does not have, from a newer agent. Skip it.
76
+ * - `invalid`: any other frame that failed the contract, one of a known type included. `issues`
77
+ * lists how, and is empty for a frame that is not JSON. `detail` joins the issues for a log line.
78
+ */
79
+ export type ReceivedFrame = {
80
+ kind: 'report';
81
+ message: AgentReport;
82
+ } | {
83
+ kind: 'reply';
84
+ answered: boolean;
85
+ message: AgentReplyMessage;
86
+ } | {
87
+ kind: 'unsupported';
88
+ type: string;
89
+ } | {
90
+ kind: 'invalid';
91
+ detail: string;
92
+ issues: ContractIssue[];
93
+ };
94
+ export interface AgentSessionOptions {
95
+ /** Writes one text frame to this agent's socket. */
96
+ send: (text: string) => void;
97
+ /**
98
+ * Milliseconds a request waits for its reply. Null waits until the socket closes. A
99
+ * {@link ControllerStopServer | stop_server} waits as long as its stop takes instead: see
100
+ * {@link replyWait}.
101
+ */
102
+ timeoutMs?: number | null;
103
+ }
104
+ export interface RequestOptions {
105
+ /** Milliseconds this request waits for its reply, in place of the session's wait; not for a stop, which waits by its grace. */
106
+ timeoutMs?: number | null;
107
+ }
108
+ export interface AgentSession {
109
+ /**
110
+ * Sends a request under an id that the session picks, and resolves with its reply, a timeout or
111
+ * the socket's close. After the agent's {@link AgentShutdown | shutdown}, it sends nothing and
112
+ * resolves with the `shutting_down` error the agent would answer.
113
+ */
114
+ request<R extends SessionRequest>(request: R, options?: RequestOptions): Promise<RequestResult<R['type']>>;
115
+ /** Sends a frame that gets no reply. */
116
+ notify(message: ControllerNotice): void;
117
+ /** Reads one frame the agent sent, as it arrived on the socket. */
118
+ receive(raw: string | ArrayBuffer | Uint8Array): ReceivedFrame;
119
+ /** Reads one frame the agent sent that the controller has already parsed from JSON. */
120
+ receiveParsed(json: unknown): ReceivedFrame;
121
+ /**
122
+ * Tells the session that the socket closed. Every open request ends `closed`, and later ones end
123
+ * `closed` immediately.
124
+ */
125
+ closed(): void;
126
+ }
127
+ export declare function createAgentSession(options: AgentSessionOptions): AgentSession;
@@ -0,0 +1,114 @@
1
+ import { AGENT_ERRORS, AgentToControllerMessage, STOP_REPLY_MARGIN_SECONDS, STOP_TIMEOUT_SECONDS, ControllerToAgentMessage, } from './index.js';
2
+ import { unknownMessageType } from './helpers.js';
3
+ /** Milliseconds a request waits for its reply unless the session or the call sets another wait. */
4
+ export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
5
+ export function replyWait(request, wait) {
6
+ if (request.type !== 'stop_server') {
7
+ return wait;
8
+ }
9
+ return (STOP_TIMEOUT_SECONDS + STOP_REPLY_MARGIN_SECONDS) * 1000;
10
+ }
11
+ const decoder = new TextDecoder();
12
+ function invalid(issues) {
13
+ return { kind: 'invalid', detail: issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join('; '), issues };
14
+ }
15
+ export function createAgentSession(options) {
16
+ const pending = new Map();
17
+ const sessionTimeout = options.timeoutMs === undefined ? DEFAULT_REQUEST_TIMEOUT_MS : options.timeoutMs;
18
+ let isClosed = false;
19
+ // After the agent's shutdown, it refuses every new request, so the session answers for it.
20
+ let isStopping = false;
21
+ function take(id) {
22
+ const open = pending.get(id);
23
+ if (open !== undefined) {
24
+ pending.delete(id);
25
+ clearTimeout(open.timer);
26
+ }
27
+ return open;
28
+ }
29
+ /** The call's wait, else the session's; a stop's by its grace period as parsed with its default. */
30
+ function waitFor(message, requestOptions) {
31
+ return replyWait(message, requestOptions.timeoutMs !== undefined ? requestOptions.timeoutMs : sessionTimeout);
32
+ }
33
+ function receiveParsed(json) {
34
+ const unknownType = unknownMessageType(AgentToControllerMessage, json);
35
+ if (unknownType !== null) {
36
+ return { kind: 'unsupported', type: unknownType };
37
+ }
38
+ const parsed = AgentToControllerMessage.safeParse(json);
39
+ if (!parsed.success) {
40
+ return invalid(parsed.error.issues);
41
+ }
42
+ const message = parsed.data;
43
+ if (message.type !== 'reply') {
44
+ if (message.type === 'shutdown') {
45
+ isStopping = true;
46
+ }
47
+ return { kind: 'report', message };
48
+ }
49
+ const open = pending.get(message.reply_to);
50
+ if (open === undefined) {
51
+ return { kind: 'reply', answered: false, message };
52
+ }
53
+ if (message.request !== null && message.request !== open.type) {
54
+ return invalid([{
55
+ code: 'custom',
56
+ path: ['request'],
57
+ message: `the reply to ${open.type} ${message.reply_to} names ${message.request}`,
58
+ input: message.request,
59
+ }]);
60
+ }
61
+ take(message.reply_to);
62
+ open.settle(message.ok ? { status: 'ok', data: message.data } : { status: 'error', error: message.error });
63
+ return { kind: 'reply', answered: true, message };
64
+ }
65
+ return {
66
+ request(request, requestOptions = {}) {
67
+ if (isClosed) {
68
+ return Promise.resolve({ status: 'closed' });
69
+ }
70
+ if (isStopping) {
71
+ return Promise.resolve({ status: 'error', error: { code: AGENT_ERRORS.shuttingDown, detail: 'the agent is shutting down' } });
72
+ }
73
+ const id = crypto.randomUUID();
74
+ // A throw in here, from the contract or the socket, rejects the promise.
75
+ return new Promise((resolve) => {
76
+ const message = ControllerToAgentMessage.parse({ ...request, id });
77
+ const timeoutMs = waitFor(message, requestOptions);
78
+ const timer = timeoutMs === null ? undefined : setTimeout(() => {
79
+ take(id);
80
+ resolve({ status: 'timeout' });
81
+ }, timeoutMs);
82
+ // The reply's `request` was checked against this type before it settles the promise.
83
+ pending.set(id, { type: request.type, settle: resolve, timer });
84
+ try {
85
+ options.send(JSON.stringify(message));
86
+ }
87
+ catch (error) {
88
+ take(id);
89
+ throw error;
90
+ }
91
+ });
92
+ },
93
+ notify(message) {
94
+ options.send(JSON.stringify(ControllerToAgentMessage.parse(message)));
95
+ },
96
+ receive(raw) {
97
+ let json;
98
+ try {
99
+ json = JSON.parse(typeof raw === 'string' ? raw : decoder.decode(raw));
100
+ }
101
+ catch {
102
+ return { kind: 'invalid', detail: 'not JSON', issues: [] };
103
+ }
104
+ return receiveParsed(json);
105
+ },
106
+ receiveParsed,
107
+ closed() {
108
+ isClosed = true;
109
+ for (const id of [...pending.keys()]) {
110
+ take(id)?.settle({ status: 'closed' });
111
+ }
112
+ },
113
+ };
114
+ }
@@ -0,0 +1,186 @@
1
+ /** Value types that more than one wire and `agent.yml` share. */
2
+ import { z } from 'zod';
3
+ /** The loopback port the agent listens on for plugins. No slot may use it. */
4
+ export declare const EVENT_RELAY_PORT = 4500;
5
+ /** The highest port a slot may use, for its game port and its GOTV port. */
6
+ export declare const MAX_PORT = 65535;
7
+ /**
8
+ * The lowest port `agent.yml` permits for a slot. A configuration front end may require a higher
9
+ * one.
10
+ */
11
+ export declare const AGENT_MIN_SLOT_PORT = 1;
12
+ /** A slot id: 1 to 32 letters, digits, `_` or `-`. It becomes part of the game container's name. */
13
+ export declare const SLOT_ID_PATTERN: RegExp;
14
+ /** A slot's two ports, before the controller has given it an id. */
15
+ export type SlotPorts = Omit<ServerSlot, 'id'>;
16
+ /**
17
+ * The shape of a node token. It has letters and digits only, so that it reads the same in a
18
+ * header, a YAML file and a shell.
19
+ */
20
+ export declare const NODE_TOKEN_PATTERN: RegExp;
21
+ /** What a node presents as `Authorization: Bearer <token>` on every request. */
22
+ export declare const NodeToken: z.ZodString;
23
+ /**
24
+ * The id the controller picks for each server it launches. It identifies that server and its
25
+ * events from then on.
26
+ *
27
+ * The id is a lowercase UUID, new for every launch. UUIDv7 is recommended, because its time-ordered
28
+ * values keep a database index on it compact as events arrive.
29
+ */
30
+ export declare const AllocationId: z.ZodUUID;
31
+ /** A slot's id, which is also part of the names of its containers. */
32
+ export declare const SlotId: z.ZodString;
33
+ /** A TCP or UDP port. */
34
+ export declare const Port: z.ZodNumber;
35
+ /** A slot's id and ports. */
36
+ export declare const ServerSlot: z.ZodObject<{
37
+ id: z.ZodString;
38
+ port: z.ZodNumber;
39
+ tv_port: z.ZodNumber;
40
+ }, z.core.$strip>;
41
+ export type ServerSlot = z.infer<typeof ServerSlot>;
42
+ /** The shape of a Game Server Login Token as Steam issues them. */
43
+ export declare const GSLT_PATTERN: RegExp;
44
+ /** A Game Server Login Token, which the servers of a slot log in to Steam with. */
45
+ export declare const Gslt: z.ZodString;
46
+ /**
47
+ * The last ten characters of a token, which the agent reports instead of the token. They are enough
48
+ * to tell apart every token on one Steam account.
49
+ */
50
+ export declare const GsltHint: z.ZodString;
51
+ /**
52
+ * Whether Steam accepts a slot's token.
53
+ *
54
+ * - `ok`: a logon with the token succeeded, either the agent's own or that of a server that
55
+ * turned `ready`.
56
+ * - `invalid`: Steam refused the token.
57
+ * - `unchecked`: the agent has seen neither since the token was set.
58
+ */
59
+ export declare const GsltState: z.ZodEnum<{
60
+ ok: "ok";
61
+ invalid: "invalid";
62
+ unchecked: "unchecked";
63
+ }>;
64
+ export type GsltState = z.infer<typeof GsltState>;
65
+ /** A slot as `agent.yml` holds it, with its full token. */
66
+ export declare const ConfiguredSlot: z.ZodObject<{
67
+ id: z.ZodString;
68
+ port: z.ZodNumber;
69
+ tv_port: z.ZodNumber;
70
+ gslt: z.ZodString;
71
+ }, z.core.$strip>;
72
+ export type ConfiguredSlot = z.infer<typeof ConfiguredSlot>;
73
+ /**
74
+ * A slot as the agent reports it, with the last ten characters of its token in place of the token.
75
+ */
76
+ export declare const ReportedSlot: z.ZodObject<{
77
+ id: z.ZodString;
78
+ port: z.ZodNumber;
79
+ tv_port: z.ZodNumber;
80
+ gslt_hint: z.ZodString;
81
+ }, z.core.$strip>;
82
+ export type ReportedSlot = z.infer<typeof ReportedSlot>;
83
+ /** A Docker image reference, as `cs2_image` and `agent_image` in `agent.yml` hold it. */
84
+ export declare const ImageReference: z.ZodString;
85
+ /**
86
+ * MiB of memory each game container may use. CS2 installs and updates are exempt.
87
+ *
88
+ * Rejected when it is not a whole number from {@link MIN_MEMORY_MB} to {@link MAX_MEMORY_MB}.
89
+ */
90
+ export declare const MemoryLimit: z.ZodNumber;
91
+ /**
92
+ * The repository of each official image. With `official_images_only` on, a node runs an image only
93
+ * from its kind's repository, with any tag or digest, such as `ghcr.io/ankorcs/ankor-agent:0.8`.
94
+ */
95
+ export declare const OFFICIAL_IMAGES: {
96
+ readonly agent: "ghcr.io/ankorcs/ankor-agent";
97
+ readonly cs2: "ghcr.io/ankorcs/ankor-cs2";
98
+ };
99
+ /**
100
+ * A dotted IPv4 address. Rejected unless it is four numbers from 0 to 255 joined by dots, each
101
+ * without leading zeros.
102
+ */
103
+ export declare const Ipv4: z.ZodString;
104
+ /**
105
+ * A host with an optional port and no scheme or path, such as `cs.example.com`,
106
+ * `192.168.0.10:3000` or `[fd00::1]`.
107
+ *
108
+ * Rejected when it contains whitespace, a control character, `/`, `\`, `?` or `#`. It is also
109
+ * rejected unless `http://` followed by the value parses as a URL that has a host, an optional port
110
+ * and nothing else, such as a user name.
111
+ */
112
+ export declare const ControllerHost: z.ZodString;
113
+ /**
114
+ * A path on the controller's host. It starts with `/`, followed by URL path characters and percent
115
+ * escapes, without a query or fragment.
116
+ */
117
+ export declare const ControllerPath: z.ZodString;
118
+ /**
119
+ * The URL finished demos are posted to. A target that needs credentials takes them in the configured
120
+ * `header`, such as `Authorization: Basic …`, which the node reports only as a hash.
121
+ *
122
+ * Rejected when the URL carries a user name or a password.
123
+ */
124
+ export declare const DemoUploadUrl: z.ZodURL;
125
+ /**
126
+ * Header names, in lower case, that a demo upload's configured header cannot take: the ones the
127
+ * agent sets itself on every upload, and the ones that change how the request is framed.
128
+ */
129
+ export declare const RESERVED_DEMO_HEADERS: readonly ["host", "content-type", "content-length", "transfer-encoding", "connection", "keep-alive", "upgrade", "expect", "te", "trailer", string, ...string[]];
130
+ /**
131
+ * One header sent with every demo upload, verbatim: any name the target expects, such as an API key
132
+ * header or `Authorization: Basic …`, except the {@link RESERVED_DEMO_HEADERS}.
133
+ */
134
+ export declare const DemoUploadHeader: z.ZodObject<{
135
+ name: z.ZodString;
136
+ value: z.ZodString;
137
+ }, z.core.$strip>;
138
+ /** Whole megabits a second, from 1 to 10 Gbit/s. */
139
+ export declare const DemoUploadLimit: z.ZodNumber;
140
+ /** Where finished demos go. The header, when set, is sent verbatim and is the only credential. */
141
+ export declare const DemoUpload: z.ZodObject<{
142
+ url: z.ZodURL;
143
+ header: z.ZodNullable<z.ZodObject<{
144
+ name: z.ZodString;
145
+ value: z.ZodString;
146
+ }, z.core.$strip>>;
147
+ limit_mbit: z.ZodNullable<z.ZodNumber>;
148
+ }, z.core.$strip>;
149
+ export type DemoUpload = z.infer<typeof DemoUpload>;
150
+ /**
151
+ * A demo upload as the node reports it, with the header's value replaced by its hash, because the
152
+ * value is a credential that the controller set.
153
+ */
154
+ export declare const ReportedDemoUpload: z.ZodObject<{
155
+ url: z.ZodURL;
156
+ limit_mbit: z.ZodNullable<z.ZodNumber>;
157
+ header: z.ZodNullable<z.ZodObject<{
158
+ name: z.ZodString;
159
+ value_sha256: z.ZodString;
160
+ }, z.core.$strip>>;
161
+ }, z.core.$strip>;
162
+ export type ReportedDemoUpload = z.infer<typeof ReportedDemoUpload>;
163
+ /**
164
+ * A demo upload as {@link ControllerSetConfig | set_config} sets it. A header without its value keeps the
165
+ * value the node has.
166
+ */
167
+ export declare const DemoUploadEdit: z.ZodObject<{
168
+ url: z.ZodURL;
169
+ limit_mbit: z.ZodNullable<z.ZodNumber>;
170
+ header: z.ZodNullable<z.ZodObject<{
171
+ name: z.ZodString;
172
+ value: z.ZodOptional<z.ZodString>;
173
+ }, z.core.$strip>>;
174
+ }, z.core.$strip>;
175
+ export type DemoUploadEdit = z.infer<typeof DemoUploadEdit>;
176
+ /**
177
+ * Where a node dials the controller: a host with an optional port, whether it encrypts, and the
178
+ * paths it reaches there.
179
+ */
180
+ export declare const ControllerAddress: z.ZodObject<{
181
+ host: z.ZodString;
182
+ tls: z.ZodBoolean;
183
+ socket_path: z.ZodString;
184
+ events_path: z.ZodString;
185
+ }, z.core.$strip>;
186
+ export type ControllerAddress = z.infer<typeof ControllerAddress>;