File size: 2,731 Bytes
68d7816
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { z } from 'zod';

import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
import type { Event } from '#/_base/event';
import { isoDateTimeSchema } from '#/_base/utils/isoDateTime';

const relativeCwdSchema = z
  .string()
  .min(1)
  .refine((value) => !isAbsolutePath(value), 'cwd must be relative to the session workspace');

export const terminalStatusSchema = z.enum(['running', 'exited']);
export type TerminalStatus = z.infer<typeof terminalStatusSchema>;

export const terminalSchema = z.object({
  id: z.string().min(1),
  session_id: z.string().min(1),
  cwd: z.string().min(1),
  shell: z.string().min(1),
  cols: z.number().int().positive(),
  rows: z.number().int().positive(),
  status: terminalStatusSchema,
  created_at: isoDateTimeSchema,
  exited_at: isoDateTimeSchema.optional(),
  exit_code: z.number().int().nullable().optional(),
});
export type Terminal = z.infer<typeof terminalSchema>;

export const createTerminalRequestSchema = z.object({
  runtime_id: z.string().min(1),
  cwd: relativeCwdSchema.optional(),
  shell: z.string().min(1).optional(),
  cols: z.number().int().positive().optional(),
  rows: z.number().int().positive().optional(),
});
export type CreateTerminalRequest = z.infer<typeof createTerminalRequestSchema>;

export interface TerminalOutputMessage {
  type: 'terminal_output';
  seq: number;
  session_id: string;
  terminal_id: string;
  timestamp: string;
  payload: { data: string };
}

export interface TerminalExitMessage {
  type: 'terminal_exit';
  session_id: string;
  terminal_id: string;
  timestamp: string;
  payload: { exit_code?: number | null | undefined };
}

export type TerminalFrame = TerminalOutputMessage | TerminalExitMessage;

function isAbsolutePath(value: string): boolean {
  return (
    value.startsWith('/') ||
    value.startsWith('\\') ||
    /^[A-Za-z]:[\\/]/.test(value)
  );
}

export interface TerminalAttachSink {
  readonly id: string;
  send(frame: TerminalFrame): void;
}

export interface TerminalAttachOptions {
  readonly sinceSeq?: number;
}

export interface TerminalSpawnOptions {
  readonly cwd: string;
  readonly shell: string;
  readonly cols: number;
  readonly rows: number;
}

export interface TerminalProcess {
  readonly onProcessData: Event<string>;
  readonly onProcessExit: Event<{ exitCode: number | null }>;
  write(data: string): void;
  resize(cols: number, rows: number): void;
  kill(): void;
}

export interface IHostTerminalService {
  readonly _serviceBrand: undefined;

  spawn(options: TerminalSpawnOptions): Promise<TerminalProcess>;
}

export const IHostTerminalService: ServiceIdentifier<IHostTerminalService> =
  createDecorator<IHostTerminalService>('hostTerminalService');