codexArtifactCtl.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import { logger } from 'ee-core/log';
  2. import {
  3. listAllArtifacts,
  4. listArtifacts,
  5. openArtifact,
  6. openArtifactRoot,
  7. readArtifactBase64,
  8. readArtifactText,
  9. revealArtifact,
  10. saveArtifactAs,
  11. } from '../service/codex/codexArtifactService';
  12. import { asMessage, fail, ok, type Rpc } from '../service/codex/types';
  13. /**
  14. * 产物(会话工作目录里的文件)读写控制器。
  15. *
  16. * 与 codexCtl 同为 ee-core 自动注册的 controller,通道前缀是 controller/codexArtifactCtl/。
  17. * 这里只做参数校验与异常包装,安全边界(root 白名单 + 路径穿越校验)在 service 里。
  18. */
  19. async function guard<T>(label: string, run: () => Promise<T>): Promise<Rpc<T>> {
  20. try {
  21. return ok(await run());
  22. } catch (error) {
  23. const message = asMessage(error);
  24. logger.error(`[codexArtifactCtl] ${label} failed:`, message);
  25. return fail(message);
  26. }
  27. }
  28. function requireString(value: unknown, label: string): string {
  29. if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} 不能为空`);
  30. return value.trim();
  31. }
  32. function optionalNumber(value: unknown): number | undefined {
  33. return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
  34. }
  35. class CodexArtifactCtl {
  36. async list(params?: { root?: string; dir?: string }): Promise<Rpc<unknown>> {
  37. return guard('list', async () =>
  38. listArtifacts(requireString(params?.root, 'root'), typeof params?.dir === 'string' ? params.dir : ''),
  39. );
  40. }
  41. /** 递归列出工作空间全部文件(「本会话产物」视图用) */
  42. async listAll(params?: { root?: string }): Promise<Rpc<unknown>> {
  43. return guard('listAll', async () => listAllArtifacts(requireString(params?.root, 'root')));
  44. }
  45. async readText(params?: { root?: string; path?: string; maxBytes?: number }): Promise<Rpc<unknown>> {
  46. return guard('readText', async () =>
  47. readArtifactText(
  48. requireString(params?.root, 'root'),
  49. requireString(params?.path, 'path'),
  50. optionalNumber(params?.maxBytes),
  51. ),
  52. );
  53. }
  54. async readBinary(params?: { root?: string; path?: string; maxBytes?: number }): Promise<Rpc<unknown>> {
  55. return guard('readBinary', async () =>
  56. readArtifactBase64(
  57. requireString(params?.root, 'root'),
  58. requireString(params?.path, 'path'),
  59. optionalNumber(params?.maxBytes),
  60. ),
  61. );
  62. }
  63. async saveAs(params?: { root?: string; path?: string; name?: string }): Promise<Rpc<unknown>> {
  64. return guard('saveAs', async () =>
  65. saveArtifactAs(
  66. requireString(params?.root, 'root'),
  67. requireString(params?.path, 'path'),
  68. typeof params?.name === 'string' ? params.name : undefined,
  69. ),
  70. );
  71. }
  72. async reveal(params?: { root?: string; path?: string }): Promise<Rpc<Record<string, never>>> {
  73. return guard('reveal', async () => {
  74. await revealArtifact(requireString(params?.root, 'root'), requireString(params?.path, 'path'));
  75. return {};
  76. });
  77. }
  78. async open(params?: { root?: string; path?: string }): Promise<Rpc<Record<string, never>>> {
  79. return guard('open', async () => {
  80. await openArtifact(requireString(params?.root, 'root'), requireString(params?.path, 'path'));
  81. return {};
  82. });
  83. }
  84. async openRoot(params?: { root?: string }): Promise<Rpc<Record<string, never>>> {
  85. return guard('openRoot', async () => {
  86. await openArtifactRoot(requireString(params?.root, 'root'));
  87. return {};
  88. });
  89. }
  90. }
  91. CodexArtifactCtl.toString = () => 'CodexArtifactCtl';
  92. export default CodexArtifactCtl;