Skip to main content

chasm/api/
sdk.rs

1// Copyright (c) 2024-2028 Nervosys LLC
2// SPDX-License-Identifier: AGPL-3.0-only
3//! SDK Module
4//!
5//! Provides SDK generation and developer tooling for custom integrations.
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10// ============================================================================
11// SDK Configuration
12// ============================================================================
13
14/// SDK configuration
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct SdkConfig {
17    /// SDK version
18    pub version: String,
19    /// Base API URL
20    pub base_url: String,
21    /// Supported languages
22    pub languages: Vec<SdkLanguage>,
23    /// API version
24    pub api_version: String,
25}
26
27/// SDK language
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
29#[serde(rename_all = "lowercase")]
30pub enum SdkLanguage {
31    Python,
32    NodeJs,
33    Go,
34    Rust,
35    Java,
36    CSharp,
37    Ruby,
38    Php,
39}
40
41impl std::fmt::Display for SdkLanguage {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            SdkLanguage::Python => write!(f, "python"),
45            SdkLanguage::NodeJs => write!(f, "nodejs"),
46            SdkLanguage::Go => write!(f, "go"),
47            SdkLanguage::Rust => write!(f, "rust"),
48            SdkLanguage::Java => write!(f, "java"),
49            SdkLanguage::CSharp => write!(f, "csharp"),
50            SdkLanguage::Ruby => write!(f, "ruby"),
51            SdkLanguage::Php => write!(f, "php"),
52        }
53    }
54}
55
56// ============================================================================
57// SDK Templates
58// ============================================================================
59
60/// Python SDK template
61pub const PYTHON_SDK_TEMPLATE: &str = r#"# Chasm Python SDK
62# Auto-generated - Do not edit directly
63# Version: {{version}}
64
65"""
66Chasm Python SDK
67
68A Python client library for the Chasm API.
69
70Usage:
71    from chasm import ChasmClient
72    
73    client = ChasmClient(api_key="your-api-key")
74    sessions = client.sessions.list()
75"""
76
77import os
78import json
79import requests
80from typing import Optional, List, Dict, Any, Union
81from dataclasses import dataclass, field
82from datetime import datetime
83from urllib.parse import urljoin
84
85__version__ = "{{version}}"
86__api_version__ = "{{api_version}}"
87
88
89@dataclass
90class ChasmConfig:
91    """Configuration for Chasm client."""
92    base_url: str = "{{base_url}}"
93    api_key: Optional[str] = None
94    timeout: int = 30
95    retry_count: int = 3
96    retry_delay: float = 1.0
97
98
99@dataclass
100class Session:
101    """Represents a chat session."""
102    id: str
103    title: str
104    provider: str
105    workspace_id: Optional[str] = None
106    message_count: int = 0
107    token_count: int = 0
108    created_at: Optional[datetime] = None
109    updated_at: Optional[datetime] = None
110    tags: List[str] = field(default_factory=list)
111    archived: bool = False
112
113
114@dataclass
115class Message:
116    """Represents a chat message."""
117    id: str
118    session_id: str
119    role: str
120    content: str
121    model: Optional[str] = None
122    token_count: int = 0
123    created_at: Optional[datetime] = None
124
125
126@dataclass
127class Workspace:
128    """Represents a workspace."""
129    id: str
130    name: str
131    path: str
132    provider: str
133    session_count: int = 0
134    created_at: Optional[datetime] = None
135
136
137class ChasmError(Exception):
138    """Base exception for Chasm errors."""
139    def __init__(self, message: str, status_code: Optional[int] = None, response: Optional[dict] = None):
140        super().__init__(message)
141        self.status_code = status_code
142        self.response = response
143
144
145class AuthenticationError(ChasmError):
146    """Authentication failed."""
147    pass
148
149
150class RateLimitError(ChasmError):
151    """Rate limit exceeded."""
152    pass
153
154
155class NotFoundError(ChasmError):
156    """Resource not found."""
157    pass
158
159
160class ApiClient:
161    """Low-level API client."""
162    
163    def __init__(self, config: ChasmConfig):
164        self.config = config
165        self.session = requests.Session()
166        if config.api_key:
167            self.session.headers["Authorization"] = f"Bearer {config.api_key}"
168        self.session.headers["Content-Type"] = "application/json"
169        self.session.headers["User-Agent"] = f"chasm-python/{__version__}"
170    
171    def request(self, method: str, path: str, **kwargs) -> dict:
172        """Make an API request."""
173        url = urljoin(self.config.base_url, path)
174        kwargs.setdefault("timeout", self.config.timeout)
175        
176        response = self.session.request(method, url, **kwargs)
177        
178        if response.status_code == 401:
179            raise AuthenticationError("Invalid API key", 401)
180        elif response.status_code == 404:
181            raise NotFoundError("Resource not found", 404)
182        elif response.status_code == 429:
183            raise RateLimitError("Rate limit exceeded", 429)
184        elif response.status_code >= 400:
185            raise ChasmError(f"API error: {response.text}", response.status_code)
186        
187        if response.content:
188            return response.json()
189        return {}
190    
191    def get(self, path: str, params: Optional[dict] = None) -> dict:
192        return self.request("GET", path, params=params)
193    
194    def post(self, path: str, data: Optional[dict] = None) -> dict:
195        return self.request("POST", path, json=data)
196    
197    def put(self, path: str, data: Optional[dict] = None) -> dict:
198        return self.request("PUT", path, json=data)
199    
200    def delete(self, path: str) -> dict:
201        return self.request("DELETE", path)
202
203
204class SessionsResource:
205    """Sessions API resource."""
206    
207    def __init__(self, client: ApiClient):
208        self._client = client
209    
210    def list(
211        self,
212        workspace_id: Optional[str] = None,
213        provider: Optional[str] = None,
214        archived: Optional[bool] = None,
215        limit: int = 20,
216        offset: int = 0,
217    ) -> List[Session]:
218        """List sessions."""
219        params = {"limit": limit, "offset": offset}
220        if workspace_id:
221            params["workspace_id"] = workspace_id
222        if provider:
223            params["provider"] = provider
224        if archived is not None:
225            params["archived"] = str(archived).lower()
226        
227        response = self._client.get("/api/sessions", params)
228        return [self._parse_session(s) for s in response.get("sessions", [])]
229    
230    def get(self, session_id: str) -> Session:
231        """Get a session by ID."""
232        response = self._client.get(f"/api/sessions/{session_id}")
233        return self._parse_session(response)
234    
235    def create(self, title: str, provider: str, workspace_id: Optional[str] = None) -> Session:
236        """Create a new session."""
237        data = {"title": title, "provider": provider}
238        if workspace_id:
239            data["workspace_id"] = workspace_id
240        response = self._client.post("/api/sessions", data)
241        return self._parse_session(response)
242    
243    def update(self, session_id: str, **kwargs) -> Session:
244        """Update a session."""
245        response = self._client.put(f"/api/sessions/{session_id}", kwargs)
246        return self._parse_session(response)
247    
248    def delete(self, session_id: str) -> bool:
249        """Delete a session."""
250        self._client.delete(f"/api/sessions/{session_id}")
251        return True
252    
253    def archive(self, session_id: str) -> Session:
254        """Archive a session."""
255        return self.update(session_id, archived=True)
256    
257    def search(self, query: str, limit: int = 20) -> List[Session]:
258        """Search sessions."""
259        response = self._client.get("/api/sessions/search", {"q": query, "limit": limit})
260        return [self._parse_session(s) for s in response.get("sessions", [])]
261    
262    def _parse_session(self, data: dict) -> Session:
263        return Session(
264            id=data["id"],
265            title=data.get("title", "Untitled"),
266            provider=data.get("provider", "unknown"),
267            workspace_id=data.get("workspace_id"),
268            message_count=data.get("message_count", 0),
269            token_count=data.get("token_count", 0),
270            tags=data.get("tags", []),
271            archived=data.get("archived", False),
272        )
273
274
275class WorkspacesResource:
276    """Workspaces API resource."""
277    
278    def __init__(self, client: ApiClient):
279        self._client = client
280    
281    def list(self, limit: int = 20, offset: int = 0) -> List[Workspace]:
282        """List workspaces."""
283        response = self._client.get("/api/workspaces", {"limit": limit, "offset": offset})
284        return [self._parse_workspace(w) for w in response.get("workspaces", [])]
285    
286    def get(self, workspace_id: str) -> Workspace:
287        """Get a workspace by ID."""
288        response = self._client.get(f"/api/workspaces/{workspace_id}")
289        return self._parse_workspace(response)
290    
291    def _parse_workspace(self, data: dict) -> Workspace:
292        return Workspace(
293            id=data["id"],
294            name=data.get("name", ""),
295            path=data.get("path", ""),
296            provider=data.get("provider", ""),
297            session_count=data.get("session_count", 0),
298        )
299
300
301class HarvestResource:
302    """Harvest API resource."""
303    
304    def __init__(self, client: ApiClient):
305        self._client = client
306    
307    def run(self, providers: Optional[List[str]] = None) -> dict:
308        """Run harvest."""
309        data = {}
310        if providers:
311            data["providers"] = providers
312        return self._client.post("/api/harvest", data)
313    
314    def status(self) -> dict:
315        """Get harvest status."""
316        return self._client.get("/api/harvest/status")
317
318
319class ChasmClient:
320    """Main Chasm client."""
321    
322    def __init__(
323        self,
324        api_key: Optional[str] = None,
325        base_url: Optional[str] = None,
326        **kwargs
327    ):
328        api_key = api_key or os.environ.get("CHASM_API_KEY")
329        config = ChasmConfig(
330            api_key=api_key,
331            base_url=base_url or "{{base_url}}",
332            **kwargs
333        )
334        self._api = ApiClient(config)
335        
336        # Resources
337        self.sessions = SessionsResource(self._api)
338        self.workspaces = WorkspacesResource(self._api)
339        self.harvest = HarvestResource(self._api)
340    
341    def health(self) -> dict:
342        """Check API health."""
343        return self._api.get("/health")
344    
345    def stats(self) -> dict:
346        """Get statistics."""
347        return self._api.get("/api/stats")
348
349
350# Convenience function
351def create_client(**kwargs) -> ChasmClient:
352    """Create a Chasm client with environment configuration."""
353    return ChasmClient(**kwargs)
354"#;
355
356/// Node.js SDK template
357pub const NODEJS_SDK_TEMPLATE: &str = r#"/**
358 * Chasm Node.js SDK
359 * Auto-generated - Do not edit directly
360 * Version: {{version}}
361 */
362
363const https = require('https');
364const http = require('http');
365const { URL } = require('url');
366
367const VERSION = '{{version}}';
368const API_VERSION = '{{api_version}}';
369
370/**
371 * Chasm client configuration
372 */
373class ChasmConfig {
374  constructor(options = {}) {
375    this.baseUrl = options.baseUrl || process.env.CHASM_BASE_URL || '{{base_url}}';
376    this.apiKey = options.apiKey || process.env.CHASM_API_KEY;
377    this.timeout = options.timeout || 30000;
378    this.retryCount = options.retryCount || 3;
379  }
380}
381
382/**
383 * Custom error classes
384 */
385class ChasmError extends Error {
386  constructor(message, statusCode, response) {
387    super(message);
388    this.name = 'ChasmError';
389    this.statusCode = statusCode;
390    this.response = response;
391  }
392}
393
394class AuthenticationError extends ChasmError {
395  constructor(message) {
396    super(message, 401);
397    this.name = 'AuthenticationError';
398  }
399}
400
401class NotFoundError extends ChasmError {
402  constructor(message) {
403    super(message, 404);
404    this.name = 'NotFoundError';
405  }
406}
407
408class RateLimitError extends ChasmError {
409  constructor(message) {
410    super(message, 429);
411    this.name = 'RateLimitError';
412  }
413}
414
415/**
416 * Low-level API client
417 */
418class ApiClient {
419  constructor(config) {
420    this.config = config;
421  }
422
423  async request(method, path, options = {}) {
424    const url = new URL(path, this.config.baseUrl);
425    const isHttps = url.protocol === 'https:';
426    const client = isHttps ? https : http;
427
428    if (options.params) {
429      Object.entries(options.params).forEach(([key, value]) => {
430        if (value !== undefined) {
431          url.searchParams.append(key, String(value));
432        }
433      });
434    }
435
436    const requestOptions = {
437      method,
438      hostname: url.hostname,
439      port: url.port || (isHttps ? 443 : 80),
440      path: url.pathname + url.search,
441      headers: {
442        'Content-Type': 'application/json',
443        'User-Agent': `chasm-nodejs/${VERSION}`,
444        ...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` }),
445      },
446      timeout: this.config.timeout,
447    };
448
449    return new Promise((resolve, reject) => {
450      const req = client.request(requestOptions, (res) => {
451        let data = '';
452        res.on('data', (chunk) => (data += chunk));
453        res.on('end', () => {
454          if (res.statusCode === 401) {
455            reject(new AuthenticationError('Invalid API key'));
456          } else if (res.statusCode === 404) {
457            reject(new NotFoundError('Resource not found'));
458          } else if (res.statusCode === 429) {
459            reject(new RateLimitError('Rate limit exceeded'));
460          } else if (res.statusCode >= 400) {
461            reject(new ChasmError(`API error: ${data}`, res.statusCode));
462          } else {
463            resolve(data ? JSON.parse(data) : {});
464          }
465        });
466      });
467
468      req.on('error', reject);
469      req.on('timeout', () => {
470        req.destroy();
471        reject(new ChasmError('Request timeout'));
472      });
473
474      if (options.body) {
475        req.write(JSON.stringify(options.body));
476      }
477      req.end();
478    });
479  }
480
481  get(path, params) {
482    return this.request('GET', path, { params });
483  }
484
485  post(path, body) {
486    return this.request('POST', path, { body });
487  }
488
489  put(path, body) {
490    return this.request('PUT', path, { body });
491  }
492
493  delete(path) {
494    return this.request('DELETE', path);
495  }
496}
497
498/**
499 * Sessions resource
500 */
501class SessionsResource {
502  constructor(client) {
503    this._client = client;
504  }
505
506  async list(options = {}) {
507    const params = {
508      limit: options.limit || 20,
509      offset: options.offset || 0,
510      workspace_id: options.workspaceId,
511      provider: options.provider,
512      archived: options.archived,
513    };
514    const response = await this._client.get('/api/sessions', params);
515    return response.sessions || [];
516  }
517
518  async get(sessionId) {
519    return this._client.get(`/api/sessions/${sessionId}`);
520  }
521
522  async create(data) {
523    return this._client.post('/api/sessions', data);
524  }
525
526  async update(sessionId, data) {
527    return this._client.put(`/api/sessions/${sessionId}`, data);
528  }
529
530  async delete(sessionId) {
531    await this._client.delete(`/api/sessions/${sessionId}`);
532    return true;
533  }
534
535  async search(query, limit = 20) {
536    const response = await this._client.get('/api/sessions/search', { q: query, limit });
537    return response.sessions || [];
538  }
539}
540
541/**
542 * Workspaces resource
543 */
544class WorkspacesResource {
545  constructor(client) {
546    this._client = client;
547  }
548
549  async list(options = {}) {
550    const params = {
551      limit: options.limit || 20,
552      offset: options.offset || 0,
553    };
554    const response = await this._client.get('/api/workspaces', params);
555    return response.workspaces || [];
556  }
557
558  async get(workspaceId) {
559    return this._client.get(`/api/workspaces/${workspaceId}`);
560  }
561}
562
563/**
564 * Harvest resource
565 */
566class HarvestResource {
567  constructor(client) {
568    this._client = client;
569  }
570
571  async run(providers) {
572    const data = providers ? { providers } : {};
573    return this._client.post('/api/harvest', data);
574  }
575
576  async status() {
577    return this._client.get('/api/harvest/status');
578  }
579}
580
581/**
582 * Main Chasm client
583 */
584class ChasmClient {
585  constructor(options = {}) {
586    const config = new ChasmConfig(options);
587    this._api = new ApiClient(config);
588
589    this.sessions = new SessionsResource(this._api);
590    this.workspaces = new WorkspacesResource(this._api);
591    this.harvest = new HarvestResource(this._api);
592  }
593
594  async health() {
595    return this._api.get('/health');
596  }
597
598  async stats() {
599    return this._api.get('/api/stats');
600  }
601}
602
603module.exports = {
604  ChasmClient,
605  ChasmConfig,
606  ChasmError,
607  AuthenticationError,
608  NotFoundError,
609  RateLimitError,
610  VERSION,
611  API_VERSION,
612};
613"#;
614
615/// Go SDK template
616pub const GO_SDK_TEMPLATE: &str = r#"// Chasm Go SDK
617// Auto-generated - Do not edit directly
618// Version: {{version}}
619
620package chasm
621
622import (
623	"bytes"
624	"encoding/json"
625	"fmt"
626	"io"
627	"net/http"
628	"net/url"
629	"os"
630	"time"
631)
632
633const (
634	Version    = "{{version}}"
635	APIVersion = "{{api_version}}"
636)
637
638// Config holds client configuration
639type Config struct {
640	BaseURL    string
641	APIKey     string
642	Timeout    time.Duration
643	RetryCount int
644}
645
646// DefaultConfig returns default configuration
647func DefaultConfig() *Config {
648	baseURL := os.Getenv("CHASM_BASE_URL")
649	if baseURL == "" {
650		baseURL = "{{base_url}}"
651	}
652	return &Config{
653		BaseURL:    baseURL,
654		APIKey:     os.Getenv("CHASM_API_KEY"),
655		Timeout:    30 * time.Second,
656		RetryCount: 3,
657	}
658}
659
660// Session represents a chat session
661type Session struct {
662	ID           string    `json:"id"`
663	Title        string    `json:"title"`
664	Provider     string    `json:"provider"`
665	WorkspaceID  *string   `json:"workspace_id,omitempty"`
666	MessageCount int       `json:"message_count"`
667	TokenCount   int       `json:"token_count"`
668	Tags         []string  `json:"tags"`
669	Archived     bool      `json:"archived"`
670	CreatedAt    time.Time `json:"created_at,omitempty"`
671	UpdatedAt    time.Time `json:"updated_at,omitempty"`
672}
673
674// Workspace represents a workspace
675type Workspace struct {
676	ID           string    `json:"id"`
677	Name         string    `json:"name"`
678	Path         string    `json:"path"`
679	Provider     string    `json:"provider"`
680	SessionCount int       `json:"session_count"`
681	CreatedAt    time.Time `json:"created_at,omitempty"`
682}
683
684// Error types
685type ChasmError struct {
686	Message    string
687	StatusCode int
688}
689
690func (e *ChasmError) Error() string {
691	return fmt.Sprintf("chasm: %s (status %d)", e.Message, e.StatusCode)
692}
693
694// Client is the main Chasm client
695type Client struct {
696	config     *Config
697	httpClient *http.Client
698	Sessions   *SessionsService
699	Workspaces *WorkspacesService
700	Harvest    *HarvestService
701}
702
703// NewClient creates a new Chasm client
704func NewClient(config *Config) *Client {
705	if config == nil {
706		config = DefaultConfig()
707	}
708	
709	c := &Client{
710		config: config,
711		httpClient: &http.Client{
712			Timeout: config.Timeout,
713		},
714	}
715	
716	c.Sessions = &SessionsService{client: c}
717	c.Workspaces = &WorkspacesService{client: c}
718	c.Harvest = &HarvestService{client: c}
719	
720	return c
721}
722
723func (c *Client) request(method, path string, body interface{}, result interface{}) error {
724	u, err := url.Parse(c.config.BaseURL + path)
725	if err != nil {
726		return err
727	}
728
729	var bodyReader io.Reader
730	if body != nil {
731		b, err := json.Marshal(body)
732		if err != nil {
733			return err
734		}
735		bodyReader = bytes.NewReader(b)
736	}
737
738	req, err := http.NewRequest(method, u.String(), bodyReader)
739	if err != nil {
740		return err
741	}
742
743	req.Header.Set("Content-Type", "application/json")
744	req.Header.Set("User-Agent", fmt.Sprintf("chasm-go/%s", Version))
745	if c.config.APIKey != "" {
746		req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
747	}
748
749	resp, err := c.httpClient.Do(req)
750	if err != nil {
751		return err
752	}
753	defer resp.Body.Close()
754
755	if resp.StatusCode >= 400 {
756		return &ChasmError{
757			Message:    fmt.Sprintf("API error: %s", resp.Status),
758			StatusCode: resp.StatusCode,
759		}
760	}
761
762	if result != nil {
763		return json.NewDecoder(resp.Body).Decode(result)
764	}
765	return nil
766}
767
768// Health checks API health
769func (c *Client) Health() (map[string]interface{}, error) {
770	var result map[string]interface{}
771	err := c.request("GET", "/health", nil, &result)
772	return result, err
773}
774
775// Stats gets statistics
776func (c *Client) Stats() (map[string]interface{}, error) {
777	var result map[string]interface{}
778	err := c.request("GET", "/api/stats", nil, &result)
779	return result, err
780}
781
782// SessionsService handles session operations
783type SessionsService struct {
784	client *Client
785}
786
787// ListOptions for listing resources
788type ListOptions struct {
789	Limit       int
790	Offset      int
791	WorkspaceID string
792	Provider    string
793	Archived    *bool
794}
795
796func (s *SessionsService) List(opts *ListOptions) ([]Session, error) {
797	path := "/api/sessions"
798	if opts != nil {
799		params := url.Values{}
800		if opts.Limit > 0 {
801			params.Set("limit", fmt.Sprintf("%d", opts.Limit))
802		}
803		if opts.Offset > 0 {
804			params.Set("offset", fmt.Sprintf("%d", opts.Offset))
805		}
806		if opts.WorkspaceID != "" {
807			params.Set("workspace_id", opts.WorkspaceID)
808		}
809		if opts.Provider != "" {
810			params.Set("provider", opts.Provider)
811		}
812		if opts.Archived != nil {
813			params.Set("archived", fmt.Sprintf("%t", *opts.Archived))
814		}
815		if len(params) > 0 {
816			path += "?" + params.Encode()
817		}
818	}
819	
820	var result struct {
821		Sessions []Session `json:"sessions"`
822	}
823	err := s.client.request("GET", path, nil, &result)
824	return result.Sessions, err
825}
826
827func (s *SessionsService) Get(id string) (*Session, error) {
828	var session Session
829	err := s.client.request("GET", "/api/sessions/"+id, nil, &session)
830	return &session, err
831}
832
833func (s *SessionsService) Create(title, provider string, workspaceID *string) (*Session, error) {
834	body := map[string]interface{}{
835		"title":    title,
836		"provider": provider,
837	}
838	if workspaceID != nil {
839		body["workspace_id"] = *workspaceID
840	}
841	var session Session
842	err := s.client.request("POST", "/api/sessions", body, &session)
843	return &session, err
844}
845
846func (s *SessionsService) Delete(id string) error {
847	return s.client.request("DELETE", "/api/sessions/"+id, nil, nil)
848}
849
850func (s *SessionsService) Search(query string, limit int) ([]Session, error) {
851	path := fmt.Sprintf("/api/sessions/search?q=%s&limit=%d", url.QueryEscape(query), limit)
852	var result struct {
853		Sessions []Session `json:"sessions"`
854	}
855	err := s.client.request("GET", path, nil, &result)
856	return result.Sessions, err
857}
858
859// WorkspacesService handles workspace operations
860type WorkspacesService struct {
861	client *Client
862}
863
864func (w *WorkspacesService) List(opts *ListOptions) ([]Workspace, error) {
865	path := "/api/workspaces"
866	if opts != nil && (opts.Limit > 0 || opts.Offset > 0) {
867		params := url.Values{}
868		if opts.Limit > 0 {
869			params.Set("limit", fmt.Sprintf("%d", opts.Limit))
870		}
871		if opts.Offset > 0 {
872			params.Set("offset", fmt.Sprintf("%d", opts.Offset))
873		}
874		path += "?" + params.Encode()
875	}
876	
877	var result struct {
878		Workspaces []Workspace `json:"workspaces"`
879	}
880	err := w.client.request("GET", path, nil, &result)
881	return result.Workspaces, err
882}
883
884func (w *WorkspacesService) Get(id string) (*Workspace, error) {
885	var workspace Workspace
886	err := w.client.request("GET", "/api/workspaces/"+id, nil, &workspace)
887	return &workspace, err
888}
889
890// HarvestService handles harvest operations
891type HarvestService struct {
892	client *Client
893}
894
895func (h *HarvestService) Run(providers []string) (map[string]interface{}, error) {
896	body := map[string]interface{}{}
897	if len(providers) > 0 {
898		body["providers"] = providers
899	}
900	var result map[string]interface{}
901	err := h.client.request("POST", "/api/harvest", body, &result)
902	return result, err
903}
904
905func (h *HarvestService) Status() (map[string]interface{}, error) {
906	var result map[string]interface{}
907	err := h.client.request("GET", "/api/harvest/status", nil, &result)
908	return result, err
909}
910"#;
911
912// ============================================================================
913// SDK Generator
914// ============================================================================
915
916/// SDK generator
917pub struct SdkGenerator {
918    config: SdkConfig,
919}
920
921impl SdkGenerator {
922    /// Create a new SDK generator
923    pub fn new(config: SdkConfig) -> Self {
924        Self { config }
925    }
926
927    /// Generate SDK for a language
928    pub fn generate(&self, language: SdkLanguage) -> String {
929        let template = match language {
930            SdkLanguage::Python => PYTHON_SDK_TEMPLATE,
931            SdkLanguage::NodeJs => NODEJS_SDK_TEMPLATE,
932            SdkLanguage::Go => GO_SDK_TEMPLATE,
933            _ => return format!("// SDK for {} not yet implemented", language),
934        };
935
936        template
937            .replace("{{version}}", &self.config.version)
938            .replace("{{api_version}}", &self.config.api_version)
939            .replace("{{base_url}}", &self.config.base_url)
940    }
941
942    /// Generate all SDKs
943    pub fn generate_all(&self) -> HashMap<SdkLanguage, String> {
944        self.config
945            .languages
946            .iter()
947            .map(|lang| (lang.clone(), self.generate(lang.clone())))
948            .collect()
949    }
950
951    /// Get SDK file name for language
952    pub fn get_filename(&self, language: &SdkLanguage) -> String {
953        match language {
954            SdkLanguage::Python => "chasm.py".to_string(),
955            SdkLanguage::NodeJs => "chasm.js".to_string(),
956            SdkLanguage::Go => "chasm.go".to_string(),
957            SdkLanguage::Rust => "chasm.rs".to_string(),
958            SdkLanguage::Java => "Chasm.java".to_string(),
959            SdkLanguage::CSharp => "Chasm.cs".to_string(),
960            SdkLanguage::Ruby => "chasm.rb".to_string(),
961            SdkLanguage::Php => "Chasm.php".to_string(),
962        }
963    }
964}
965
966impl Default for SdkConfig {
967    fn default() -> Self {
968        Self {
969            version: "1.0.0".to_string(),
970            base_url: "http://localhost:8787".to_string(),
971            languages: vec![SdkLanguage::Python, SdkLanguage::NodeJs, SdkLanguage::Go],
972            api_version: "v1".to_string(),
973        }
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980
981    #[test]
982    fn test_sdk_generation() {
983        let config = SdkConfig::default();
984        let generator = SdkGenerator::new(config);
985
986        let python_sdk = generator.generate(SdkLanguage::Python);
987        assert!(python_sdk.contains("class ChasmClient"));
988        assert!(python_sdk.contains("1.0.0"));
989
990        let nodejs_sdk = generator.generate(SdkLanguage::NodeJs);
991        assert!(nodejs_sdk.contains("class ChasmClient"));
992
993        let go_sdk = generator.generate(SdkLanguage::Go);
994        assert!(go_sdk.contains("type Client struct"));
995    }
996}