codexArtifactCtl.ts 3.2 KB

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