raysurfer 0.4.1__py3-none-any.whl

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.
raysurfer/client.py ADDED
@@ -0,0 +1,773 @@
1
+ """RaySurfer SDK client"""
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from raysurfer.exceptions import APIError, AuthenticationError
8
+ from raysurfer.sdk_types import CodeFile, GetCodeFilesResponse
9
+ from raysurfer.types import (
10
+ AgentReview,
11
+ AgentVerdict,
12
+ AlternativeCandidate,
13
+ BestMatch,
14
+ CodeBlock,
15
+ CodeBlockMatch,
16
+ ExecutionIO,
17
+ ExecutionState,
18
+ FewShotExample,
19
+ FileWritten,
20
+ RetrieveBestResponse,
21
+ RetrieveCodeBlockResponse,
22
+ SnipsDesired,
23
+ StoreCodeBlockResponse,
24
+ StoreExecutionResponse,
25
+ SubmitExecutionResultResponse,
26
+ TaskPattern,
27
+ )
28
+
29
+ DEFAULT_BASE_URL = "https://web-production-3d338.up.railway.app"
30
+
31
+
32
+ class AsyncRaySurfer:
33
+ """Async client for RaySurfer API"""
34
+
35
+ def __init__(
36
+ self,
37
+ api_key: str | None = None,
38
+ base_url: str = DEFAULT_BASE_URL,
39
+ timeout: float = 60.0,
40
+ organization_id: str | None = None,
41
+ workspace_id: str | None = None,
42
+ public_snips: bool = False,
43
+ snips_desired: SnipsDesired | str | None = None,
44
+ ):
45
+ """
46
+ Initialize the RaySurfer async client.
47
+
48
+ Args:
49
+ api_key: RaySurfer API key (or set RAYSURFER_API_KEY env var)
50
+ base_url: API base URL
51
+ timeout: Request timeout in seconds
52
+ organization_id: Optional organization ID for dedicated namespace (team/enterprise)
53
+ workspace_id: Optional workspace ID for client-specific namespace (enterprise only)
54
+ public_snips: Whether to include public/shared snippets in retrieval (default: False)
55
+ snips_desired: Scope of private snippets - "company" (Team/Enterprise) or "client" (Enterprise only)
56
+ """
57
+ self.api_key = api_key
58
+ self.base_url = base_url.rstrip("/")
59
+ self.timeout = timeout
60
+ self.organization_id = organization_id
61
+ self.workspace_id = workspace_id
62
+ self.public_snips = public_snips
63
+ # Convert string to SnipsDesired if needed
64
+ if isinstance(snips_desired, str):
65
+ self.snips_desired = SnipsDesired(snips_desired) if snips_desired else None
66
+ else:
67
+ self.snips_desired = snips_desired
68
+ self._client: httpx.AsyncClient | None = None
69
+
70
+ async def _get_client(self) -> httpx.AsyncClient:
71
+ if self._client is None:
72
+ headers = {"Content-Type": "application/json"}
73
+ if self.api_key:
74
+ headers["Authorization"] = f"Bearer {self.api_key}"
75
+ # Add organization/workspace headers for namespace routing
76
+ if self.organization_id:
77
+ headers["X-Raysurfer-Org-Id"] = self.organization_id
78
+ if self.workspace_id:
79
+ headers["X-Raysurfer-Workspace-Id"] = self.workspace_id
80
+ # Add snippet retrieval scope headers
81
+ if self.public_snips:
82
+ headers["X-Raysurfer-Public-Snips"] = "true"
83
+ if self.snips_desired:
84
+ headers["X-Raysurfer-Snips-Desired"] = self.snips_desired.value
85
+ self._client = httpx.AsyncClient(
86
+ base_url=self.base_url,
87
+ headers=headers,
88
+ timeout=self.timeout,
89
+ )
90
+ return self._client
91
+
92
+ async def close(self) -> None:
93
+ if self._client:
94
+ await self._client.aclose()
95
+ self._client = None
96
+
97
+ async def __aenter__(self) -> "AsyncRaySurfer":
98
+ return self
99
+
100
+ async def __aexit__(self, *args: Any) -> None:
101
+ await self.close()
102
+
103
+ async def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
104
+ client = await self._get_client()
105
+ response = await client.request(method, path, **kwargs)
106
+
107
+ if response.status_code == 401:
108
+ raise AuthenticationError("Invalid API key")
109
+ if response.status_code >= 400:
110
+ raise APIError(response.text, status_code=response.status_code)
111
+
112
+ return response.json()
113
+
114
+ # =========================================================================
115
+ # Store API
116
+ # =========================================================================
117
+
118
+ async def store_code_block(
119
+ self,
120
+ name: str,
121
+ source: str,
122
+ entrypoint: str,
123
+ language: str,
124
+ description: str = "",
125
+ input_schema: dict[str, Any] | None = None,
126
+ output_schema: dict[str, Any] | None = None,
127
+ language_version: str | None = None,
128
+ dependencies: list[str] | None = None,
129
+ tags: list[str] | None = None,
130
+ capabilities: list[str] | None = None,
131
+ example_queries: list[str] | None = None,
132
+ ) -> StoreCodeBlockResponse:
133
+ """Store a new code block"""
134
+ data = {
135
+ "name": name,
136
+ "description": description,
137
+ "source": source,
138
+ "entrypoint": entrypoint,
139
+ "language": language,
140
+ "input_schema": input_schema or {},
141
+ "output_schema": output_schema or {},
142
+ "language_version": language_version,
143
+ "dependencies": dependencies or [],
144
+ "tags": tags or [],
145
+ "capabilities": capabilities or [],
146
+ "example_queries": example_queries,
147
+ }
148
+ result = await self._request("POST", "/api/store/code-block", json=data)
149
+ return StoreCodeBlockResponse(**result)
150
+
151
+ async def store_execution(
152
+ self,
153
+ code_block_id: str,
154
+ triggering_task: str,
155
+ input_data: dict[str, Any],
156
+ output_data: Any,
157
+ execution_state: ExecutionState = ExecutionState.COMPLETED,
158
+ duration_ms: int = 0,
159
+ error_message: str | None = None,
160
+ error_type: str | None = None,
161
+ verdict: AgentVerdict | None = None,
162
+ review: AgentReview | None = None,
163
+ ) -> StoreExecutionResponse:
164
+ """Store an execution record"""
165
+ io = ExecutionIO(
166
+ input_data=input_data,
167
+ output_data=output_data,
168
+ output_type=type(output_data).__name__,
169
+ )
170
+ data = {
171
+ "code_block_id": code_block_id,
172
+ "triggering_task": triggering_task,
173
+ "io": io.model_dump(),
174
+ "execution_state": execution_state.value,
175
+ "duration_ms": duration_ms,
176
+ "error_message": error_message,
177
+ "error_type": error_type,
178
+ "verdict": verdict.value if verdict else None,
179
+ "review": review.model_dump() if review else None,
180
+ }
181
+ result = await self._request("POST", "/api/store/execution", json=data)
182
+ return StoreExecutionResponse(**result)
183
+
184
+ async def submit_execution_result(
185
+ self,
186
+ task: str,
187
+ files_written: list[FileWritten],
188
+ succeeded: bool,
189
+ ) -> SubmitExecutionResultResponse:
190
+ """
191
+ Submit raw execution result - backend handles all processing.
192
+
193
+ This is the simplified API for agent integrations. Just send:
194
+ - The task that was executed
195
+ - Files that were written during execution
196
+ - Whether the task succeeded
197
+
198
+ Backend handles: entrypoint detection, tag extraction, language detection,
199
+ deduplication, quality checks, and storage.
200
+ """
201
+ data = {
202
+ "task": task,
203
+ "files_written": [f.model_dump() for f in files_written],
204
+ "succeeded": succeeded,
205
+ }
206
+ result = await self._request("POST", "/api/store/execution-result", json=data)
207
+ return SubmitExecutionResultResponse(**result)
208
+
209
+ # =========================================================================
210
+ # Retrieve API
211
+ # =========================================================================
212
+
213
+ async def retrieve(
214
+ self,
215
+ task: str,
216
+ top_k: int = 10,
217
+ min_verdict_score: float = 0.0,
218
+ ) -> RetrieveCodeBlockResponse:
219
+ """Retrieve code blocks by task description (semantic search)"""
220
+ data = {
221
+ "task": task,
222
+ "top_k": top_k,
223
+ "min_verdict_score": min_verdict_score,
224
+ }
225
+ result = await self._request("POST", "/api/retrieve/code-blocks", json=data)
226
+
227
+ code_blocks = [
228
+ CodeBlockMatch(
229
+ code_block=CodeBlock(**cb["code_block"]),
230
+ score=cb["score"],
231
+ verdict_score=cb["verdict_score"],
232
+ thumbs_up=cb["thumbs_up"],
233
+ thumbs_down=cb["thumbs_down"],
234
+ recent_executions=cb.get("recent_executions", []),
235
+ )
236
+ for cb in result["code_blocks"]
237
+ ]
238
+ return RetrieveCodeBlockResponse(
239
+ code_blocks=code_blocks,
240
+ total_found=result["total_found"],
241
+ )
242
+
243
+ async def retrieve_best(
244
+ self,
245
+ task: str,
246
+ top_k: int = 10,
247
+ min_verdict_score: float = 0.0,
248
+ ) -> RetrieveBestResponse:
249
+ """Get the single best code block for a task using verdict-aware scoring"""
250
+ data = {
251
+ "task": task,
252
+ "top_k": top_k,
253
+ "min_verdict_score": min_verdict_score,
254
+ }
255
+ result = await self._request("POST", "/api/retrieve/best-for-task", json=data)
256
+
257
+ best_match = None
258
+ if result.get("best_match"):
259
+ bm = result["best_match"]
260
+ best_match = BestMatch(
261
+ code_block=CodeBlock(**bm["code_block"]),
262
+ combined_score=bm["combined_score"],
263
+ vector_score=bm["vector_score"],
264
+ verdict_score=bm["verdict_score"],
265
+ error_resilience=bm["error_resilience"],
266
+ thumbs_up=bm["thumbs_up"],
267
+ thumbs_down=bm["thumbs_down"],
268
+ )
269
+
270
+ alternatives = [AlternativeCandidate(**alt) for alt in result.get("alternative_candidates", [])]
271
+
272
+ return RetrieveBestResponse(
273
+ best_match=best_match,
274
+ alternative_candidates=alternatives,
275
+ retrieval_confidence=result["retrieval_confidence"],
276
+ )
277
+
278
+ async def get_few_shot_examples(
279
+ self,
280
+ task: str,
281
+ k: int = 3,
282
+ ) -> list[FewShotExample]:
283
+ """Retrieve few-shot examples for code generation"""
284
+ data = {"task": task, "k": k}
285
+ result = await self._request("POST", "/api/retrieve/few-shot-examples", json=data)
286
+ return [FewShotExample(**ex) for ex in result["examples"]]
287
+
288
+ async def get_task_patterns(
289
+ self,
290
+ task: str | None = None,
291
+ code_block_id: str | None = None,
292
+ min_thumbs_up: int = 0,
293
+ top_k: int = 20,
294
+ ) -> list[TaskPattern]:
295
+ """Retrieve proven task→code mappings"""
296
+ data = {
297
+ "task": task,
298
+ "code_block_id": code_block_id,
299
+ "min_thumbs_up": min_thumbs_up,
300
+ "top_k": top_k,
301
+ }
302
+ result = await self._request("POST", "/api/retrieve/task-patterns", json=data)
303
+ return [TaskPattern(**p) for p in result["patterns"]]
304
+
305
+ async def get_code_files(
306
+ self,
307
+ task: str,
308
+ top_k: int = 5,
309
+ min_verdict_score: float = 0.3,
310
+ prefer_complete: bool = True,
311
+ ) -> GetCodeFilesResponse:
312
+ """
313
+ Get code files for a task, ready to download to sandbox.
314
+
315
+ Returns code blocks with full source code, optimized for:
316
+ - High verdict scores (proven to work)
317
+ - More complete implementations (prefer longer source)
318
+ - Task relevance (semantic similarity)
319
+ """
320
+ data = {
321
+ "task": task,
322
+ "top_k": top_k,
323
+ "min_verdict_score": min_verdict_score,
324
+ "prefer_complete": prefer_complete,
325
+ }
326
+ result = await self._request("POST", "/api/retrieve/code-files", json=data)
327
+ return GetCodeFilesResponse(
328
+ files=[CodeFile(**f) for f in result["files"]],
329
+ task=result["task"],
330
+ total_found=result["total_found"],
331
+ )
332
+
333
+ async def record_cache_usage(
334
+ self,
335
+ task: str,
336
+ code_block_id: str,
337
+ code_block_name: str,
338
+ code_block_description: str,
339
+ succeeded: bool,
340
+ ) -> dict[str, Any]:
341
+ """
342
+ Record that a cached code block was used for a task.
343
+
344
+ This triggers background voting to assess whether the cached code
345
+ actually helped complete the task successfully.
346
+ """
347
+ data = {
348
+ "task": task,
349
+ "code_block_id": code_block_id,
350
+ "code_block_name": code_block_name,
351
+ "code_block_description": code_block_description,
352
+ "succeeded": succeeded,
353
+ }
354
+ return await self._request("POST", "/api/store/cache-usage", json=data)
355
+
356
+ # =========================================================================
357
+ # Simplified API (aliases)
358
+ # =========================================================================
359
+
360
+ async def get_code_snips(
361
+ self,
362
+ task: str,
363
+ top_k: int = 10,
364
+ min_verdict_score: float = 0.0,
365
+ ) -> RetrieveCodeBlockResponse:
366
+ """
367
+ Get cached code snippets for a task.
368
+
369
+ Alias for retrieve() - searches for code blocks by task description.
370
+ """
371
+ return await self.retrieve(task, top_k, min_verdict_score)
372
+
373
+ async def upload_new_code_snips(
374
+ self,
375
+ task: str,
376
+ files_written: list[FileWritten],
377
+ succeeded: bool,
378
+ ) -> SubmitExecutionResultResponse:
379
+ """
380
+ Upload new code snippets from an execution.
381
+
382
+ Alias for submit_execution_result() - stores code files for future reuse.
383
+ """
384
+ return await self.submit_execution_result(task, files_written, succeeded)
385
+
386
+ async def vote_code_snip(
387
+ self,
388
+ task: str,
389
+ code_block_id: str,
390
+ code_block_name: str,
391
+ code_block_description: str,
392
+ succeeded: bool,
393
+ ) -> dict[str, Any]:
394
+ """
395
+ Vote on whether a cached code snippet was useful.
396
+
397
+ Alias for record_cache_usage() - triggers background voting.
398
+ """
399
+ return await self.record_cache_usage(
400
+ task, code_block_id, code_block_name, code_block_description, succeeded
401
+ )
402
+
403
+
404
+ class RaySurfer:
405
+ """Sync client for RaySurfer API"""
406
+
407
+ def __init__(
408
+ self,
409
+ api_key: str | None = None,
410
+ base_url: str = DEFAULT_BASE_URL,
411
+ timeout: float = 60.0,
412
+ organization_id: str | None = None,
413
+ workspace_id: str | None = None,
414
+ public_snips: bool = False,
415
+ snips_desired: SnipsDesired | str | None = None,
416
+ ):
417
+ """
418
+ Initialize the RaySurfer sync client.
419
+
420
+ Args:
421
+ api_key: RaySurfer API key (or set RAYSURFER_API_KEY env var)
422
+ base_url: API base URL
423
+ timeout: Request timeout in seconds
424
+ organization_id: Optional organization ID for dedicated namespace (team/enterprise)
425
+ workspace_id: Optional workspace ID for client-specific namespace (enterprise only)
426
+ public_snips: Whether to include public/shared snippets in retrieval (default: False)
427
+ snips_desired: Scope of private snippets - "company" (Team/Enterprise) or "client" (Enterprise only)
428
+ """
429
+ self.api_key = api_key
430
+ self.base_url = base_url.rstrip("/")
431
+ self.timeout = timeout
432
+ self.organization_id = organization_id
433
+ self.workspace_id = workspace_id
434
+ self.public_snips = public_snips
435
+ # Convert string to SnipsDesired if needed
436
+ if isinstance(snips_desired, str):
437
+ self.snips_desired = SnipsDesired(snips_desired) if snips_desired else None
438
+ else:
439
+ self.snips_desired = snips_desired
440
+ self._client: httpx.Client | None = None
441
+
442
+ def _get_client(self) -> httpx.Client:
443
+ if self._client is None:
444
+ headers = {"Content-Type": "application/json"}
445
+ if self.api_key:
446
+ headers["Authorization"] = f"Bearer {self.api_key}"
447
+ # Add organization/workspace headers for namespace routing
448
+ if self.organization_id:
449
+ headers["X-Raysurfer-Org-Id"] = self.organization_id
450
+ if self.workspace_id:
451
+ headers["X-Raysurfer-Workspace-Id"] = self.workspace_id
452
+ # Add snippet retrieval scope headers
453
+ if self.public_snips:
454
+ headers["X-Raysurfer-Public-Snips"] = "true"
455
+ if self.snips_desired:
456
+ headers["X-Raysurfer-Snips-Desired"] = self.snips_desired.value
457
+ self._client = httpx.Client(
458
+ base_url=self.base_url,
459
+ headers=headers,
460
+ timeout=self.timeout,
461
+ )
462
+ return self._client
463
+
464
+ def close(self) -> None:
465
+ if self._client:
466
+ self._client.close()
467
+ self._client = None
468
+
469
+ def __enter__(self) -> "RaySurfer":
470
+ return self
471
+
472
+ def __exit__(self, *args: Any) -> None:
473
+ self.close()
474
+
475
+ def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
476
+ client = self._get_client()
477
+ response = client.request(method, path, **kwargs)
478
+
479
+ if response.status_code == 401:
480
+ raise AuthenticationError("Invalid API key")
481
+ if response.status_code >= 400:
482
+ raise APIError(response.text, status_code=response.status_code)
483
+
484
+ return response.json()
485
+
486
+ # =========================================================================
487
+ # Store API
488
+ # =========================================================================
489
+
490
+ def store_code_block(
491
+ self,
492
+ name: str,
493
+ source: str,
494
+ entrypoint: str,
495
+ language: str,
496
+ description: str = "",
497
+ input_schema: dict[str, Any] | None = None,
498
+ output_schema: dict[str, Any] | None = None,
499
+ language_version: str | None = None,
500
+ dependencies: list[str] | None = None,
501
+ tags: list[str] | None = None,
502
+ capabilities: list[str] | None = None,
503
+ example_queries: list[str] | None = None,
504
+ ) -> StoreCodeBlockResponse:
505
+ """Store a new code block"""
506
+ data = {
507
+ "name": name,
508
+ "description": description,
509
+ "source": source,
510
+ "entrypoint": entrypoint,
511
+ "language": language,
512
+ "input_schema": input_schema or {},
513
+ "output_schema": output_schema or {},
514
+ "language_version": language_version,
515
+ "dependencies": dependencies or [],
516
+ "tags": tags or [],
517
+ "capabilities": capabilities or [],
518
+ "example_queries": example_queries,
519
+ }
520
+ result = self._request("POST", "/api/store/code-block", json=data)
521
+ return StoreCodeBlockResponse(**result)
522
+
523
+ def store_execution(
524
+ self,
525
+ code_block_id: str,
526
+ triggering_task: str,
527
+ input_data: dict[str, Any],
528
+ output_data: Any,
529
+ execution_state: ExecutionState = ExecutionState.COMPLETED,
530
+ duration_ms: int = 0,
531
+ error_message: str | None = None,
532
+ error_type: str | None = None,
533
+ verdict: AgentVerdict | None = None,
534
+ review: AgentReview | None = None,
535
+ ) -> StoreExecutionResponse:
536
+ """Store an execution record"""
537
+ io = ExecutionIO(
538
+ input_data=input_data,
539
+ output_data=output_data,
540
+ output_type=type(output_data).__name__,
541
+ )
542
+ data = {
543
+ "code_block_id": code_block_id,
544
+ "triggering_task": triggering_task,
545
+ "io": io.model_dump(),
546
+ "execution_state": execution_state.value,
547
+ "duration_ms": duration_ms,
548
+ "error_message": error_message,
549
+ "error_type": error_type,
550
+ "verdict": verdict.value if verdict else None,
551
+ "review": review.model_dump() if review else None,
552
+ }
553
+ result = self._request("POST", "/api/store/execution", json=data)
554
+ return StoreExecutionResponse(**result)
555
+
556
+ def submit_execution_result(
557
+ self,
558
+ task: str,
559
+ files_written: list[FileWritten],
560
+ succeeded: bool,
561
+ ) -> SubmitExecutionResultResponse:
562
+ """
563
+ Submit raw execution result - backend handles all processing.
564
+
565
+ This is the simplified API for agent integrations. Just send:
566
+ - The task that was executed
567
+ - Files that were written during execution
568
+ - Whether the task succeeded
569
+
570
+ Backend handles: entrypoint detection, tag extraction, language detection,
571
+ deduplication, quality checks, and storage.
572
+ """
573
+ data = {
574
+ "task": task,
575
+ "files_written": [f.model_dump() for f in files_written],
576
+ "succeeded": succeeded,
577
+ }
578
+ result = self._request("POST", "/api/store/execution-result", json=data)
579
+ return SubmitExecutionResultResponse(**result)
580
+
581
+ # =========================================================================
582
+ # Retrieve API
583
+ # =========================================================================
584
+
585
+ def retrieve(
586
+ self,
587
+ task: str,
588
+ top_k: int = 10,
589
+ min_verdict_score: float = 0.0,
590
+ ) -> RetrieveCodeBlockResponse:
591
+ """Retrieve code blocks by task description (semantic search)"""
592
+ data = {
593
+ "task": task,
594
+ "top_k": top_k,
595
+ "min_verdict_score": min_verdict_score,
596
+ }
597
+ result = self._request("POST", "/api/retrieve/code-blocks", json=data)
598
+
599
+ code_blocks = [
600
+ CodeBlockMatch(
601
+ code_block=CodeBlock(**cb["code_block"]),
602
+ score=cb["score"],
603
+ verdict_score=cb["verdict_score"],
604
+ thumbs_up=cb["thumbs_up"],
605
+ thumbs_down=cb["thumbs_down"],
606
+ recent_executions=cb.get("recent_executions", []),
607
+ )
608
+ for cb in result["code_blocks"]
609
+ ]
610
+ return RetrieveCodeBlockResponse(
611
+ code_blocks=code_blocks,
612
+ total_found=result["total_found"],
613
+ )
614
+
615
+ def retrieve_best(
616
+ self,
617
+ task: str,
618
+ top_k: int = 10,
619
+ min_verdict_score: float = 0.0,
620
+ ) -> RetrieveBestResponse:
621
+ """Get the single best code block for a task using verdict-aware scoring"""
622
+ data = {
623
+ "task": task,
624
+ "top_k": top_k,
625
+ "min_verdict_score": min_verdict_score,
626
+ }
627
+ result = self._request("POST", "/api/retrieve/best-for-task", json=data)
628
+
629
+ best_match = None
630
+ if result.get("best_match"):
631
+ bm = result["best_match"]
632
+ best_match = BestMatch(
633
+ code_block=CodeBlock(**bm["code_block"]),
634
+ combined_score=bm["combined_score"],
635
+ vector_score=bm["vector_score"],
636
+ verdict_score=bm["verdict_score"],
637
+ error_resilience=bm["error_resilience"],
638
+ thumbs_up=bm["thumbs_up"],
639
+ thumbs_down=bm["thumbs_down"],
640
+ )
641
+
642
+ alternatives = [AlternativeCandidate(**alt) for alt in result.get("alternative_candidates", [])]
643
+
644
+ return RetrieveBestResponse(
645
+ best_match=best_match,
646
+ alternative_candidates=alternatives,
647
+ retrieval_confidence=result["retrieval_confidence"],
648
+ )
649
+
650
+ def get_few_shot_examples(
651
+ self,
652
+ task: str,
653
+ k: int = 3,
654
+ ) -> list[FewShotExample]:
655
+ """Retrieve few-shot examples for code generation"""
656
+ data = {"task": task, "k": k}
657
+ result = self._request("POST", "/api/retrieve/few-shot-examples", json=data)
658
+ return [FewShotExample(**ex) for ex in result["examples"]]
659
+
660
+ def get_task_patterns(
661
+ self,
662
+ task: str | None = None,
663
+ code_block_id: str | None = None,
664
+ min_thumbs_up: int = 0,
665
+ top_k: int = 20,
666
+ ) -> list[TaskPattern]:
667
+ """Retrieve proven task→code mappings"""
668
+ data = {
669
+ "task": task,
670
+ "code_block_id": code_block_id,
671
+ "min_thumbs_up": min_thumbs_up,
672
+ "top_k": top_k,
673
+ }
674
+ result = self._request("POST", "/api/retrieve/task-patterns", json=data)
675
+ return [TaskPattern(**p) for p in result["patterns"]]
676
+
677
+ def get_code_files(
678
+ self,
679
+ task: str,
680
+ top_k: int = 5,
681
+ min_verdict_score: float = 0.3,
682
+ prefer_complete: bool = True,
683
+ ) -> GetCodeFilesResponse:
684
+ """
685
+ Get code files for a task, ready to download to sandbox.
686
+
687
+ Returns code blocks with full source code, optimized for:
688
+ - High verdict scores (proven to work)
689
+ - More complete implementations (prefer longer source)
690
+ - Task relevance (semantic similarity)
691
+ """
692
+ data = {
693
+ "task": task,
694
+ "top_k": top_k,
695
+ "min_verdict_score": min_verdict_score,
696
+ "prefer_complete": prefer_complete,
697
+ }
698
+ result = self._request("POST", "/api/retrieve/code-files", json=data)
699
+ return GetCodeFilesResponse(
700
+ files=[CodeFile(**f) for f in result["files"]],
701
+ task=result["task"],
702
+ total_found=result["total_found"],
703
+ )
704
+
705
+ def record_cache_usage(
706
+ self,
707
+ task: str,
708
+ code_block_id: str,
709
+ code_block_name: str,
710
+ code_block_description: str,
711
+ succeeded: bool,
712
+ ) -> dict[str, Any]:
713
+ """
714
+ Record that a cached code block was used for a task.
715
+
716
+ This triggers background voting to assess whether the cached code
717
+ actually helped complete the task successfully.
718
+ """
719
+ data = {
720
+ "task": task,
721
+ "code_block_id": code_block_id,
722
+ "code_block_name": code_block_name,
723
+ "code_block_description": code_block_description,
724
+ "succeeded": succeeded,
725
+ }
726
+ return self._request("POST", "/api/store/cache-usage", json=data)
727
+
728
+ # =========================================================================
729
+ # Simplified API (aliases)
730
+ # =========================================================================
731
+
732
+ def get_code_snips(
733
+ self,
734
+ task: str,
735
+ top_k: int = 10,
736
+ min_verdict_score: float = 0.0,
737
+ ) -> RetrieveCodeBlockResponse:
738
+ """
739
+ Get cached code snippets for a task.
740
+
741
+ Alias for retrieve() - searches for code blocks by task description.
742
+ """
743
+ return self.retrieve(task, top_k, min_verdict_score)
744
+
745
+ def upload_new_code_snips(
746
+ self,
747
+ task: str,
748
+ files_written: list[FileWritten],
749
+ succeeded: bool,
750
+ ) -> SubmitExecutionResultResponse:
751
+ """
752
+ Upload new code snippets from an execution.
753
+
754
+ Alias for submit_execution_result() - stores code files for future reuse.
755
+ """
756
+ return self.submit_execution_result(task, files_written, succeeded)
757
+
758
+ def vote_code_snip(
759
+ self,
760
+ task: str,
761
+ code_block_id: str,
762
+ code_block_name: str,
763
+ code_block_description: str,
764
+ succeeded: bool,
765
+ ) -> dict[str, Any]:
766
+ """
767
+ Vote on whether a cached code snippet was useful.
768
+
769
+ Alias for record_cache_usage() - triggers background voting.
770
+ """
771
+ return self.record_cache_usage(
772
+ task, code_block_id, code_block_name, code_block_description, succeeded
773
+ )