@codeguide/core 0.0.28 → 0.0.33

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.
Files changed (85) hide show
  1. package/__tests__/services/codespace/codespace-v2.test.ts +53 -0
  2. package/__tests__/services/usage/usage-service.test.ts +458 -104
  3. package/codeguide.ts +3 -0
  4. package/dist/codeguide.d.ts +2 -1
  5. package/dist/codeguide.js +1 -0
  6. package/dist/index.d.ts +4 -3
  7. package/dist/services/base/base-service.d.ts +21 -0
  8. package/dist/services/base/base-service.js +114 -0
  9. package/dist/services/codespace/codespace-service.d.ts +64 -1
  10. package/dist/services/codespace/codespace-service.js +272 -0
  11. package/dist/services/codespace/codespace-types.d.ts +213 -12
  12. package/dist/services/codespace/index.d.ts +1 -1
  13. package/dist/services/index.d.ts +2 -0
  14. package/dist/services/index.js +4 -1
  15. package/dist/services/projects/project-types.d.ts +66 -32
  16. package/dist/services/starter-kits/index.d.ts +2 -0
  17. package/dist/services/starter-kits/index.js +20 -0
  18. package/dist/services/starter-kits/starter-kits-service.d.ts +13 -0
  19. package/dist/services/starter-kits/starter-kits-service.js +27 -0
  20. package/dist/services/starter-kits/starter-kits-types.d.ts +34 -0
  21. package/dist/services/starter-kits/starter-kits-types.js +2 -0
  22. package/dist/services/subscriptions/subscription-service.d.ts +11 -1
  23. package/dist/services/subscriptions/subscription-service.js +14 -0
  24. package/dist/services/tasks/task-service.d.ts +2 -1
  25. package/dist/services/tasks/task-service.js +8 -0
  26. package/dist/services/tasks/task-types.d.ts +26 -7
  27. package/dist/services/usage/usage-service.d.ts +5 -2
  28. package/dist/services/usage/usage-service.js +58 -9
  29. package/dist/services/usage/usage-types.d.ts +157 -34
  30. package/dist/types.d.ts +18 -2
  31. package/docs/.vitepress/README.md +51 -0
  32. package/docs/.vitepress/config.ts +139 -0
  33. package/docs/.vitepress/theme/custom.css +80 -0
  34. package/docs/.vitepress/theme/index.ts +13 -0
  35. package/docs/.vitepress/tsconfig.json +19 -0
  36. package/docs/QUICKSTART.md +77 -0
  37. package/docs/README.md +134 -0
  38. package/docs/README_SETUP.md +46 -0
  39. package/docs/authentication.md +351 -0
  40. package/docs/codeguide-client.md +350 -0
  41. package/docs/codespace-models.md +1004 -0
  42. package/docs/codespace-service.md +444 -0
  43. package/docs/index.md +135 -0
  44. package/docs/package.json +14 -0
  45. package/docs/projects-service.md +688 -0
  46. package/docs/security-keys-service.md +773 -0
  47. package/docs/starter-kits-service.md +249 -0
  48. package/docs/task-service.md +955 -0
  49. package/docs/testsprite_tests/TC001_Homepage_Load_and_Hero_Section_Display.py +70 -0
  50. package/docs/testsprite_tests/TC002_Sidebar_Navigation_ExpandCollapse_Functionality.py +73 -0
  51. package/docs/testsprite_tests/TC003_Full_Text_Local_Search_with_Keyboard_Shortcut.py +90 -0
  52. package/docs/testsprite_tests/TC004_Dark_Mode_Toggle_and_Persistence.py +73 -0
  53. package/docs/testsprite_tests/TC005_Mobile_Responsiveness_and_Touch_Navigation.py +113 -0
  54. package/docs/testsprite_tests/TC006_GitHub_Integration_Edit_this_page_Links.py +73 -0
  55. package/docs/testsprite_tests/TC007_Syntax_Highlighting_and_Code_Copy_Functionality.py +73 -0
  56. package/docs/testsprite_tests/TC008_Auto_Generated_Table_of_Contents_Accuracy.py +73 -0
  57. package/docs/testsprite_tests/TC009_SEO_and_Content_Discoverability_Verification.py +73 -0
  58. package/docs/testsprite_tests/TC010_Accessibility_Compliance_WCAG_AA.py +73 -0
  59. package/docs/testsprite_tests/TC011_Local_Development_Workflow_Build_and_Hot_Reload.py +74 -0
  60. package/docs/testsprite_tests/TC012_Performance_Metrics_Compliance.py +73 -0
  61. package/docs/testsprite_tests/standard_prd.json +122 -0
  62. package/docs/testsprite_tests/testsprite-mcp-test-report.html +2508 -0
  63. package/docs/testsprite_tests/testsprite-mcp-test-report.md +273 -0
  64. package/docs/testsprite_tests/testsprite_frontend_test_plan.json +390 -0
  65. package/docs/usage-service.md +291 -1
  66. package/index.ts +11 -3
  67. package/package.json +17 -2
  68. package/plans/CODESPACE_LOGS_STREAMING_GUIDE.md +320 -0
  69. package/plans/CODESPACE_TASK_LOGS_API_COMPLETE_GUIDE.md +821 -0
  70. package/services/base/base-service.ts +130 -0
  71. package/services/codespace/codespace-service.ts +359 -0
  72. package/services/codespace/codespace-types.ts +295 -13
  73. package/services/codespace/index.ts +21 -1
  74. package/services/index.ts +2 -0
  75. package/services/projects/README.md +107 -34
  76. package/services/projects/project-types.ts +69 -32
  77. package/services/starter-kits/index.ts +2 -0
  78. package/services/starter-kits/starter-kits-service.ts +33 -0
  79. package/services/starter-kits/starter-kits-types.ts +38 -0
  80. package/services/subscriptions/subscription-service.ts +23 -5
  81. package/services/tasks/task-service.ts +10 -0
  82. package/services/tasks/task-types.ts +29 -7
  83. package/services/usage/usage-service.ts +59 -10
  84. package/services/usage/usage-types.ts +186 -36
  85. package/types.ts +22 -2
@@ -238,6 +238,11 @@ export abstract class BaseService {
238
238
  return response.data
239
239
  }
240
240
 
241
+ protected async patch<T>(url: string, data?: any, config?: any): Promise<T> {
242
+ const response = await this.client.patch<T>(url, data, config)
243
+ return response.data
244
+ }
245
+
241
246
  protected async delete<T>(url: string, config?: any): Promise<T> {
242
247
  const response = await this.client.delete<T>(url, config)
243
248
  return response.data
@@ -246,4 +251,129 @@ export abstract class BaseService {
246
251
  protected buildUrl(endpoint: string): string {
247
252
  return endpoint.startsWith('/') ? endpoint : `/${endpoint}`
248
253
  }
254
+
255
+ /**
256
+ * Create a streaming connection for Server-Sent Events (SSE)
257
+ *
258
+ * @param url - The endpoint URL for streaming
259
+ * @param config - Optional configuration for the stream request
260
+ * @returns Promise that resolves to EventSource for SSE streaming
261
+ */
262
+ protected async createStream(url: string, config?: any): Promise<EventSource> {
263
+ // Build full URL
264
+ const fullUrl = `${this.client.defaults.baseURL}${this.buildUrl(url)}`
265
+
266
+ // For SSE, we need to use EventSource API directly
267
+ // However, we need to handle authentication differently since EventSource doesn't support custom headers
268
+ // We'll pass auth info as query parameters for SSE endpoints
269
+
270
+ return new Promise((resolve, reject) => {
271
+ try {
272
+ const eventSource = new EventSource(fullUrl)
273
+ resolve(eventSource)
274
+ } catch (error) {
275
+ reject(error)
276
+ }
277
+ })
278
+ }
279
+
280
+ /**
281
+ * Alternative streaming method using fetch for environments where EventSource is not available
282
+ * or when custom headers are needed for authentication
283
+ *
284
+ * @param url - The endpoint URL for streaming
285
+ * @param onMessage - Callback function for handling stream messages
286
+ * @param onError - Callback function for handling stream errors
287
+ * @param onComplete - Callback function for when stream completes
288
+ * @param config - Optional configuration for the stream request
289
+ * @returns Promise that resolves to a cleanup function
290
+ */
291
+ protected async createStreamWithFetch(
292
+ url: string,
293
+ onMessage: (data: any) => void,
294
+ onError?: (error: any) => void,
295
+ onComplete?: () => void,
296
+ config?: any
297
+ ): Promise<() => void> {
298
+ const streamConfig = {
299
+ ...config,
300
+ headers: {
301
+ ...this.client.defaults.headers,
302
+ 'Accept': 'text/event-stream',
303
+ 'Cache-Control': 'no-cache',
304
+ ...config?.headers,
305
+ },
306
+ }
307
+
308
+ const fullUrl = `${this.client.defaults.baseURL}${this.buildUrl(url)}`
309
+
310
+ try {
311
+ const response = await fetch(fullUrl, streamConfig)
312
+
313
+ if (!response.ok) {
314
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
315
+ }
316
+
317
+ const reader = response.body?.getReader()
318
+ if (!reader) {
319
+ throw new Error('Response body is not readable')
320
+ }
321
+
322
+ const decoder = new TextDecoder()
323
+ let buffer = ''
324
+
325
+ const processStream = async () => {
326
+ try {
327
+ while (true) {
328
+ const { done, value } = await reader.read()
329
+
330
+ if (done) {
331
+ onComplete?.()
332
+ break
333
+ }
334
+
335
+ buffer += decoder.decode(value, { stream: true })
336
+ const lines = buffer.split('\n')
337
+ buffer = lines.pop() || '' // Keep incomplete line in buffer
338
+
339
+ for (const line of lines) {
340
+ if (line.startsWith('data: ')) {
341
+ const data = line.slice(6) // Remove 'data: ' prefix
342
+ if (data.trim()) {
343
+ try {
344
+ const parsedData = JSON.parse(data)
345
+ onMessage(parsedData)
346
+ } catch (parseError) {
347
+ console.warn('Failed to parse SSE data:', data, parseError)
348
+ }
349
+ }
350
+ } else if (line.startsWith('event: ')) {
351
+ const eventType = line.slice(7) // Remove 'event: ' prefix
352
+ // Handle different event types if needed
353
+ if (eventType === 'complete') {
354
+ onComplete?.()
355
+ return
356
+ } else if (eventType === 'error') {
357
+ onError?.(new Error('Stream error event received'))
358
+ return
359
+ }
360
+ }
361
+ }
362
+ }
363
+ } catch (error) {
364
+ onError?.(error)
365
+ }
366
+ }
367
+
368
+ processStream()
369
+
370
+ // Return cleanup function
371
+ return () => {
372
+ reader.cancel().catch(console.warn)
373
+ }
374
+ } catch (error) {
375
+ onError?.(error)
376
+ return () => {} // Return empty cleanup function on error
377
+ }
378
+ }
249
379
  }
@@ -22,6 +22,23 @@ import {
22
22
  GetLLMModelProvidersResponse,
23
23
  GetLLMModelProviderResponse,
24
24
  GetModelsByProviderResponse,
25
+ // GET /codespace/tasks Types
26
+ GetCodespaceTasksRequest,
27
+ GetCodespaceTasksResponse,
28
+ // GET /tasks/by-codespace-id Types
29
+ GetTasksByCodespaceIdRequest,
30
+ GetTasksByCodespaceIdResponse,
31
+ // Final Report Popup State Types
32
+ UpdateFinalReportPopupStateRequest,
33
+ UpdateFinalReportPopupStateResponse,
34
+ // Codespace Task Logs Types
35
+ GetCodespaceTaskLogsRequest,
36
+ CodespaceTaskLogsResponse,
37
+ StreamCodespaceTaskLogsRequest,
38
+ CodespaceLogStreamEvent,
39
+ // Project Summary Types
40
+ GetCodespaceProjectSummaryRequest,
41
+ GetCodespaceProjectSummaryResponse,
25
42
  } from './codespace-types'
26
43
 
27
44
  export class CodespaceService extends BaseService {
@@ -93,6 +110,25 @@ export class CodespaceService extends BaseService {
93
110
  return this.get<GetCodespaceTasksByProjectResponse>(url)
94
111
  }
95
112
 
113
+ /**
114
+ * Get aggregated statistics for all codespace tasks within a project
115
+ *
116
+ * GET /codespace/project/{project_id}/summary
117
+ *
118
+ * @param params - Request parameters including project_id
119
+ * @returns Promise resolving to aggregated task statistics by status
120
+ */
121
+ async getCodespaceProjectSummary(
122
+ params: GetCodespaceProjectSummaryRequest
123
+ ): Promise<GetCodespaceProjectSummaryResponse> {
124
+ if (!params.project_id) {
125
+ throw new Error('project_id is required')
126
+ }
127
+
128
+ const url = `/codespace/project/${params.project_id}/summary`
129
+ return this.get<GetCodespaceProjectSummaryResponse>(url)
130
+ }
131
+
96
132
  async getCodespaceTaskDetailed(codespaceTaskId: string): Promise<CodespaceTaskDetailedResponse> {
97
133
  if (!codespaceTaskId) {
98
134
  throw new Error('codespace_task_id is required')
@@ -100,6 +136,216 @@ export class CodespaceService extends BaseService {
100
136
  return this.get<CodespaceTaskDetailedResponse>(`/codespace/task/${codespaceTaskId}/detailed`)
101
137
  }
102
138
 
139
+ /**
140
+ * Get tasks by codespace task ID with optional pagination and sorting
141
+ *
142
+ * GET /tasks/by-codespace-id/{codespace_task_id}
143
+ *
144
+ * @param params - Request parameters including codespace_task_id and optional pagination/sorting
145
+ * @returns Promise resolving to paginated list of tasks associated with the codespace task ID
146
+ */
147
+ async getTasksByCodespaceId(
148
+ params: GetTasksByCodespaceIdRequest
149
+ ): Promise<GetTasksByCodespaceIdResponse> {
150
+ this.validateGetTasksByCodespaceIdRequest(params)
151
+
152
+ const queryParams = new URLSearchParams()
153
+
154
+ // Add pagination parameters
155
+ if (params.limit !== undefined) {
156
+ queryParams.append('limit', params.limit.toString())
157
+ }
158
+ if (params.offset !== undefined) {
159
+ queryParams.append('offset', params.offset.toString())
160
+ }
161
+
162
+ // Add sorting parameters
163
+ if (params.sort_by) {
164
+ queryParams.append('sort_by', params.sort_by)
165
+ }
166
+ if (params.sort_order) {
167
+ queryParams.append('sort_order', params.sort_order)
168
+ }
169
+
170
+ const url = `/codespace/tasks/by-codespace-id/${params.codespace_task_id}${
171
+ queryParams.toString() ? `?${queryParams.toString()}` : ''
172
+ }`
173
+
174
+ return this.get<GetTasksByCodespaceIdResponse>(url)
175
+ }
176
+
177
+ /**
178
+ * Get codespace tasks with optional filtering and pagination
179
+ *
180
+ * GET /codespace/tasks
181
+ *
182
+ * @param params - Optional query parameters for filtering, sorting, and pagination
183
+ * @returns Promise resolving to paginated list of codespace tasks with model and attachment info
184
+ */
185
+ async getCodespaceTasks(params?: GetCodespaceTasksRequest): Promise<GetCodespaceTasksResponse> {
186
+ const queryParams = new URLSearchParams()
187
+
188
+ if (params?.task_status) queryParams.append('task_status', params.task_status)
189
+ if (params?.project_id) queryParams.append('project_id', params.project_id)
190
+ if (params?.limit !== undefined) {
191
+ // Validate limit is between 1 and 100
192
+ if (params.limit < 1 || params.limit > 100) {
193
+ throw new Error('limit must be between 1 and 100')
194
+ }
195
+ queryParams.append('limit', params.limit.toString())
196
+ }
197
+ if (params?.offset !== undefined) {
198
+ // Validate offset is non-negative
199
+ if (params.offset < 0) {
200
+ throw new Error('offset must be 0 or greater')
201
+ }
202
+ queryParams.append('offset', params.offset.toString())
203
+ }
204
+ if (params?.sort_by) queryParams.append('sort_by', params.sort_by)
205
+ if (params?.sort_order) queryParams.append('sort_order', params.sort_order)
206
+
207
+ const url = `/codespace/tasks${queryParams.toString() ? `?${queryParams.toString()}` : ''}`
208
+ return this.get<GetCodespaceTasksResponse>(url)
209
+ }
210
+
211
+ // ============================================================================
212
+ // Task Status Update Methods
213
+ // ============================================================================
214
+
215
+ /**
216
+ * Update the final report popup state for a codespace task
217
+ *
218
+ * PATCH /codespace/task/{codespace_task_id}/final-report-popup-state
219
+ *
220
+ * @param codespaceTaskId - The ID of the codespace task
221
+ * @param request - The request body containing the new popup state
222
+ * @returns Promise resolving to the updated popup state response
223
+ */
224
+ async updateFinalReportPopupState(
225
+ codespaceTaskId: string,
226
+ request: UpdateFinalReportPopupStateRequest
227
+ ): Promise<UpdateFinalReportPopupStateResponse> {
228
+ if (!codespaceTaskId) {
229
+ throw new Error('codespace_task_id is required')
230
+ }
231
+
232
+ // Validate the popup state value
233
+ if (!['not_ready', 'open', 'closed'].includes(request.final_report_popup_state)) {
234
+ throw new Error('final_report_popup_state must be "not_ready", "open", or "closed"')
235
+ }
236
+
237
+ return this.patch<UpdateFinalReportPopupStateResponse>(
238
+ `/codespace/task/${codespaceTaskId}/final-report-popup-state`,
239
+ request
240
+ )
241
+ }
242
+
243
+ // ============================================================================
244
+ // Codespace Task Logs Methods
245
+ // ============================================================================
246
+
247
+ /**
248
+ * Get paginated logs for a codespace task with optional filtering and sorting
249
+ *
250
+ * GET /codespace/task/{codespace_task_id}/logs
251
+ *
252
+ * @param request - Request parameters including codespace_task_id and optional filters
253
+ * @returns Promise resolving to paginated logs response
254
+ */
255
+ async getCodespaceTaskLogs(
256
+ request: GetCodespaceTaskLogsRequest
257
+ ): Promise<CodespaceTaskLogsResponse> {
258
+ this.validateGetLogsRequest(request)
259
+
260
+ const queryParams = new URLSearchParams()
261
+
262
+ // Add pagination parameters
263
+ if (request.limit !== undefined) {
264
+ queryParams.append('limit', request.limit.toString())
265
+ }
266
+ if (request.offset !== undefined) {
267
+ queryParams.append('offset', request.offset.toString())
268
+ }
269
+
270
+ // Add filter parameters
271
+ if (request.log_type) {
272
+ queryParams.append('log_type', request.log_type)
273
+ }
274
+ if (request.step_name) {
275
+ queryParams.append('step_name', request.step_name)
276
+ }
277
+ if (request.search) {
278
+ queryParams.append('search', request.search)
279
+ }
280
+ if (request.since) {
281
+ queryParams.append('since', request.since)
282
+ }
283
+
284
+ // Add sorting parameters
285
+ if (request.sort_by) {
286
+ queryParams.append('sort_by', request.sort_by)
287
+ }
288
+ if (request.sort_order) {
289
+ queryParams.append('sort_order', request.sort_order)
290
+ }
291
+
292
+ const url = `/codespace/task/${request.codespace_task_id}/logs${
293
+ queryParams.toString() ? `?${queryParams.toString()}` : ''
294
+ }`
295
+
296
+ return this.get<CodespaceTaskLogsResponse>(url)
297
+ }
298
+
299
+ /**
300
+ * Stream real-time logs from a codespace task using Server-Sent Events (SSE)
301
+ *
302
+ * GET /codespace/task/{codespace_task_id}/logs/stream
303
+ *
304
+ * @param request - Request parameters including codespace_task_id and optional streaming parameters
305
+ * @param onLog - Callback function for handling log events
306
+ * @param onHeartbeat - Callback function for handling heartbeat events
307
+ * @param onComplete - Callback function for handling completion events
308
+ * @param onError - Callback function for handling error events
309
+ * @param onTimeout - Callback function for handling timeout events
310
+ * @returns Promise resolving to a cleanup function to stop streaming
311
+ */
312
+ async streamCodespaceTaskLogs(
313
+ request: StreamCodespaceTaskLogsRequest,
314
+ onLog: (log: CodespaceLogStreamEvent) => void,
315
+ onHeartbeat?: (data: any) => void,
316
+ onComplete?: (data: any) => void,
317
+ onError?: (error: any) => void,
318
+ onTimeout?: (data: any) => void
319
+ ): Promise<() => void> {
320
+ this.validateStreamLogsRequest(request)
321
+
322
+ const queryParams = new URLSearchParams()
323
+
324
+ // Add streaming parameters
325
+ if (request.since) {
326
+ queryParams.append('since', request.since)
327
+ }
328
+ if (request.timeout !== undefined) {
329
+ queryParams.append('timeout', request.timeout.toString())
330
+ }
331
+
332
+ const url = `/codespace/task/${request.codespace_task_id}/logs/stream${
333
+ queryParams.toString() ? `?${queryParams.toString()}` : ''
334
+ }`
335
+
336
+ // Use the fetch-based streaming method since it supports custom headers for authentication
337
+ return this.createStreamWithFetch(
338
+ url,
339
+ data => {
340
+ // Handle different types of events based on the context
341
+ // The server will send different event types through the same data channel
342
+ onLog(data as CodespaceLogStreamEvent)
343
+ },
344
+ onError,
345
+ () => onComplete?.({}) // Call onComplete with empty data when stream completes
346
+ )
347
+ }
348
+
103
349
  // ============================================================================
104
350
  // Codespace Models Methods
105
351
  // ============================================================================
@@ -285,4 +531,117 @@ export class CodespaceService extends BaseService {
285
531
  }
286
532
  }
287
533
  }
534
+
535
+ private validateGetLogsRequest(request: GetCodespaceTaskLogsRequest): void {
536
+ if (!request.codespace_task_id) {
537
+ throw new Error('codespace_task_id is required')
538
+ }
539
+
540
+ // Validate limit
541
+ if (request.limit !== undefined) {
542
+ if (!Number.isInteger(request.limit)) {
543
+ throw new Error('limit must be an integer')
544
+ }
545
+ if (request.limit < 1 || request.limit > 500) {
546
+ throw new Error('limit must be between 1 and 500')
547
+ }
548
+ }
549
+
550
+ // Validate offset
551
+ if (request.offset !== undefined) {
552
+ if (!Number.isInteger(request.offset)) {
553
+ throw new Error('offset must be an integer')
554
+ }
555
+ if (request.offset < 0) {
556
+ throw new Error('offset must be 0 or greater')
557
+ }
558
+ }
559
+
560
+ // Validate sort_by
561
+ if (request.sort_by && !['created_at', 'step_name', 'log_type'].includes(request.sort_by)) {
562
+ throw new Error('sort_by must be one of: created_at, step_name, log_type')
563
+ }
564
+
565
+ // Validate sort_order
566
+ if (request.sort_order && !['asc', 'desc'].includes(request.sort_order)) {
567
+ throw new Error('sort_order must be either "asc" or "desc"')
568
+ }
569
+
570
+ // Validate log_type
571
+ const validLogTypes = ['thinking', 'coding', 'info', 'error', 'success']
572
+ if (request.log_type && !validLogTypes.includes(request.log_type)) {
573
+ throw new Error(`log_type must be one of: ${validLogTypes.join(', ')}`)
574
+ }
575
+
576
+ // Validate since timestamp format (basic ISO format check)
577
+ if (request.since) {
578
+ const sinceDate = new Date(request.since)
579
+ if (isNaN(sinceDate.getTime())) {
580
+ throw new Error('since must be a valid ISO timestamp')
581
+ }
582
+ }
583
+ }
584
+
585
+ private validateStreamLogsRequest(request: StreamCodespaceTaskLogsRequest): void {
586
+ if (!request.codespace_task_id) {
587
+ throw new Error('codespace_task_id is required')
588
+ }
589
+
590
+ // Validate timeout
591
+ if (request.timeout !== undefined) {
592
+ if (!Number.isInteger(request.timeout)) {
593
+ throw new Error('timeout must be an integer')
594
+ }
595
+ if (request.timeout < 30 || request.timeout > 1800) {
596
+ throw new Error('timeout must be between 30 and 1800 seconds')
597
+ }
598
+ }
599
+
600
+ // Validate since timestamp format (basic ISO format check)
601
+ if (request.since) {
602
+ const sinceDate = new Date(request.since)
603
+ if (isNaN(sinceDate.getTime())) {
604
+ throw new Error('since must be a valid ISO timestamp')
605
+ }
606
+ }
607
+ }
608
+
609
+ private validateGetTasksByCodespaceIdRequest(request: GetTasksByCodespaceIdRequest): void {
610
+ if (!request.codespace_task_id) {
611
+ throw new Error('codespace_task_id is required')
612
+ }
613
+
614
+ // Validate limit
615
+ if (request.limit !== undefined) {
616
+ if (!Number.isInteger(request.limit)) {
617
+ throw new Error('limit must be an integer')
618
+ }
619
+ if (request.limit < 1 || request.limit > 100) {
620
+ throw new Error('limit must be between 1 and 100')
621
+ }
622
+ }
623
+
624
+ // Validate offset
625
+ if (request.offset !== undefined) {
626
+ if (!Number.isInteger(request.offset)) {
627
+ throw new Error('offset must be an integer')
628
+ }
629
+ if (request.offset < 0) {
630
+ throw new Error('offset must be 0 or greater')
631
+ }
632
+ }
633
+
634
+ // Validate sort_by
635
+ if (
636
+ request.sort_by &&
637
+ !['created_at', 'updated_at', 'status', 'title'].includes(request.sort_by)
638
+ ) {
639
+ throw new Error('sort_by must be one of: created_at, updated_at, status, title')
640
+ }
641
+
642
+ // Validate sort_order
643
+ if (request.sort_order && !['asc', 'desc'].includes(request.sort_order)) {
644
+ throw new Error('sort_order must be either "asc" or "desc"')
645
+ }
646
+ }
288
647
  }