@llmsafespaces/sdk 0.23.0 → 0.24.0

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.
package/dist/index.cjs CHANGED
@@ -56,6 +56,8 @@ var NotFoundError = class extends LLMSafeSpacesError {
56
56
  }
57
57
  };
58
58
  var ConflictError = class extends LLMSafeSpacesError {
59
+ /** Current workspace phase, when the 409 body carries one (upload phase gate, Epic 67 D5). */
60
+ phase;
59
61
  constructor(message) {
60
62
  super(message, 409, "CONFLICT");
61
63
  this.name = "ConflictError";
@@ -120,6 +122,9 @@ var LLMSafeSpaces = class {
120
122
  agentRoles;
121
123
  workflows;
122
124
  triggers;
125
+ mcpServers;
126
+ adminMcpServers;
127
+ orgMcpServers;
123
128
  constructor(options) {
124
129
  this.baseUrl = options.baseUrl.replace(/\/$/, "");
125
130
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
@@ -142,11 +147,15 @@ var LLMSafeSpaces = class {
142
147
  this.agentRoles = new AgentRolesAPI(this);
143
148
  this.workflows = new WorkflowsAPI(this);
144
149
  this.triggers = new TriggersAPI(this);
150
+ this.mcpServers = new McpServersAPI(this);
151
+ this.adminMcpServers = new AdminMcpServersAPI(this);
152
+ this.orgMcpServers = new OrgMcpServersAPI(this);
145
153
  }
146
154
  /** Internal: make an authenticated request. */
147
155
  async request(method, path, body, timeout) {
148
156
  const url = `${this.baseUrl}/api/v1${path}`;
149
- const headers = { "Content-Type": "application/json" };
157
+ const isForm = typeof FormData !== "undefined" && body instanceof FormData;
158
+ const headers = isForm ? {} : { "Content-Type": "application/json" };
150
159
  if (this.apiKey) {
151
160
  headers["Authorization"] = `Bearer ${this.apiKey}`;
152
161
  } else if (this.token) {
@@ -162,7 +171,7 @@ var LLMSafeSpaces = class {
162
171
  res = await this.fetchFn(url, {
163
172
  method,
164
173
  headers,
165
- body: body ? JSON.stringify(body) : void 0,
174
+ body: body ? isForm ? body : JSON.stringify(body) : void 0,
166
175
  signal: controller.signal
167
176
  });
168
177
  } catch (e) {
@@ -186,8 +195,12 @@ var LLMSafeSpaces = class {
186
195
  throw new AuthError(msg, res.status);
187
196
  case 404:
188
197
  throw new NotFoundError(msg);
189
- case 409:
190
- throw new ConflictError(msg);
198
+ case 409: {
199
+ const phase = errBody.phase;
200
+ const conflict = new ConflictError(msg);
201
+ if (phase) conflict.phase = phase;
202
+ throw conflict;
203
+ }
191
204
  case 429:
192
205
  throw new RateLimitError(msg);
193
206
  case 503: {
@@ -292,6 +305,18 @@ var WorkspacesAPI = class {
292
305
  getStatus(id) {
293
306
  return this.client.request("GET", `/workspaces/${id}/status`);
294
307
  }
308
+ /**
309
+ * Uploads a file into the workspace (Epic 67): multipart POST with a
310
+ * single part named `file`; the file lands on the workspace PVC under
311
+ * /workspace/uploads/. The returned path feeds the `files` parameter of
312
+ * sessions.sendPromptAsync / sessions.enqueue. The workspace must be
313
+ * Active; a 409 rejects with ConflictError carrying `phase`.
314
+ */
315
+ upload(id, filename, content) {
316
+ const form = new FormData();
317
+ form.append("file", typeof content === "string" ? new Blob([content]) : content, filename);
318
+ return this.client.request("POST", `/workspaces/${id}/uploads`, form);
319
+ }
295
320
  activate(id) {
296
321
  return this.client.request("POST", `/workspaces/${id}/activate`);
297
322
  }
@@ -393,14 +418,24 @@ var SessionsAPI = class {
393
418
  get(workspaceId, sessionId) {
394
419
  return this.client.request("GET", `/workspaces/${workspaceId}/sessions/${sessionId}`);
395
420
  }
396
- sendPromptAsync(workspaceId, sessionId, message) {
397
- return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/prompt`, { message });
421
+ /**
422
+ * Sends a prompt asynchronously (202; the reply arrives on the workspace
423
+ * SSE stream). Optional `files` (Epic 67) are upload-namespace paths —
424
+ * the API composes the v1 attachment manifest into the dispatched text.
425
+ */
426
+ sendPromptAsync(workspaceId, sessionId, message, files) {
427
+ const body = { parts: [{ type: "text", text: message }] };
428
+ if (files && files.length > 0) body.files = files;
429
+ return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/prompt`, body);
398
430
  }
399
431
  delete(workspaceId, sessionId) {
400
432
  return this.client.request("DELETE", `/workspaces/${workspaceId}/sessions/${sessionId}`);
401
433
  }
402
- enqueue(workspaceId, sessionId, text) {
403
- return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/queue`, { text });
434
+ /** Enqueues a message for a busy session; optional `files` as in sendPromptAsync. */
435
+ enqueue(workspaceId, sessionId, text, files) {
436
+ const body = { text };
437
+ if (files && files.length > 0) body.files = files;
438
+ return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/queue`, body);
404
439
  }
405
440
  /**
406
441
  * @deprecated Under the V2 session-queue model (Epic 63), the queue is
@@ -735,6 +770,110 @@ var TriggersAPI = class {
735
770
  return this.client.request("DELETE", `/me/triggers/${id}`);
736
771
  }
737
772
  };
773
+ var McpServersAPI = class {
774
+ constructor(client) {
775
+ this.client = client;
776
+ }
777
+ client;
778
+ list() {
779
+ return this.client.request("GET", "/me/mcp-servers").then((r) => Array.isArray(r) ? r : r.servers ?? []);
780
+ }
781
+ get(id) {
782
+ return this.client.request("GET", `/me/mcp-servers/${id}`);
783
+ }
784
+ create(req) {
785
+ return this.client.request("POST", "/me/mcp-servers", req);
786
+ }
787
+ update(id, req) {
788
+ return this.client.request("PUT", `/me/mcp-servers/${id}`, req);
789
+ }
790
+ delete(id) {
791
+ return this.client.request("DELETE", `/me/mcp-servers/${id}`);
792
+ }
793
+ bind(id, workspaceId) {
794
+ return this.client.request("POST", `/me/mcp-servers/${id}/bindings`, { workspaceId });
795
+ }
796
+ unbind(id, workspaceId) {
797
+ return this.client.request("DELETE", `/me/mcp-servers/${id}/bindings/${workspaceId}`);
798
+ }
799
+ createAutoApply(id, targetType, targetId) {
800
+ return this.client.request("POST", `/me/mcp-servers/${id}/auto-apply`, { targetType, targetId });
801
+ }
802
+ listAutoApply(id) {
803
+ return this.client.request("GET", `/me/mcp-servers/${id}/auto-apply`).then((r) => Array.isArray(r) ? r : r.rules ?? []);
804
+ }
805
+ };
806
+ var AdminMcpServersAPI = class {
807
+ constructor(client) {
808
+ this.client = client;
809
+ }
810
+ client;
811
+ list() {
812
+ return this.client.request("GET", "/admin/mcp-servers").then((r) => Array.isArray(r) ? r : r.servers ?? []);
813
+ }
814
+ get(id) {
815
+ return this.client.request("GET", `/admin/mcp-servers/${id}`);
816
+ }
817
+ create(req) {
818
+ return this.client.request("POST", "/admin/mcp-servers", req);
819
+ }
820
+ update(id, req) {
821
+ return this.client.request("PUT", `/admin/mcp-servers/${id}`, req);
822
+ }
823
+ delete(id) {
824
+ return this.client.request("DELETE", `/admin/mcp-servers/${id}`);
825
+ }
826
+ bind(id, workspaceId) {
827
+ return this.client.request("POST", `/admin/mcp-servers/${id}/bindings`, { workspaceId });
828
+ }
829
+ unbind(id, workspaceId) {
830
+ return this.client.request("DELETE", `/admin/mcp-servers/${id}/bindings/${workspaceId}`);
831
+ }
832
+ createAutoApply(id, targetType, targetId) {
833
+ return this.client.request("POST", `/admin/mcp-servers/${id}/auto-apply`, { targetType, targetId });
834
+ }
835
+ listAutoApply(id) {
836
+ return this.client.request("GET", `/admin/mcp-servers/${id}/auto-apply`).then((r) => Array.isArray(r) ? r : r.rules ?? []);
837
+ }
838
+ /** targetId omitted → removes every rule of the targetType. */
839
+ deleteAutoApply(id, targetType, targetId) {
840
+ const suffix = targetId ? `/${targetId}` : "";
841
+ return this.client.request("DELETE", `/admin/mcp-servers/${id}/auto-apply/${targetType}${suffix}`);
842
+ }
843
+ };
844
+ var OrgMcpServersAPI = class {
845
+ constructor(client) {
846
+ this.client = client;
847
+ }
848
+ client;
849
+ list(orgId) {
850
+ return this.client.request("GET", `/orgs/${orgId}/mcp-servers`).then((r) => Array.isArray(r) ? r : r.servers ?? []);
851
+ }
852
+ get(orgId, id) {
853
+ return this.client.request("GET", `/orgs/${orgId}/mcp-servers/${id}`);
854
+ }
855
+ create(orgId, req) {
856
+ return this.client.request("POST", `/orgs/${orgId}/mcp-servers`, req);
857
+ }
858
+ update(orgId, id, req) {
859
+ return this.client.request("PUT", `/orgs/${orgId}/mcp-servers/${id}`, req);
860
+ }
861
+ delete(orgId, id) {
862
+ return this.client.request("DELETE", `/orgs/${orgId}/mcp-servers/${id}`);
863
+ }
864
+ bind(orgId, id, workspaceId) {
865
+ return this.client.request("POST", `/orgs/${orgId}/mcp-servers/${id}/bindings`, { workspaceId });
866
+ }
867
+ unbind(orgId, id, workspaceId) {
868
+ return this.client.request("DELETE", `/orgs/${orgId}/mcp-servers/${id}/bindings/${workspaceId}`);
869
+ }
870
+ createAutoApply(orgId, id, targetType, targetId) {
871
+ return this.client.request("POST", `/orgs/${orgId}/mcp-servers/${id}/auto-apply`, { targetType, targetId });
872
+ }
873
+ listAutoApply(orgId, id) {
874
+ return this.client.request("GET", `/orgs/${orgId}/mcp-servers/${id}/auto-apply`).then((r) => Array.isArray(r) ? r : r.rules ?? []);
875
+ }
876
+ };
738
877
 
739
878
  // src/types.ts
740
879
  var SECRET_NAME_PATTERN = /^[a-z0-9._-]+$/;
package/dist/index.d.cts CHANGED
@@ -294,6 +294,58 @@ interface QueuedMessage {
294
294
  enqueued_at: string;
295
295
  retry_count: number;
296
296
  }
297
+ /**
298
+ * Result of a workspace file upload (Epic 67): the absolute path of the
299
+ * stored file on the workspace PVC (/workspace/uploads/<uuid>-<name>),
300
+ * its sanitized name, and the stored byte count.
301
+ */
302
+ interface FileUpload {
303
+ path: string;
304
+ name: string;
305
+ size: number;
306
+ }
307
+ interface McpServer {
308
+ id: string;
309
+ name: string;
310
+ transport: "http" | "sse" | "stdio";
311
+ url?: string;
312
+ command?: string;
313
+ args?: string[];
314
+ timeoutMs?: number;
315
+ hasSecret: boolean;
316
+ enabled: boolean;
317
+ createdAt?: string;
318
+ updatedAt?: string;
319
+ }
320
+ interface CreateMcpServerRequest {
321
+ name: string;
322
+ transport: "http" | "sse" | "stdio";
323
+ url?: string;
324
+ command?: string;
325
+ args?: string[];
326
+ timeoutMs?: number;
327
+ enabled?: boolean;
328
+ env?: Record<string, string>;
329
+ headers?: Record<string, string>;
330
+ autoApply?: {
331
+ targetType: string;
332
+ targetId?: string;
333
+ };
334
+ }
335
+ interface UpdateMcpServerRequest {
336
+ name?: string;
337
+ url?: string;
338
+ command?: string;
339
+ args?: string[];
340
+ timeoutMs?: number;
341
+ enabled?: boolean;
342
+ env?: Record<string, string>;
343
+ headers?: Record<string, string>;
344
+ }
345
+ interface McpAutoApplyRule {
346
+ targetType: string;
347
+ targetId?: string;
348
+ }
297
349
 
298
350
  declare class LLMSafeSpaces {
299
351
  readonly baseUrl: string;
@@ -319,6 +371,9 @@ declare class LLMSafeSpaces {
319
371
  readonly agentRoles: AgentRolesAPI;
320
372
  readonly workflows: WorkflowsAPI;
321
373
  readonly triggers: TriggersAPI;
374
+ readonly mcpServers: McpServersAPI;
375
+ readonly adminMcpServers: AdminMcpServersAPI;
376
+ readonly orgMcpServers: OrgMcpServersAPI;
322
377
  constructor(options: ClientOptions);
323
378
  /** Internal: make an authenticated request. */
324
379
  request<T>(method: string, path: string, body?: unknown, timeout?: number): Promise<T>;
@@ -341,6 +396,14 @@ declare class WorkspacesAPI {
341
396
  rename(id: string, name: string): Promise<void>;
342
397
  delete(id: string): Promise<void>;
343
398
  getStatus(id: string): Promise<WorkspaceStatusResult>;
399
+ /**
400
+ * Uploads a file into the workspace (Epic 67): multipart POST with a
401
+ * single part named `file`; the file lands on the workspace PVC under
402
+ * /workspace/uploads/. The returned path feeds the `files` parameter of
403
+ * sessions.sendPromptAsync / sessions.enqueue. The workspace must be
404
+ * Active; a 409 rejects with ConflictError carrying `phase`.
405
+ */
406
+ upload(id: string, filename: string, content: Blob | string): Promise<FileUpload>;
344
407
  activate(id: string): Promise<ActivateWorkspaceResponse>;
345
408
  suspend(id: string): Promise<void>;
346
409
  restart(id: string): Promise<void>;
@@ -400,9 +463,15 @@ declare class SessionsAPI {
400
463
  }>;
401
464
  abort(workspaceId: string, sessionId: string): Promise<void>;
402
465
  get(workspaceId: string, sessionId: string): Promise<Record<string, unknown>>;
403
- sendPromptAsync(workspaceId: string, sessionId: string, message: string): Promise<void>;
466
+ /**
467
+ * Sends a prompt asynchronously (202; the reply arrives on the workspace
468
+ * SSE stream). Optional `files` (Epic 67) are upload-namespace paths —
469
+ * the API composes the v1 attachment manifest into the dispatched text.
470
+ */
471
+ sendPromptAsync(workspaceId: string, sessionId: string, message: string, files?: string[]): Promise<void>;
404
472
  delete(workspaceId: string, sessionId: string): Promise<void>;
405
- enqueue(workspaceId: string, sessionId: string, text: string): Promise<{
473
+ /** Enqueues a message for a busy session; optional `files` as in sendPromptAsync. */
474
+ enqueue(workspaceId: string, sessionId: string, text: string, files?: string[]): Promise<{
406
475
  messageID: string;
407
476
  }>;
408
477
  /**
@@ -660,6 +729,50 @@ declare class TriggersAPI {
660
729
  }): Promise<TriggerResponse>;
661
730
  delete(id: string): Promise<void>;
662
731
  }
732
+ /** MCP servers owned by the caller (/me/mcp-servers, Epic 53). */
733
+ declare class McpServersAPI {
734
+ private readonly client;
735
+ constructor(client: LLMSafeSpaces);
736
+ list(): Promise<McpServer[]>;
737
+ get(id: string): Promise<McpServer>;
738
+ create(req: CreateMcpServerRequest): Promise<McpServer>;
739
+ update(id: string, req: UpdateMcpServerRequest): Promise<McpServer>;
740
+ delete(id: string): Promise<void>;
741
+ bind(id: string, workspaceId: string): Promise<void>;
742
+ unbind(id: string, workspaceId: string): Promise<void>;
743
+ createAutoApply(id: string, targetType: string, targetId?: string): Promise<void>;
744
+ listAutoApply(id: string): Promise<McpAutoApplyRule[]>;
745
+ }
746
+ /** Platform MCP servers (/admin/mcp-servers; admin scope). */
747
+ declare class AdminMcpServersAPI {
748
+ private readonly client;
749
+ constructor(client: LLMSafeSpaces);
750
+ list(): Promise<McpServer[]>;
751
+ get(id: string): Promise<McpServer>;
752
+ create(req: CreateMcpServerRequest): Promise<McpServer>;
753
+ update(id: string, req: UpdateMcpServerRequest): Promise<McpServer>;
754
+ delete(id: string): Promise<void>;
755
+ bind(id: string, workspaceId: string): Promise<void>;
756
+ unbind(id: string, workspaceId: string): Promise<void>;
757
+ createAutoApply(id: string, targetType: string, targetId?: string): Promise<void>;
758
+ listAutoApply(id: string): Promise<McpAutoApplyRule[]>;
759
+ /** targetId omitted → removes every rule of the targetType. */
760
+ deleteAutoApply(id: string, targetType: string, targetId?: string): Promise<void>;
761
+ }
762
+ /** Organization MCP servers (/orgs/{orgId}/mcp-servers; org-admin scope). */
763
+ declare class OrgMcpServersAPI {
764
+ private readonly client;
765
+ constructor(client: LLMSafeSpaces);
766
+ list(orgId: string): Promise<McpServer[]>;
767
+ get(orgId: string, id: string): Promise<McpServer>;
768
+ create(orgId: string, req: CreateMcpServerRequest): Promise<McpServer>;
769
+ update(orgId: string, id: string, req: UpdateMcpServerRequest): Promise<McpServer>;
770
+ delete(orgId: string, id: string): Promise<void>;
771
+ bind(orgId: string, id: string, workspaceId: string): Promise<void>;
772
+ unbind(orgId: string, id: string, workspaceId: string): Promise<void>;
773
+ createAutoApply(orgId: string, id: string, targetType: string, targetId?: string): Promise<void>;
774
+ listAutoApply(orgId: string, id: string): Promise<McpAutoApplyRule[]>;
775
+ }
663
776
 
664
777
  /** Base error for all LLMSafeSpaces API errors. */
665
778
  declare class LLMSafeSpacesError extends Error {
@@ -674,6 +787,8 @@ declare class NotFoundError extends LLMSafeSpacesError {
674
787
  constructor(message: string);
675
788
  }
676
789
  declare class ConflictError extends LLMSafeSpacesError {
790
+ /** Current workspace phase, when the 409 body carries one (upload phase gate, Epic 67 D5). */
791
+ phase?: string;
677
792
  constructor(message: string);
678
793
  }
679
794
  declare class TimeoutError extends LLMSafeSpacesError {
@@ -698,4 +813,4 @@ declare class ServiceUnavailableError extends LLMSafeSpacesError {
698
813
  constructor(message?: string, reason?: string, retryAfter?: number);
699
814
  }
700
815
 
701
- export { type APIKey, type ActivateWorkspaceResponse, type ActiveSessionsResponse, AuthError, type AuthResponse, type ClientOptions, ConflictError, type Cost, type CreateProviderCredentialRequest, type CreateSecretRequest, type CreateWorkspaceRequest, type EnsureSessionResponse, type FetchFn, type FileDiff, type InputOption, type InputRequest, LLMSafeSpaces, LLMSafeSpacesError, type Message, type ModelRef, NotFoundError, type PaginationMetadata, type Part, type ProviderCredential, type QueuedMessage, RateLimitError, type RefreshWorkspaceResult, SECRET_NAME_PATTERN, type SecretResponse, ServiceUnavailableError, type SessionListItem, type TerminalTicket, TimeoutError, type ToolPart, type ToolRef, type UpdateProviderCredentialRequest, type User, type Workspace, type WorkspaceCondition, type WorkspaceListItem, type WorkspaceListResult, type WorkspaceStatusResult };
816
+ export { type APIKey, type ActivateWorkspaceResponse, type ActiveSessionsResponse, AuthError, type AuthResponse, type ClientOptions, ConflictError, type Cost, type CreateMcpServerRequest, type CreateProviderCredentialRequest, type CreateSecretRequest, type CreateWorkspaceRequest, type EnsureSessionResponse, type FetchFn, type FileDiff, type FileUpload, type InputOption, type InputRequest, LLMSafeSpaces, LLMSafeSpacesError, type McpAutoApplyRule, type McpServer, type Message, type ModelRef, NotFoundError, type PaginationMetadata, type Part, type ProviderCredential, type QueuedMessage, RateLimitError, type RefreshWorkspaceResult, SECRET_NAME_PATTERN, type SecretResponse, ServiceUnavailableError, type SessionListItem, type TerminalTicket, TimeoutError, type ToolPart, type ToolRef, type UpdateMcpServerRequest, type UpdateProviderCredentialRequest, type User, type Workspace, type WorkspaceCondition, type WorkspaceListItem, type WorkspaceListResult, type WorkspaceStatusResult };
package/dist/index.d.ts CHANGED
@@ -294,6 +294,58 @@ interface QueuedMessage {
294
294
  enqueued_at: string;
295
295
  retry_count: number;
296
296
  }
297
+ /**
298
+ * Result of a workspace file upload (Epic 67): the absolute path of the
299
+ * stored file on the workspace PVC (/workspace/uploads/&lt;uuid&gt;-&lt;name&gt;),
300
+ * its sanitized name, and the stored byte count.
301
+ */
302
+ interface FileUpload {
303
+ path: string;
304
+ name: string;
305
+ size: number;
306
+ }
307
+ interface McpServer {
308
+ id: string;
309
+ name: string;
310
+ transport: "http" | "sse" | "stdio";
311
+ url?: string;
312
+ command?: string;
313
+ args?: string[];
314
+ timeoutMs?: number;
315
+ hasSecret: boolean;
316
+ enabled: boolean;
317
+ createdAt?: string;
318
+ updatedAt?: string;
319
+ }
320
+ interface CreateMcpServerRequest {
321
+ name: string;
322
+ transport: "http" | "sse" | "stdio";
323
+ url?: string;
324
+ command?: string;
325
+ args?: string[];
326
+ timeoutMs?: number;
327
+ enabled?: boolean;
328
+ env?: Record<string, string>;
329
+ headers?: Record<string, string>;
330
+ autoApply?: {
331
+ targetType: string;
332
+ targetId?: string;
333
+ };
334
+ }
335
+ interface UpdateMcpServerRequest {
336
+ name?: string;
337
+ url?: string;
338
+ command?: string;
339
+ args?: string[];
340
+ timeoutMs?: number;
341
+ enabled?: boolean;
342
+ env?: Record<string, string>;
343
+ headers?: Record<string, string>;
344
+ }
345
+ interface McpAutoApplyRule {
346
+ targetType: string;
347
+ targetId?: string;
348
+ }
297
349
 
298
350
  declare class LLMSafeSpaces {
299
351
  readonly baseUrl: string;
@@ -319,6 +371,9 @@ declare class LLMSafeSpaces {
319
371
  readonly agentRoles: AgentRolesAPI;
320
372
  readonly workflows: WorkflowsAPI;
321
373
  readonly triggers: TriggersAPI;
374
+ readonly mcpServers: McpServersAPI;
375
+ readonly adminMcpServers: AdminMcpServersAPI;
376
+ readonly orgMcpServers: OrgMcpServersAPI;
322
377
  constructor(options: ClientOptions);
323
378
  /** Internal: make an authenticated request. */
324
379
  request<T>(method: string, path: string, body?: unknown, timeout?: number): Promise<T>;
@@ -341,6 +396,14 @@ declare class WorkspacesAPI {
341
396
  rename(id: string, name: string): Promise<void>;
342
397
  delete(id: string): Promise<void>;
343
398
  getStatus(id: string): Promise<WorkspaceStatusResult>;
399
+ /**
400
+ * Uploads a file into the workspace (Epic 67): multipart POST with a
401
+ * single part named `file`; the file lands on the workspace PVC under
402
+ * /workspace/uploads/. The returned path feeds the `files` parameter of
403
+ * sessions.sendPromptAsync / sessions.enqueue. The workspace must be
404
+ * Active; a 409 rejects with ConflictError carrying `phase`.
405
+ */
406
+ upload(id: string, filename: string, content: Blob | string): Promise<FileUpload>;
344
407
  activate(id: string): Promise<ActivateWorkspaceResponse>;
345
408
  suspend(id: string): Promise<void>;
346
409
  restart(id: string): Promise<void>;
@@ -400,9 +463,15 @@ declare class SessionsAPI {
400
463
  }>;
401
464
  abort(workspaceId: string, sessionId: string): Promise<void>;
402
465
  get(workspaceId: string, sessionId: string): Promise<Record<string, unknown>>;
403
- sendPromptAsync(workspaceId: string, sessionId: string, message: string): Promise<void>;
466
+ /**
467
+ * Sends a prompt asynchronously (202; the reply arrives on the workspace
468
+ * SSE stream). Optional `files` (Epic 67) are upload-namespace paths —
469
+ * the API composes the v1 attachment manifest into the dispatched text.
470
+ */
471
+ sendPromptAsync(workspaceId: string, sessionId: string, message: string, files?: string[]): Promise<void>;
404
472
  delete(workspaceId: string, sessionId: string): Promise<void>;
405
- enqueue(workspaceId: string, sessionId: string, text: string): Promise<{
473
+ /** Enqueues a message for a busy session; optional `files` as in sendPromptAsync. */
474
+ enqueue(workspaceId: string, sessionId: string, text: string, files?: string[]): Promise<{
406
475
  messageID: string;
407
476
  }>;
408
477
  /**
@@ -660,6 +729,50 @@ declare class TriggersAPI {
660
729
  }): Promise<TriggerResponse>;
661
730
  delete(id: string): Promise<void>;
662
731
  }
732
+ /** MCP servers owned by the caller (/me/mcp-servers, Epic 53). */
733
+ declare class McpServersAPI {
734
+ private readonly client;
735
+ constructor(client: LLMSafeSpaces);
736
+ list(): Promise<McpServer[]>;
737
+ get(id: string): Promise<McpServer>;
738
+ create(req: CreateMcpServerRequest): Promise<McpServer>;
739
+ update(id: string, req: UpdateMcpServerRequest): Promise<McpServer>;
740
+ delete(id: string): Promise<void>;
741
+ bind(id: string, workspaceId: string): Promise<void>;
742
+ unbind(id: string, workspaceId: string): Promise<void>;
743
+ createAutoApply(id: string, targetType: string, targetId?: string): Promise<void>;
744
+ listAutoApply(id: string): Promise<McpAutoApplyRule[]>;
745
+ }
746
+ /** Platform MCP servers (/admin/mcp-servers; admin scope). */
747
+ declare class AdminMcpServersAPI {
748
+ private readonly client;
749
+ constructor(client: LLMSafeSpaces);
750
+ list(): Promise<McpServer[]>;
751
+ get(id: string): Promise<McpServer>;
752
+ create(req: CreateMcpServerRequest): Promise<McpServer>;
753
+ update(id: string, req: UpdateMcpServerRequest): Promise<McpServer>;
754
+ delete(id: string): Promise<void>;
755
+ bind(id: string, workspaceId: string): Promise<void>;
756
+ unbind(id: string, workspaceId: string): Promise<void>;
757
+ createAutoApply(id: string, targetType: string, targetId?: string): Promise<void>;
758
+ listAutoApply(id: string): Promise<McpAutoApplyRule[]>;
759
+ /** targetId omitted → removes every rule of the targetType. */
760
+ deleteAutoApply(id: string, targetType: string, targetId?: string): Promise<void>;
761
+ }
762
+ /** Organization MCP servers (/orgs/{orgId}/mcp-servers; org-admin scope). */
763
+ declare class OrgMcpServersAPI {
764
+ private readonly client;
765
+ constructor(client: LLMSafeSpaces);
766
+ list(orgId: string): Promise<McpServer[]>;
767
+ get(orgId: string, id: string): Promise<McpServer>;
768
+ create(orgId: string, req: CreateMcpServerRequest): Promise<McpServer>;
769
+ update(orgId: string, id: string, req: UpdateMcpServerRequest): Promise<McpServer>;
770
+ delete(orgId: string, id: string): Promise<void>;
771
+ bind(orgId: string, id: string, workspaceId: string): Promise<void>;
772
+ unbind(orgId: string, id: string, workspaceId: string): Promise<void>;
773
+ createAutoApply(orgId: string, id: string, targetType: string, targetId?: string): Promise<void>;
774
+ listAutoApply(orgId: string, id: string): Promise<McpAutoApplyRule[]>;
775
+ }
663
776
 
664
777
  /** Base error for all LLMSafeSpaces API errors. */
665
778
  declare class LLMSafeSpacesError extends Error {
@@ -674,6 +787,8 @@ declare class NotFoundError extends LLMSafeSpacesError {
674
787
  constructor(message: string);
675
788
  }
676
789
  declare class ConflictError extends LLMSafeSpacesError {
790
+ /** Current workspace phase, when the 409 body carries one (upload phase gate, Epic 67 D5). */
791
+ phase?: string;
677
792
  constructor(message: string);
678
793
  }
679
794
  declare class TimeoutError extends LLMSafeSpacesError {
@@ -698,4 +813,4 @@ declare class ServiceUnavailableError extends LLMSafeSpacesError {
698
813
  constructor(message?: string, reason?: string, retryAfter?: number);
699
814
  }
700
815
 
701
- export { type APIKey, type ActivateWorkspaceResponse, type ActiveSessionsResponse, AuthError, type AuthResponse, type ClientOptions, ConflictError, type Cost, type CreateProviderCredentialRequest, type CreateSecretRequest, type CreateWorkspaceRequest, type EnsureSessionResponse, type FetchFn, type FileDiff, type InputOption, type InputRequest, LLMSafeSpaces, LLMSafeSpacesError, type Message, type ModelRef, NotFoundError, type PaginationMetadata, type Part, type ProviderCredential, type QueuedMessage, RateLimitError, type RefreshWorkspaceResult, SECRET_NAME_PATTERN, type SecretResponse, ServiceUnavailableError, type SessionListItem, type TerminalTicket, TimeoutError, type ToolPart, type ToolRef, type UpdateProviderCredentialRequest, type User, type Workspace, type WorkspaceCondition, type WorkspaceListItem, type WorkspaceListResult, type WorkspaceStatusResult };
816
+ export { type APIKey, type ActivateWorkspaceResponse, type ActiveSessionsResponse, AuthError, type AuthResponse, type ClientOptions, ConflictError, type Cost, type CreateMcpServerRequest, type CreateProviderCredentialRequest, type CreateSecretRequest, type CreateWorkspaceRequest, type EnsureSessionResponse, type FetchFn, type FileDiff, type FileUpload, type InputOption, type InputRequest, LLMSafeSpaces, LLMSafeSpacesError, type McpAutoApplyRule, type McpServer, type Message, type ModelRef, NotFoundError, type PaginationMetadata, type Part, type ProviderCredential, type QueuedMessage, RateLimitError, type RefreshWorkspaceResult, SECRET_NAME_PATTERN, type SecretResponse, ServiceUnavailableError, type SessionListItem, type TerminalTicket, TimeoutError, type ToolPart, type ToolRef, type UpdateMcpServerRequest, type UpdateProviderCredentialRequest, type User, type Workspace, type WorkspaceCondition, type WorkspaceListItem, type WorkspaceListResult, type WorkspaceStatusResult };
package/dist/index.js CHANGED
@@ -22,6 +22,8 @@ var NotFoundError = class extends LLMSafeSpacesError {
22
22
  }
23
23
  };
24
24
  var ConflictError = class extends LLMSafeSpacesError {
25
+ /** Current workspace phase, when the 409 body carries one (upload phase gate, Epic 67 D5). */
26
+ phase;
25
27
  constructor(message) {
26
28
  super(message, 409, "CONFLICT");
27
29
  this.name = "ConflictError";
@@ -86,6 +88,9 @@ var LLMSafeSpaces = class {
86
88
  agentRoles;
87
89
  workflows;
88
90
  triggers;
91
+ mcpServers;
92
+ adminMcpServers;
93
+ orgMcpServers;
89
94
  constructor(options) {
90
95
  this.baseUrl = options.baseUrl.replace(/\/$/, "");
91
96
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
@@ -108,11 +113,15 @@ var LLMSafeSpaces = class {
108
113
  this.agentRoles = new AgentRolesAPI(this);
109
114
  this.workflows = new WorkflowsAPI(this);
110
115
  this.triggers = new TriggersAPI(this);
116
+ this.mcpServers = new McpServersAPI(this);
117
+ this.adminMcpServers = new AdminMcpServersAPI(this);
118
+ this.orgMcpServers = new OrgMcpServersAPI(this);
111
119
  }
112
120
  /** Internal: make an authenticated request. */
113
121
  async request(method, path, body, timeout) {
114
122
  const url = `${this.baseUrl}/api/v1${path}`;
115
- const headers = { "Content-Type": "application/json" };
123
+ const isForm = typeof FormData !== "undefined" && body instanceof FormData;
124
+ const headers = isForm ? {} : { "Content-Type": "application/json" };
116
125
  if (this.apiKey) {
117
126
  headers["Authorization"] = `Bearer ${this.apiKey}`;
118
127
  } else if (this.token) {
@@ -128,7 +137,7 @@ var LLMSafeSpaces = class {
128
137
  res = await this.fetchFn(url, {
129
138
  method,
130
139
  headers,
131
- body: body ? JSON.stringify(body) : void 0,
140
+ body: body ? isForm ? body : JSON.stringify(body) : void 0,
132
141
  signal: controller.signal
133
142
  });
134
143
  } catch (e) {
@@ -152,8 +161,12 @@ var LLMSafeSpaces = class {
152
161
  throw new AuthError(msg, res.status);
153
162
  case 404:
154
163
  throw new NotFoundError(msg);
155
- case 409:
156
- throw new ConflictError(msg);
164
+ case 409: {
165
+ const phase = errBody.phase;
166
+ const conflict = new ConflictError(msg);
167
+ if (phase) conflict.phase = phase;
168
+ throw conflict;
169
+ }
157
170
  case 429:
158
171
  throw new RateLimitError(msg);
159
172
  case 503: {
@@ -258,6 +271,18 @@ var WorkspacesAPI = class {
258
271
  getStatus(id) {
259
272
  return this.client.request("GET", `/workspaces/${id}/status`);
260
273
  }
274
+ /**
275
+ * Uploads a file into the workspace (Epic 67): multipart POST with a
276
+ * single part named `file`; the file lands on the workspace PVC under
277
+ * /workspace/uploads/. The returned path feeds the `files` parameter of
278
+ * sessions.sendPromptAsync / sessions.enqueue. The workspace must be
279
+ * Active; a 409 rejects with ConflictError carrying `phase`.
280
+ */
281
+ upload(id, filename, content) {
282
+ const form = new FormData();
283
+ form.append("file", typeof content === "string" ? new Blob([content]) : content, filename);
284
+ return this.client.request("POST", `/workspaces/${id}/uploads`, form);
285
+ }
261
286
  activate(id) {
262
287
  return this.client.request("POST", `/workspaces/${id}/activate`);
263
288
  }
@@ -359,14 +384,24 @@ var SessionsAPI = class {
359
384
  get(workspaceId, sessionId) {
360
385
  return this.client.request("GET", `/workspaces/${workspaceId}/sessions/${sessionId}`);
361
386
  }
362
- sendPromptAsync(workspaceId, sessionId, message) {
363
- return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/prompt`, { message });
387
+ /**
388
+ * Sends a prompt asynchronously (202; the reply arrives on the workspace
389
+ * SSE stream). Optional `files` (Epic 67) are upload-namespace paths —
390
+ * the API composes the v1 attachment manifest into the dispatched text.
391
+ */
392
+ sendPromptAsync(workspaceId, sessionId, message, files) {
393
+ const body = { parts: [{ type: "text", text: message }] };
394
+ if (files && files.length > 0) body.files = files;
395
+ return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/prompt`, body);
364
396
  }
365
397
  delete(workspaceId, sessionId) {
366
398
  return this.client.request("DELETE", `/workspaces/${workspaceId}/sessions/${sessionId}`);
367
399
  }
368
- enqueue(workspaceId, sessionId, text) {
369
- return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/queue`, { text });
400
+ /** Enqueues a message for a busy session; optional `files` as in sendPromptAsync. */
401
+ enqueue(workspaceId, sessionId, text, files) {
402
+ const body = { text };
403
+ if (files && files.length > 0) body.files = files;
404
+ return this.client.request("POST", `/workspaces/${workspaceId}/sessions/${sessionId}/queue`, body);
370
405
  }
371
406
  /**
372
407
  * @deprecated Under the V2 session-queue model (Epic 63), the queue is
@@ -701,6 +736,110 @@ var TriggersAPI = class {
701
736
  return this.client.request("DELETE", `/me/triggers/${id}`);
702
737
  }
703
738
  };
739
+ var McpServersAPI = class {
740
+ constructor(client) {
741
+ this.client = client;
742
+ }
743
+ client;
744
+ list() {
745
+ return this.client.request("GET", "/me/mcp-servers").then((r) => Array.isArray(r) ? r : r.servers ?? []);
746
+ }
747
+ get(id) {
748
+ return this.client.request("GET", `/me/mcp-servers/${id}`);
749
+ }
750
+ create(req) {
751
+ return this.client.request("POST", "/me/mcp-servers", req);
752
+ }
753
+ update(id, req) {
754
+ return this.client.request("PUT", `/me/mcp-servers/${id}`, req);
755
+ }
756
+ delete(id) {
757
+ return this.client.request("DELETE", `/me/mcp-servers/${id}`);
758
+ }
759
+ bind(id, workspaceId) {
760
+ return this.client.request("POST", `/me/mcp-servers/${id}/bindings`, { workspaceId });
761
+ }
762
+ unbind(id, workspaceId) {
763
+ return this.client.request("DELETE", `/me/mcp-servers/${id}/bindings/${workspaceId}`);
764
+ }
765
+ createAutoApply(id, targetType, targetId) {
766
+ return this.client.request("POST", `/me/mcp-servers/${id}/auto-apply`, { targetType, targetId });
767
+ }
768
+ listAutoApply(id) {
769
+ return this.client.request("GET", `/me/mcp-servers/${id}/auto-apply`).then((r) => Array.isArray(r) ? r : r.rules ?? []);
770
+ }
771
+ };
772
+ var AdminMcpServersAPI = class {
773
+ constructor(client) {
774
+ this.client = client;
775
+ }
776
+ client;
777
+ list() {
778
+ return this.client.request("GET", "/admin/mcp-servers").then((r) => Array.isArray(r) ? r : r.servers ?? []);
779
+ }
780
+ get(id) {
781
+ return this.client.request("GET", `/admin/mcp-servers/${id}`);
782
+ }
783
+ create(req) {
784
+ return this.client.request("POST", "/admin/mcp-servers", req);
785
+ }
786
+ update(id, req) {
787
+ return this.client.request("PUT", `/admin/mcp-servers/${id}`, req);
788
+ }
789
+ delete(id) {
790
+ return this.client.request("DELETE", `/admin/mcp-servers/${id}`);
791
+ }
792
+ bind(id, workspaceId) {
793
+ return this.client.request("POST", `/admin/mcp-servers/${id}/bindings`, { workspaceId });
794
+ }
795
+ unbind(id, workspaceId) {
796
+ return this.client.request("DELETE", `/admin/mcp-servers/${id}/bindings/${workspaceId}`);
797
+ }
798
+ createAutoApply(id, targetType, targetId) {
799
+ return this.client.request("POST", `/admin/mcp-servers/${id}/auto-apply`, { targetType, targetId });
800
+ }
801
+ listAutoApply(id) {
802
+ return this.client.request("GET", `/admin/mcp-servers/${id}/auto-apply`).then((r) => Array.isArray(r) ? r : r.rules ?? []);
803
+ }
804
+ /** targetId omitted → removes every rule of the targetType. */
805
+ deleteAutoApply(id, targetType, targetId) {
806
+ const suffix = targetId ? `/${targetId}` : "";
807
+ return this.client.request("DELETE", `/admin/mcp-servers/${id}/auto-apply/${targetType}${suffix}`);
808
+ }
809
+ };
810
+ var OrgMcpServersAPI = class {
811
+ constructor(client) {
812
+ this.client = client;
813
+ }
814
+ client;
815
+ list(orgId) {
816
+ return this.client.request("GET", `/orgs/${orgId}/mcp-servers`).then((r) => Array.isArray(r) ? r : r.servers ?? []);
817
+ }
818
+ get(orgId, id) {
819
+ return this.client.request("GET", `/orgs/${orgId}/mcp-servers/${id}`);
820
+ }
821
+ create(orgId, req) {
822
+ return this.client.request("POST", `/orgs/${orgId}/mcp-servers`, req);
823
+ }
824
+ update(orgId, id, req) {
825
+ return this.client.request("PUT", `/orgs/${orgId}/mcp-servers/${id}`, req);
826
+ }
827
+ delete(orgId, id) {
828
+ return this.client.request("DELETE", `/orgs/${orgId}/mcp-servers/${id}`);
829
+ }
830
+ bind(orgId, id, workspaceId) {
831
+ return this.client.request("POST", `/orgs/${orgId}/mcp-servers/${id}/bindings`, { workspaceId });
832
+ }
833
+ unbind(orgId, id, workspaceId) {
834
+ return this.client.request("DELETE", `/orgs/${orgId}/mcp-servers/${id}/bindings/${workspaceId}`);
835
+ }
836
+ createAutoApply(orgId, id, targetType, targetId) {
837
+ return this.client.request("POST", `/orgs/${orgId}/mcp-servers/${id}/auto-apply`, { targetType, targetId });
838
+ }
839
+ listAutoApply(orgId, id) {
840
+ return this.client.request("GET", `/orgs/${orgId}/mcp-servers/${id}/auto-apply`).then((r) => Array.isArray(r) ? r : r.rules ?? []);
841
+ }
842
+ };
704
843
 
705
844
  // src/types.ts
706
845
  var SECRET_NAME_PATTERN = /^[a-z0-9._-]+$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmsafespaces/sdk",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "TypeScript SDK for LLMSafeSpaces API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",