diff --git a/internal/sources/cloudhealthcare/cloud_healthcare.go b/internal/sources/cloudhealthcare/cloud_healthcare.go index 0d4b7e8ddf7..82cfbd583e5 100644 --- a/internal/sources/cloudhealthcare/cloud_healthcare.go +++ b/internal/sources/cloudhealthcare/cloud_healthcare.go @@ -16,8 +16,12 @@ package cloudhealthcare import ( "context" + "encoding/base64" + "encoding/json" "fmt" + "io" "net/http" + "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" @@ -255,3 +259,299 @@ func (s *Source) IsDICOMStoreAllowed(storeID string) bool { func (s *Source) UseClientAuthorization() bool { return s.UseClientOAuth } + +func parseResults(resp *http.Response) (any, error) { + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("could not read response: %w", err) + } + if resp.StatusCode > 299 { + return nil, fmt.Errorf("status %d %s: %s", resp.StatusCode, resp.Status, respBytes) + } + var jsonMap map[string]interface{} + if err := json.Unmarshal(respBytes, &jsonMap); err != nil { + return nil, fmt.Errorf("could not unmarshal response as json: %w", err) + } + return jsonMap, nil +} + +func (s *Source) getService(tokenStr string) (*healthcare.Service, error) { + svc := s.Service() + var err error + // Initialize new service if using user OAuth token + if s.UseClientAuthorization() { + svc, err = s.ServiceCreator()(tokenStr) + if err != nil { + return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) + } + } + return svc, nil +} + +func (s *Source) FHIRFetchPage(ctx context.Context, url, tokenStr string) (any, error) { + var httpClient *http.Client + if s.UseClientAuthorization() { + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tokenStr}) + httpClient = oauth2.NewClient(ctx, ts) + } else { + // The source.Service() object holds a client with the default credentials. + // However, the client is not exported, so we have to create a new one. + var err error + httpClient, err = google.DefaultClient(ctx, healthcare.CloudHealthcareScope) + if err != nil { + return nil, fmt.Errorf("failed to create default http client: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create http request: %w", err) + } + req.Header.Set("Accept", "application/fhir+json;charset=utf-8") + + resp, err := httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to get fhir page from %q: %w", url, err) + } + defer resp.Body.Close() + return parseResults(resp) +} + +func (s *Source) FHIRPatientEverything(storeID, patientID, tokenStr string, opts []googleapi.CallOption) (any, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/Patient/%s", s.Project(), s.Region(), s.DatasetID(), storeID, patientID) + resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.PatientEverything(name).Do(opts...) + if err != nil { + return nil, fmt.Errorf("failed to call patient everything for %q: %w", name, err) + } + defer resp.Body.Close() + return parseResults(resp) +} + +func (s *Source) FHIRPatientSearch(storeID, tokenStr string, opts []googleapi.CallOption) (any, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", s.Project(), s.Region(), s.DatasetID(), storeID) + resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.SearchType(name, "Patient", &healthcare.SearchResourcesRequest{ResourceType: "Patient"}).Do(opts...) + if err != nil { + return nil, fmt.Errorf("failed to search patient resources: %w", err) + } + defer resp.Body.Close() + return parseResults(resp) +} + +func (s *Source) GetDataset(tokenStr string) (*healthcare.Dataset, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", s.Project(), s.Region(), s.DatasetID()) + dataset, err := svc.Projects.Locations.Datasets.Get(datasetName).Do() + if err != nil { + return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err) + } + return dataset, nil +} + +func (s *Source) GetFHIRResource(storeID, resType, resID, tokenStr string) (any, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/%s/%s", s.Project(), s.Region(), s.DatasetID(), storeID, resType, resID) + call := svc.Projects.Locations.Datasets.FhirStores.Fhir.Read(name) + call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8") + resp, err := call.Do() + if err != nil { + return nil, fmt.Errorf("failed to get fhir resource %q: %w", name, err) + } + defer resp.Body.Close() + return parseResults(resp) +} + +func (s *Source) GetDICOMStore(storeID, tokenStr string) (*healthcare.DicomStore, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", s.Project(), s.Region(), s.DatasetID(), storeID) + store, err := svc.Projects.Locations.Datasets.DicomStores.Get(storeName).Do() + if err != nil { + return nil, fmt.Errorf("failed to get DICOM store %q: %w", storeName, err) + } + return store, nil +} + +func (s *Source) GetFHIRStore(storeID, tokenStr string) (*healthcare.FhirStore, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", s.Project(), s.Region(), s.DatasetID(), storeID) + store, err := svc.Projects.Locations.Datasets.FhirStores.Get(storeName).Do() + if err != nil { + return nil, fmt.Errorf("failed to get FHIR store %q: %w", storeName, err) + } + return store, nil +} + +func (s *Source) GetDICOMStoreMetrics(storeID, tokenStr string) (*healthcare.DicomStoreMetrics, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", s.Project(), s.Region(), s.DatasetID(), storeID) + store, err := svc.Projects.Locations.Datasets.DicomStores.GetDICOMStoreMetrics(storeName).Do() + if err != nil { + return nil, fmt.Errorf("failed to get metrics for DICOM store %q: %w", storeName, err) + } + return store, nil +} + +func (s *Source) GetFHIRStoreMetrics(storeID, tokenStr string) (*healthcare.FhirStoreMetrics, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", s.Project(), s.Region(), s.DatasetID(), storeID) + store, err := svc.Projects.Locations.Datasets.FhirStores.GetFHIRStoreMetrics(storeName).Do() + if err != nil { + return nil, fmt.Errorf("failed to get metrics for FHIR store %q: %w", storeName, err) + } + return store, nil +} + +func (s *Source) ListDICOMStores(tokenStr string) ([]*healthcare.DicomStore, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", s.Project(), s.Region(), s.DatasetID()) + stores, err := svc.Projects.Locations.Datasets.DicomStores.List(datasetName).Do() + if err != nil { + return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err) + } + var filtered []*healthcare.DicomStore + for _, store := range stores.DicomStores { + if len(s.AllowedDICOMStores()) == 0 { + filtered = append(filtered, store) + continue + } + if len(store.Name) == 0 { + continue + } + parts := strings.Split(store.Name, "/") + if _, ok := s.AllowedDICOMStores()[parts[len(parts)-1]]; ok { + filtered = append(filtered, store) + } + } + return filtered, nil +} + +func (s *Source) ListFHIRStores(tokenStr string) ([]*healthcare.FhirStore, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", s.Project(), s.Region(), s.DatasetID()) + stores, err := svc.Projects.Locations.Datasets.FhirStores.List(datasetName).Do() + if err != nil { + return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err) + } + var filtered []*healthcare.FhirStore + for _, store := range stores.FhirStores { + if len(s.AllowedFHIRStores()) == 0 { + filtered = append(filtered, store) + continue + } + if len(store.Name) == 0 { + continue + } + parts := strings.Split(store.Name, "/") + if _, ok := s.AllowedFHIRStores()[parts[len(parts)-1]]; ok { + filtered = append(filtered, store) + } + } + return filtered, nil +} + +func (s *Source) RetrieveRenderedDICOMInstance(storeID, study, series, sop string, frame int, tokenStr string) (any, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + + name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", s.Project(), s.Region(), s.DatasetID(), storeID) + dicomWebPath := fmt.Sprintf("studies/%s/series/%s/instances/%s/frames/%d/rendered", study, series, sop, frame) + call := svc.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances.Frames.RetrieveRendered(name, dicomWebPath) + call.Header().Set("Accept", "image/jpeg") + resp, err := call.Do() + if err != nil { + return nil, fmt.Errorf("unable to retrieve dicom instance rendered image: %w", err) + } + defer resp.Body.Close() + + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("could not read response: %w", err) + } + if resp.StatusCode > 299 { + return nil, fmt.Errorf("RetrieveRendered: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) + } + base64String := base64.StdEncoding.EncodeToString(respBytes) + return base64String, nil +} + +func (s *Source) SearchDICOM(toolKind, storeID, dicomWebPath, tokenStr string, opts []googleapi.CallOption) (any, error) { + svc, err := s.getService(tokenStr) + if err != nil { + return nil, err + } + name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", s.Project(), s.Region(), s.DatasetID(), storeID) + var resp *http.Response + switch toolKind { + case "cloud-healthcare-search-dicom-instances": + resp, err = svc.Projects.Locations.Datasets.DicomStores.SearchForInstances(name, dicomWebPath).Do(opts...) + case "cloud-healthcare-search-dicom-series": + resp, err = svc.Projects.Locations.Datasets.DicomStores.SearchForSeries(name, dicomWebPath).Do(opts...) + case "cloud-healthcare-search-dicom-studies": + resp, err = svc.Projects.Locations.Datasets.DicomStores.SearchForStudies(name, dicomWebPath).Do(opts...) + default: + return nil, fmt.Errorf("incompatible tool kind: %s", toolKind) + } + if err != nil { + return nil, fmt.Errorf("failed to search dicom series: %w", err) + } + defer resp.Body.Close() + + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("could not read response: %w", err) + } + if resp.StatusCode > 299 { + return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) + } + if len(respBytes) == 0 { + return []interface{}{}, nil + } + var result []interface{} + if err := json.Unmarshal(respBytes, &result); err != nil { + return nil, fmt.Errorf("could not unmarshal response as list: %w", err) + } + return result, nil +} diff --git a/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage/cloudhealthcarefhirfetchpage.go b/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage/cloudhealthcarefhirfetchpage.go index 50698fb0429..2f3332403c8 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage/cloudhealthcarefhirfetchpage.go +++ b/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage/cloudhealthcarefhirfetchpage.go @@ -16,22 +16,13 @@ package fhirfetchpage import ( "context" - "encoding/json" "fmt" - "io" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util/parameters" - "google.golang.org/api/healthcare/v1" - - "net/http" - - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" ) const kind string = "cloud-healthcare-fhir-fetch-page" @@ -54,13 +45,8 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string - AllowedFHIRStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + FHIRFetchPage(context.Context, string, string) (any, error) } type Config struct { @@ -118,48 +104,11 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", pageURLKey) } - var httpClient *http.Client - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tokenStr}) - httpClient = oauth2.NewClient(ctx, ts) - } else { - // The source.Service() object holds a client with the default credentials. - // However, the client is not exported, so we have to create a new one. - var err error - httpClient, err = google.DefaultClient(ctx, healthcare.CloudHealthcareScope) - if err != nil { - return nil, fmt.Errorf("failed to create default http client: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return nil, fmt.Errorf("failed to create http request: %w", err) - } - req.Header.Set("Accept", "application/fhir+json;charset=utf-8") - - resp, err := httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get fhir page from %q: %w", url, err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("read: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - var jsonMap map[string]interface{} - if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil { - return nil, fmt.Errorf("could not unmarshal response as json: %w", err) + return nil, fmt.Errorf("error parsing access token: %w", err) } - return jsonMap, nil + return source.FHIRFetchPage(ctx, url, tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything/cloudhealthcarefhirpatienteverything.go b/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything/cloudhealthcarefhirpatienteverything.go index 314208cc9c6..37e8288e3cd 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything/cloudhealthcarefhirpatienteverything.go +++ b/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything/cloudhealthcarefhirpatienteverything.go @@ -16,20 +16,16 @@ package fhirpatienteverything import ( "context" - "encoding/json" "fmt" - "io" "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" "google.golang.org/api/googleapi" - "google.golang.org/api/healthcare/v1" ) const kind string = "cloud-healthcare-fhir-patient-everything" @@ -54,13 +50,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedFHIRStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + FHIRPatientEverything(string, string, string, []googleapi.CallOption) (any, error) } type Config struct { @@ -139,20 +131,11 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", patientIDKey) } - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } + tokenStr, err := accessToken.ParseBearerToken() + if err != nil { + return nil, fmt.Errorf("error parsing access token: %w", err) } - name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/Patient/%s", source.Project(), source.Region(), source.DatasetID(), storeID, patientID) var opts []googleapi.CallOption if val, ok := params.AsMap()[typeFilterKey]; ok { types, ok := val.([]any) @@ -176,25 +159,7 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para opts = append(opts, googleapi.QueryParameter("_since", sinceStr)) } } - - resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.PatientEverything(name).Do(opts...) - if err != nil { - return nil, fmt.Errorf("failed to call patient everything for %q: %w", name, err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("patient-everything: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - var jsonMap map[string]interface{} - if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil { - return nil, fmt.Errorf("could not unmarshal response as json: %w", err) - } - return jsonMap, nil + return source.FHIRPatientEverything(storeID, patientID, tokenStr, opts) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch/cloudhealthcarefhirpatientsearch.go b/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch/cloudhealthcarefhirpatientsearch.go index 269fee8a449..1a92ac0bdd7 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch/cloudhealthcarefhirpatientsearch.go +++ b/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch/cloudhealthcarefhirpatientsearch.go @@ -16,20 +16,16 @@ package fhirpatientsearch import ( "context" - "encoding/json" "fmt" - "io" "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" "google.golang.org/api/googleapi" - "google.golang.org/api/healthcare/v1" ) const kind string = "cloud-healthcare-fhir-patient-search" @@ -70,13 +66,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedFHIRStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + FHIRPatientSearch(string, string, []googleapi.CallOption) (any, error) } type Config struct { @@ -169,17 +161,9 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para return nil, err } - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } + tokenStr, err := accessToken.ParseBearerToken() + if err != nil { + return nil, fmt.Errorf("error parsing access token: %w", err) } var summary bool @@ -248,26 +232,7 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if summary { opts = append(opts, googleapi.QueryParameter("_summary", "text")) } - - name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.SearchType(name, "Patient", &healthcare.SearchResourcesRequest{ResourceType: "Patient"}).Do(opts...) - if err != nil { - return nil, fmt.Errorf("failed to search patient resources: %w", err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - var jsonMap map[string]interface{} - if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil { - return nil, fmt.Errorf("could not unmarshal response as json: %w", err) - } - return jsonMap, nil + return source.FHIRPatientSearch(storeID, tokenStr, opts) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaregetdataset/cloudhealthcaregetdataset.go b/internal/tools/cloudhealthcare/cloudhealthcaregetdataset/cloudhealthcaregetdataset.go index 907613d0320..b16bc0944de 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaregetdataset/cloudhealthcaregetdataset.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaregetdataset/cloudhealthcaregetdataset.go @@ -21,7 +21,6 @@ import ( "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util/parameters" "google.golang.org/api/healthcare/v1" @@ -44,12 +43,8 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + GetDataset(string) (*healthcare.Dataset, error) } type Config struct { @@ -100,27 +95,11 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - - svc := source.Service() - - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", source.Project(), source.Region(), source.DatasetID()) - dataset, err := svc.Projects.Locations.Datasets.Get(datasetName).Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err) + return nil, fmt.Errorf("error parsing access token: %w", err) } - return dataset, nil + return source.GetDataset(tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore/cloudhealthcaregetdicomstore.go b/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore/cloudhealthcaregetdicomstore.go index 87e50290157..fb6d6144b58 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore/cloudhealthcaregetdicomstore.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore/cloudhealthcaregetdicomstore.go @@ -21,7 +21,6 @@ import ( "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" @@ -45,13 +44,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedDICOMStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + GetDICOMStore(string, string) (*healthcare.DicomStore, error) } type Config struct { @@ -117,31 +112,15 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - storeID, err := common.ValidateAndFetchStoreID(params, source.AllowedDICOMStores()) if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - store, err := svc.Projects.Locations.Datasets.DicomStores.Get(storeName).Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get DICOM store %q: %w", storeName, err) + return nil, fmt.Errorf("error parsing access token: %w", err) } - return store, nil + return source.GetDICOMStore(storeID, tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics/cloudhealthcaregetdicomstoremetrics.go b/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics/cloudhealthcaregetdicomstoremetrics.go index 5215ccab83d..8f669d5dca2 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics/cloudhealthcaregetdicomstoremetrics.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics/cloudhealthcaregetdicomstoremetrics.go @@ -21,7 +21,6 @@ import ( "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" @@ -45,13 +44,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedDICOMStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + GetDICOMStoreMetrics(string, string) (*healthcare.DicomStoreMetrics, error) } type Config struct { @@ -117,31 +112,15 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - storeID, err := common.ValidateAndFetchStoreID(params, source.AllowedDICOMStores()) if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - store, err := svc.Projects.Locations.Datasets.DicomStores.GetDICOMStoreMetrics(storeName).Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get metrics for DICOM store %q: %w", storeName, err) + return nil, fmt.Errorf("error parsing access token: %w", err) } - return store, nil + return source.GetDICOMStoreMetrics(storeID, tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource/cloudhealthcaregetfhirresource.go b/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource/cloudhealthcaregetfhirresource.go index 117d3641d54..3c8656535ed 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource/cloudhealthcaregetfhirresource.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource/cloudhealthcaregetfhirresource.go @@ -16,18 +16,14 @@ package getfhirresource import ( "context" - "encoding/json" "fmt" - "io" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" - "google.golang.org/api/healthcare/v1" ) const kind string = "cloud-healthcare-get-fhir-resource" @@ -51,13 +47,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedFHIRStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + GetFHIRResource(string, string, string, string) (any, error) } type Config struct { @@ -134,46 +126,15 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if !ok { return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", typeKey) } - resID, ok := params.AsMap()[idKey].(string) if !ok { return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", idKey) } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/%s/%s", source.Project(), source.Region(), source.DatasetID(), storeID, resType, resID) - call := svc.Projects.Locations.Datasets.FhirStores.Fhir.Read(name) - call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8") - resp, err := call.Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get fhir resource %q: %w", name, err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("read: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - var jsonMap map[string]interface{} - if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil { - return nil, fmt.Errorf("could not unmarshal response as json: %w", err) + return nil, fmt.Errorf("error parsing access token: %w", err) } - return jsonMap, nil + return source.GetFHIRResource(storeID, resType, resID, tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore/cloudhealthcaregetfhirstore.go b/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore/cloudhealthcaregetfhirstore.go index 1322a8cdc3a..6ab02049d28 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore/cloudhealthcaregetfhirstore.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore/cloudhealthcaregetfhirstore.go @@ -21,7 +21,6 @@ import ( "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" @@ -45,13 +44,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedFHIRStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + GetFHIRStore(string, string) (*healthcare.FhirStore, error) } type Config struct { @@ -117,31 +112,15 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - storeID, err := common.ValidateAndFetchStoreID(params, source.AllowedFHIRStores()) if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - store, err := svc.Projects.Locations.Datasets.FhirStores.Get(storeName).Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get FHIR store %q: %w", storeName, err) + return nil, fmt.Errorf("error parsing access token: %w", err) } - return store, nil + return source.GetFHIRStore(storeID, tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics/cloudhealthcaregetfhirstoremetrics.go b/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics/cloudhealthcaregetfhirstoremetrics.go index a69daa3b76f..2def5f7997a 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics/cloudhealthcaregetfhirstoremetrics.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics/cloudhealthcaregetfhirstoremetrics.go @@ -21,7 +21,6 @@ import ( "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" @@ -45,13 +44,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedFHIRStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + GetFHIRStoreMetrics(string, string) (*healthcare.FhirStoreMetrics, error) } type Config struct { @@ -117,31 +112,15 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - storeID, err := common.ValidateAndFetchStoreID(params, source.AllowedFHIRStores()) if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - store, err := svc.Projects.Locations.Datasets.FhirStores.GetFHIRStoreMetrics(storeName).Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get metrics for FHIR store %q: %w", storeName, err) + return nil, fmt.Errorf("error parsing access token: %w", err) } - return store, nil + return source.GetFHIRStoreMetrics(storeID, tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores/cloudhealthcarelistdicomstores.go b/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores/cloudhealthcarelistdicomstores.go index 39e4ad4397d..1f3a18a6df5 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores/cloudhealthcarelistdicomstores.go +++ b/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores/cloudhealthcarelistdicomstores.go @@ -17,12 +17,10 @@ package listdicomstores import ( "context" "fmt" - "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util/parameters" "google.golang.org/api/healthcare/v1" @@ -45,13 +43,8 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string - AllowedDICOMStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + ListDICOMStores(tokenStr string) ([]*healthcare.DicomStore, error) } type Config struct { @@ -102,41 +95,11 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - - svc := source.Service() - - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", source.Project(), source.Region(), source.DatasetID()) - stores, err := svc.Projects.Locations.Datasets.DicomStores.List(datasetName).Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err) - } - var filtered []*healthcare.DicomStore - for _, store := range stores.DicomStores { - if len(source.AllowedDICOMStores()) == 0 { - filtered = append(filtered, store) - continue - } - if len(store.Name) == 0 { - continue - } - parts := strings.Split(store.Name, "/") - if _, ok := source.AllowedDICOMStores()[parts[len(parts)-1]]; ok { - filtered = append(filtered, store) - } + return nil, fmt.Errorf("error parsing access token: %w", err) } - return filtered, nil + return source.ListDICOMStores(tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores/cloudhealthcarelistfhirstores.go b/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores/cloudhealthcarelistfhirstores.go index 9862e4c6e62..6e812433676 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores/cloudhealthcarelistfhirstores.go +++ b/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores/cloudhealthcarelistfhirstores.go @@ -17,12 +17,10 @@ package listfhirstores import ( "context" "fmt" - "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util/parameters" "google.golang.org/api/healthcare/v1" @@ -45,13 +43,8 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string - AllowedFHIRStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + ListFHIRStores(string) ([]*healthcare.FhirStore, error) } type Config struct { @@ -102,41 +95,11 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - - svc := source.Service() - - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } - } - - datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", source.Project(), source.Region(), source.DatasetID()) - stores, err := svc.Projects.Locations.Datasets.FhirStores.List(datasetName).Do() + tokenStr, err := accessToken.ParseBearerToken() if err != nil { - return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err) - } - var filtered []*healthcare.FhirStore - for _, store := range stores.FhirStores { - if len(source.AllowedFHIRStores()) == 0 { - filtered = append(filtered, store) - continue - } - if len(store.Name) == 0 { - continue - } - parts := strings.Split(store.Name, "/") - if _, ok := source.AllowedFHIRStores()[parts[len(parts)-1]]; ok { - filtered = append(filtered, store) - } + return nil, fmt.Errorf("error parsing access token: %w", err) } - return filtered, nil + return source.ListFHIRStores(tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance/cloudhealthcareretrieverendereddicominstance.go b/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance/cloudhealthcareretrieverendereddicominstance.go index 6bdd1b864fb..06de2adc354 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance/cloudhealthcareretrieverendereddicominstance.go +++ b/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance/cloudhealthcareretrieverendereddicominstance.go @@ -16,18 +16,14 @@ package retrieverendereddicominstance import ( "context" - "encoding/base64" "fmt" - "io" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" - "google.golang.org/api/healthcare/v1" ) const kind string = "cloud-healthcare-retrieve-rendered-dicom-instance" @@ -53,13 +49,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedDICOMStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + RetrieveRenderedDICOMInstance(string, string, string, string, int, string) (any, error) } type Config struct { @@ -135,20 +127,10 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } + tokenStr, err := accessToken.ParseBearerToken() + if err != nil { + return nil, fmt.Errorf("error parsing access token: %w", err) } - study, ok := params.AsMap()[studyInstanceUIDKey].(string) if !ok { return nil, fmt.Errorf("invalid '%s' parameter; expected a string", studyInstanceUIDKey) @@ -165,25 +147,7 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if !ok { return nil, fmt.Errorf("invalid '%s' parameter; expected an integer", frameNumberKey) } - name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - dicomWebPath := fmt.Sprintf("studies/%s/series/%s/instances/%s/frames/%d/rendered", study, series, sop, frame) - call := svc.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances.Frames.RetrieveRendered(name, dicomWebPath) - call.Header().Set("Accept", "image/jpeg") - resp, err := call.Do() - if err != nil { - return nil, fmt.Errorf("unable to retrieve dicom instance rendered image: %w", err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("RetrieveRendered: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - base64String := base64.StdEncoding.EncodeToString(respBytes) - return base64String, nil + return source.RetrieveRenderedDICOMInstance(storeID, study, series, sop, frame, tokenStr) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances/cloudhealthcaresearchdicominstances.go b/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances/cloudhealthcaresearchdicominstances.go index 34d8e7251d3..a7ea6cb2756 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances/cloudhealthcaresearchdicominstances.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances/cloudhealthcaresearchdicominstances.go @@ -16,20 +16,16 @@ package searchdicominstances import ( "context" - "encoding/json" "fmt" - "io" "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" "google.golang.org/api/googleapi" - "google.golang.org/api/healthcare/v1" ) const kind string = "cloud-healthcare-search-dicom-instances" @@ -60,13 +56,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedDICOMStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + SearchDICOM(string, string, string, string, []googleapi.CallOption) (any, error) } type Config struct { @@ -144,23 +136,13 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - storeID, err := common.ValidateAndFetchStoreID(params, source.AllowedDICOMStores()) if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } + tokenStr, err := accessToken.ParseBearerToken() + if err != nil { + return nil, fmt.Errorf("error parsing access token: %w", err) } opts, err := common.ParseDICOMSearchParameters(params, []string{sopInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey, modalityKey}) @@ -191,29 +173,7 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para } } } - - name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForInstances(name, dicomWebPath).Do(opts...) - if err != nil { - return nil, fmt.Errorf("failed to search dicom instances: %w", err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - if len(respBytes) == 0 { - return []interface{}{}, nil - } - var result []interface{} - if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil { - return nil, fmt.Errorf("could not unmarshal response as list: %w", err) - } - return result, nil + return source.SearchDICOM(t.Kind, storeID, dicomWebPath, tokenStr, opts) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries/cloudhealthcaresearchdicomseries.go b/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries/cloudhealthcaresearchdicomseries.go index 3af9484387a..7b7175c207b 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries/cloudhealthcaresearchdicomseries.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries/cloudhealthcaresearchdicomseries.go @@ -16,18 +16,15 @@ package searchdicomseries import ( "context" - "encoding/json" "fmt" - "io" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" - "google.golang.org/api/healthcare/v1" + "google.golang.org/api/googleapi" ) const kind string = "cloud-healthcare-search-dicom-series" @@ -57,13 +54,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedDICOMStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + SearchDICOM(string, string, string, string, []googleapi.CallOption) (any, error) } type Config struct { @@ -145,18 +138,9 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } + tokenStr, err := accessToken.ParseBearerToken() + if err != nil { + return nil, fmt.Errorf("error parsing access token: %w", err) } opts, err := common.ParseDICOMSearchParameters(params, []string{seriesInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey, modalityKey}) @@ -174,29 +158,7 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para dicomWebPath = fmt.Sprintf("studies/%s/series", id) } } - - name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForSeries(name, dicomWebPath).Do(opts...) - if err != nil { - return nil, fmt.Errorf("failed to search dicom series: %w", err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - if len(respBytes) == 0 { - return []interface{}{}, nil - } - var result []interface{} - if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil { - return nil, fmt.Errorf("could not unmarshal response as list: %w", err) - } - return result, nil + return source.SearchDICOM(t.Kind, storeID, dicomWebPath, tokenStr, opts) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) { diff --git a/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies/cloudhealthcaresearchdicomstudies.go b/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies/cloudhealthcaresearchdicomstudies.go index c0bd3db3c76..34f8e276d73 100644 --- a/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies/cloudhealthcaresearchdicomstudies.go +++ b/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies/cloudhealthcaresearchdicomstudies.go @@ -16,18 +16,15 @@ package searchdicomstudies import ( "context" - "encoding/json" "fmt" - "io" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/embeddingmodels" "github.com/googleapis/genai-toolbox/internal/sources" - healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common" "github.com/googleapis/genai-toolbox/internal/util/parameters" - "google.golang.org/api/healthcare/v1" + "google.golang.org/api/googleapi" ) const kind string = "cloud-healthcare-search-dicom-studies" @@ -55,13 +52,9 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T } type compatibleSource interface { - Project() string - Region() string - DatasetID() string AllowedDICOMStores() map[string]struct{} - Service() *healthcare.Service - ServiceCreator() healthcareds.HealthcareServiceCreator UseClientAuthorization() bool + SearchDICOM(string, string, string, string, []googleapi.CallOption) (any, error) } type Config struct { @@ -136,51 +129,20 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para if err != nil { return nil, err } - storeID, err := common.ValidateAndFetchStoreID(params, source.AllowedDICOMStores()) if err != nil { return nil, err } - - svc := source.Service() - // Initialize new service if using user OAuth token - if source.UseClientAuthorization() { - tokenStr, err := accessToken.ParseBearerToken() - if err != nil { - return nil, fmt.Errorf("error parsing access token: %w", err) - } - svc, err = source.ServiceCreator()(tokenStr) - if err != nil { - return nil, fmt.Errorf("error creating service from OAuth access token: %w", err) - } + tokenStr, err := accessToken.ParseBearerToken() + if err != nil { + return nil, fmt.Errorf("error parsing access token: %w", err) } - opts, err := common.ParseDICOMSearchParameters(params, []string{studyInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey}) if err != nil { return nil, err } - name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", source.Project(), source.Region(), source.DatasetID(), storeID) - resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForStudies(name, "studies").Do(opts...) - if err != nil { - return nil, fmt.Errorf("failed to search dicom studies: %w", err) - } - defer resp.Body.Close() - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("could not read response: %w", err) - } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes) - } - if len(respBytes) == 0 { - return []interface{}{}, nil - } - var result []interface{} - if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil { - return nil, fmt.Errorf("could not unmarshal response as list: %w", err) - } - return result, nil + dicomWebPath := "studies" + return source.SearchDICOM(t.Kind, storeID, dicomWebPath, tokenStr, opts) } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) {