shell.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. export * as Shell from "./shell.js"
  2. import { Schema } from "effect"
  3. import { optional } from "./schema.js"
  4. import { ephemeral, inventory } from "./event.js"
  5. import { ascending } from "./identifier.js"
  6. import { NonNegativeInt, statics } from "./schema.js"
  7. const IDSchema = Schema.String.check(Schema.isStartsWith("sh_")).pipe(Schema.brand("Shell.ID"))
  8. export const ID = IDSchema.pipe(
  9. statics((schema: typeof IDSchema) => {
  10. const create = () => schema.make("sh_" + ascending())
  11. return {
  12. create,
  13. ascending: (id?: string) => (id === undefined ? create() : schema.make(id)),
  14. }
  15. }),
  16. )
  17. export type ID = typeof ID.Type
  18. export const Status = Schema.Literals(["running", "exited", "timeout", "killed"])
  19. export type Status = typeof Status.Type
  20. export const Time = Schema.Struct({
  21. started: Schema.Finite,
  22. completed: optional(Schema.Finite),
  23. })
  24. export interface Time extends Schema.Schema.Type<typeof Time> {}
  25. // Opaque caller-supplied tags echoed back on Info and events. The Shell service never interprets
  26. // these; callers (e.g. ShellTool stores the originating session ID) use them to filter or correlate.
  27. export const Metadata = Schema.Record(Schema.String, Schema.Unknown)
  28. export type Metadata = typeof Metadata.Type
  29. export const Info = Schema.Struct({
  30. id: ID,
  31. status: Status,
  32. command: Schema.String,
  33. cwd: Schema.String,
  34. shell: Schema.String,
  35. // Absolute path of the file capturing combined stdout/stderr. Page through it via `output`.
  36. file: Schema.String,
  37. pid: optional(NonNegativeInt),
  38. exit: optional(Schema.Finite),
  39. // Always present; defaults to an empty object when the creator supplies no metadata.
  40. metadata: Metadata,
  41. time: Time,
  42. }).annotate({ identifier: "Shell.Info" })
  43. export interface Info extends Schema.Schema.Type<typeof Info> {}
  44. const Created = ephemeral({ type: "shell.created", schema: { info: Info } })
  45. const Exited = ephemeral({ type: "shell.exited", schema: { id: ID, exit: optional(Schema.Finite), status: Status } })
  46. const Deleted = ephemeral({ type: "shell.deleted", schema: { id: ID } })
  47. export const Event = { Created, Exited, Deleted, Definitions: inventory(Created, Exited, Deleted) }
  48. export const CreateInput = Schema.Struct({
  49. command: Schema.String,
  50. cwd: optional(Schema.String),
  51. timeout: NonNegativeInt,
  52. metadata: optional(Metadata),
  53. })
  54. export interface CreateInput extends Schema.Schema.Type<typeof CreateInput> {}
  55. export const OutputInput = Schema.Struct({
  56. cursor: optional(NonNegativeInt),
  57. limit: optional(NonNegativeInt),
  58. })
  59. export interface OutputInput extends Schema.Schema.Type<typeof OutputInput> {}
  60. export const Output = Schema.Struct({
  61. output: Schema.String,
  62. // Absolute cursor after this page. Equals `size` once fully caught up.
  63. cursor: NonNegativeInt,
  64. // Total bytes captured so far. A consumer has more to page while `cursor < size`.
  65. size: NonNegativeInt,
  66. truncated: Schema.Boolean,
  67. })
  68. export interface Output extends Schema.Schema.Type<typeof Output> {}