From b19d0f1f3e2f1b339776e70adbb50fbde33d3120 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 16:09:58 +0000 Subject: [PATCH 1/8] docs: add AI provider integration guide and Supported AI Providers section Add a step-by-step guide for AI infrastructure companies to implement ai.Model and contribute a provider to go-micro. Covers the full lifecycle: skeleton, tool call handling, tests, registration, and PR checklist. Add a "Supported AI Providers" section to the project README that lists current providers (Anthropic, OpenAI) in a table and links to the integration guide with a call-to-action for new providers and sponsors. Streamline the "Adding a New Provider" section in ai/README.md to point to the new guide instead of duplicating a full code listing. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5228c35305..34d904ae67 100644 --- a/README.md +++ b/README.md @@ -408,7 +408,7 @@ m := ai.New("openai", ) ``` -**Want to add your platform?** See the [AI Provider Integration Guide](internal/website/docs/guides/ai-provider-guide.md) for how to implement `ai.Model` and submit a PR. We welcome both code contributions and sponsorships from AI infrastructure companies — reach out via a [GitHub issue](https://github.com/micro/go-micro/issues). +**Want to add your platform?** See the [AI Provider Integration Guide](internal/website/docs/guides/ai-provider-guide.md) for how to implement `ai.Model` and submit a PR. We welcome both code contributions and sponsorships from AI infrastructure companies — reach out via a [GitHub issue](https://github.com/micro/go-micro/issues) or [Discord](https://discord.gg/jwTYuUVAGh). ## Adopters From 7016249a01df725fcf6be5f9fede5b6735dc788a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 12:31:11 +0000 Subject: [PATCH 2/8] fix: remove nonexistent Discord link from README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34d904ae67..5228c35305 100644 --- a/README.md +++ b/README.md @@ -408,7 +408,7 @@ m := ai.New("openai", ) ``` -**Want to add your platform?** See the [AI Provider Integration Guide](internal/website/docs/guides/ai-provider-guide.md) for how to implement `ai.Model` and submit a PR. We welcome both code contributions and sponsorships from AI infrastructure companies — reach out via a [GitHub issue](https://github.com/micro/go-micro/issues) or [Discord](https://discord.gg/jwTYuUVAGh). +**Want to add your platform?** See the [AI Provider Integration Guide](internal/website/docs/guides/ai-provider-guide.md) for how to implement `ai.Model` and submit a PR. We welcome both code contributions and sponsorships from AI infrastructure companies — reach out via a [GitHub issue](https://github.com/micro/go-micro/issues). ## Adopters From 6dc849b0eea04232214e5f674ce4f42d8aa7afb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 15:22:32 +0000 Subject: [PATCH 3/8] fix(website): set content container width to 800px on desktop Move the 800px max-width from .markdown-body up to .content so the entire content pane (not just the inner body) is sized correctly. The container now fills up to 800px beside the sidebar. --- internal/website/_layouts/default.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/website/_layouts/default.html b/internal/website/_layouts/default.html index 80f990b18f..a77edbd1d3 100644 --- a/internal/website/_layouts/default.html +++ b/internal/website/_layouts/default.html @@ -36,8 +36,8 @@ .sidebar li { margin:.35rem 0; } .sidebar a { color:#222; display:block; padding:.25rem .4rem; border-radius:4px; } .sidebar a:hover { background:#f2f5f8; } - .content { flex:1; min-width:0; padding:2rem 2.5rem; } - .content .markdown-body { max-width: 800px; } + .content { flex:1; min-width:0; max-width: 800px; padding:2rem 2.5rem; } + .content .markdown-body { max-width: 100%; } pre, code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; } pre { background:#f6f8fa; border:1px solid #d0d7de; padding:.9rem 1rem; border-radius:6px; overflow-x:auto; max-width:100%; } code { word-wrap: break-word; } From 9f4690592c7753193e602976804e266a9cfe9ba4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:29:35 +0000 Subject: [PATCH 4/8] feat(ai): wire Atlas Cloud into server and auto-detection Import atlascloud provider in the micro server so it is available when running micro run / micro server. Add atlascloud to AutoDetectProvider so --ai_base_url with an atlascloud domain selects the right provider automatically. --- ai/model.go | 9 ++++++--- cmd/micro/server/server.go | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ai/model.go b/ai/model.go index 9de0201b28..34fb2438a9 100644 --- a/ai/model.go +++ b/ai/model.go @@ -111,11 +111,14 @@ func AutoDetectProvider(baseURL string) string { if baseURL == "" { return "openai" } - // Simple detection based on URL - if strings.Contains(baseURL, "anthropic") { + switch { + case strings.Contains(baseURL, "anthropic"): return "anthropic" + case strings.Contains(baseURL, "atlascloud"): + return "atlascloud" + default: + return "openai" } - return "openai" } // DefaultModel is a default model instance diff --git a/cmd/micro/server/server.go b/cmd/micro/server/server.go index 4e336d572f..066718f472 100644 --- a/cmd/micro/server/server.go +++ b/cmd/micro/server/server.go @@ -30,6 +30,7 @@ import ( codecBytes "go-micro.dev/v5/codec/bytes" "go-micro.dev/v5/ai" _ "go-micro.dev/v5/ai/anthropic" + _ "go-micro.dev/v5/ai/atlascloud" _ "go-micro.dev/v5/ai/openai" "go-micro.dev/v5/registry" "go-micro.dev/v5/store" From 4e3f8fe2288473d84e16caa1a014480522f0c02d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:36:38 +0000 Subject: [PATCH 5/8] feat(ai): add Google Gemini provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ai/gemini implementing ai.Model for Google's Gemini API. Uses the native generateContent endpoint with system_instruction, contents/parts, and functionDeclarations — not an OpenAI shim. Default model gemini-2.5-flash, auth via x-goog-api-key header. Wire into micro server imports and AutoDetectProvider (matches googleapis.com and google in base URL). Update README.md and ai/README.md with provider listing. --- README.md | 1 + ai/README.md | 14 +++ ai/gemini/gemini.go | 226 +++++++++++++++++++++++++++++++++++++ ai/gemini/gemini_test.go | 104 +++++++++++++++++ ai/model.go | 2 + cmd/micro/server/server.go | 1 + 6 files changed, 348 insertions(+) create mode 100644 ai/gemini/gemini.go create mode 100644 ai/gemini/gemini_test.go diff --git a/README.md b/README.md index 5228c35305..efb5e2febd 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,7 @@ Go Micro’s `ai` package gives every provider the same interface: `Init`, `Gene | Provider | Import | Default Model | |----------|--------|---------------| | **Anthropic** | `go-micro.dev/v5/ai/anthropic` | `claude-sonnet-4-20250514` | +| **Google Gemini** | `go-micro.dev/v5/ai/gemini` | `gemini-2.5-flash` | | **OpenAI** | `go-micro.dev/v5/ai/openai` | `gpt-4o` | | **Atlas Cloud** | `go-micro.dev/v5/ai/atlascloud` | `llama-3.3-70b` | diff --git a/ai/README.md b/ai/README.md index 17808c2607..1c890e2073 100644 --- a/ai/README.md +++ b/ai/README.md @@ -152,6 +152,20 @@ m := ai.New("openai", Default model: `gpt-4o` Default base URL: `https://api.openai.com` +### Google Gemini + +```go +m := ai.New("gemini", + ai.WithAPIKey("your-key"), + ai.WithModel("gemini-2.5-flash"), // default +) +``` + +Default model: `gemini-2.5-flash` +Default base URL: `https://generativelanguage.googleapis.com` + +Google Gemini uses its own API format with `system_instruction`, `contents` (not `messages`), and `functionDeclarations` for tool calling. The provider handles the translation automatically. + ### Atlas Cloud ```go diff --git a/ai/gemini/gemini.go b/ai/gemini/gemini.go new file mode 100644 index 0000000000..f73e0b5d2c --- /dev/null +++ b/ai/gemini/gemini.go @@ -0,0 +1,226 @@ +// Package gemini implements the Google Gemini model provider. +// +// Usage: +// +// import _ "go-micro.dev/v5/ai/gemini" +// +// m := ai.New("gemini", +// ai.WithAPIKey("your-api-key"), +// ) +package gemini + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "go-micro.dev/v5/ai" +) + +func init() { + ai.Register("gemini", func(opts ...ai.Option) ai.Model { + return NewProvider(opts...) + }) +} + +// Provider implements the ai.Model interface for Google Gemini. +type Provider struct { + opts ai.Options +} + +// NewProvider creates a new Gemini provider. +func NewProvider(opts ...ai.Option) *Provider { + options := ai.NewOptions(opts...) + + if options.Model == "" { + options.Model = "gemini-2.5-flash" + } + if options.BaseURL == "" { + options.BaseURL = "https://generativelanguage.googleapis.com" + } + + return &Provider{opts: options} +} + +func (p *Provider) Init(opts ...ai.Option) error { + for _, o := range opts { + o(&p.opts) + } + return nil +} + +func (p *Provider) Options() ai.Options { return p.opts } +func (p *Provider) String() string { return "gemini" } + +func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (*ai.Response, error) { + var tools []map[string]any + for _, t := range req.Tools { + tools = append(tools, map[string]any{ + "name": t.Name, + "description": t.Description, + "parameters": map[string]any{ + "type": "object", + "properties": t.Properties, + }, + }) + } + + contents := []map[string]any{ + {"role": "user", "parts": []map[string]any{{"text": req.Prompt}}}, + } + + apiReq := map[string]any{ + "contents": contents, + } + + if req.SystemPrompt != "" { + apiReq["system_instruction"] = map[string]any{ + "parts": []map[string]any{{"text": req.SystemPrompt}}, + } + } + + if len(tools) > 0 { + apiReq["tools"] = []map[string]any{ + {"functionDeclarations": tools}, + } + } + + resp, rawParts, err := p.callAPI(ctx, apiReq) + if err != nil { + return nil, err + } + + if len(resp.ToolCalls) == 0 { + return resp, nil + } + + if p.opts.ToolHandler != nil { + var resultParts []map[string]any + for _, tc := range resp.ToolCalls { + result, _ := p.opts.ToolHandler(tc.Name, tc.Input) + resultParts = append(resultParts, map[string]any{ + "functionResponse": map[string]any{ + "name": tc.Name, + "id": tc.ID, + "response": result, + }, + }) + } + + followUpContents := append(contents, + map[string]any{"role": "model", "parts": rawParts}, + map[string]any{"role": "user", "parts": resultParts}, + ) + + followUpReq := map[string]any{ + "contents": followUpContents, + } + if req.SystemPrompt != "" { + followUpReq["system_instruction"] = map[string]any{ + "parts": []map[string]any{{"text": req.SystemPrompt}}, + } + } + + followUpResp, _, err := p.callAPI(ctx, followUpReq) + if err == nil && followUpResp.Reply != "" { + resp.Answer = followUpResp.Reply + } + } + + return resp, nil +} + +func (p *Provider) Stream(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (ai.Stream, error) { + return nil, fmt.Errorf("streaming not yet implemented for gemini provider") +} + +func (p *Provider) callAPI(ctx context.Context, req map[string]any) (*ai.Response, []map[string]any, error) { + reqBody, err := json.Marshal(req) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal request: %w", err) + } + + apiURL := strings.TrimRight(p.opts.BaseURL, "/") + + "/v1beta/models/" + p.opts.Model + ":generateContent" + + httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(reqBody)) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("x-goog-api-key", p.opts.APIKey) + + httpResp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, nil, fmt.Errorf("API request failed: %w", err) + } + defer httpResp.Body.Close() + + respBody, _ := io.ReadAll(httpResp.Body) + if httpResp.StatusCode != 200 { + return nil, nil, fmt.Errorf("API error (%s): %s", httpResp.Status, string(respBody)) + } + + var geminiResp struct { + Candidates []struct { + Content struct { + Parts []struct { + Text string `json:"text"` + FunctionCall *functionCallPB `json:"functionCall"` + } `json:"parts"` + } `json:"content"` + } `json:"candidates"` + } + + if err := json.Unmarshal(respBody, &geminiResp); err != nil { + return nil, nil, fmt.Errorf("failed to parse response: %w", err) + } + + if len(geminiResp.Candidates) == 0 { + return nil, nil, fmt.Errorf("no response from API") + } + + parts := geminiResp.Candidates[0].Content.Parts + response := &ai.Response{} + + var replyParts []string + var rawParts []map[string]any + + for _, part := range parts { + if part.Text != "" { + replyParts = append(replyParts, part.Text) + rawParts = append(rawParts, map[string]any{"text": part.Text}) + } + if part.FunctionCall != nil { + response.ToolCalls = append(response.ToolCalls, ai.ToolCall{ + ID: part.FunctionCall.ID, + Name: part.FunctionCall.Name, + Input: part.FunctionCall.Args, + }) + rawParts = append(rawParts, map[string]any{ + "functionCall": map[string]any{ + "id": part.FunctionCall.ID, + "name": part.FunctionCall.Name, + "args": part.FunctionCall.Args, + }, + }) + } + } + + if len(replyParts) > 0 { + response.Reply = strings.Join(replyParts, "\n") + } + + return response, rawParts, nil +} + +type functionCallPB struct { + ID string `json:"id"` + Name string `json:"name"` + Args map[string]any `json:"args"` +} diff --git a/ai/gemini/gemini_test.go b/ai/gemini/gemini_test.go new file mode 100644 index 0000000000..848d35223f --- /dev/null +++ b/ai/gemini/gemini_test.go @@ -0,0 +1,104 @@ +package gemini + +import ( + "context" + "testing" + + "go-micro.dev/v5/ai" +) + +func TestProvider_String(t *testing.T) { + p := NewProvider() + if p.String() != "gemini" { + t.Errorf("Expected provider name 'gemini', got '%s'", p.String()) + } +} + +func TestProvider_Init(t *testing.T) { + p := NewProvider() + + err := p.Init( + ai.WithModel("gemini-2.0-flash"), + ai.WithAPIKey("test-key"), + ai.WithBaseURL("https://test.com"), + ) + + if err != nil { + t.Fatalf("Init failed: %v", err) + } + + opts := p.Options() + if opts.Model != "gemini-2.0-flash" { + t.Errorf("Expected model 'gemini-2.0-flash', got '%s'", opts.Model) + } + if opts.APIKey != "test-key" { + t.Errorf("Expected API key 'test-key', got '%s'", opts.APIKey) + } + if opts.BaseURL != "https://test.com" { + t.Errorf("Expected base URL 'https://test.com', got '%s'", opts.BaseURL) + } +} + +func TestProvider_Options(t *testing.T) { + p := NewProvider( + ai.WithModel("custom-model"), + ai.WithAPIKey("my-key"), + ) + + opts := p.Options() + if opts.Model != "custom-model" { + t.Errorf("Expected model 'custom-model', got '%s'", opts.Model) + } + if opts.APIKey != "my-key" { + t.Errorf("Expected API key 'my-key', got '%s'", opts.APIKey) + } +} + +func TestProvider_Defaults(t *testing.T) { + p := NewProvider() + + opts := p.Options() + if opts.Model != "gemini-2.5-flash" { + t.Errorf("Expected default model 'gemini-2.5-flash', got '%s'", opts.Model) + } + if opts.BaseURL != "https://generativelanguage.googleapis.com" { + t.Errorf("Expected default base URL 'https://generativelanguage.googleapis.com', got '%s'", opts.BaseURL) + } +} + +func TestProvider_Generate_NoAPIKey(t *testing.T) { + p := NewProvider() + + req := &ai.Request{ + Prompt: "Hello", + SystemPrompt: "You are helpful", + } + + _, err := p.Generate(context.Background(), req) + if err == nil { + t.Error("Expected error when API key is missing, got nil") + } +} + +func TestProvider_Stream_NotImplemented(t *testing.T) { + p := NewProvider() + + req := &ai.Request{ + Prompt: "Hello", + } + + _, err := p.Stream(context.Background(), req) + if err == nil { + t.Error("Expected error for unimplemented streaming, got nil") + } +} + +func TestProvider_Registration(t *testing.T) { + m := ai.New("gemini", ai.WithAPIKey("test")) + if m == nil { + t.Fatal("ai.New('gemini') returned nil — provider not registered") + } + if m.String() != "gemini" { + t.Errorf("Expected 'gemini', got '%s'", m.String()) + } +} diff --git a/ai/model.go b/ai/model.go index 34fb2438a9..ce7fad5ce2 100644 --- a/ai/model.go +++ b/ai/model.go @@ -116,6 +116,8 @@ func AutoDetectProvider(baseURL string) string { return "anthropic" case strings.Contains(baseURL, "atlascloud"): return "atlascloud" + case strings.Contains(baseURL, "googleapis.com"), strings.Contains(baseURL, "google"): + return "gemini" default: return "openai" } diff --git a/cmd/micro/server/server.go b/cmd/micro/server/server.go index 066718f472..b5594a9db6 100644 --- a/cmd/micro/server/server.go +++ b/cmd/micro/server/server.go @@ -31,6 +31,7 @@ import ( "go-micro.dev/v5/ai" _ "go-micro.dev/v5/ai/anthropic" _ "go-micro.dev/v5/ai/atlascloud" + _ "go-micro.dev/v5/ai/gemini" _ "go-micro.dev/v5/ai/openai" "go-micro.dev/v5/registry" "go-micro.dev/v5/store" From 651ba92286d7eabd12fa523ff0b22d6deda58963 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:43:28 +0000 Subject: [PATCH 6/8] feat(ai): add Groq, Mistral, and Together AI providers Add three new OpenAI-compatible providers: - ai/groq: ultra-fast inference, default model llama-3.3-70b-versatile - ai/mistral: Mistral AI, default model mistral-large-latest - ai/together: Together AI, default model Llama-3.3-70B-Instruct-Turbo All three are wired into the micro server imports and AutoDetectProvider. README and ai/README updated with the full provider table. --- README.md | 3 + ai/README.md | 42 ++++++++ ai/groq/groq.go | 194 +++++++++++++++++++++++++++++++++++ ai/groq/groq_test.go | 56 ++++++++++ ai/mistral/mistral.go | 194 +++++++++++++++++++++++++++++++++++ ai/mistral/mistral_test.go | 56 ++++++++++ ai/model.go | 6 ++ ai/together/together.go | 194 +++++++++++++++++++++++++++++++++++ ai/together/together_test.go | 56 ++++++++++ cmd/micro/server/server.go | 3 + 10 files changed, 804 insertions(+) create mode 100644 ai/groq/groq.go create mode 100644 ai/groq/groq_test.go create mode 100644 ai/mistral/mistral.go create mode 100644 ai/mistral/mistral_test.go create mode 100644 ai/together/together.go create mode 100644 ai/together/together_test.go diff --git a/README.md b/README.md index efb5e2febd..297957fe14 100644 --- a/README.md +++ b/README.md @@ -397,7 +397,10 @@ Go Micro’s `ai` package gives every provider the same interface: `Init`, `Gene |----------|--------|---------------| | **Anthropic** | `go-micro.dev/v5/ai/anthropic` | `claude-sonnet-4-20250514` | | **Google Gemini** | `go-micro.dev/v5/ai/gemini` | `gemini-2.5-flash` | +| **Groq** | `go-micro.dev/v5/ai/groq` | `llama-3.3-70b-versatile` | +| **Mistral** | `go-micro.dev/v5/ai/mistral` | `mistral-large-latest` | | **OpenAI** | `go-micro.dev/v5/ai/openai` | `gpt-4o` | +| **Together AI** | `go-micro.dev/v5/ai/together` | `Llama-3.3-70B-Instruct-Turbo` | | **Atlas Cloud** | `go-micro.dev/v5/ai/atlascloud` | `llama-3.3-70b` | Any provider that exposes an OpenAI-compatible API can also be used directly: diff --git a/ai/README.md b/ai/README.md index 1c890e2073..3137e75ca6 100644 --- a/ai/README.md +++ b/ai/README.md @@ -166,6 +166,48 @@ Default base URL: `https://generativelanguage.googleapis.com` Google Gemini uses its own API format with `system_instruction`, `contents` (not `messages`), and `functionDeclarations` for tool calling. The provider handles the translation automatically. +### Groq + +```go +m := ai.New("groq", + ai.WithAPIKey("your-key"), + ai.WithModel("llama-3.3-70b-versatile"), // default +) +``` + +Default model: `llama-3.3-70b-versatile` +Default base URL: `https://api.groq.com/openai` + +Groq provides ultra-fast inference for open-weight models via an OpenAI-compatible endpoint. + +### Mistral + +```go +m := ai.New("mistral", + ai.WithAPIKey("your-key"), + ai.WithModel("mistral-large-latest"), // default +) +``` + +Default model: `mistral-large-latest` +Default base URL: `https://api.mistral.ai` + +Mistral AI is a European AI company offering high-performance models via an OpenAI-compatible endpoint. + +### Together AI + +```go +m := ai.New("together", + ai.WithAPIKey("your-key"), + ai.WithModel("meta-llama/Llama-3.3-70B-Instruct-Turbo"), // default +) +``` + +Default model: `meta-llama/Llama-3.3-70B-Instruct-Turbo` +Default base URL: `https://api.together.xyz` + +Together AI provides fast inference for open-weight models via an OpenAI-compatible endpoint. + ### Atlas Cloud ```go diff --git a/ai/groq/groq.go b/ai/groq/groq.go new file mode 100644 index 0000000000..02009b1e04 --- /dev/null +++ b/ai/groq/groq.go @@ -0,0 +1,194 @@ +// Package groq implements the Groq model provider. +// +// Groq provides ultra-fast inference for open-weight models via an +// OpenAI-compatible chat completions endpoint. +// +// Usage: +// +// import _ "go-micro.dev/v5/ai/groq" +// +// m := ai.New("groq", +// ai.WithAPIKey("your-api-key"), +// ) +package groq + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "go-micro.dev/v5/ai" +) + +func init() { + ai.Register("groq", func(opts ...ai.Option) ai.Model { + return NewProvider(opts...) + }) +} + +type Provider struct { + opts ai.Options +} + +func NewProvider(opts ...ai.Option) *Provider { + options := ai.NewOptions(opts...) + if options.Model == "" { + options.Model = "llama-3.3-70b-versatile" + } + if options.BaseURL == "" { + options.BaseURL = "https://api.groq.com/openai" + } + return &Provider{opts: options} +} + +func (p *Provider) Init(opts ...ai.Option) error { + for _, o := range opts { + o(&p.opts) + } + return nil +} + +func (p *Provider) Options() ai.Options { return p.opts } +func (p *Provider) String() string { return "groq" } + +func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (*ai.Response, error) { + var tools []map[string]any + for _, t := range req.Tools { + tools = append(tools, map[string]any{ + "type": "function", + "function": map[string]any{ + "name": t.Name, + "description": t.Description, + "parameters": map[string]any{ + "type": "object", + "properties": t.Properties, + }, + }, + }) + } + + messages := []map[string]any{ + {"role": "system", "content": req.SystemPrompt}, + {"role": "user", "content": req.Prompt}, + } + + apiReq := map[string]any{ + "model": p.opts.Model, + "messages": messages, + } + if len(tools) > 0 { + apiReq["tools"] = tools + } + + resp, rawMessage, err := p.callAPI(ctx, apiReq) + if err != nil { + return nil, err + } + if len(resp.ToolCalls) == 0 { + return resp, nil + } + + if p.opts.ToolHandler != nil { + followUpMessages := append(messages, map[string]any{ + "role": "assistant", + "content": rawMessage["content"], + "tool_calls": rawMessage["tool_calls"], + }) + for _, tc := range resp.ToolCalls { + _, content := p.opts.ToolHandler(tc.Name, tc.Input) + followUpMessages = append(followUpMessages, map[string]any{ + "role": "tool", + "tool_call_id": tc.ID, + "content": content, + }) + } + followUpResp, _, err := p.callAPI(ctx, map[string]any{ + "model": p.opts.Model, + "messages": followUpMessages, + }) + if err == nil && followUpResp.Reply != "" { + resp.Answer = followUpResp.Reply + } + } + + return resp, nil +} + +func (p *Provider) Stream(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (ai.Stream, error) { + return nil, fmt.Errorf("streaming not yet implemented for groq provider") +} + +func (p *Provider) callAPI(ctx context.Context, req map[string]any) (*ai.Response, map[string]any, error) { + reqBody, err := json.Marshal(req) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal request: %w", err) + } + + apiURL := strings.TrimRight(p.opts.BaseURL, "/") + "/v1/chat/completions" + httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(reqBody)) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Authorization", "Bearer "+p.opts.APIKey) + + httpResp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, nil, fmt.Errorf("API request failed: %w", err) + } + defer httpResp.Body.Close() + + respBody, _ := io.ReadAll(httpResp.Body) + if httpResp.StatusCode != 200 { + return nil, nil, fmt.Errorf("API error (%s): %s", httpResp.Status, string(respBody)) + } + + var chatResp struct { + Choices []struct { + Message struct { + Content string `json:"content"` + ToolCalls []struct { + ID string `json:"id"` + Function struct { + Name string `json:"name"` + Arguments string `json:"arguments"` + } `json:"function"` + } `json:"tool_calls"` + } `json:"message"` + } `json:"choices"` + } + + if err := json.Unmarshal(respBody, &chatResp); err != nil { + return nil, nil, fmt.Errorf("failed to parse response: %w", err) + } + if len(chatResp.Choices) == 0 { + return nil, nil, fmt.Errorf("no response from API") + } + + choice := chatResp.Choices[0] + response := &ai.Response{Reply: choice.Message.Content} + + for _, tc := range choice.Message.ToolCalls { + var input map[string]any + if err := json.Unmarshal([]byte(tc.Function.Arguments), &input); err != nil { + input = map[string]any{} + } + response.ToolCalls = append(response.ToolCalls, ai.ToolCall{ + ID: tc.ID, + Name: tc.Function.Name, + Input: input, + }) + } + + rawMessage := map[string]any{ + "content": choice.Message.Content, + "tool_calls": choice.Message.ToolCalls, + } + + return response, rawMessage, nil +} diff --git a/ai/groq/groq_test.go b/ai/groq/groq_test.go new file mode 100644 index 0000000000..0885ebf97c --- /dev/null +++ b/ai/groq/groq_test.go @@ -0,0 +1,56 @@ +package groq + +import ( + "context" + "testing" + + "go-micro.dev/v5/ai" +) + +func TestProvider_String(t *testing.T) { + if NewProvider().String() != "groq" { + t.Errorf("got %q", NewProvider().String()) + } +} + +func TestProvider_Defaults(t *testing.T) { + opts := NewProvider().Options() + if opts.Model != "llama-3.3-70b-versatile" { + t.Errorf("default model = %q", opts.Model) + } + if opts.BaseURL != "https://api.groq.com/openai" { + t.Errorf("default base URL = %q", opts.BaseURL) + } +} + +func TestProvider_Init(t *testing.T) { + p := NewProvider() + if err := p.Init(ai.WithModel("m"), ai.WithAPIKey("k")); err != nil { + t.Fatal(err) + } + if p.Options().Model != "m" || p.Options().APIKey != "k" { + t.Error("Init did not apply options") + } +} + +func TestProvider_Generate_NoAPIKey(t *testing.T) { + if _, err := NewProvider().Generate(context.Background(), &ai.Request{Prompt: "hi"}); err == nil { + t.Error("expected error without API key") + } +} + +func TestProvider_Stream_NotImplemented(t *testing.T) { + if _, err := NewProvider().Stream(context.Background(), &ai.Request{Prompt: "hi"}); err == nil { + t.Error("expected error") + } +} + +func TestProvider_Registration(t *testing.T) { + m := ai.New("groq", ai.WithAPIKey("test")) + if m == nil { + t.Fatal("provider not registered") + } + if m.String() != "groq" { + t.Errorf("got %q", m.String()) + } +} diff --git a/ai/mistral/mistral.go b/ai/mistral/mistral.go new file mode 100644 index 0000000000..c85d95e725 --- /dev/null +++ b/ai/mistral/mistral.go @@ -0,0 +1,194 @@ +// Package mistral implements the Mistral AI model provider. +// +// Mistral AI is a European AI company offering high-performance models +// via an OpenAI-compatible chat completions endpoint. +// +// Usage: +// +// import _ "go-micro.dev/v5/ai/mistral" +// +// m := ai.New("mistral", +// ai.WithAPIKey("your-api-key"), +// ) +package mistral + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "go-micro.dev/v5/ai" +) + +func init() { + ai.Register("mistral", func(opts ...ai.Option) ai.Model { + return NewProvider(opts...) + }) +} + +type Provider struct { + opts ai.Options +} + +func NewProvider(opts ...ai.Option) *Provider { + options := ai.NewOptions(opts...) + if options.Model == "" { + options.Model = "mistral-large-latest" + } + if options.BaseURL == "" { + options.BaseURL = "https://api.mistral.ai" + } + return &Provider{opts: options} +} + +func (p *Provider) Init(opts ...ai.Option) error { + for _, o := range opts { + o(&p.opts) + } + return nil +} + +func (p *Provider) Options() ai.Options { return p.opts } +func (p *Provider) String() string { return "mistral" } + +func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (*ai.Response, error) { + var tools []map[string]any + for _, t := range req.Tools { + tools = append(tools, map[string]any{ + "type": "function", + "function": map[string]any{ + "name": t.Name, + "description": t.Description, + "parameters": map[string]any{ + "type": "object", + "properties": t.Properties, + }, + }, + }) + } + + messages := []map[string]any{ + {"role": "system", "content": req.SystemPrompt}, + {"role": "user", "content": req.Prompt}, + } + + apiReq := map[string]any{ + "model": p.opts.Model, + "messages": messages, + } + if len(tools) > 0 { + apiReq["tools"] = tools + } + + resp, rawMessage, err := p.callAPI(ctx, apiReq) + if err != nil { + return nil, err + } + if len(resp.ToolCalls) == 0 { + return resp, nil + } + + if p.opts.ToolHandler != nil { + followUpMessages := append(messages, map[string]any{ + "role": "assistant", + "content": rawMessage["content"], + "tool_calls": rawMessage["tool_calls"], + }) + for _, tc := range resp.ToolCalls { + _, content := p.opts.ToolHandler(tc.Name, tc.Input) + followUpMessages = append(followUpMessages, map[string]any{ + "role": "tool", + "tool_call_id": tc.ID, + "content": content, + }) + } + followUpResp, _, err := p.callAPI(ctx, map[string]any{ + "model": p.opts.Model, + "messages": followUpMessages, + }) + if err == nil && followUpResp.Reply != "" { + resp.Answer = followUpResp.Reply + } + } + + return resp, nil +} + +func (p *Provider) Stream(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (ai.Stream, error) { + return nil, fmt.Errorf("streaming not yet implemented for mistral provider") +} + +func (p *Provider) callAPI(ctx context.Context, req map[string]any) (*ai.Response, map[string]any, error) { + reqBody, err := json.Marshal(req) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal request: %w", err) + } + + apiURL := strings.TrimRight(p.opts.BaseURL, "/") + "/v1/chat/completions" + httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(reqBody)) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Authorization", "Bearer "+p.opts.APIKey) + + httpResp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, nil, fmt.Errorf("API request failed: %w", err) + } + defer httpResp.Body.Close() + + respBody, _ := io.ReadAll(httpResp.Body) + if httpResp.StatusCode != 200 { + return nil, nil, fmt.Errorf("API error (%s): %s", httpResp.Status, string(respBody)) + } + + var chatResp struct { + Choices []struct { + Message struct { + Content string `json:"content"` + ToolCalls []struct { + ID string `json:"id"` + Function struct { + Name string `json:"name"` + Arguments string `json:"arguments"` + } `json:"function"` + } `json:"tool_calls"` + } `json:"message"` + } `json:"choices"` + } + + if err := json.Unmarshal(respBody, &chatResp); err != nil { + return nil, nil, fmt.Errorf("failed to parse response: %w", err) + } + if len(chatResp.Choices) == 0 { + return nil, nil, fmt.Errorf("no response from API") + } + + choice := chatResp.Choices[0] + response := &ai.Response{Reply: choice.Message.Content} + + for _, tc := range choice.Message.ToolCalls { + var input map[string]any + if err := json.Unmarshal([]byte(tc.Function.Arguments), &input); err != nil { + input = map[string]any{} + } + response.ToolCalls = append(response.ToolCalls, ai.ToolCall{ + ID: tc.ID, + Name: tc.Function.Name, + Input: input, + }) + } + + rawMessage := map[string]any{ + "content": choice.Message.Content, + "tool_calls": choice.Message.ToolCalls, + } + + return response, rawMessage, nil +} diff --git a/ai/mistral/mistral_test.go b/ai/mistral/mistral_test.go new file mode 100644 index 0000000000..338f03ba50 --- /dev/null +++ b/ai/mistral/mistral_test.go @@ -0,0 +1,56 @@ +package mistral + +import ( + "context" + "testing" + + "go-micro.dev/v5/ai" +) + +func TestProvider_String(t *testing.T) { + if NewProvider().String() != "mistral" { + t.Errorf("got %q", NewProvider().String()) + } +} + +func TestProvider_Defaults(t *testing.T) { + opts := NewProvider().Options() + if opts.Model != "mistral-large-latest" { + t.Errorf("default model = %q", opts.Model) + } + if opts.BaseURL != "https://api.mistral.ai" { + t.Errorf("default base URL = %q", opts.BaseURL) + } +} + +func TestProvider_Init(t *testing.T) { + p := NewProvider() + if err := p.Init(ai.WithModel("m"), ai.WithAPIKey("k")); err != nil { + t.Fatal(err) + } + if p.Options().Model != "m" || p.Options().APIKey != "k" { + t.Error("Init did not apply options") + } +} + +func TestProvider_Generate_NoAPIKey(t *testing.T) { + if _, err := NewProvider().Generate(context.Background(), &ai.Request{Prompt: "hi"}); err == nil { + t.Error("expected error without API key") + } +} + +func TestProvider_Stream_NotImplemented(t *testing.T) { + if _, err := NewProvider().Stream(context.Background(), &ai.Request{Prompt: "hi"}); err == nil { + t.Error("expected error") + } +} + +func TestProvider_Registration(t *testing.T) { + m := ai.New("mistral", ai.WithAPIKey("test")) + if m == nil { + t.Fatal("provider not registered") + } + if m.String() != "mistral" { + t.Errorf("got %q", m.String()) + } +} diff --git a/ai/model.go b/ai/model.go index ce7fad5ce2..08c56a046d 100644 --- a/ai/model.go +++ b/ai/model.go @@ -118,6 +118,12 @@ func AutoDetectProvider(baseURL string) string { return "atlascloud" case strings.Contains(baseURL, "googleapis.com"), strings.Contains(baseURL, "google"): return "gemini" + case strings.Contains(baseURL, "groq"): + return "groq" + case strings.Contains(baseURL, "mistral"): + return "mistral" + case strings.Contains(baseURL, "together"): + return "together" default: return "openai" } diff --git a/ai/together/together.go b/ai/together/together.go new file mode 100644 index 0000000000..11e5b677e4 --- /dev/null +++ b/ai/together/together.go @@ -0,0 +1,194 @@ +// Package together implements the Together AI model provider. +// +// Together AI provides fast inference for open-weight models via an +// OpenAI-compatible chat completions endpoint. +// +// Usage: +// +// import _ "go-micro.dev/v5/ai/together" +// +// m := ai.New("together", +// ai.WithAPIKey("your-api-key"), +// ) +package together + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "go-micro.dev/v5/ai" +) + +func init() { + ai.Register("together", func(opts ...ai.Option) ai.Model { + return NewProvider(opts...) + }) +} + +type Provider struct { + opts ai.Options +} + +func NewProvider(opts ...ai.Option) *Provider { + options := ai.NewOptions(opts...) + if options.Model == "" { + options.Model = "meta-llama/Llama-3.3-70B-Instruct-Turbo" + } + if options.BaseURL == "" { + options.BaseURL = "https://api.together.xyz" + } + return &Provider{opts: options} +} + +func (p *Provider) Init(opts ...ai.Option) error { + for _, o := range opts { + o(&p.opts) + } + return nil +} + +func (p *Provider) Options() ai.Options { return p.opts } +func (p *Provider) String() string { return "together" } + +func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (*ai.Response, error) { + var tools []map[string]any + for _, t := range req.Tools { + tools = append(tools, map[string]any{ + "type": "function", + "function": map[string]any{ + "name": t.Name, + "description": t.Description, + "parameters": map[string]any{ + "type": "object", + "properties": t.Properties, + }, + }, + }) + } + + messages := []map[string]any{ + {"role": "system", "content": req.SystemPrompt}, + {"role": "user", "content": req.Prompt}, + } + + apiReq := map[string]any{ + "model": p.opts.Model, + "messages": messages, + } + if len(tools) > 0 { + apiReq["tools"] = tools + } + + resp, rawMessage, err := p.callAPI(ctx, apiReq) + if err != nil { + return nil, err + } + if len(resp.ToolCalls) == 0 { + return resp, nil + } + + if p.opts.ToolHandler != nil { + followUpMessages := append(messages, map[string]any{ + "role": "assistant", + "content": rawMessage["content"], + "tool_calls": rawMessage["tool_calls"], + }) + for _, tc := range resp.ToolCalls { + _, content := p.opts.ToolHandler(tc.Name, tc.Input) + followUpMessages = append(followUpMessages, map[string]any{ + "role": "tool", + "tool_call_id": tc.ID, + "content": content, + }) + } + followUpResp, _, err := p.callAPI(ctx, map[string]any{ + "model": p.opts.Model, + "messages": followUpMessages, + }) + if err == nil && followUpResp.Reply != "" { + resp.Answer = followUpResp.Reply + } + } + + return resp, nil +} + +func (p *Provider) Stream(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (ai.Stream, error) { + return nil, fmt.Errorf("streaming not yet implemented for together provider") +} + +func (p *Provider) callAPI(ctx context.Context, req map[string]any) (*ai.Response, map[string]any, error) { + reqBody, err := json.Marshal(req) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal request: %w", err) + } + + apiURL := strings.TrimRight(p.opts.BaseURL, "/") + "/v1/chat/completions" + httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(reqBody)) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Authorization", "Bearer "+p.opts.APIKey) + + httpResp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, nil, fmt.Errorf("API request failed: %w", err) + } + defer httpResp.Body.Close() + + respBody, _ := io.ReadAll(httpResp.Body) + if httpResp.StatusCode != 200 { + return nil, nil, fmt.Errorf("API error (%s): %s", httpResp.Status, string(respBody)) + } + + var chatResp struct { + Choices []struct { + Message struct { + Content string `json:"content"` + ToolCalls []struct { + ID string `json:"id"` + Function struct { + Name string `json:"name"` + Arguments string `json:"arguments"` + } `json:"function"` + } `json:"tool_calls"` + } `json:"message"` + } `json:"choices"` + } + + if err := json.Unmarshal(respBody, &chatResp); err != nil { + return nil, nil, fmt.Errorf("failed to parse response: %w", err) + } + if len(chatResp.Choices) == 0 { + return nil, nil, fmt.Errorf("no response from API") + } + + choice := chatResp.Choices[0] + response := &ai.Response{Reply: choice.Message.Content} + + for _, tc := range choice.Message.ToolCalls { + var input map[string]any + if err := json.Unmarshal([]byte(tc.Function.Arguments), &input); err != nil { + input = map[string]any{} + } + response.ToolCalls = append(response.ToolCalls, ai.ToolCall{ + ID: tc.ID, + Name: tc.Function.Name, + Input: input, + }) + } + + rawMessage := map[string]any{ + "content": choice.Message.Content, + "tool_calls": choice.Message.ToolCalls, + } + + return response, rawMessage, nil +} diff --git a/ai/together/together_test.go b/ai/together/together_test.go new file mode 100644 index 0000000000..c406baae83 --- /dev/null +++ b/ai/together/together_test.go @@ -0,0 +1,56 @@ +package together + +import ( + "context" + "testing" + + "go-micro.dev/v5/ai" +) + +func TestProvider_String(t *testing.T) { + if NewProvider().String() != "together" { + t.Errorf("got %q", NewProvider().String()) + } +} + +func TestProvider_Defaults(t *testing.T) { + opts := NewProvider().Options() + if opts.Model != "meta-llama/Llama-3.3-70B-Instruct-Turbo" { + t.Errorf("default model = %q", opts.Model) + } + if opts.BaseURL != "https://api.together.xyz" { + t.Errorf("default base URL = %q", opts.BaseURL) + } +} + +func TestProvider_Init(t *testing.T) { + p := NewProvider() + if err := p.Init(ai.WithModel("m"), ai.WithAPIKey("k")); err != nil { + t.Fatal(err) + } + if p.Options().Model != "m" || p.Options().APIKey != "k" { + t.Error("Init did not apply options") + } +} + +func TestProvider_Generate_NoAPIKey(t *testing.T) { + if _, err := NewProvider().Generate(context.Background(), &ai.Request{Prompt: "hi"}); err == nil { + t.Error("expected error without API key") + } +} + +func TestProvider_Stream_NotImplemented(t *testing.T) { + if _, err := NewProvider().Stream(context.Background(), &ai.Request{Prompt: "hi"}); err == nil { + t.Error("expected error") + } +} + +func TestProvider_Registration(t *testing.T) { + m := ai.New("together", ai.WithAPIKey("test")) + if m == nil { + t.Fatal("provider not registered") + } + if m.String() != "together" { + t.Errorf("got %q", m.String()) + } +} diff --git a/cmd/micro/server/server.go b/cmd/micro/server/server.go index b5594a9db6..ba63f03a02 100644 --- a/cmd/micro/server/server.go +++ b/cmd/micro/server/server.go @@ -32,7 +32,10 @@ import ( _ "go-micro.dev/v5/ai/anthropic" _ "go-micro.dev/v5/ai/atlascloud" _ "go-micro.dev/v5/ai/gemini" + _ "go-micro.dev/v5/ai/groq" + _ "go-micro.dev/v5/ai/mistral" _ "go-micro.dev/v5/ai/openai" + _ "go-micro.dev/v5/ai/together" "go-micro.dev/v5/registry" "go-micro.dev/v5/store" "golang.org/x/crypto/bcrypt" From 69d69ec2f203022e91bbaf1f19c415c98aeafc9a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:50:00 +0000 Subject: [PATCH 7/8] feat(ai): add ai/tools helper and 'micro chat' interactive agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the registry-discovery + RPC-execution loop from the web agent playground into a reusable ai/tools package: - tools.New(reg) creates a Set bound to a registry - Set.Discover() walks the registry and returns []ai.Tool with LLM-safe (underscored) names, remembering the mapping back to the original dotted form - Set.Handler(client) returns an ai.ToolHandler that resolves the safe name and issues the RPC Add cmd/micro/chat — an interactive 'micro chat' REPL that uses ai/tools to let users talk to their services through any registered AI provider. Supports --prompt for single-shot use, auto-detects the provider from --base_url, and falls back to the provider's conventional env var (ANTHROPIC_API_KEY, etc). Update README with the new command and the programmatic example. --- README.md | 28 ++++++ ai/tools/tools.go | 197 +++++++++++++++++++++++++++++++++++++++ ai/tools/tools_test.go | 123 ++++++++++++++++++++++++ cmd/micro/chat/chat.go | 206 +++++++++++++++++++++++++++++++++++++++++ cmd/micro/main.go | 1 + 5 files changed, 555 insertions(+) create mode 100644 ai/tools/tools.go create mode 100644 ai/tools/tools_test.go create mode 100644 cmd/micro/chat/chat.go diff --git a/README.md b/README.md index 297957fe14..41fe6a0964 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,34 @@ micro run Use `micro mcp serve` for local AI tools like Claude Code, or connect any MCP-compatible agent to the HTTP endpoint. +### micro chat + +For an interactive terminal session that lets you talk to your services through an LLM: + +```bash +ANTHROPIC_API_KEY=sk-ant-... micro chat --provider anthropic +> list all users +> create an order for product 42 +``` + +`micro chat` discovers every service in the registry, exposes each endpoint as a tool, and lets the model orchestrate calls. The same building blocks (`ai/tools`) work from your own services: + +```go +import "go-micro.dev/v5/ai/tools" + +set := tools.New(service.Registry()) +discovered, _ := set.Discover() + +m := ai.New("anthropic", + ai.WithAPIKey(key), + ai.WithToolHandler(set.Handler(service.Client())), +) +resp, _ := m.Generate(ctx, &ai.Request{ + Prompt: userInput, + Tools: discovered, +}) +``` + See the [MCP guide](https://go-micro.dev/docs/mcp.html) for authentication, scopes, and advanced usage. ## Multi-Service Binaries diff --git a/ai/tools/tools.go b/ai/tools/tools.go new file mode 100644 index 0000000000..fadcfbea92 --- /dev/null +++ b/ai/tools/tools.go @@ -0,0 +1,197 @@ +// Package tools turns go-micro services into ai.Tool definitions and +// provides an ai.ToolHandler that executes tool calls by issuing RPCs +// to the corresponding service. +// +// This is the building block that lets any go-micro service reason +// about and call other services through an LLM: +// +// m := ai.New("anthropic", +// ai.WithAPIKey(key), +// ai.WithToolHandler(tools.Handler(service.Client())), +// ) +// resp, _ := m.Generate(ctx, &ai.Request{ +// Prompt: userInput, +// Tools: tools.FromRegistry(service.Registry()), +// }) +package tools + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "sync" + + "go-micro.dev/v5/ai" + "go-micro.dev/v5/client" + codecBytes "go-micro.dev/v5/codec/bytes" + "go-micro.dev/v5/registry" +) + +// nameMap holds the mapping between LLM-safe tool names (no dots) and the +// original `service.Endpoint` names used by the registry/client. Many +// providers reject dots in tool names, so we substitute underscores when +// presenting the tool and restore the original when executing. +type nameMap struct { + mu sync.RWMutex + m map[string]string +} + +func (n *nameMap) put(safe, original string) { + n.mu.Lock() + n.m[safe] = original + n.mu.Unlock() +} + +func (n *nameMap) get(safe string) (string, bool) { + n.mu.RLock() + v, ok := n.m[safe] + n.mu.RUnlock() + return v, ok +} + +// Set is the shared discovery state between FromRegistry and Handler. +// Use New, then Discover + Handler together when you want the handler +// to recognise LLM-safe tool names that were emitted by Discover. +// +// FromRegistry+Handler are convenience wrappers that create their own +// internal Set; Set is exposed for callers that want to control the +// lifecycle (e.g. cache the tool list and reuse it across turns). +type Set struct { + registry registry.Registry + names *nameMap +} + +// New creates an empty Set bound to the given registry. Call Discover +// to populate it. The registry is only used by Discover; Handler does +// not need it. +func New(reg registry.Registry) *Set { + return &Set{ + registry: reg, + names: &nameMap{m: map[string]string{}}, + } +} + +// Discover walks the registry and returns one ai.Tool per service +// endpoint. The returned tools have LLM-safe names (dots replaced with +// underscores); the Set remembers the mapping so Handler can route +// calls back to the right service. +func (s *Set) Discover() ([]ai.Tool, error) { + services, err := s.registry.ListServices() + if err != nil { + return nil, err + } + + var tools []ai.Tool + for _, svc := range services { + full, err := s.registry.GetService(svc.Name) + if err != nil || len(full) == 0 { + continue + } + for _, ep := range full[0].Endpoints { + original := fmt.Sprintf("%s.%s", svc.Name, ep.Name) + safe := strings.ReplaceAll(original, ".", "_") + s.names.put(safe, original) + + desc := fmt.Sprintf("Call %s on %s service", ep.Name, svc.Name) + if ep.Metadata != nil { + if d, ok := ep.Metadata["description"]; ok && d != "" { + desc = d + } + } + + props := map[string]any{} + if ep.Request != nil { + for _, field := range ep.Request.Values { + props[field.Name] = map[string]any{ + "type": jsonType(field.Type), + "description": fmt.Sprintf("%s (%s)", field.Name, field.Type), + } + } + } + + tools = append(tools, ai.Tool{ + Name: safe, + OriginalName: original, + Description: desc, + Properties: props, + }) + } + } + + return tools, nil +} + +// Handler returns an ai.ToolHandler that executes tool calls against +// the given client. Tool names may be the LLM-safe form (with +// underscores) emitted by Discover or the original dotted form; both +// resolve to the same RPC. +func (s *Set) Handler(c client.Client) ai.ToolHandler { + if c == nil { + c = client.DefaultClient + } + return func(name string, input map[string]any) (any, string) { + if orig, ok := s.names.get(name); ok { + name = orig + } + parts := strings.SplitN(name, ".", 2) + if len(parts) != 2 { + return errResult("invalid tool name: " + name) + } + + inputBytes, err := json.Marshal(input) + if err != nil { + return errResult("failed to marshal input: " + err.Error()) + } + + req := c.NewRequest(parts[0], parts[1], &codecBytes.Frame{Data: inputBytes}) + var rsp codecBytes.Frame + if err := c.Call(context.Background(), req, &rsp); err != nil { + return errResult(err.Error()) + } + + var result any + if err := json.Unmarshal(rsp.Data, &result); err != nil { + result = string(rsp.Data) + } + return result, string(rsp.Data) + } +} + +// FromRegistry is a convenience that builds a one-shot Set, discovers +// tools, and returns just the tool list. Use NewSet directly if you +// need to also wire up Handler against the same name mapping. +func FromRegistry(reg registry.Registry) ([]ai.Tool, error) { + return New(reg).Discover() +} + +// Handler is a convenience that returns an ai.ToolHandler bound to the +// given client. It only resolves dotted "service.Endpoint" names — it +// has no awareness of any LLM-safe name mapping. For full round-tripping +// of underscored names emitted by FromRegistry, construct a Set with +// New and call Set.Handler. +func Handler(c client.Client) ai.ToolHandler { + return (&Set{names: &nameMap{m: map[string]string{}}}).Handler(c) +} + +func errResult(msg string) (any, string) { + encoded, _ := json.Marshal(map[string]string{"error": msg}) + return map[string]string{"error": msg}, string(encoded) +} + +// jsonType maps Go types to JSON schema types. Anything that isn't a +// recognised primitive becomes "object". +func jsonType(goType string) string { + switch goType { + case "string": + return "string" + case "int", "int32", "int64", "uint", "uint32", "uint64": + return "integer" + case "float32", "float64": + return "number" + case "bool": + return "boolean" + default: + return "object" + } +} diff --git a/ai/tools/tools_test.go b/ai/tools/tools_test.go new file mode 100644 index 0000000000..a3f37d9c80 --- /dev/null +++ b/ai/tools/tools_test.go @@ -0,0 +1,123 @@ +package tools + +import ( + "testing" + + "go-micro.dev/v5/registry" +) + +func TestJSONType(t *testing.T) { + cases := map[string]string{ + "string": "string", + "int": "integer", + "int64": "integer", + "float64": "number", + "bool": "boolean", + "User": "object", + "": "object", + } + for in, want := range cases { + if got := jsonType(in); got != want { + t.Errorf("jsonType(%q) = %q, want %q", in, got, want) + } + } +} + +func TestFromRegistry_Empty(t *testing.T) { + reg := registry.NewMemoryRegistry() + tools, err := FromRegistry(reg) + if err != nil { + t.Fatalf("FromRegistry: %v", err) + } + if len(tools) != 0 { + t.Errorf("expected 0 tools, got %d", len(tools)) + } +} + +func TestFromRegistry_DiscoversEndpoints(t *testing.T) { + reg := registry.NewMemoryRegistry() + svc := ®istry.Service{ + Name: "users", + Version: "1.0.0", + Nodes: []*registry.Node{ + {Id: "users-1", Address: "127.0.0.1:9000"}, + }, + Endpoints: []*registry.Endpoint{ + { + Name: "Users.Get", + Metadata: map[string]string{ + "description": "Fetch a user by ID", + }, + Request: ®istry.Value{ + Name: "GetRequest", + Type: "GetRequest", + Values: []*registry.Value{ + {Name: "id", Type: "string"}, + {Name: "expand", Type: "bool"}, + }, + }, + }, + }, + } + if err := reg.Register(svc); err != nil { + t.Fatalf("Register: %v", err) + } + + tools, err := FromRegistry(reg) + if err != nil { + t.Fatalf("FromRegistry: %v", err) + } + if len(tools) != 1 { + t.Fatalf("expected 1 tool, got %d", len(tools)) + } + + tool := tools[0] + if tool.Name != "users_Users_Get" { + t.Errorf("safe name = %q, want users_Users_Get", tool.Name) + } + if tool.OriginalName != "users.Users.Get" { + t.Errorf("original = %q", tool.OriginalName) + } + if tool.Description != "Fetch a user by ID" { + t.Errorf("description = %q", tool.Description) + } + + id, ok := tool.Properties["id"].(map[string]any) + if !ok { + t.Fatal("missing id property") + } + if id["type"] != "string" { + t.Errorf("id type = %v", id["type"]) + } + expand, ok := tool.Properties["expand"].(map[string]any) + if !ok { + t.Fatal("missing expand property") + } + if expand["type"] != "boolean" { + t.Errorf("expand type = %v", expand["type"]) + } +} + +func TestSet_HandlerResolvesSafeName(t *testing.T) { + s := New(registry.NewMemoryRegistry()) + s.names.put("users_Users_Get", "users.Users.Get") + + resolved, ok := s.names.get("users_Users_Get") + if !ok || resolved != "users.Users.Get" { + t.Errorf("name map lookup = (%q, %v)", resolved, ok) + } +} + +func TestSet_HandlerInvalidName(t *testing.T) { + s := New(registry.NewMemoryRegistry()) + h := s.Handler(nil) + + // "foo" has no dot, no mapping entry — should error cleanly. + result, content := h("foo", map[string]any{}) + if result == nil { + t.Fatal("expected error result") + } + if content == "" { + t.Error("expected non-empty content") + } +} diff --git a/cmd/micro/chat/chat.go b/cmd/micro/chat/chat.go new file mode 100644 index 0000000000..3e905d26a8 --- /dev/null +++ b/cmd/micro/chat/chat.go @@ -0,0 +1,206 @@ +// Package chat implements the 'micro chat' interactive agent command. +// +// micro chat opens a terminal REPL where you can talk to your services +// through an LLM. It discovers all services from the registry, exposes +// each endpoint as a tool, and lets the model orchestrate calls in +// response to natural-language prompts. +package chat + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/urfave/cli/v2" + "go-micro.dev/v5/ai" + "go-micro.dev/v5/ai/tools" + "go-micro.dev/v5/client" + "go-micro.dev/v5/cmd" + "go-micro.dev/v5/registry" + + // Side-effect imports register the AI providers. + _ "go-micro.dev/v5/ai/anthropic" + _ "go-micro.dev/v5/ai/atlascloud" + _ "go-micro.dev/v5/ai/gemini" + _ "go-micro.dev/v5/ai/groq" + _ "go-micro.dev/v5/ai/mistral" + _ "go-micro.dev/v5/ai/openai" + _ "go-micro.dev/v5/ai/together" +) + +const systemPrompt = "You are an agent that helps users interact with microservices. " + + "Use the available tools to fulfill user requests. " + + "When you call a tool, explain what you are doing." + +func init() { + cmd.Register(&cli.Command{ + Name: "chat", + Usage: "Interactive AI chat that orchestrates your services", + Description: `Start an interactive chat session that uses an LLM to call your services. + +micro chat discovers every service in the registry, exposes each endpoint as a +tool, and lets you ask natural-language questions like "list all users" or +"create an order for product 42". The model decides which tool to call and +issues RPCs to the right service. + +Examples: + # Chat with Anthropic Claude (uses ANTHROPIC_API_KEY) + ANTHROPIC_API_KEY=sk-ant-... micro chat --provider anthropic + + # Use a single prompt and exit + micro chat --provider openai --prompt "list all users" + + # Use a custom provider via base URL (auto-detected) + micro chat --api_key $KEY --base_url https://api.groq.com/openai + +Environment variables: + MICRO_AI_PROVIDER Provider name (anthropic, openai, gemini, groq, ...) + MICRO_AI_API_KEY API key for the provider + MICRO_AI_MODEL Model name override + MICRO_AI_BASE_URL Base URL override + ANTHROPIC_API_KEY Fallback API key for the anthropic provider + OPENAI_API_KEY Fallback API key for the openai provider + GEMINI_API_KEY Fallback API key for the gemini provider`, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "provider", Usage: "AI provider (anthropic, openai, gemini, groq, mistral, together, atlascloud)", EnvVars: []string{"MICRO_AI_PROVIDER"}}, + &cli.StringFlag{Name: "api_key", Usage: "API key for the provider", EnvVars: []string{"MICRO_AI_API_KEY"}}, + &cli.StringFlag{Name: "model", Usage: "Model name (uses provider default if unset)", EnvVars: []string{"MICRO_AI_MODEL"}}, + &cli.StringFlag{Name: "base_url", Usage: "Override the provider's base URL", EnvVars: []string{"MICRO_AI_BASE_URL"}}, + &cli.StringFlag{Name: "prompt", Usage: "Send a single prompt and exit (non-interactive)"}, + }, + Action: run, + }) +} + +func run(c *cli.Context) error { + provider := c.String("provider") + apiKey := c.String("api_key") + model := c.String("model") + baseURL := c.String("base_url") + singlePrompt := c.String("prompt") + + if provider == "" { + provider = ai.AutoDetectProvider(baseURL) + } + if apiKey == "" { + apiKey = fallbackAPIKey(provider) + } + if apiKey == "" { + return fmt.Errorf("no API key configured; set --api_key or %s", envVarForProvider(provider)) + } + + // Discover tools and wire up a handler that routes to the right RPC. + reg := registry.DefaultRegistry + cli := client.DefaultClient + + toolSet := tools.New(reg) + discovered, err := toolSet.Discover() + if err != nil { + return fmt.Errorf("discover tools: %w", err) + } + + opts := []ai.Option{ + ai.WithAPIKey(apiKey), + ai.WithToolHandler(toolSet.Handler(cli)), + } + if model != "" { + opts = append(opts, ai.WithModel(model)) + } + if baseURL != "" { + opts = append(opts, ai.WithBaseURL(baseURL)) + } + + m := ai.New(provider, opts...) + if m == nil { + return fmt.Errorf("unknown provider: %s", provider) + } + + if singlePrompt != "" { + return ask(c.Context, m, discovered, singlePrompt) + } + + fmt.Printf("micro chat — provider=%s, model=%s, %d tools discovered\n", + provider, m.Options().Model, len(discovered)) + fmt.Println("Type a prompt and press enter. Ctrl-D or 'exit' to quit.") + fmt.Println() + + scanner := bufio.NewScanner(os.Stdin) + scanner.Buffer(make([]byte, 0, 4096), 1024*1024) + for { + fmt.Print("> ") + if !scanner.Scan() { + fmt.Println() + return nil + } + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + if line == "exit" || line == "quit" { + return nil + } + if err := ask(c.Context, m, discovered, line); err != nil { + fmt.Printf("error: %v\n", err) + } + fmt.Println() + } +} + +func ask(ctx context.Context, m ai.Model, toolList []ai.Tool, prompt string) error { + resp, err := m.Generate(ctx, &ai.Request{ + Prompt: prompt, + SystemPrompt: systemPrompt, + Tools: toolList, + }) + if err != nil { + return err + } + + if resp.Reply != "" { + fmt.Println(resp.Reply) + } + for _, tc := range resp.ToolCalls { + args, _ := json.Marshal(tc.Input) + fmt.Printf(" → called %s(%s)\n", tc.Name, args) + } + if resp.Answer != "" { + fmt.Println() + fmt.Println(resp.Answer) + } + return nil +} + +// fallbackAPIKey returns the provider-specific environment variable when +// neither --api_key nor MICRO_AI_API_KEY is set. This lets users keep +// existing ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY vars +// without re-exporting them. +func fallbackAPIKey(provider string) string { + if v := os.Getenv(envVarForProvider(provider)); v != "" { + return v + } + return "" +} + +func envVarForProvider(provider string) string { + switch provider { + case "anthropic": + return "ANTHROPIC_API_KEY" + case "openai": + return "OPENAI_API_KEY" + case "gemini": + return "GEMINI_API_KEY" + case "groq": + return "GROQ_API_KEY" + case "mistral": + return "MISTRAL_API_KEY" + case "together": + return "TOGETHER_API_KEY" + case "atlascloud": + return "ATLASCLOUD_API_KEY" + default: + return "MICRO_AI_API_KEY" + } +} diff --git a/cmd/micro/main.go b/cmd/micro/main.go index 968709f286..1342ad0e9b 100644 --- a/cmd/micro/main.go +++ b/cmd/micro/main.go @@ -4,6 +4,7 @@ import ( "embed" "go-micro.dev/v5/cmd" + _ "go-micro.dev/v5/cmd/micro/chat" _ "go-micro.dev/v5/cmd/micro/cli" _ "go-micro.dev/v5/cmd/micro/cli/build" _ "go-micro.dev/v5/cmd/micro/cli/deploy" From 5d37a7e1a020266c99809c4c79b4b77f444456dd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 17:06:22 +0000 Subject: [PATCH 8/8] feat(examples): add gRPC interop example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add examples/grpc-interop showing that any standard gRPC client can call a go-micro service — no go-micro SDK required on the client side. Includes: - proto/greeter.proto with generated Go, gRPC, and micro stubs - server/ using go-micro gRPC transport - client/ using stock google.golang.org/grpc (no go-micro imports) - README with Python example and explanation of how routing works Addresses the confusion from issue #2818 where users didn't know that go-micro gRPC services are callable by any gRPC client. --- README.md | 1 + examples/grpc-interop/README.md | 66 +++ examples/grpc-interop/client/main.go | 43 ++ examples/grpc-interop/go.mod | 72 ++++ examples/grpc-interop/go.sum | 387 ++++++++++++++++++ examples/grpc-interop/proto/greeter.pb.go | 173 ++++++++ .../grpc-interop/proto/greeter.pb.micro.go | 79 ++++ examples/grpc-interop/proto/greeter.proto | 17 + .../grpc-interop/proto/greeter_grpc.pb.go | 121 ++++++ examples/grpc-interop/server/main.go | 53 +++ 10 files changed, 1012 insertions(+) create mode 100644 examples/grpc-interop/README.md create mode 100644 examples/grpc-interop/client/main.go create mode 100644 examples/grpc-interop/go.mod create mode 100644 examples/grpc-interop/go.sum create mode 100644 examples/grpc-interop/proto/greeter.pb.go create mode 100644 examples/grpc-interop/proto/greeter.pb.micro.go create mode 100644 examples/grpc-interop/proto/greeter.proto create mode 100644 examples/grpc-interop/proto/greeter_grpc.pb.go create mode 100644 examples/grpc-interop/server/main.go diff --git a/README.md b/README.md index 41fe6a0964..702b33b09a 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,7 @@ Every service gets `Client()`, `Server()`, and `Model()` — call services, hand Check out [/examples](examples/) for runnable code: - [hello-world](examples/hello-world/) - Basic RPC service +- [grpc-interop](examples/grpc-interop/) - Call go-micro from any gRPC client - [web-service](examples/web-service/) - HTTP REST API - [multi-service](examples/multi-service/) - Multiple services in one binary - [mcp](examples/mcp/) - MCP integration with AI agents diff --git a/examples/grpc-interop/README.md b/examples/grpc-interop/README.md new file mode 100644 index 0000000000..504168ee7c --- /dev/null +++ b/examples/grpc-interop/README.md @@ -0,0 +1,66 @@ +# gRPC Interop Example + +This example shows that **any standard gRPC client** can call a go-micro service — no go-micro SDK required on the client side. + +The server is a normal go-micro service using the gRPC transport. The client is a plain `google.golang.org/grpc` client with no go-micro imports. + +## How it works + +go-micro's gRPC server uses a [`grpc.UnknownServiceHandler`](https://pkg.go.dev/google.golang.org/grpc#UnknownServiceHandler) that catches all incoming requests and routes them by parsing the standard gRPC method path (`/package.Service/Method`). This means any language with gRPC support (Python, Java, Rust, etc.) can call go-micro services using generated protobuf stubs. + +## Running + +Start the server: + +```bash +cd examples/grpc-interop +go run ./server/ +``` + +In another terminal, call it with the standard gRPC client: + +```bash +cd examples/grpc-interop +go run ./client/ --name Alice +# Response: Hello Alice +``` + +## Calling from other languages + +Generate stubs from `proto/greeter.proto` in your language of choice and point them at `localhost:50051`. For example, with Python: + +```bash +pip install grpcio-tools +python -m grpc_tools.protoc -Iproto --python_out=. --grpc_python_out=. proto/greeter.proto +``` + +```python +import grpc +import greeter_pb2 +import greeter_pb2_grpc + +channel = grpc.insecure_channel("localhost:50051") +stub = greeter_pb2_grpc.GreeterStub(channel) + +response = stub.Hello(greeter_pb2.HelloRequest(name="Alice")) +print(response.message) # Hello Alice +``` + +## Key points + +- The go-micro server registers handlers via `pb.RegisterGreeterHandler()` +- The standard gRPC client uses stubs generated by `protoc-gen-go-grpc` +- Both share the same `.proto` file — that's the contract +- The server uses protobuf encoding on the wire, same as any gRPC service +- Service discovery (mDNS, consul, etc.) is only needed for go-micro-to-go-micro calls; direct gRPC clients connect by address + +## Regenerating proto stubs + +```bash +protoc --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + --micro_out=. --micro_opt=paths=source_relative \ + proto/greeter.proto +``` + +Requires `protoc-gen-go`, `protoc-gen-go-grpc`, and `protoc-gen-micro`. diff --git a/examples/grpc-interop/client/main.go b/examples/grpc-interop/client/main.go new file mode 100644 index 0000000000..bdd4df8388 --- /dev/null +++ b/examples/grpc-interop/client/main.go @@ -0,0 +1,43 @@ +// client calls the go-micro Greeter service using a standard gRPC +// client — no go-micro SDK. This proves any language with gRPC support +// can call go-micro services. +package main + +import ( + "context" + "flag" + "fmt" + "log" + "time" + + pb "example/proto" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +func main() { + addr := flag.String("addr", "localhost:50051", "server address") + name := flag.String("name", "World", "name to greet") + flag.Parse() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + conn, err := grpc.NewClient(*addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + log.Fatalf("connect: %v", err) + } + defer conn.Close() + + client := pb.NewGreeterClient(conn) + + resp, err := client.Hello(ctx, &pb.HelloRequest{Name: *name}) + if err != nil { + log.Fatalf("Hello: %v", err) + } + + fmt.Printf("Response: %s\n", resp.Message) +} diff --git a/examples/grpc-interop/go.mod b/examples/grpc-interop/go.mod new file mode 100644 index 0000000000..a8648105fa --- /dev/null +++ b/examples/grpc-interop/go.mod @@ -0,0 +1,72 @@ +module example + +go 1.24 + +require ( + go-micro.dev/v5 v5.16.0 + google.golang.org/grpc v1.71.1 + google.golang.org/protobuf v1.36.6 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/bitly/go-simplejson v0.5.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/cornelk/hashmap v1.0.8 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/consul/api v1.32.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/serf v0.10.1 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/miekg/dns v1.1.50 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/hashstructure v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/nats-io/nats.go v1.42.0 // indirect + github.com/nats-io/nkeys v0.4.11 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rabbitmq/amqp091-go v1.10.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/urfave/cli/v2 v2.27.6 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + go.etcd.io/bbolt v1.4.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.21 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect + go.etcd.io/etcd/client/v3 v3.5.21 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect +) + +replace go-micro.dev/v5 => ../.. diff --git a/examples/grpc-interop/go.sum b/examples/grpc-interop/go.sum new file mode 100644 index 0000000000..e774d2de46 --- /dev/null +++ b/examples/grpc-interop/go.sum @@ -0,0 +1,387 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc= +github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= +github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE= +github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= +github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= +github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= +github.com/nats-io/nats-server/v2 v2.11.3 h1:AbGtXxuwjo0gBroLGGr/dE0vf24kTKdRnBq/3z/Fdoc= +github.com/nats-io/nats-server/v2 v2.11.3/go.mod h1:6Z6Fd+JgckqzKig7DYwhgrE7bJ6fypPHnGPND+DqgMY= +github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM= +github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= +github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= +github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= +go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= +go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= +go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs= +go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY= +go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057 h1:lPv+iqlAyiKMjbL3ivJlAASixPknLv806R6zaoE4PUM= +google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057/go.mod h1:WPWnet+nYurNGpV0rVYHI1YuOJwVHeM3t8f76m410XM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/grpc-interop/proto/greeter.pb.go b/examples/grpc-interop/proto/greeter.pb.go new file mode 100644 index 0000000000..fc0328a328 --- /dev/null +++ b/examples/grpc-interop/proto/greeter.pb.go @@ -0,0 +1,173 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.21.12 +// source: proto/greeter.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HelloRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelloRequest) Reset() { + *x = HelloRequest{} + mi := &file_proto_greeter_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelloRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloRequest) ProtoMessage() {} + +func (x *HelloRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_greeter_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. +func (*HelloRequest) Descriptor() ([]byte, []int) { + return file_proto_greeter_proto_rawDescGZIP(), []int{0} +} + +func (x *HelloRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type HelloResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelloResponse) Reset() { + *x = HelloResponse{} + mi := &file_proto_greeter_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelloResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloResponse) ProtoMessage() {} + +func (x *HelloResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_greeter_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead. +func (*HelloResponse) Descriptor() ([]byte, []int) { + return file_proto_greeter_proto_rawDescGZIP(), []int{1} +} + +func (x *HelloResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_proto_greeter_proto protoreflect.FileDescriptor + +const file_proto_greeter_proto_rawDesc = "" + + "\n" + + "\x13proto/greeter.proto\x12\agreeter\"\"\n" + + "\fHelloRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\")\n" + + "\rHelloResponse\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage2C\n" + + "\aGreeter\x128\n" + + "\x05Hello\x12\x15.greeter.HelloRequest\x1a\x16.greeter.HelloResponse\"\x00B\tZ\a./protob\x06proto3" + +var ( + file_proto_greeter_proto_rawDescOnce sync.Once + file_proto_greeter_proto_rawDescData []byte +) + +func file_proto_greeter_proto_rawDescGZIP() []byte { + file_proto_greeter_proto_rawDescOnce.Do(func() { + file_proto_greeter_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_greeter_proto_rawDesc), len(file_proto_greeter_proto_rawDesc))) + }) + return file_proto_greeter_proto_rawDescData +} + +var file_proto_greeter_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_greeter_proto_goTypes = []any{ + (*HelloRequest)(nil), // 0: greeter.HelloRequest + (*HelloResponse)(nil), // 1: greeter.HelloResponse +} +var file_proto_greeter_proto_depIdxs = []int32{ + 0, // 0: greeter.Greeter.Hello:input_type -> greeter.HelloRequest + 1, // 1: greeter.Greeter.Hello:output_type -> greeter.HelloResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_proto_greeter_proto_init() } +func file_proto_greeter_proto_init() { + if File_proto_greeter_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_greeter_proto_rawDesc), len(file_proto_greeter_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_greeter_proto_goTypes, + DependencyIndexes: file_proto_greeter_proto_depIdxs, + MessageInfos: file_proto_greeter_proto_msgTypes, + }.Build() + File_proto_greeter_proto = out.File + file_proto_greeter_proto_goTypes = nil + file_proto_greeter_proto_depIdxs = nil +} diff --git a/examples/grpc-interop/proto/greeter.pb.micro.go b/examples/grpc-interop/proto/greeter.pb.micro.go new file mode 100644 index 0000000000..176348892b --- /dev/null +++ b/examples/grpc-interop/proto/greeter.pb.micro.go @@ -0,0 +1,79 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: proto/greeter.proto + +package proto + +import ( + fmt "fmt" + proto "google.golang.org/protobuf/proto" + math "math" +) + +import ( + context "context" + client "go-micro.dev/v5/client" + server "go-micro.dev/v5/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Greeter service + +type GreeterService interface { + Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) +} + +type greeterService struct { + c client.Client + name string +} + +func NewGreeterService(name string, c client.Client) GreeterService { + return &greeterService{ + c: c, + name: name, + } +} + +func (c *greeterService) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) { + req := c.c.NewRequest(c.name, "Greeter.Hello", in) + out := new(HelloResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Greeter service + +type GreeterHandler interface { + Hello(context.Context, *HelloRequest, *HelloResponse) error +} + +func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error { + type greeter interface { + Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error + } + type Greeter struct { + greeter + } + h := &greeterHandler{hdlr} + return s.Handle(s.NewHandler(&Greeter{h}, opts...)) +} + +type greeterHandler struct { + GreeterHandler +} + +func (h *greeterHandler) Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error { + return h.GreeterHandler.Hello(ctx, in, out) +} diff --git a/examples/grpc-interop/proto/greeter.proto b/examples/grpc-interop/proto/greeter.proto new file mode 100644 index 0000000000..7dfc4a09ab --- /dev/null +++ b/examples/grpc-interop/proto/greeter.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package greeter; + +option go_package = "./proto"; + +service Greeter { + rpc Hello(HelloRequest) returns (HelloResponse) {} +} + +message HelloRequest { + string name = 1; +} + +message HelloResponse { + string message = 1; +} diff --git a/examples/grpc-interop/proto/greeter_grpc.pb.go b/examples/grpc-interop/proto/greeter_grpc.pb.go new file mode 100644 index 0000000000..13f4ff90cb --- /dev/null +++ b/examples/grpc-interop/proto/greeter_grpc.pb.go @@ -0,0 +1,121 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc v3.21.12 +// source: proto/greeter.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Greeter_Hello_FullMethodName = "/greeter.Greeter/Hello" +) + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GreeterClient interface { + Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) +} + +type greeterClient struct { + cc grpc.ClientConnInterface +} + +func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HelloResponse) + err := c.cc.Invoke(ctx, Greeter_Hello_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServer is the server API for Greeter service. +// All implementations must embed UnimplementedGreeterServer +// for forward compatibility. +type GreeterServer interface { + Hello(context.Context, *HelloRequest) (*HelloResponse, error) + mustEmbedUnimplementedGreeterServer() +} + +// UnimplementedGreeterServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedGreeterServer struct{} + +func (UnimplementedGreeterServer) Hello(context.Context, *HelloRequest) (*HelloResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Hello not implemented") +} +func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} +func (UnimplementedGreeterServer) testEmbeddedByValue() {} + +// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GreeterServer will +// result in compilation errors. +type UnsafeGreeterServer interface { + mustEmbedUnimplementedGreeterServer() +} + +func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { + // If the following call panics, it indicates UnimplementedGreeterServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Greeter_ServiceDesc, srv) +} + +func _Greeter_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).Hello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Greeter_Hello_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).Hello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Greeter_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "greeter.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Hello", + Handler: _Greeter_Hello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/greeter.proto", +} diff --git a/examples/grpc-interop/server/main.go b/examples/grpc-interop/server/main.go new file mode 100644 index 0000000000..24517a3fd7 --- /dev/null +++ b/examples/grpc-interop/server/main.go @@ -0,0 +1,53 @@ +// server starts a go-micro gRPC service. Any standard gRPC client +// (Go, Python, Java, etc.) can call it — no go-micro SDK required on +// the client side. +package main + +import ( + "context" + "fmt" + "log" + + micro "go-micro.dev/v5" + "go-micro.dev/v5/client" + grpcclient "go-micro.dev/v5/client/grpc" + "go-micro.dev/v5/server" + grpcserver "go-micro.dev/v5/server/grpc" + + pb "example/proto" +) + +type Greeter struct{} + +func (g *Greeter) Hello(ctx context.Context, req *pb.HelloRequest, rsp *pb.HelloResponse) error { + log.Printf("Received request: name=%q", req.Name) + rsp.Message = "Hello " + req.Name + return nil +} + +func main() { + addr := ":50051" + + service := micro.New("greeter", + micro.Server(grpcserver.NewServer( + server.Name("greeter"), + server.Address(addr), + )), + micro.Client(grpcclient.NewClient( + client.ContentType("application/grpc+proto"), + )), + ) + + service.Init() + + pb.RegisterGreeterHandler(service.Server(), new(Greeter)) + + fmt.Println("Go-Micro gRPC server listening on", addr) + fmt.Println() + fmt.Println("Call with a standard gRPC client:") + fmt.Println(" go run ../client/main.go") + + if err := service.Run(); err != nil { + log.Fatal(err) + } +}