diff --git a/callbacks/tls/README.md b/callbacks/tls/README.md new file mode 100644 index 000000000..7672fb722 --- /dev/null +++ b/callbacks/tls/README.md @@ -0,0 +1,131 @@ +# Volcengine TLS Callbacks + +English | [简体中文](README_zh.md) + +`callbacks/tls` provides a TLS-based observability handler for [Eino](https://github.com/cloudwego/eino). + +## Features + +- Implements `github.com/cloudwego/eino/callbacks.Handler` +- Supports session-scoped tracing via `SetSession` +- Supports custom callback parsers through `WithCallbackDataParser` + +## Installation + +```bash +go get github.com/cloudwego/eino-ext/callbacks/tls +``` + +## Quick Start + +```go +package main + +import ( + "context" + "log" + + "github.com/cloudwego/eino-ext/callbacks/tls" + "github.com/cloudwego/eino/callbacks" +) + +func main() { + ctx := tls.SetSession(context.Background(), + tls.WithSessionID("session-id"), + tls.WithUserID("user-id"), + ) + + handler, shutdown, err := tls.NewTLSHandler(&tls.TLSConfig{ + TLSEndpoint: "tls-cn-beijing.volces.com:4317", + AppName: "eino-app", + TLSOTLPHeadersStr: "Authorization=Bearer ", + Release: "v0.0.1", + }) + if err != nil { + log.Fatal(err) + } + defer func() { + if shutdown != nil { + _ = shutdown(ctx) + } + }() + + callbacks.AppendGlobalHandlers(handler) + + // Build and run your Eino graph or chain with ctx here. +} +``` + +## Configuration + +```go +type TLSConfig struct { + TLSEndpoint string + AppName string + TLSOTLPHeader map[string]string + TLSOTLPHeadersStr string + Release string + TLSExporterEnabled bool + TLSLogEndpoint string + TLSLogRegion string + TLSLogTopicID string + TLSLogAPIKey string + TLSLogAccessKeyID string + TLSLogAccessKeySecret string +} +``` + +### TLS exporter, dashboard, and LogApp transport + +Set `TLSExporterEnabled` to use the TLS trace schema. Eino callbacks +then emit `agent.turn`, `llm.request`, and `tool.call` spans; they include +`tls.app.type=eino`, `session.id`, standard model token fields, and tool +input/output fields. This is the schema consumed by the Eino LogApp dashboard. + +Set the `TLSLog*` fields to write directly to the Trace Topic through TLS +Producer `SendLogs`. Use either an API key or an AK/SK pair, never hard-code +either in source code. + +```go +handler, shutdown, err := tls.NewTLSHandler(&tls.TLSConfig{ + AppName: "Eino", + TLSExporterEnabled: true, + TLSLogEndpoint: os.Getenv("TLS_LOG_ENDPOINT"), + TLSLogRegion: os.Getenv("TLS_LOG_REGION"), + TLSLogTopicID: os.Getenv("TLS_LOG_TRACE_TOPIC_ID"), + TLSLogAPIKey: os.Getenv("TLS_LOG_API_KEY"), // or TLSLogAccessKeyID/TLSLogAccessKeySecret +}) +``` + +The equivalent environment-based configuration is `TLS_EXPORTER_ENABLED=true` +together with `TLS_APP_NAME`, `TLS_LOG_ENDPOINT`, `TLS_LOG_REGION`, +`TLS_LOG_TRACE_TOPIC_ID`, and either `TLS_LOG_API_KEY` or `TLS_LOG_AK` plus +`TLS_LOG_SK`. + +## Advanced Usage + +```go +handler, shutdown, err := tls.NewTLSHandlerWithOptions(cfg, + tls.WithCallbackDataParser(customParser), + tls.WithAggrMessageOutput(true), +) +``` + +### Manual Spans + +Use manual spans for work that does not emit Eino callbacks, such as an ACP protocol request. + +```go +ctx = handler.StartSpanWithKind(ctx, "session/prompt", trace.SpanKindServer, map[string]any{ + "rpc.system.name": "jsonrpc", + "rpc.method": "session/prompt", +}) +defer func() { + handler.FinishSpanWithError(ctx, nil, err) +}() +``` + +## References + +- [Volcengine TLS Documentation](https://www.volcengine.com/docs/6431/69092) +- [Eino Documentation](https://github.com/cloudwego/eino) diff --git a/callbacks/tls/README_zh.md b/callbacks/tls/README_zh.md new file mode 100644 index 000000000..7806bbd57 --- /dev/null +++ b/callbacks/tls/README_zh.md @@ -0,0 +1,129 @@ +# 火山引擎 TLS 回调 + +[English](README.md) | 简体中文 + +`callbacks/tls` 为 [Eino](https://github.com/cloudwego/eino) 提供基于 TLS 的可观测回调实现。 + +## 特性 + +- 实现 `github.com/cloudwego/eino/callbacks.Handler` +- 支持通过 `SetSession` 传递会话级 tracing 信息 +- 支持通过 `WithCallbackDataParser` 自定义 callback 数据解析逻辑 + +## 安装 + +```bash +go get github.com/cloudwego/eino-ext/callbacks/tls +``` + +## 快速开始 + +```go +package main + +import ( + "context" + "log" + + "github.com/cloudwego/eino-ext/callbacks/tls" + "github.com/cloudwego/eino/callbacks" +) + +func main() { + ctx := tls.SetSession(context.Background(), + tls.WithSessionID("session-id"), + tls.WithUserID("user-id"), + ) + + handler, shutdown, err := tls.NewTLSHandler(&tls.TLSConfig{ + TLSEndpoint: "tls-cn-beijing.volces.com:4317", + AppName: "eino-app", + TLSOTLPHeadersStr: "Authorization=Bearer ", + Release: "v0.0.1", + }) + if err != nil { + log.Fatal(err) + } + defer func() { + if shutdown != nil { + _ = shutdown(ctx) + } + }() + + callbacks.AppendGlobalHandlers(handler) + + // 在这里使用带有 ctx 的 Eino graph 或 chain。 +} +``` + +## 配置 + +```go +type TLSConfig struct { + TLSEndpoint string + AppName string + TLSOTLPHeader map[string]string + TLSOTLPHeadersStr string + Release string + TLSExporterEnabled bool + TLSLogEndpoint string + TLSLogRegion string + TLSLogTopicID string + TLSLogAPIKey string + TLSLogAccessKeyID string + TLSLogAccessKeySecret string +} +``` + +### TLS exporter、大盘与 LogApp 写入 + +设置 `TLSExporterEnabled` 后,Eino 回调会使用 TLS Trace Schema: +`agent.turn`、`llm.request`、`tool.call` 三类 span,并补齐 +`tls.app.type=eino`、`session.id`、标准 Token 字段和工具入参/结果字段;这些 +字段可直接被 Eino LogApp 大盘消费。 + +同时设置 `TLSLog*` 字段会通过 TLS Producer 的 `SendLogs` 直接写入 Trace +Topic。认证使用 API Key 或 AK/SK 二选一,不要将任何凭证写入源码。 + +```go +handler, shutdown, err := tls.NewTLSHandler(&tls.TLSConfig{ + AppName: "Eino", + TLSExporterEnabled: true, + TLSLogEndpoint: os.Getenv("TLS_LOG_ENDPOINT"), + TLSLogRegion: os.Getenv("TLS_LOG_REGION"), + TLSLogTopicID: os.Getenv("TLS_LOG_TRACE_TOPIC_ID"), + TLSLogAPIKey: os.Getenv("TLS_LOG_API_KEY"), // 或 TLSLogAccessKeyID/TLSLogAccessKeySecret +}) +``` + +环境变量方式对应 `TLS_EXPORTER_ENABLED=true`,以及 `TLS_APP_NAME`、 +`TLS_LOG_ENDPOINT`、`TLS_LOG_REGION`、`TLS_LOG_TRACE_TOPIC_ID` 和 +`TLS_LOG_API_KEY`,或 `TLS_LOG_AK` + `TLS_LOG_SK`。 + +## 进阶用法 + +```go +handler, shutdown, err := tls.NewTLSHandlerWithOptions(cfg, + tls.WithCallbackDataParser(customParser), + tls.WithAggrMessageOutput(true), +) +``` + +### 手动 Span + +对于 ACP 协议请求等不会触发 Eino callback 的工作,可使用手动 Span。 + +```go +ctx = handler.StartSpanWithKind(ctx, "session/prompt", trace.SpanKindServer, map[string]any{ + "rpc.system.name": "jsonrpc", + "rpc.method": "session/prompt", +}) +defer func() { + handler.FinishSpanWithError(ctx, nil, err) +}() +``` + +## 参考资料 + +- [火山引擎 TLS 文档](https://www.volcengine.com/docs/6431/69092) +- [Eino 文档](https://github.com/cloudwego/eino) diff --git a/callbacks/tls/convert.go b/callbacks/tls/convert.go new file mode 100644 index 000000000..f0341cb85 --- /dev/null +++ b/callbacks/tls/convert.go @@ -0,0 +1,409 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + + "github.com/bytedance/sonic" + sem_ai "github.com/cloudwego/eino-ext/callbacks/tls/semconv" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/components/retriever" + "github.com/cloudwego/eino/schema" +) + +const toolTypeFunction = "function" + +func convertModelInput(input *model.CallbackInput) *sem_ai.ModelInput { + if input == nil { + return nil + } + + return &sem_ai.ModelInput{ + Messages: iterSlice(input.Messages, convertModelMessage), + Tools: iterSlice(input.Tools, convertTool), + } +} + +func convertModelOutput(output *model.CallbackOutput) *sem_ai.ModelOutput { + if output == nil { + return nil + } + return &sem_ai.ModelOutput{ + Choices: []*sem_ai.ModelChoice{ + { + Index: 0, + FinishReason: getFinishReason(output.Message), + Message: convertModelMessage(output.Message)}, + }, + } +} + +func getFinishReason(msg *schema.Message) string { + if msg == nil || msg.ResponseMeta == nil { + return "" + } + + return msg.ResponseMeta.FinishReason +} + +func convertModelMessage(message *schema.Message) *sem_ai.ModelMessage { + if message == nil { + return nil + } + + msg := &sem_ai.ModelMessage{ + Role: string(message.Role), + Content: message.Content, + ReasoningContent: message.ReasoningContent, + Name: message.Name, + ToolCalls: make([]*sem_ai.ModelToolCall, len(message.ToolCalls)), + ToolCallID: message.ToolCallID, + } + + // MultiContent is retained for older Eino clients. Newer Eino versions + // split input and output multimodal parts into dedicated fields; prefer + // those fields when present so a message is represented exactly once. + if len(message.UserInputMultiContent) > 0 { + for _, part := range message.UserInputMultiContent { + msg.Parts = append(msg.Parts, convertInputMessagePart(part)) + } + } else if len(message.AssistantGenMultiContent) > 0 { + for _, part := range message.AssistantGenMultiContent { + msg.Parts = append(msg.Parts, convertOutputMessagePart(part)) + } + } else { + for _, part := range message.MultiContent { + msg.Parts = append(msg.Parts, convertLegacyMessagePart(part)) + } + } + + for i := range message.ToolCalls { + tc := message.ToolCalls[i] + + msg.ToolCalls[i] = &sem_ai.ModelToolCall{ + ID: tc.ID, + Type: toolTypeFunction, + Function: &sem_ai.ModelToolCallFunction{ + Name: tc.Function.Name, + Arguments: tc.Function.Arguments, + }, + } + } + + if message.Extra != nil { + msg.Metadata = make(map[string]string, len(message.Extra)) + for k, v := range message.Extra { + if sv, err := sonic.MarshalString(v); err == nil { + msg.Metadata[k] = sv + } + } + } + + return msg +} + +func convertInputMessagePart(part schema.MessageInputPart) *sem_ai.ModelMessagePart { + result := &sem_ai.ModelMessagePart{Type: sem_ai.ModelMessagePartType(part.Type), Text: part.Text} + switch part.Type { + case schema.ChatMessagePartTypeImageURL: + if part.Image != nil { + result.ImageURL = &sem_ai.ModelImageURL{ + URL: derefString(part.Image.URL), + Detail: string(part.Image.Detail), + MIMEType: part.Image.MIMEType, + } + } + case schema.ChatMessagePartTypeAudioURL: + if part.Audio != nil { + result.AudioURL = &sem_ai.ModelMediaURL{ + URL: derefString(part.Audio.URL), + MIMEType: part.Audio.MIMEType, + } + } + case schema.ChatMessagePartTypeVideoURL: + if part.Video != nil { + result.VideoURL = &sem_ai.ModelMediaURL{ + URL: derefString(part.Video.URL), + MIMEType: part.Video.MIMEType, + } + } + case schema.ChatMessagePartTypeFileURL: + if part.File != nil { + result.FileURL = &sem_ai.ModelFileURL{ + Name: part.File.Name, + URL: derefString(part.File.URL), + MIMEType: part.File.MIMEType, + } + } + } + return result +} + +func convertOutputMessagePart(part schema.MessageOutputPart) *sem_ai.ModelMessagePart { + result := &sem_ai.ModelMessagePart{Type: sem_ai.ModelMessagePartType(part.Type), Text: part.Text} + switch part.Type { + case schema.ChatMessagePartTypeImageURL: + if part.Image != nil { + result.ImageURL = &sem_ai.ModelImageURL{ + URL: derefString(part.Image.URL), + MIMEType: part.Image.MIMEType, + } + } + case schema.ChatMessagePartTypeAudioURL: + if part.Audio != nil { + result.AudioURL = &sem_ai.ModelMediaURL{ + URL: derefString(part.Audio.URL), + MIMEType: part.Audio.MIMEType, + } + } + case schema.ChatMessagePartTypeVideoURL: + if part.Video != nil { + result.VideoURL = &sem_ai.ModelMediaURL{ + URL: derefString(part.Video.URL), + MIMEType: part.Video.MIMEType, + } + } + case schema.ChatMessagePartTypeReasoning: + if part.Reasoning != nil { + result.Text = part.Reasoning.Text + } + } + return result +} + +func convertLegacyMessagePart(part schema.ChatMessagePart) *sem_ai.ModelMessagePart { + result := &sem_ai.ModelMessagePart{Type: sem_ai.ModelMessagePartType(part.Type), Text: part.Text} + switch part.Type { + case schema.ChatMessagePartTypeImageURL: + if part.ImageURL != nil { + result.ImageURL = &sem_ai.ModelImageURL{ + URL: firstNonEmpty(part.ImageURL.URL, part.ImageURL.URI), + Detail: string(part.ImageURL.Detail), + MIMEType: part.ImageURL.MIMEType, + } + } + case schema.ChatMessagePartTypeAudioURL: + if part.AudioURL != nil { + result.AudioURL = &sem_ai.ModelMediaURL{ + URL: firstNonEmpty(part.AudioURL.URL, part.AudioURL.URI), + MIMEType: part.AudioURL.MIMEType, + } + } + case schema.ChatMessagePartTypeVideoURL: + if part.VideoURL != nil { + result.VideoURL = &sem_ai.ModelMediaURL{ + URL: firstNonEmpty(part.VideoURL.URL, part.VideoURL.URI), + MIMEType: part.VideoURL.MIMEType, + } + } + case schema.ChatMessagePartTypeFileURL: + if part.FileURL != nil { + result.FileURL = &sem_ai.ModelFileURL{ + Name: part.FileURL.Name, + URL: firstNonEmpty(part.FileURL.URL, part.FileURL.URI), + MIMEType: part.FileURL.MIMEType, + } + } + } + return result +} + +func derefString(value *string) string { + if value == nil { + return "" + } + return *value +} + +func firstNonEmpty(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +func addToolName(ctx context.Context, message *sem_ai.ModelMessage) *sem_ai.ModelMessage { + if message == nil { + return message + } + + toolIDNameMap := getToolIDNameMapFromCtx(ctx) + if toolIDNameMap == nil { + return message + } + + toolName, ok := toolIDNameMap[message.ToolCallID] + if !ok { + return message + } + + message.Name = toolName + return message +} + +func convertTool(tool *schema.ToolInfo) *sem_ai.ModelTool { + if tool == nil { + return nil + } + + var params []byte + if raw, err := tool.ToJSONSchema(); err == nil && raw != nil { + params, _ = raw.MarshalJSON() + } + + t := &sem_ai.ModelTool{ + Type: toolTypeFunction, + Function: &sem_ai.ModelToolFunction{ + Name: tool.Name, + Description: tool.Desc, + Parameters: params, + }, + } + + return t +} + +func convertModelCallOption(config *model.Config) *sem_ai.ModelCallOption { + if config == nil { + return nil + } + + return &sem_ai.ModelCallOption{ + Temperature: config.Temperature, + MaxTokens: int64(config.MaxTokens), + TopP: config.TopP, + } +} + +// Prompt + +func convertPromptInput(input *prompt.CallbackInput) *sem_ai.PromptInput { + if input == nil { + return nil + } + + return &sem_ai.PromptInput{ + Templates: iterSlice(input.Templates, convertTemplate), + Arguments: convertPromptArguments(input.Variables), + } +} + +func convertPromptOutput(output *prompt.CallbackOutput) *sem_ai.PromptOutput { + if output == nil { + return nil + } + + return &sem_ai.PromptOutput{ + Prompts: iterSlice(output.Result, convertModelMessage), + } +} + +func convertTemplate(template schema.MessagesTemplate) *sem_ai.ModelMessage { + if template == nil { + return nil + } + + switch t := template.(type) { + case *schema.Message: + return convertModelMessage(t) + default: // messagePlaceholder etc. + return nil + } +} + +func convertPromptArguments(variables map[string]any) []*sem_ai.PromptArgument { + if variables == nil { + return nil + } + + resp := make([]*sem_ai.PromptArgument, 0, len(variables)) + + for k := range variables { + resp = append(resp, &sem_ai.PromptArgument{ + Key: k, + Value: variables[k], + // Source: "", + }) + } + + return resp +} + +// Retriever + +func convertRetrieverOutput(output *retriever.CallbackOutput) *sem_ai.RetrieverOutput { + if output == nil { + return nil + } + + return &sem_ai.RetrieverOutput{ + Documents: iterSlice(output.Docs, convertDocument), + } +} + +func convertRetrieverCallOption(input *retriever.CallbackInput) *sem_ai.RetrieverCallOption { + if input == nil { + return nil + } + + opt := &sem_ai.RetrieverCallOption{ + TopK: int64(input.TopK), + Filter: input.Filter, + } + + if input.ScoreThreshold != nil { + opt.MinScore = input.ScoreThreshold + } + + return opt +} + +func convertDocument(doc *schema.Document) *sem_ai.RetrieverDocument { + if doc == nil { + return nil + } + + return &sem_ai.RetrieverDocument{ + ID: doc.ID, + Content: doc.Content, + Score: doc.Score(), + // Index: "", + Vector: doc.DenseVector(), + } +} + +func iterSlice[A, B any](sa []A, fb func(a A) B) []B { + r := make([]B, len(sa)) + for i := range sa { + r[i] = fb(sa[i]) + } + + return r +} + +func iterSliceWithCtx[A, B any](ctx context.Context, sa []A, fb func(ctx context.Context, a A) B) []B { + r := make([]B, len(sa)) + for i := range sa { + r[i] = fb(ctx, sa[i]) + } + + return r +} diff --git a/callbacks/tls/coverage_test.go b/callbacks/tls/coverage_test.go new file mode 100644 index 000000000..72fe0ca1b --- /dev/null +++ b/callbacks/tls/coverage_test.go @@ -0,0 +1,299 @@ +/* + * Copyright 2026 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "errors" + "reflect" + "strings" + "testing" + + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/components/retriever" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/schema" + "go.opentelemetry.io/otel/codes" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" +) + +func TestConvertCallbacksToTLSData(t *testing.T) { + toolInfo := &schema.ToolInfo{ + Name: "weather", + Desc: "returns the weather for a city", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "city": {Type: schema.String, Required: true}, + }), + } + convertedTool := convertTool(toolInfo) + if convertedTool == nil || convertedTool.Function == nil { + t.Fatal("convertTool returned an incomplete tool") + } + if convertedTool.Type != toolTypeFunction || convertedTool.Function.Name != "weather" { + t.Fatalf("unexpected converted tool: %#v", convertedTool) + } + if !strings.Contains(string(convertedTool.Function.Parameters), "city") { + t.Fatalf("tool parameters did not preserve the JSON schema: %s", convertedTool.Function.Parameters) + } + if convertTool(nil) != nil { + t.Fatal("convertTool(nil) must return nil") + } + + config := &model.Config{Temperature: 0.7, MaxTokens: 128, TopP: 0.9} + modelOptions := convertModelCallOption(config) + if modelOptions == nil || modelOptions.MaxTokens != 128 || modelOptions.Temperature != 0.7 || modelOptions.TopP != 0.9 { + t.Fatalf("unexpected model call options: %#v", modelOptions) + } + if convertModelCallOption(nil) != nil { + t.Fatal("convertModelCallOption(nil) must return nil") + } + + promptInput := convertPromptInput(&prompt.CallbackInput{ + Templates: []schema.MessagesTemplate{ + &schema.Message{Role: schema.System, Content: "be concise"}, + schema.MessagesPlaceholder("history", true), + }, + Variables: map[string]any{"city": "Guilin", "days": 3}, + }) + if promptInput == nil || len(promptInput.Templates) != 2 || promptInput.Templates[0].Content != "be concise" || promptInput.Templates[1] != nil { + t.Fatalf("unexpected prompt input: %#v", promptInput) + } + if len(promptInput.Arguments) != 2 { + t.Fatalf("prompt arguments = %#v, want two arguments", promptInput.Arguments) + } + if convertPromptInput(nil) != nil || convertPromptOutput(nil) != nil || convertTemplate(nil) != nil || convertPromptArguments(nil) != nil { + t.Fatal("nil prompt values must remain nil") + } + + promptOutput := convertPromptOutput(&prompt.CallbackOutput{Result: []*schema.Message{{Role: schema.Assistant, Content: "sunny"}}}) + if promptOutput == nil || len(promptOutput.Prompts) != 1 || promptOutput.Prompts[0].Content != "sunny" { + t.Fatalf("unexpected prompt output: %#v", promptOutput) + } + + document := (&schema.Document{ID: "doc-1", Content: "TLS callback docs"}).WithScore(0.95).WithDenseVector([]float64{1, 2}) + retrieverOutput := convertRetrieverOutput(&retriever.CallbackOutput{Docs: []*schema.Document{document, nil}}) + if retrieverOutput == nil || len(retrieverOutput.Documents) != 2 || retrieverOutput.Documents[0].Score != 0.95 || !reflect.DeepEqual(retrieverOutput.Documents[0].Vector, []float64{1, 2}) || retrieverOutput.Documents[1] != nil { + t.Fatalf("unexpected retriever output: %#v", retrieverOutput) + } + threshold := 0.8 + retrieverOptions := convertRetrieverCallOption(&retriever.CallbackInput{TopK: 5, Filter: "lang=zh", ScoreThreshold: &threshold}) + if retrieverOptions == nil || retrieverOptions.TopK != 5 || retrieverOptions.Filter != "lang=zh" || retrieverOptions.MinScore == nil || *retrieverOptions.MinScore != threshold { + t.Fatalf("unexpected retriever options: %#v", retrieverOptions) + } + if convertRetrieverOutput(nil) != nil || convertRetrieverCallOption(nil) != nil || convertDocument(nil) != nil { + t.Fatal("nil retriever values must remain nil") + } +} + +func TestOptionsAndLegacyMessageSerialization(t *testing.T) { + parser := NewDefaultDataParser(false) + options := newOptions( + WithCallbackDataParser(parser), + WithConcatFunction(func(chunks []string) (string, error) { return strings.Join(chunks, ""), nil }), + WithAggrMessageOutput(false), + ) + if options.parser != parser || options.enableAggrOutput { + t.Fatalf("unexpected options: %#v", options) + } + if _, ok := options.concatFuncs[reflect.TypeOf("")]; !ok { + t.Fatalf("string concat function was not registered: %#v", options.concatFuncs) + } + if _, ok := newDefaultDataParserWithConcatFuncs(nil, true).(*defaultDataParser); !ok { + t.Fatal("nil concat functions must use the default parser") + } + if custom, ok := newDefaultDataParserWithConcatFuncs(options.concatFuncs, false).(*defaultDataParser); !ok || custom.enableAggrMessageOutput { + t.Fatalf("custom concat parser did not preserve options: %#v", custom) + } + + if got := _promptTemplateInput(&prompt.CallbackInput{}); got != nil { + t.Fatalf("empty prompt template attributes = %#v, want nil", got) + } + if got := _promptTemplateInput(&prompt.CallbackInput{Templates: []schema.MessagesTemplate{&schema.Message{Role: schema.User, Content: "hello"}}}); len(got) != 0 { + t.Fatalf("prompt template attributes = %#v, want empty", got) + } + + message := &schema.Message{ + Role: schema.User, + Content: "describe these media items", + MultiContent: []schema.ChatMessagePart{ + {Type: schema.ChatMessagePartTypeText, Text: "caption"}, + {Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URL: "https://example.com/image.png"}}, + {Type: schema.ChatMessagePartTypeAudioURL, AudioURL: &schema.ChatMessageAudioURL{URL: "https://example.com/audio.wav"}}, + {Type: schema.ChatMessagePartTypeVideoURL, VideoURL: &schema.ChatMessageVideoURL{URL: "https://example.com/video.mp4"}}, + {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URL: "https://example.com/spec.pdf"}}, + }, + } + parts, err := convertMultiContentToJSON(message) + if err != nil || len(parts) != 5 { + t.Fatalf("convertMultiContentToJSON() = %#v, %v; want five parts", parts, err) + } + if _, err := convertMultiContentToJSON(nil); err == nil { + t.Fatal("convertMultiContentToJSON(nil) must return an error") + } + + inputAttrs := _llmModelInput(&model.CallbackInput{ + Messages: []*schema.Message{message}, + Tools: []*schema.ToolInfo{{Name: "weather", Desc: "looks up weather"}}, + Config: &model.Config{Model: "ep-test", Temperature: 0.2, TopP: 0.4}, + }) + if len(inputAttrs) < 7 { + t.Fatalf("model input attributes = %#v, want prompt, multimodal, tool, and config attributes", inputAttrs) + } + if _llmModelInput(nil) != nil || _llmModelInput(&model.CallbackInput{}) != nil { + t.Fatal("empty model input must not create attributes") + } + + outputAttrs := _llmModelOutput(&model.CallbackOutput{ + Message: &schema.Message{ReasoningContent: "checked forecast"}, + TokenUsage: &model.TokenUsage{PromptTokens: 2, CompletionTokens: 3, TotalTokens: 5}, + }) + if len(outputAttrs) != 4 { + t.Fatalf("model output attributes = %#v, want token and reasoning attributes", outputAttrs) + } + if _llmModelOutput(nil) != nil || _llmModelOutput(&model.CallbackOutput{}) != nil { + t.Fatal("empty model output must not create attributes") + } + if _toolsOutput(nil) != nil || _toolsOutput(&tool.CallbackOutput{}) != nil { + t.Fatal("empty tool output must not create attributes") + } + if got := _toolsOutput(&tool.CallbackOutput{Response: "sunny"}); len(got) != 0 { + t.Fatalf("tool output attributes = %#v, want empty until a tool result semantic convention is emitted", got) + } + + tags := spanTags{} + tags.set("string", "value").set("string", "overwritten").set("object", map[string]any{"ok": true}) + tags.setIfNotZero("zero", 0) + tags.setFromExtraIfNotZero("extra", map[string]any{"extra": "set", "empty": ""}) + if tags["string"] != "value" || !strings.Contains(tags["object"].(string), "ok") || tags["zero"] != nil || tags["extra"] != "set" { + t.Fatalf("unexpected span tags: %#v", tags) + } +} + +func TestTLSConstructorsAndErrorCallback(t *testing.T) { + cfg := &TLSConfig{ + TLSEndpoint: "127.0.0.1:4317", + AppName: "tls-coverage-test", + TLSOTLPHeadersStr: "authorization=Bearer test, x-scope = test", + } + if err := ValidateTLSConfig(cfg); err != nil { + t.Fatalf("ValidateTLSConfig() error = %v", err) + } + if cfg.TLSOTLPHeader["authorization"] != "Bearer test" || cfg.TLSOTLPHeader["x-scope"] != "test" { + t.Fatalf("TLS OTLP headers were not parsed: %#v", cfg.TLSOTLPHeader) + } + if got := parseOTLPHeaders("invalid, a = one, b= two=three"); got["a"] != "one" || got["b"] != "two=three" || len(got) != 2 { + t.Fatalf("parseOTLPHeaders() = %#v", got) + } + t.Setenv("TLS_COVERAGE_VALUE", "configured") + if got := getEnvOrDefault("TLS_COVERAGE_VALUE", "fallback"); got != "configured" { + t.Fatalf("getEnvOrDefault() = %q, want configured", got) + } + if got := getEnvOrDefault("TLS_COVERAGE_UNSET", "fallback"); got != "fallback" { + t.Fatalf("getEnvOrDefault() = %q, want fallback", got) + } + if resolved, err := resolveTLSConfig(cfg); err != nil || resolved != cfg { + t.Fatalf("resolveTLSConfig() = %#v, %v", resolved, err) + } + + constructors := []struct { + name string + new func() (func(context.Context) error, error) + }{ + { + name: "callback handler", + new: func() (func(context.Context) error, error) { + _, shutdown, err := NewTLSCallbackHandler(cfg) + return shutdown, err + }, + }, + { + name: "callback handler with options", + new: func() (func(context.Context) error, error) { + _, shutdown, err := NewTLSCallbackHandlerWithOptions(cfg, WithAggrMessageOutput(false)) + return shutdown, err + }, + }, + { + name: "callback handler from env compatible API", + new: func() (func(context.Context) error, error) { + _, shutdown, err := NewTLSCallbackHandlerFromEnv(cfg) + return shutdown, err + }, + }, + { + name: "TLS handler", + new: func() (func(context.Context) error, error) { + _, shutdown, err := NewTLSHandler(cfg) + return shutdown, err + }, + }, + { + name: "TLS handler with options", + new: func() (func(context.Context) error, error) { + _, shutdown, err := NewTLSHandlerWithOptions(cfg, WithAggrMessageOutput(false)) + return shutdown, err + }, + }, + { + name: "TLS handler from env compatible API", + new: func() (func(context.Context) error, error) { + _, shutdown, err := NewTLSHandlerFromEnv(cfg) + return shutdown, err + }, + }, + } + for _, tt := range constructors { + t.Run(tt.name, func(t *testing.T) { + shutdown, err := tt.new() + if err != nil { + t.Fatalf("constructor returned error: %v", err) + } + if shutdown == nil { + t.Fatal("constructor returned nil shutdown") + } + if err := shutdown(context.Background()); err != nil { + t.Fatalf("shutdown returned error: %v", err) + } + }) + } + + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + t.Cleanup(func() { _ = provider.Shutdown(context.Background()) }) + handler := &TLSCallbackHandler{ + Tracer: provider.Tracer(scopeName), + dataParser: NewDefaultDataParser(true), + TLSExporterEnabled: true, + } + info := &callbacks.RunInfo{Name: "weather", Type: "coverage", Component: components.ComponentOfTool} + ctx := handler.OnStart(context.Background(), info, nil) + handler.OnError(ctx, info, errors.New("tool request failed")) + + spans := recorder.Ended() + if len(spans) != 2 { + t.Fatalf("ended spans = %d, want synthetic root plus tool span", len(spans)) + } + for _, span := range spans { + if span.Status().Code != codes.Error { + t.Fatalf("span %q status = %s, want error", span.Name(), span.Status().Code) + } + } +} diff --git a/callbacks/tls/examples/main.go b/callbacks/tls/examples/main.go new file mode 100644 index 000000000..3fceebbc0 --- /dev/null +++ b/callbacks/tls/examples/main.go @@ -0,0 +1,44 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + "log" + + "github.com/cloudwego/eino-ext/callbacks/tls" + "github.com/cloudwego/eino/callbacks" +) + +func main() { + ctx := tls.SetSession(context.Background(), tls.WithSessionID("session-1"), tls.WithUserID("user-1")) + + handler, shutdown, err := tls.NewTLSHandler() + if err != nil { + log.Fatal(err) + } + defer func() { + if shutdown != nil { + _ = shutdown(ctx) + } + }() + + callbacks.AppendGlobalHandlers(handler) + + // Build and run your Eino graph or chain with ctx here. + _ = ctx +} diff --git a/callbacks/tls/go.mod b/callbacks/tls/go.mod new file mode 100644 index 000000000..8dd9fa145 --- /dev/null +++ b/callbacks/tls/go.mod @@ -0,0 +1,70 @@ +module github.com/cloudwego/eino-ext/callbacks/tls + +go 1.23.0 + +require ( + github.com/bytedance/mockey v1.2.14 + github.com/bytedance/sonic v1.15.0 + github.com/cloudwego/eino v0.9.1 + github.com/cloudwego/eino-ext/libs/acl/opentelemetry v0.0.0-20250225080340-5935633151d3 + github.com/smartystreets/goconvey v1.8.1 + github.com/volcengine/volc-sdk-golang v1.0.252 + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/trace v1.34.0 +) + +require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/eino-contrib/jsonschema v1.0.3 // indirect + github.com/go-kit/kit v0.12.0 // indirect + github.com/go-kit/log v0.2.0 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/goph/emperror v0.17.2 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nikolalohinski/gonja v1.5.3 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect + github.com/smarty/assertions v1.15.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/yargevad/filepathx v1.0.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + golang.org/x/arch v0.11.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/callbacks/tls/go.sum b/callbacks/tls/go.sum new file mode 100644 index 000000000..8777f1940 --- /dev/null +++ b/callbacks/tls/go.sum @@ -0,0 +1,1820 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8= +cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= +cloud.google.com/go/accessapproval v1.7.7/go.mod h1:10ZDPYiTm8tgxuMPid8s2DL93BfCt6xBh/Vg0Xd8pU0= +cloud.google.com/go/accesscontextmanager v1.8.6/go.mod h1:rMC0Z8pCe/JR6yQSksprDc6swNKjMEvkfCbaesh+OS0= +cloud.google.com/go/accesscontextmanager v1.8.7/go.mod h1:jSvChL1NBQ+uLY9zUBdPy9VIlozPoHptdBnRYeWuQoM= +cloud.google.com/go/aiplatform v1.67.0/go.mod h1:s/sJ6btBEr6bKnrNWdK9ZgHCvwbZNdP90b3DDtxxw+Y= +cloud.google.com/go/analytics v0.23.2/go.mod h1:vtE3olAXZ6edJYk1UOndEs6EfaEc9T2B28Y4G5/a7Fo= +cloud.google.com/go/apigateway v1.6.7/go.mod h1:7wAMb/33Rzln+PrGK16GbGOfA1zAO5Pq6wp19jtIt7c= +cloud.google.com/go/apigeeconnect v1.6.7/go.mod h1:hZxCKvAvDdKX8+eT0g5eEAbRSS9Gkzi+MPWbgAMAy5U= +cloud.google.com/go/apigeeregistry v0.8.5/go.mod h1:ZMg60hq2K35tlqZ1VVywb9yjFzk9AJ7zqxrysOxLi3o= +cloud.google.com/go/appengine v1.8.7/go.mod h1:1Fwg2+QTgkmN6Y+ALGwV8INLbdkI7+vIvhcKPZCML0g= +cloud.google.com/go/area120 v0.8.7/go.mod h1:L/xTq4NLP9mmxiGdcsVz7y1JLc9DI8pfaXRXbnjkR6w= +cloud.google.com/go/artifactregistry v1.14.9/go.mod h1:n2OsUqbYoUI2KxpzQZumm6TtBgtRf++QulEohdnlsvI= +cloud.google.com/go/asset v1.19.1/go.mod h1:kGOS8DiCXv6wU/JWmHWCgaErtSZ6uN5noCy0YwVaGfs= +cloud.google.com/go/assuredworkloads v1.11.7/go.mod h1:CqXcRH9N0KCDtHhFisv7kk+cl//lyV+pYXGi1h8rCEU= +cloud.google.com/go/auth v0.2.1/go.mod h1:khQRBNrvNoHiHhV1iu2x8fSnlNbCaVHilznW5MAI5GY= +cloud.google.com/go/auth v0.2.2/go.mod h1:2bDNJWtWziDT3Pu1URxHHbkHE/BbOCuyUiKIGcNvafo= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.1/go.mod h1:tOdK/k+D2e4GEwfBRA48dKNQiDsqIXxLh7VU319eV0g= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/automl v1.13.7/go.mod h1:E+s0VOsYXUdXpq0y4gNZpi0A/s6y9+lAarmV5Eqlg40= +cloud.google.com/go/baremetalsolution v1.2.6/go.mod h1:KkS2BtYXC7YGbr42067nzFr+ABFMs6cxEcA1F+cedIw= +cloud.google.com/go/batch v1.8.6/go.mod h1:rQovrciYbtuY40Uprg/IWLlhmUR1GZYzX9xnymUdfBU= +cloud.google.com/go/beyondcorp v1.0.6/go.mod h1:wRkenqrVRtnGFfnyvIg0zBFUdN2jIfeojFF9JJDwVIA= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.61.0/go.mod h1:PjZUje0IocbuTOdq4DBOJLNYB0WF3pAKBHzAYyxCwFo= +cloud.google.com/go/billing v1.18.5/go.mod h1:lHw7fxS6p7hLWEPzdIolMtOd0ahLwlokW06BzbleKP8= +cloud.google.com/go/binaryauthorization v1.8.3/go.mod h1:Cul4SsGlbzEsWPOz2sH8m+g2Xergb6ikspUyQ7iOThE= +cloud.google.com/go/certificatemanager v1.8.1/go.mod h1:hDQzr50Vx2gDB+dOfmDSsQzJy/UPrYRdzBdJ5gAVFIc= +cloud.google.com/go/channel v1.17.7/go.mod h1:b+FkgBrhMKM3GOqKUvqHFY/vwgp+rwsAuaMd54wCdN4= +cloud.google.com/go/cloudbuild v1.16.1/go.mod h1:c2KUANTtCBD8AsRavpPout6Vx8W+fsn5zTsWxCpWgq4= +cloud.google.com/go/clouddms v1.7.6/go.mod h1:8HWZ2tznZ0mNAtTpfnRNT0QOThqn9MBUqTj0Lx8npIs= +cloud.google.com/go/cloudtasks v1.12.8/go.mod h1:aX8qWCtmVf4H4SDYUbeZth9C0n9dBj4dwiTYi4Or/P4= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= +cloud.google.com/go/compute v1.27.0/go.mod h1:LG5HwRmWFKM2C5XxHRiNzkLLXW48WwvyVC0mfWsYPOM= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/contactcenterinsights v1.13.2/go.mod h1:AfkSB8t7mt2sIY6WpfO61nD9J9fcidIchtxm9FqJVXk= +cloud.google.com/go/container v1.35.1/go.mod h1:udm8fgLm3TtpnjFN4QLLjZezAIIp/VnMo316yIRVRQU= +cloud.google.com/go/containeranalysis v0.11.6/go.mod h1:YRf7nxcTcN63/Kz9f86efzvrV33g/UV8JDdudRbYEUI= +cloud.google.com/go/datacatalog v1.20.0/go.mod h1:fSHaKjIroFpmRrYlwz9XBB2gJBpXufpnxyAKaT4w6L0= +cloud.google.com/go/datacatalog v1.20.1/go.mod h1:Jzc2CoHudhuZhpv78UBAjMEg3w7I9jHA11SbRshWUjk= +cloud.google.com/go/dataflow v0.9.7/go.mod h1:3BjkOxANrm1G3+/EBnEsTEEgJu1f79mFqoOOZfz3v+E= +cloud.google.com/go/dataform v0.9.4/go.mod h1:jjo4XY+56UrNE0wsEQsfAw4caUs4DLJVSyFBDelRDtQ= +cloud.google.com/go/datafusion v1.7.7/go.mod h1:qGTtQcUs8l51lFA9ywuxmZJhS4ozxsBSus6ItqCUWMU= +cloud.google.com/go/datalabeling v0.8.7/go.mod h1:/PPncW5gxrU15UzJEGQoOT3IobeudHGvoExrtZ8ZBwo= +cloud.google.com/go/dataplex v1.16.0/go.mod h1:OlBoytuQ56+7aUCC03D34CtoF/4TJ5SiIrLsBdDu87Q= +cloud.google.com/go/dataproc/v2 v2.4.2/go.mod h1:smGSj1LZP3wtnsM9eyRuDYftNAroAl6gvKp/Wk64XDE= +cloud.google.com/go/dataqna v0.8.7/go.mod h1:hvxGaSvINAVH5EJJsONIwT1y+B7OQogjHPjizOFoWOo= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.17.0/go.mod h1:RiRZU0G6VVlIVlv1HRo3vSAPFHULV0ddBNsXO+Sony4= +cloud.google.com/go/datastream v1.10.6/go.mod h1:lPeXWNbQ1rfRPjBFBLUdi+5r7XrniabdIiEaCaAU55o= +cloud.google.com/go/deploy v1.19.0/go.mod h1:BW9vAujmxi4b/+S7ViEuYR65GiEsqL6Mhf5S/9TeDRU= +cloud.google.com/go/dialogflow v1.53.0/go.mod h1:LqAvxq7bXiiGC3/DWIz9XXCxth2z2qpSnBAAmlNOj6U= +cloud.google.com/go/dlp v1.13.0/go.mod h1:5T/dFtKOn2Q3QLnaKjjir7nEGA8K00WaqoKodLkbF/c= +cloud.google.com/go/documentai v1.28.1/go.mod h1:dOMSDsZQoyguECOiT1XeR4PoJeALsXqlJjLIEk+QneY= +cloud.google.com/go/domains v0.9.7/go.mod h1:u/yVf3BgfPJW3QDZl51qTJcDXo9PLqnEIxfGmGgbHEc= +cloud.google.com/go/edgecontainer v1.2.1/go.mod h1:OE2D0lbkmGDVYLCvpj8Y0M4a4K076QB7E2JupqOR/qU= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.6.8/go.mod h1:EHONVDSum2xxG2p+myyVda/FwwvGbY58ZYC4XqI/lDQ= +cloud.google.com/go/eventarc v1.13.6/go.mod h1:QReOaYnDNdjwAQQWNC7nfr63WnaKFUw7MSdQ9PXJYj0= +cloud.google.com/go/filestore v1.8.3/go.mod h1:QTpkYpKBF6jlPRmJwhLqXfJQjVrQisplyb4e2CwfJWc= +cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= +cloud.google.com/go/functions v1.16.2/go.mod h1:+gMvV5E3nMb9EPqX6XwRb646jTyVz8q4yk3DD6xxHpg= +cloud.google.com/go/gkebackup v1.5.0/go.mod h1:eLaf/+n8jEmIvOvDriGjo99SN7wRvVadoqzbZu0WzEw= +cloud.google.com/go/gkeconnect v0.8.7/go.mod h1:iUH1jgQpTyNFMK5LgXEq2o0beIJ2p7KKUUFerkf/eGc= +cloud.google.com/go/gkehub v0.14.7/go.mod h1:NLORJVTQeCdxyAjDgUwUp0A6BLEaNLq84mCiulsM4OE= +cloud.google.com/go/gkemulticloud v1.2.0/go.mod h1:iN5wBxTLPR6VTBWpkUsOP2zuPOLqZ/KbgG1bZir1Cng= +cloud.google.com/go/grafeas v0.3.5/go.mod h1:y54iTBcI+lgUdI+kAPKb8jtPqeTkA2dsYzWSrQtpc5s= +cloud.google.com/go/gsuiteaddons v1.6.7/go.mod h1:u+sGBvr07OKNnOnQiB/Co1q4U2cjo50ERQwvnlcpNis= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/iap v1.9.6/go.mod h1:YiK+tbhDszhaVifvzt2zTEF2ch9duHtp6xzxj9a0sQk= +cloud.google.com/go/ids v1.4.7/go.mod h1:yUkDC71u73lJoTaoONy0dsA0T7foekvg6ZRg9IJL0AA= +cloud.google.com/go/iot v1.7.7/go.mod h1:tr0bCOSPXtsg64TwwZ/1x+ReTWKlQRVXbM+DnrE54yM= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs= +cloud.google.com/go/kms v1.17.1/go.mod h1:DCMnCF/apA6fZk5Cj4XsD979OyHAqFasPuA5Sd0kGlQ= +cloud.google.com/go/language v1.12.5/go.mod h1:w/6a7+Rhg6Bc2Uzw6thRdKKNjnOzfKTJuxzD0JZZ0nM= +cloud.google.com/go/lifesciences v0.9.7/go.mod h1:FQ713PhjAOHqUVnuwsCe1KPi9oAdaTfh58h1xPiW13g= +cloud.google.com/go/logging v1.10.0/go.mod h1:EHOwcxlltJrYGqMGfghSet736KR3hX1MAj614mrMk9I= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.5.0/go.mod h1:0JNuqRShmscVAhIACGtskSAWtqtOoPkwP0YF1oVEchc= +cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +cloud.google.com/go/managedidentities v1.6.7/go.mod h1:UzslJgHnc6luoyx2JV19cTCi2Fni/7UtlcLeSYRzTV8= +cloud.google.com/go/maps v1.10.0/go.mod h1:lbl3+NkLJ88H4qv3rO8KWOHOYhJiOwsqHOAXMHb9seA= +cloud.google.com/go/mediatranslation v0.8.7/go.mod h1:6eJbPj1QJwiCP8R4K413qMx6ZHZJUi9QFpApqY88xWU= +cloud.google.com/go/memcache v1.10.7/go.mod h1:SrU6+QBhvXJV0TA59+B3oCHtLkPx37eqdKmRUlmSE1k= +cloud.google.com/go/metastore v1.13.6/go.mod h1:OBCVMCP7X9vA4KKD+5J4Q3d+tiyKxalQZnksQMq5MKY= +cloud.google.com/go/monitoring v1.19.0/go.mod h1:25IeMR5cQ5BoZ8j1eogHE5VPJLlReQ7zFp5OiLgiGZw= +cloud.google.com/go/networkconnectivity v1.14.6/go.mod h1:/azB7+oCSmyBs74Z26EogZ2N3UcXxdCHkCPcz8G32bU= +cloud.google.com/go/networkmanagement v1.13.2/go.mod h1:24VrV/5HFIOXMEtVQEUoB4m/w8UWvUPAYjfnYZcBc4c= +cloud.google.com/go/networksecurity v0.9.7/go.mod h1:aB6UiPnh/l32+TRvgTeOxVRVAHAFFqvK+ll3idU5BoY= +cloud.google.com/go/notebooks v1.11.5/go.mod h1:pz6P8l2TvhWqAW3sysIsS0g2IUJKOzEklsjWJfi8sd4= +cloud.google.com/go/optimization v1.6.5/go.mod h1:eiJjNge1NqqLYyY75AtIGeQWKO0cvzD1ct/moCFaP2Q= +cloud.google.com/go/orchestration v1.9.2/go.mod h1:8bGNigqCQb/O1kK7PeStSNlyi58rQvZqDiuXT9KAcbg= +cloud.google.com/go/orgpolicy v1.12.2/go.mod h1:XycP+uWN8Fev47r1XibYjOgZod8SjXQtZGsO2I8KXX8= +cloud.google.com/go/orgpolicy v1.12.3/go.mod h1:6BOgIgFjWfJzTsVcib/4QNHOAeOjCdaBj69aJVs//MA= +cloud.google.com/go/osconfig v1.12.6/go.mod h1:2dcXGl5qNbKo6Hjsnqbt5t6H2GX7UCAaPjF6BwDlFq8= +cloud.google.com/go/osconfig v1.12.7/go.mod h1:ID7Lbqr0fiihKMwAOoPomWRqsZYKWxfiuafNZ9j1Y1M= +cloud.google.com/go/oslogin v1.13.3/go.mod h1:WW7Rs1OJQ1iSUckZDilvNBSNPE8on740zF+4ZDR4o8U= +cloud.google.com/go/phishingprotection v0.8.7/go.mod h1:FtYaOyGc/HQQU7wY4sfwYZBFDKAL+YtVBjUj8E3A3/I= +cloud.google.com/go/policytroubleshooter v1.10.5/go.mod h1:bpOf94YxjWUqsVKokzPBibMSAx937Jp2UNGVoMAtGYI= +cloud.google.com/go/privatecatalog v0.9.7/go.mod h1:NWLa8MCL6NkRSt8jhL8Goy2A/oHkvkeAxiA0gv0rIXI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsub v1.38.0/go.mod h1:IPMJSWSus/cu57UyR01Jqa/bNOQA+XnPF6Z4dKW4fAA= +cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0= +cloud.google.com/go/recaptchaenterprise/v2 v2.13.0/go.mod h1:jNYyn2ScR4DTg+VNhjhv/vJQdaU8qz+NpmpIzEE7HFQ= +cloud.google.com/go/recommendationengine v0.8.7/go.mod h1:YsUIbweUcpm46OzpVEsV5/z+kjuV6GzMxl7OAKIGgKE= +cloud.google.com/go/recommender v1.12.3/go.mod h1:OgN0MjV7/6FZUUPgF2QPQtYErtZdZc4u+5onvurcGEI= +cloud.google.com/go/redis v1.15.0/go.mod h1:X9Fp3vG5kqr5ho+5YM6AgJxypn+I9Ea5ANCuFKXLdX0= +cloud.google.com/go/resourcemanager v1.9.7/go.mod h1:cQH6lJwESufxEu6KepsoNAsjrUtYYNXRwxm4QFE5g8A= +cloud.google.com/go/resourcesettings v1.6.7/go.mod h1:zwRL5ZoNszs1W6+eJYMk6ILzgfnTj13qfU4Wvfupuqk= +cloud.google.com/go/retail v1.16.2/go.mod h1:T7UcBh4/eoxRBpP3vwZCoa+PYA9/qWRTmOCsV8DRdZ0= +cloud.google.com/go/run v1.3.7/go.mod h1:iEUflDx4Js+wK0NzF5o7hE9Dj7QqJKnRj0/b6rhVq20= +cloud.google.com/go/scheduler v1.10.8/go.mod h1:0YXHjROF1f5qTMvGTm4o7GH1PGAcmu/H/7J7cHOiHl0= +cloud.google.com/go/secretmanager v1.13.1/go.mod h1:y9Ioh7EHp1aqEKGYXk3BOC+vkhlHm9ujL7bURT4oI/4= +cloud.google.com/go/security v1.17.0/go.mod h1:eSuFs0SlBv1gWg7gHIoF0hYOvcSwJCek/GFXtgO6aA0= +cloud.google.com/go/securitycenter v1.30.0/go.mod h1:/tmosjS/dfTnzJxOzZhTXdX3MXWsCmPWfcYOgkJmaJk= +cloud.google.com/go/servicedirectory v1.11.7/go.mod h1:fiO/tM0jBpVhpCAe7Yp5HmEsmxSUcOoc4vPrO02v68I= +cloud.google.com/go/shell v1.7.7/go.mod h1:7OYaMm3TFMSZBh8+QYw6Qef+fdklp7CjjpxYAoJpZbQ= +cloud.google.com/go/spanner v1.63.0/go.mod h1:iqDx7urZpgD7RekZ+CFvBRH6kVTW1ZSEb2HMDKOp5Cc= +cloud.google.com/go/speech v1.23.1/go.mod h1:UNgzNxhNBuo/OxpF1rMhA/U2rdai7ILL6PBXFs70wq0= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= +cloud.google.com/go/storagetransfer v1.10.6/go.mod h1:3sAgY1bx1TpIzfSzdvNGHrGYldeCTyGI/Rzk6Lc6A7w= +cloud.google.com/go/talent v1.6.8/go.mod h1:kqPAJvhxmhoUTuqxjjk2KqA8zUEeTDmH+qKztVubGlQ= +cloud.google.com/go/texttospeech v1.7.7/go.mod h1:XO4Wr2VzWHjzQpMe3gS58Oj68nmtXMyuuH+4t0wy9eA= +cloud.google.com/go/tpu v1.6.7/go.mod h1:o8qxg7/Jgt7TCgZc3jNkd4kTsDwuYD3c4JTMqXZ36hU= +cloud.google.com/go/trace v1.10.7/go.mod h1:qk3eiKmZX0ar2dzIJN/3QhY2PIFh1eqcIdaN5uEjQPM= +cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs= +cloud.google.com/go/video v1.20.6/go.mod h1:d5AOlIfWXpDg15wvztHmjFvKTTImWJU7EnMVWkoiEAk= +cloud.google.com/go/videointelligence v1.11.7/go.mod h1:iMCXbfjurmBVgKuyLedTzv90kcnppOJ6ttb0+rLDID0= +cloud.google.com/go/vision/v2 v2.8.2/go.mod h1:BHZA1LC7dcHjSr9U9OVhxMtLKd5l2jKPzLRALEJvuaw= +cloud.google.com/go/vmmigration v1.7.7/go.mod h1:qYIK5caZY3IDMXQK+A09dy81QU8qBW0/JDTc39OaKRw= +cloud.google.com/go/vmwareengine v1.1.3/go.mod h1:UoyF6LTdrIJRvDN8uUB8d0yimP5A5Ehkr1SRzL1APZw= +cloud.google.com/go/vpcaccess v1.7.7/go.mod h1:EzfSlgkoAnFWEMznZW0dVNvdjFjEW97vFlKk4VNBhwY= +cloud.google.com/go/webrisk v1.9.7/go.mod h1:7FkQtqcKLeNwXCdhthdXHIQNcFWPF/OubrlyRcLHNuQ= +cloud.google.com/go/websecurityscanner v1.6.7/go.mod h1:EpiW84G5KXxsjtFKK7fSMQNt8JcuLA8tQp7j0cyV458= +cloud.google.com/go/workflows v1.12.6/go.mod h1:oDbEHKa4otYg4abwdw2Z094jB0TLLiFGAPA78EDAKag= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y= +github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +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/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= +github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= +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.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +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/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +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/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/mockey v1.2.14 h1:KZaFgPdiUwW+jOWFieo3Lr7INM1P+6adO3hxZhDswY8= +github.com/bytedance/mockey v1.2.14/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +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/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cloudwego/eino v0.9.1 h1:eSwgXfsaxmgTXsTgWi9OMBcm8hKvVhb1q0PPk58p6f8= +github.com/cloudwego/eino v0.9.1/go.mod h1:OBD1mrkfkt/pJa4rkg1P0VnaMeOVl7l8IAdEqY//3IQ= +github.com/cloudwego/eino-ext/libs/acl/opentelemetry v0.0.0-20250225080340-5935633151d3 h1:p1hlOXmAj1yIhJl3JRvwP+9WtEhuOnn6H+lIXIMeDzU= +github.com/cloudwego/eino-ext/libs/acl/opentelemetry v0.0.0-20250225080340-5935633151d3/go.mod h1:YeW4PJOQPzvjZWRnSXotbllWZaIu3drWRzRTpELoc80= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/eino-contrib/jsonschema v1.0.3 h1:2Kfsm1xlMV0ssY2nuxshS4AwbLFuqmPmzIjLVJ1Fsp0= +github.com/eino-contrib/jsonschema v1.0.3/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +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.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +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-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= +github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= +github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +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-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= +github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.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.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +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-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-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +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-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.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/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.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +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/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +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/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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.8/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.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.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +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.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= +github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c= +github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/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_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +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.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +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/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +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/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +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/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/volcengine/volc-sdk-golang v1.0.252 h1:Gtyj+E0k5KV2oQhHs47Op4uMQhR+RYGwhpEHkukwk/4= +github.com/volcengine/volc-sdk-golang v1.0.252/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= +github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/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= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.einride.tech/aip v0.67.1/go.mod h1:ZGX4/zKw8dcgzdLsrvpOOGxfxI2QSk12SlP7d6c0/XI= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +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/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +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.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +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/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= +golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20190130150945-aca44879d564/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/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-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +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= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= +google.golang.org/api v0.125.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= +google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.167.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8= +google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= +google.golang.org/api v0.175.0/go.mod h1:Rra+ltKu14pps/4xTycZfobMgLpbosoaaL7c+SEMrO8= +google.golang.org/api v0.176.1/go.mod h1:j2MaSDYcvYV1lkZ1+SMW4IeF90SrEyFA+tluDYWRrFg= +google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= +google.golang.org/api v0.178.0/go.mod h1:84/k2v8DFpDRebpGcooklv/lais3MEfqpaBLA12gl2U= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= +google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= +google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e/go.mod h1:LweJcLbyVij6rCex8YunD8DYR5VDonap/jYl3ZRxcIU= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:ZSvZ8l+AWJwXw91DoTjWjaVLpWU6o0eZ4YLYpH8aLeQ= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240205150955-31a09d347014/go.mod h1:EhZbXt+eY4Yr3YVaEGLdNZF5viWowOJZ8KTPqjYMKzg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:om8Bj876Z0v9ei+RD1LnEWig7vpHQ371PUqsgjmLQEA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240311132316-a219d84964c2/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240318140521-94a12d6c2237/go.mod h1:IN9OQUXZ0xT+26MDwZL8fJcYw+y99b0eYPA2U15Jt8o= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:IN9OQUXZ0xT+26MDwZL8fJcYw+y99b0eYPA2U15Jt8o= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240429193739-8cf5692501f6/go.mod h1:ULqtoQMxDLNRfW+pJbKA68wtIy1OiYjdIsJs3PMpzh8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228201840-1f18d85a4ec2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +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.3/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.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= +modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= +modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= +modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= +modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= +modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= +modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= +modernc.org/libc v1.22.4/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/callbacks/tls/option.go b/callbacks/tls/option.go new file mode 100644 index 000000000..ad1ca0bc2 --- /dev/null +++ b/callbacks/tls/option.go @@ -0,0 +1,61 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import "reflect" + +type options struct { + parser CallbackDataParser + concatFuncs map[reflect.Type]any + enableAggrOutput bool +} + +type Option func(*options) + +func newOptions(opts ...Option) *options { + o := &options{ + enableAggrOutput: true, + } + + for _, opt := range opts { + opt(o) + } + + return o +} + +func WithCallbackDataParser(parser CallbackDataParser) Option { + return func(o *options) { + o.parser = parser + } +} + +func WithConcatFunction[T any](fn func([]T) (T, error)) Option { + return func(o *options) { + if o.concatFuncs == nil { + o.concatFuncs = make(map[reflect.Type]any) + } + + o.concatFuncs[reflect.TypeOf((*T)(nil)).Elem()] = fn + } +} + +func WithAggrMessageOutput(enable bool) Option { + return func(o *options) { + o.enableAggrOutput = enable + } +} diff --git a/callbacks/tls/semconv/spec.go b/callbacks/tls/semconv/spec.go new file mode 100644 index 000000000..75444c9c2 --- /dev/null +++ b/callbacks/tls/semconv/spec.go @@ -0,0 +1,177 @@ +/* + * Copyright 2026 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package semconv + +import "encoding/json" + +// ModelInput is the input for model span, for tag key: input +type ModelInput struct { + Messages []*ModelMessage `json:"messages,omitempty"` + Tools []*ModelTool `json:"tools,omitempty"` + ModelToolChoice *ModelToolChoice `json:"tool_choice,omitempty"` +} + +// ModelOutput is the output for model span, for tag key: output +type ModelOutput struct { + Choices []*ModelChoice `json:"choices"` +} + +// ModelCallOption is the option for model span, for tag key: call_options +type ModelCallOption struct { + Temperature float32 `json:"temperature"` + MaxTokens int64 `json:"max_tokens,omitempty"` + Stop []string `json:"stop,omitempty"` + TopP float32 `json:"top_p,omitempty"` + N int64 `json:"n,omitempty"` + TopK *int64 `json:"top_k,omitempty"` + PresencePenalty *float32 `json:"presence_penalty,omitempty"` + FrequencyPenalty *float32 `json:"frequency_penalty,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` +} + +type ModelMessage struct { + Role string `json:"role"` // from enum VRole in span_value + Content string `json:"content,omitempty"` // single content + ReasoningContent string `json:"reasoning_content,omitempty"` // only for output + Parts []*ModelMessagePart `json:"parts,omitempty"` // multi-modality content + Name string `json:"name,omitempty"` + ToolCalls []*ModelToolCall `json:"tool_calls,omitempty"` + ToolCallID string `json:"tool_call_id,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +type ModelMessagePart struct { + Type ModelMessagePartType `json:"type"` // Required. The type of the content. + Text string `json:"text,omitempty"` + ImageURL *ModelImageURL `json:"image_url,omitempty"` + AudioURL *ModelMediaURL `json:"audio_url,omitempty"` + VideoURL *ModelMediaURL `json:"video_url,omitempty"` + FileURL *ModelFileURL `json:"file_url,omitempty"` +} + +type ModelMessagePartType string + +var ( + ModelMessagePartTypeText ModelMessagePartType = "text" + ModelMessagePartTypeImage ModelMessagePartType = "image_url" + ModelMessagePartTypeAudio ModelMessagePartType = "audio_url" + ModelMessagePartTypeVideo ModelMessagePartType = "video_url" + ModelMessagePartTypeFile ModelMessagePartType = "file_url" + ModelMessagePartTypeReasoning ModelMessagePartType = "reasoning" +) + +type ModelImageURL struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + Detail string `json:"detail,omitempty"` + MIMEType string `json:"mime_type,omitempty"` +} + +// ModelMediaURL is the common representation used for audio and video parts. +type ModelMediaURL struct { + URL string `json:"url,omitempty"` + MIMEType string `json:"mime_type,omitempty"` +} + +type ModelFileURL struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + Detail string `json:"detail,omitempty"` + Suffix string `json:"suffix,omitempty"` + MIMEType string `json:"mime_type,omitempty"` +} + +type ModelToolCall struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` // Always be: "function" + Function *ModelToolCallFunction `json:"function"` +} + +type ModelToolCallFunction struct { + Name string `json:"name"` + Arguments string `json:"arguments,omitempty"` +} + +type ModelTool struct { + Type string `json:"type"` // Always be: "function" + Function *ModelToolFunction `json:"function"` +} + +type ModelToolFunction struct { + Name string `json:"name"` + Description string `json:"description"` + Parameters json.RawMessage `json:"parameters"` +} + +type ModelChoice struct { + FinishReason string `json:"finish_reason"` + Index int64 `json:"index"` + Message *ModelMessage `json:"message"` +} + +type ModelToolChoice struct { + Type string `json:"type"` // from enum VToolChoice in span_value + Function *ModelToolCallFunction `json:"function,omitempty"` // field name only. +} + +// PromptInput is the input of prompt span, for tag key: input +type PromptInput struct { + Templates []*ModelMessage `json:"templates"` + Arguments []*PromptArgument `json:"arguments"` +} + +type PromptArgument struct { + Key string `json:"key"` + Value any `json:"value"` + Source string `json:"source"` // from enum VPromptArgSource in span_value.go + ValueType PromptArgumentValueType `json:"value_type"` +} + +type PromptArgumentValueType string + +var ( + PromptArgumentValueTypeText PromptArgumentValueType = "text" + PromptArgumentValueTypeModelMessage PromptArgumentValueType = "model_message" + PromptArgumentValueTypeMessagePart PromptArgumentValueType = "model_message_part" +) + +// PromptOutput is the output of prompt span, for tag key: output +type PromptOutput struct { + Prompts []*ModelMessage `json:"prompts"` +} + +type RetrieverInput struct { + Query string `json:"query,omitempty"` +} + +type RetrieverOutput struct { + Documents []*RetrieverDocument `json:"documents,omitempty"` +} + +type RetrieverDocument struct { + ID string `json:"id,omitempty"` + Index string `json:"index,omitempty"` + Content string `json:"content"` + Vector []float64 `json:"vector,omitempty"` + Score float64 `json:"score"` +} + +type RetrieverCallOption struct { + TopK int64 `json:"top_k,omitempty"` + MinScore *float64 `json:"min_score,omitempty"` + Filter string `json:"filter,omitempty"` +} diff --git a/callbacks/tls/semconv/trace.go b/callbacks/tls/semconv/trace.go new file mode 100644 index 000000000..d0a11cc45 --- /dev/null +++ b/callbacks/tls/semconv/trace.go @@ -0,0 +1,211 @@ +/* + * Copyright 2026 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package semconv + +const ( + EinoImportPath = "github.com/cloudwego/eino" +) + +const ( + GEN_AI_USAGE_COMPLETION_TOKENS = "gen_ai.usage.completion_tokens" + GEN_AI_USAGE_PROMPT_TOKENS = "gen_ai.usage.prompt_tokens" + GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens" + GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens" + GEN_AI_REQUEST_MAX_TOKENS = "gen_ai.request.max_tokens" + GEN_AI_REQUEST_TOP_P = "gen_ai.request.top_p" + GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature" + GEN_AI_REQUEST_STOP = "gen_ai.request.stop" + GEN_AI_RESPONSE_MODEL = "gen_ai.response.model" + GEN_AI_REQUEST_ID = "gen_ai.request.id" + GEN_AI_REQUEST_N = "gen_ai.request.n" + GEN_AI_REQUEST_DURATION_MS = "gen_ai.request.duration_ms" + GEN_AI_USAGE_NUM_SEQUENCES = "gen_ai.usage.num_sequences" + GEN_AI_LATENCY_TIME_IN_QUEUE = "gen_ai.latency.time_in_queue" + GEN_AI_LATENCY_TIME_TO_FIRST_TOKEN = "gen_ai.latency.time_to_first_token" + GEN_AI_LATENCY_E2E = "gen_ai.latency.e2e" +) + +const ( + GEN_AI_TOKEN_TYPE = "gen_ai.token.type" + GEN_AI_INPUT = "input.value" + GEN_AI_OUTPUT = "output.value" + GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages" + GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages" + OUTPUT_MIME_TYPE = "output.mime_type" + INPUT_VALUE = "input.value" + INPUT_MIME_TYPE = "input.mime_type" + EMBEDDING_EMBEDDINGS = "embedding.embeddings" + GEN_AI_PROMPT = "gen_ai.prompt" + GEN_AI_COMPLETION = "gen_ai.completion" + GEN_AI_PROMPT_TEMPLATE = "gen_ai.prompt_template.template" + GEN_AI_PROMPT_VARIABLES = "gen_ai.prompt_template.variables" + GEN_AI_PROMPT_VERSION = "gen_ai.prompt_template.version" + + GEN_AI_SYSTEM = "gen_ai.system" + TASK_NAME = "task.name" + TLS_APP_TYPE = "tls.app.type" + + GEN_AI_OPERATION_NAME = "gen_ai.operation.name" + GEN_AI_PROVIDER_NAME = "gen_ai.provider.name" + + GEN_AI_SPAN_KIND = "gen_ai.span.kind" + + GEN_AI_SPAN_SUB_KIND = "gen_ai.span.sub_kind" + + SESSION_ID = "session.id" + GEN_AI_SESSION_ID = "gen_ai.session.id" + + USER_ID = "user.id" + GEN_AI_USER_ID = "gen_ai.user.id" + + GEN_AI_USER_NAME = "gen_ai.user.name" + + GEN_AI_FRAMEWORK = "gen_ai.framework" + + GEN_AI_USAGE_TOTAL_TOKENS = "gen_ai.usage.total_tokens" + + GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read_input_tokens" + GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_V2 = "gen_ai.usage.cache_read.input_tokens" + GEN_AI_USAGE_CACHED_TOKENS = "gen_ai.usage.cached_tokens" + GEN_AI_USAGE_CACHE_CREATE_INPUT_TOKENS = "gen_ai.usage.cache_create_input_tokens" + GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation.input_tokens" + GEN_AI_USAGE_REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens" + + GEN_AI_REQUEST_IS_STREAM = "gen_ai.request.is_stream" + GEN_AI_IS_STREAMING = "gen_ai.is_streaming" + + GEN_AI_REQUEST_MODEL = "gen_ai.request.model" + + GEN_AI_REQUEST_STOP_SEQUENCES = "gen_ai.request.stop_sequences" + + GEN_AI_REQUEST_TOOL_DEFINITIONS = "gen_ai.tool.definitions" + + GEN_AI_REQUEST_PARAMETERS = "gen_ai.request.parameters" + + GEN_AI_RESPONSE_FINISH_REASON = "gen_ai.response.finish_reason" + + GEN_AI_REASONING_CONTENT = "gen_ai.response.reasoning_content" + + GEN_AI_USER_TIME_TO_FIRST_TOKEN = "gen_ai.user.time_to_first_token" + + GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN = "gen_ai.response.time_to_first_token" + + GEN_AI_RESPONSE_TIME_OF_FIRST_TOKEN = "gen_ai.response.time_of_first_token" + + GEN_AI_MODEL_NAME = "gen_ai.model_name" + + VOLC_TRACE_FLAG = "volc.trace.flag" + + CONTENT = "content" +) + +const ( + MESSAGE_ROLE = "message.role" + MESSAGE_CONTENT = "message.content" + MESSAGE_NAME = "message.name" + MESSAGE_TOOL_CALLS = "message.tool_calls" + MESSAGE_FUNCTION_CALL_NAME = "message.function_call_name" + MESSAGE_FUNCTION_CALL_ARGUMENTS_JSON = "message.function_call_arguments_json" +) + +const ( + DOCUMENT_ID = "document.id" + + DOCUMENT_SCORE = "document.score" + + DOCUMENT_CONTENT = "document.content" + + DOCUMENT_METADATA = "document.metadata" +) + +const ( + RERANKER_INPUT_DOCUMENTS = "reranker.input_documents" + RERANKER_OUTPUT_DOCUMENTS = "reranker.output_documents" + RERANKER_QUERY = "reranker.query" + RERANKER_MODEL_NAME = "reranker.model_name" + RERANKER_TOP_K = "reranker.top_k" +) + +const ( + GEN_AI_RETRIEVAL_QUERY = "gen_ai.retrieval.query" + GEN_AI_RETRIEVAL_DOCUMENTS = "gen_ai.retrieval.documents" +) + +const ( + EMBEDDING_TEXT = "embedding.text" + EMBEDDING_VECTOR = "embedding.vector" + EMBEDDING_VECTOR_SIZE = "embedding.vector_size" + GEN_AI_EMBEDDINGS_DIMENSION_COUNT = "gen_ai.embeddings.dimension.count" +) + +const ( + GEN_AI_TOOL_CALL_ID = "gen_ai.tool_call.id" + GEN_AI_TOOL_DESCRIPTION = "gen_ai.tool.description" + GEN_AI_TOOL_NAME = "gen_ai.tool.name" + GEN_AI_TOOL_TYPE = "gen_ai.tool.type" + GEN_AI_TOOL_CALL_ARGUMENTS = "gen_ai.tool.call.arguments" + GEN_AI_TOOL_CALL_RESULT = "gen_ai.tool.call.result" + TOOL_SUCCESS = "tool.success" + TOOL_ERROR = "tool.error" + TOOL_CALL_FUNCTION_NAME = "tool_call.function.name" + TOOL_CALL_FUNCTION_ARGUMENTS = "tool_call.function.arguments" + TOOL_CALL_FUNCTION_DESCRIPTION = "tool_call.function.description" + TOOL_CALL_FUNCTION_THOUGHTS = "tool_call.function.thoughts" +) + +const ( + AGENT_ACTION = "agent.action" + AGENT_ACTION_INPUT = "agent.action_input" + AGENT_INTERMEDIATE_STEPS = "agent.intermediate_steps" + AGENT_PLAN = "agent.plan" +) + +const ( + MCP_METHOD_NAME = "mcp.method.name" + MCP_METHOD_ARGUMENTS_JSON = "mcp.method.arguments" + MCP_METHOD_DESCRIPTION = "mcp.method.description" +) + +const ( + XTRACE = "xtrace" + TLS = "tls" + LLAMA_INDEX = "llama_index" + + BUFFER = "buffer" + VECTOR = "vector" + KVDB = "kv-db" + SQLDB = "sql-db" + + CHAT = "chat" + COMPLETION = "completion" + WORKFLOW = "workflow" + + TEXT = "text/plain" + JSON = "application/json" +) + +const ( + GEN_AI_SPAN_KIND_LLM = "LLM" + GEN_AI_SPAN_KIND_TOOL = "TOOL" + GEN_AI_SPAN_KIND_CHAIN = "CHAIN" + GEN_AI_SPAN_KIND_RETRIEVER = "RETRIEVER" + GEN_AI_SPAN_KIND_EMBEDDING = "EMBEDDING" + GEN_AI_SPAN_KIND_AGENT = "AGENT" + GEN_AI_SPAN_KIND_RERANKER = "RERANKER" + GEN_AI_SPAN_KIND_TASK = "TASK" + GEN_AI_SPAN_KIND_DOCUMENT = "DOCUMENT" +) diff --git a/callbacks/tls/session.go b/callbacks/tls/session.go new file mode 100644 index 000000000..85a231ec3 --- /dev/null +++ b/callbacks/tls/session.go @@ -0,0 +1,47 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import "context" + +type sessionOptions struct { + UserID string + SessionID string +} + +type tlsSessionOptionKey struct{} + +func SetSession(ctx context.Context, opts ...SessionOption) context.Context { + options := &sessionOptions{} + for _, opt := range opts { + opt(options) + } + return context.WithValue(ctx, tlsSessionOptionKey{}, options) +} + +type SessionOption func(*sessionOptions) + +func WithUserID(userID string) SessionOption { + return func(o *sessionOptions) { + o.UserID = userID + } +} +func WithSessionID(sessionID string) SessionOption { + return func(o *sessionOptions) { + o.SessionID = sessionID + } +} diff --git a/callbacks/tls/session_test.go b/callbacks/tls/session_test.go new file mode 100644 index 000000000..bad9b2195 --- /dev/null +++ b/callbacks/tls/session_test.go @@ -0,0 +1,59 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "testing" + + "github.com/bytedance/mockey" + "github.com/smartystreets/goconvey/convey" +) + +// Test_SetSession tests the SetSession function +func Test_SetSession(t *testing.T) { + mockey.PatchConvey("Test SetSession function", t, func() { + mockey.PatchConvey("No SessionOption parameters", func() { + // Initialize a context + ctx := context.Background() + // Call the function under test + newCtx := SetSession(ctx) + // Get sessionOptions from context + options, ok := newCtx.Value(tlsSessionOptionKey{}).(*sessionOptions) + // Assert retrieval success + convey.So(ok, convey.ShouldBeTrue) + // Assert UserID is empty + convey.So(options.UserID, convey.ShouldEqual, "") + // Assert SessionID is empty + convey.So(options.SessionID, convey.ShouldEqual, "") + }) + + mockey.PatchConvey("With SessionOption parameters", func() { + // Initialize a context + ctx := context.Background() + // Call the function with SessionOption parameters + newCtx := SetSession(ctx, WithUserID("testUser"), WithSessionID("testSession")) + // Get sessionOptions from context + options, ok := newCtx.Value(tlsSessionOptionKey{}).(*sessionOptions) + // Assert retrieval success + convey.So(ok, convey.ShouldBeTrue) + // Assert UserID, SessionID matches + convey.So(options.UserID, convey.ShouldEqual, "testUser") + convey.So(options.SessionID, convey.ShouldEqual, "testSession") + }) + }) +} diff --git a/callbacks/tls/tls_exporter.go b/callbacks/tls/tls_exporter.go new file mode 100644 index 000000000..7f60af832 --- /dev/null +++ b/callbacks/tls/tls_exporter.go @@ -0,0 +1,305 @@ +/* + * Copyright 2026 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "sync" + + "github.com/cloudwego/eino-ext/libs/acl/opentelemetry" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/volcengine/volc-sdk-golang/service/tls/pb" + "github.com/volcengine/volc-sdk-golang/service/tls/producer" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + oteltrace "go.opentelemetry.io/otel/trace" +) + +const ( + tlsAppType = "eino" + tlsRootSpanName = "agent.turn" + tlsModelSpanName = "llm.request" + tlsToolSpanName = "tool.call" + tlsTraceFileName = "eino-trace" +) + +// tlsLogExporter converts finished OpenTelemetry spans into the TLS trace +// envelope and publishes them through TLS Producer SendLogs. +type tlsLogExporter struct { + producer tlsLogProducer + topicID string + source string +} + +type tlsLogProducer interface { + SendLog(shardHash, topic, source, filename string, log *pb.Log, callback producer.CallBack) error + Close() +} + +type tlsProducerCallback struct { + once sync.Once + done chan error +} + +func newTLSProducerCallback() *tlsProducerCallback { + return &tlsProducerCallback{done: make(chan error, 1)} +} + +func (c *tlsProducerCallback) Success(_ *producer.Result) { + c.once.Do(func() { c.done <- nil }) +} + +func (c *tlsProducerCallback) Fail(result *producer.Result) { + c.once.Do(func() { c.done <- tlsProducerResultError(result) }) +} + +func tlsProducerResultError(result *producer.Result) error { + if result == nil || len(result.Attempts) == 0 { + return fmt.Errorf("TLS Producer SendLog failed without an attempt result") + } + last := result.Attempts[len(result.Attempts)-1] + return fmt.Errorf("TLS Producer SendLog failed: request_id=%s error_code=%s error_message=%s", last.RequestId, last.ErrorCode, last.ErrorMessage) +} + +func (e *tlsLogExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { + if len(spans) == 0 { + return nil + } + if ctx == nil { + ctx = context.Background() + } + + callbacks := make([]*tlsProducerCallback, 0, len(spans)) + for _, span := range spans { + log, err := spanToTLSLog(span) + if err != nil { + return err + } + callback := newTLSProducerCallback() + if err := e.producer.SendLog("", e.topicID, e.source, tlsTraceFileName, log, callback); err != nil { + return fmt.Errorf("enqueue TLS span: %w", err) + } + callbacks = append(callbacks, callback) + } + + for _, callback := range callbacks { + select { + case err := <-callback.done: + if err != nil { + return err + } + case <-ctx.Done(): + return fmt.Errorf("wait for TLS Producer SendLog callback: %w", ctx.Err()) + } + } + return nil +} + +func (e *tlsLogExporter) Shutdown(_ context.Context) error { + if e != nil && e.producer != nil { + e.producer.Close() + } + return nil +} + +func newTLSLogProvider(cfg *TLSConfig) (*opentelemetry.OtelProvider, error) { + exporter, err := newTLSLogExporter(cfg) + if err != nil { + return nil, err + } + + res, err := resource.New( + context.Background(), + resource.WithHost(), + resource.WithFromEnv(), + resource.WithProcessPID(), + resource.WithTelemetrySDK(), + resource.WithAttributes( + semconv.ServiceNameKey.String(cfg.AppName), + attribute.String("tls.business_type", "gen_ai"), + attribute.String("tls.app.type", tlsAppType), + ), + ) + if err != nil { + return nil, fmt.Errorf("create OpenTelemetry resource: %w", err) + } + + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(res), + sdktrace.WithBatcher(exporter), + ) + return &opentelemetry.OtelProvider{TracerProvider: tracerProvider}, nil +} + +func newTLSLogExporter(cfg *TLSConfig) (*tlsLogExporter, error) { + if cfg == nil { + return nil, fmt.Errorf("TLS config cannot be nil") + } + + producerConfig := producer.GetDefaultProducerConfig() + producerConfig.Endpoint = normalizeTLSProducerEndpoint(cfg.TLSLogEndpoint) + producerConfig.Region = cfg.TLSLogRegion + producerConfig.NoRetryStatusCodeList = []int{400, 401, 403, 404} + if cfg.TLSLogAPIKey != "" { + producerConfig.APIKey = cfg.TLSLogAPIKey + } else { + producerConfig.AccessKeyID = cfg.TLSLogAccessKeyID + producerConfig.AccessKeySecret = cfg.TLSLogAccessKeySecret + } + + source, err := os.Hostname() + if err != nil || source == "" { + source = "127.0.0.1" + } + + p := producer.NewProducer(producerConfig) + p.Start() + return &tlsLogExporter{producer: p, topicID: cfg.TLSLogTopicID, source: source}, nil +} + +func normalizeTLSProducerEndpoint(endpoint string) string { + endpoint = strings.TrimSpace(endpoint) + endpoint = strings.TrimRight(endpoint, "/") + if endpoint == "" || strings.HasPrefix(endpoint, "https://") || strings.HasPrefix(endpoint, "http://") { + return endpoint + } + return "https://" + endpoint +} + +func spanToTLSLog(span sdktrace.ReadOnlySpan) (*pb.Log, error) { + attributesJSON, err := json.Marshal(otelAttributesToMap(span.Attributes())) + if err != nil { + return nil, fmt.Errorf("serialize span attributes: %w", err) + } + resourceJSON, err := json.Marshal(otelAttributesToMap(span.Resource().Attributes())) + if err != nil { + return nil, fmt.Errorf("serialize span resource: %w", err) + } + + status := "OK" + if span.Status().Code == codes.Error { + status = "ERROR" + } + resourceAttrs := otelAttributesToMap(span.Resource().Attributes()) + serviceName, _ := resourceAttrs["service.name"].(string) + host, _ := resourceAttrs["host.name"].(string) + scope := span.InstrumentationScope() + // OpenTelemetry represents a root span's missing parent as the all-zero + // SpanID. TLS TraceTableV2 identifies root spans with an empty + // ParentSpanID, so preserve that distinction at the transport boundary. + parentSpanID := "" + if parent := span.Parent(); parent.IsValid() { + parentSpanID = parent.SpanID().String() + } + + contents := map[string]string{ + "TraceID": span.SpanContext().TraceID().String(), + "SpanID": span.SpanContext().SpanID().String(), + "ParentSpanID": parentSpanID, + "Name": span.Name(), + "Kind": tlsSpanKind(span.SpanKind()), + "Start": fmt.Sprintf("%d", span.StartTime().UnixMicro()), + "End": fmt.Sprintf("%d", span.EndTime().UnixMicro()), + "Duration": fmt.Sprintf("%d", span.EndTime().Sub(span.StartTime()).Microseconds()), + "StatusCode": status, + "StatusDescription": span.Status().Description, + "Attributes": string(attributesJSON), + "Resource": string(resourceJSON), + "ServiceName": serviceName, + "Host": host, + "OTLPName": scope.Name, + "OTLPVersion": scope.Version, + "Events": "[]", + "Links": "[]", + "TraceState": span.SpanContext().TraceState().String(), + } + logContents := make([]*pb.LogContent, 0, len(contents)) + for key, value := range contents { + logContents = append(logContents, &pb.LogContent{Key: key, Value: value}) + } + return &pb.Log{Time: span.StartTime().Unix(), Contents: logContents}, nil +} + +func otelAttributesToMap(attributes []attribute.KeyValue) map[string]any { + result := make(map[string]any, len(attributes)) + for _, kv := range attributes { + result[string(kv.Key)] = kv.Value.AsInterface() + } + return result +} + +func tlsSpanName(ctx context.Context, info *callbacks.RunInfo) string { + if info == nil { + return tlsRootSpanName + } + switch info.Component { + case components.ComponentOfChatModel: + return tlsModelSpanName + case components.ComponentOfTool: + return tlsToolSpanName + default: + if ctx.Value(TLSStateKey{}) == nil { + return tlsRootSpanName + } + return getSpanName(info) + } +} + +func tlsSpanKind(kind oteltrace.SpanKind) string { + switch kind { + case oteltrace.SpanKindServer: + return "server" + case oteltrace.SpanKindClient: + return "client" + case oteltrace.SpanKindProducer: + return "producer" + case oteltrace.SpanKindConsumer: + return "consumer" + default: + return "internal" + } +} + +func tlsOperationName(info *callbacks.RunInfo, isRoot bool) string { + if isRoot { + return "invoke_agent" + } + if info == nil { + return "invoke_agent" + } + switch info.Component { + case components.ComponentOfChatModel: + return "chat" + case components.ComponentOfTool: + return "execute_tool" + default: + return "invoke" + } +} + +func (cfg *TLSConfig) usesTLSLogTransport() bool { + return cfg != nil && cfg.TLSExporterEnabled && cfg.TLSLogTopicID != "" +} diff --git a/callbacks/tls/tls_exporter_integration_test.go b/callbacks/tls/tls_exporter_integration_test.go new file mode 100644 index 000000000..36512c07c --- /dev/null +++ b/callbacks/tls/tls_exporter_integration_test.go @@ -0,0 +1,176 @@ +/* + * Copyright 2026 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +// TestTLSExporterE2E sends one complete agent.turn -> llm.request -> tool.call +// trace to a real TLS Trace Topic. It is opt-in so normal package tests never +// require credentials or network access. +func TestTLSExporterE2E(t *testing.T) { + if os.Getenv("TLS_EXPORTER_E2E") != "1" { + t.Skip("set TLS_EXPORTER_E2E=1 with TLS_LOG_* credentials to run") + } + + cfg, err := LoadTLSConfigFromEnv() + if err != nil { + t.Fatalf("load TLS exporter config: %v", err) + } + handler, shutdown, err := NewTLSCallbackHandler(cfg) + if err != nil { + t.Fatalf("create TLS exporter handler: %v", err) + } + t.Cleanup(func() { + if shutdown != nil { + _ = shutdown(context.Background()) + } + }) + + h, ok := handler.(*TLSCallbackHandler) + if !ok { + t.Fatalf("handler type = %T, want *TLSCallbackHandler", handler) + } + sessionID := fmt.Sprintf("eino-tls-exporter-e2e-%d", time.Now().UnixNano()) + rootInfo := &callbacks.RunInfo{Component: compose.ComponentOfLambda, Name: "eino-e2e-agent"} + rootCtx := h.OnStart(SetSession(context.Background(), WithSessionID(sessionID)), rootInfo, "TLS exporter e2e input") + + modelInfo := &callbacks.RunInfo{Component: components.ComponentOfChatModel, Name: "eino-e2e-model"} + modelCtx := h.OnStart(rootCtx, modelInfo, &model.CallbackInput{ + Messages: []*schema.Message{{Role: schema.User, Content: "TLS exporter e2e input"}}, + Config: &model.Config{Model: "eino-e2e-model"}, + }) + h.OnEnd(modelCtx, modelInfo, &model.CallbackOutput{ + Message: &schema.Message{Role: schema.Assistant, Content: "TLS exporter e2e output"}, + TokenUsage: &model.TokenUsage{ + PromptTokens: 17, + CompletionTokens: 8, + TotalTokens: 25, + }, + }) + + toolInfo := &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "eino-e2e-tool"} + toolCtx := h.OnStart(rootCtx, toolInfo, &tool.CallbackInput{}) + h.OnEnd(toolCtx, toolInfo, &tool.CallbackOutput{}) + h.OnEnd(rootCtx, rootInfo, "TLS exporter e2e done") + + if err := shutdown(context.Background()); err != nil { + t.Fatalf("flush TLS exporter: %v", err) + } + shutdown = nil + t.Logf("exported Eino trace session %s", sessionID) +} + +// TestTLSExporterE2EMultimodalMatrix writes one real Trace Topic record set +// through the same handler applications register. It intentionally uses only +// public example URLs: the exporter records media metadata; it does not fetch +// or upload the media itself. +func TestTLSExporterE2EMultimodalMatrix(t *testing.T) { + if os.Getenv("TLS_EXPORTER_E2E") != "1" { + t.Skip("set TLS_EXPORTER_E2E=1 with TLS_LOG_* credentials to run") + } + cfg, err := LoadTLSConfigFromEnv() + if err != nil { + t.Fatalf("load TLS exporter config: %v", err) + } + handler, shutdown, err := NewTLSCallbackHandler(cfg) + if err != nil { + t.Fatalf("create TLS exporter handler: %v", err) + } + t.Cleanup(func() { + if shutdown != nil { + _ = shutdown(context.Background()) + } + }) + h, ok := handler.(*TLSCallbackHandler) + if !ok { + t.Fatalf("handler type = %T, want *TLSCallbackHandler", handler) + } + + imageURL := "https://example.com/eino-e2e-image.png" + videoURL := "https://example.com/eino-e2e-video.mp4" + sessionID := fmt.Sprintf("eino-tls-matrix-%d", time.Now().UnixNano()) + rootInfo := &callbacks.RunInfo{Component: compose.ComponentOfLambda, Name: "eino-e2e-matrix"} + rootCtx := h.OnStart(SetSession(context.Background(), WithSessionID(sessionID)), rootInfo, "Eino E2E matrix") + modelInfo := &callbacks.RunInfo{Component: components.ComponentOfChatModel, Name: "eino-e2e-model"} + + // Text, non-streaming. + textCtx := h.OnStart(rootCtx, modelInfo, &model.CallbackInput{Messages: []*schema.Message{{Role: schema.User, Content: "E2E text non-stream"}}, Config: &model.Config{Model: "eino-e2e-model"}}) + h.OnEnd(textCtx, modelInfo, &model.CallbackOutput{Message: &schema.Message{Role: schema.Assistant, Content: "E2E text non-stream result"}, TokenUsage: &model.TokenUsage{PromptTokens: 5, CompletionTokens: 4, TotalTokens: 9}}) + + // Image + video + text, non-streaming, followed by a real tool-call-shaped + // assistant response and the matching tool callback. + multimodal := &schema.Message{Role: schema.User, UserInputMultiContent: []schema.MessageInputPart{ + {Type: schema.ChatMessagePartTypeText, Text: "E2E multimodal non-stream"}, + {Type: schema.ChatMessagePartTypeImageURL, Image: &schema.MessageInputImage{MessagePartCommon: schema.MessagePartCommon{URL: &imageURL, MIMEType: "image/png"}}}, + {Type: schema.ChatMessagePartTypeVideoURL, Video: &schema.MessageInputVideo{MessagePartCommon: schema.MessagePartCommon{URL: &videoURL, MIMEType: "video/mp4"}}}, + }} + multiCtx := h.OnStart(rootCtx, modelInfo, &model.CallbackInput{Messages: []*schema.Message{multimodal}, Config: &model.Config{Model: "eino-e2e-model"}}) + h.OnEnd(multiCtx, modelInfo, &model.CallbackOutput{Message: &schema.Message{Role: schema.Assistant, ToolCalls: []schema.ToolCall{{ID: "call-e2e-weather", Type: "function", Function: schema.FunctionCall{Name: "weather", Arguments: `{"city":"Guilin"}`}}}}, TokenUsage: &model.TokenUsage{PromptTokens: 12, CompletionTokens: 5, TotalTokens: 17}}) + toolInfo := &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "weather"} + toolCtx := h.OnStart(rootCtx, toolInfo, &tool.CallbackInput{ArgumentsInJSON: `{"city":"Guilin"}`}) + h.OnEnd(toolCtx, toolInfo, &tool.CallbackOutput{Response: `{"temperature":25,"unit":"C"}`}) + + // Image + video + text, streaming. + inReader, inWriter := schema.Pipe[callbacks.CallbackInput](1) + streamCtx := h.OnStartWithStreamInput(rootCtx, modelInfo, inReader) + inWriter.Send(&model.CallbackInput{Messages: []*schema.Message{{Role: schema.User, UserInputMultiContent: []schema.MessageInputPart{ + {Type: schema.ChatMessagePartTypeText, Text: "E2E multimodal stream"}, + {Type: schema.ChatMessagePartTypeImageURL, Image: &schema.MessageInputImage{MessagePartCommon: schema.MessagePartCommon{URL: &imageURL}}}, + {Type: schema.ChatMessagePartTypeVideoURL, Video: &schema.MessageInputVideo{MessagePartCommon: schema.MessagePartCommon{URL: &videoURL}}}, + }}}, Config: &model.Config{Model: "eino-e2e-model"}}, nil) + inWriter.Close() + outReader, outWriter := schema.Pipe[callbacks.CallbackOutput](2) + h.OnEndWithStreamOutput(streamCtx, modelInfo, outReader) + outWriter.Send(&model.CallbackOutput{Message: &schema.Message{Role: schema.Assistant, Content: "E2E stream "}}, nil) + outWriter.Send(&model.CallbackOutput{Message: &schema.Message{Role: schema.Assistant, Content: "result"}, TokenUsage: &model.TokenUsage{PromptTokens: 13, CompletionTokens: 6, TotalTokens: 19}}, nil) + outWriter.Close() + + // Streaming tool input/output gives the exporter fragmented real payloads. + toolInReader, toolInWriter := schema.Pipe[callbacks.CallbackInput](2) + streamToolCtx := h.OnStartWithStreamInput(rootCtx, toolInfo, toolInReader) + toolInWriter.Send(&tool.CallbackInput{ArgumentsInJSON: `{"city":`}, nil) + toolInWriter.Send(&tool.CallbackInput{ArgumentsInJSON: `"Guilin"}`}, nil) + toolInWriter.Close() + toolOutReader, toolOutWriter := schema.Pipe[callbacks.CallbackOutput](2) + h.OnEndWithStreamOutput(streamToolCtx, toolInfo, toolOutReader) + toolOutWriter.Send(&tool.CallbackOutput{Response: `{"temperature":`}, nil) + toolOutWriter.Send(&tool.CallbackOutput{Response: `25}`}, nil) + toolOutWriter.Close() + + // Stream callbacks parse asynchronously; wait until readers are drained + // before ending the root span and flushing the Producer. + time.Sleep(100 * time.Millisecond) + h.OnEnd(rootCtx, rootInfo, "Eino E2E matrix done") + if err := shutdown(context.Background()); err != nil { + t.Fatalf("flush TLS exporter: %v", err) + } + shutdown = nil + t.Logf("exported Eino multimodal matrix session %s", sessionID) +} diff --git a/callbacks/tls/tls_exporter_test.go b/callbacks/tls/tls_exporter_test.go new file mode 100644 index 000000000..e4e73e03a --- /dev/null +++ b/callbacks/tls/tls_exporter_test.go @@ -0,0 +1,804 @@ +/* + * Copyright 2026 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "encoding/json" + "strings" + "testing" + "time" + + sem_ai "github.com/cloudwego/eino-ext/callbacks/tls/semconv" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" + "github.com/volcengine/volc-sdk-golang/service/tls/pb" + "github.com/volcengine/volc-sdk-golang/service/tls/producer" + "go.opentelemetry.io/otel/attribute" + sdkresource "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "go.opentelemetry.io/otel/trace" +) + +type fakeTLSLogProducer struct { + result *producer.Result +} + +func (p *fakeTLSLogProducer) SendLog(_ string, _ string, _ string, _ string, _ *pb.Log, callback producer.CallBack) error { + if p.result != nil && !p.result.SuccessFlag { + callback.Fail(p.result) + return nil + } + callback.Success(&producer.Result{SuccessFlag: true}) + return nil +} + +func (p *fakeTLSLogProducer) Close() {} + +func TestTLSExporterSpanNames(t *testing.T) { + if got := tlsSpanName(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfChatModel}); got != tlsModelSpanName { + t.Fatalf("model span name = %q, want %q", got, tlsModelSpanName) + } + if got := tlsSpanName(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfTool}); got != tlsToolSpanName { + t.Fatalf("tool span name = %q, want %q", got, tlsToolSpanName) + } + if got := tlsSpanName(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfRetriever}); got != tlsRootSpanName { + t.Fatalf("root span name = %q, want %q", got, tlsRootSpanName) + } + ctx := context.WithValue(context.Background(), TLSStateKey{}, &TLSState{}) + if got := tlsSpanName(ctx, &callbacks.RunInfo{Name: "retriever", Component: components.ComponentOfRetriever}); got != "retriever" { + t.Fatalf("nested span name = %q, want retriever", got) + } +} + +func TestTLSExporterSpanKinds(t *testing.T) { + handler := &TLSCallbackHandler{TLSExporterEnabled: true} + if got := handler.spanKind(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfRetriever}); got != trace.SpanKindServer { + t.Fatalf("root span kind = %s, want server", got) + } + if got := handler.spanKind(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfChatModel}); got != trace.SpanKindClient { + t.Fatalf("model span kind = %s, want client", got) + } +} + +func TestTLSModelTokenAliases(t *testing.T) { + tags := make(spanTags) + setModelConfigAndTokenUsage(tags, &model.CallbackOutput{TokenUsage: &model.TokenUsage{ + PromptTokens: 11, + CompletionTokens: 7, + TotalTokens: 18, + PromptTokenDetails: model.PromptTokenDetails{ + CachedTokens: 3, + }, + CompletionTokensDetails: model.CompletionTokensDetails{ + ReasoningTokens: 4, + }, + }}, false) + + for key, want := range map[string]int{ + sem_ai.GEN_AI_USAGE_INPUT_TOKENS: 11, + sem_ai.GEN_AI_USAGE_OUTPUT_TOKENS: 7, + sem_ai.GEN_AI_USAGE_TOTAL_TOKENS: 18, + sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_V2: 3, + sem_ai.GEN_AI_USAGE_CACHED_TOKENS: 3, + sem_ai.GEN_AI_USAGE_CACHE_CREATE_INPUT_TOKENS: 0, + sem_ai.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS: 0, + sem_ai.GEN_AI_USAGE_REASONING_OUTPUT_TOKENS: 4, + } { + if got, ok := tags[key].(int); !ok || got != want { + t.Errorf("%s = %#v, want %d", key, tags[key], want) + } + } +} + +func TestSpanToTLSLog(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + resource := sdkresource.NewWithAttributes("", attribute.String("service.name", "Eino")) + provider := sdktrace.NewTracerProvider( + sdktrace.WithResource(resource), + sdktrace.WithSpanProcessor(recorder), + ) + defer func() { _ = provider.Shutdown(context.Background()) }() + + _, span := provider.Tracer("test").Start(context.Background(), tlsModelSpanName, + trace.WithSpanKind(trace.SpanKindClient), + trace.WithTimestamp(time.Unix(1_700_000_000, 0)), + ) + span.SetAttributes( + attribute.String(sem_ai.TLS_APP_TYPE, tlsAppType), + attribute.String(sem_ai.SESSION_ID, "session-1"), + attribute.Int(sem_ai.GEN_AI_USAGE_INPUT_TOKENS, 11), + ) + span.End(trace.WithTimestamp(time.Unix(1_700_000_001, 0))) + + spans := recorder.Ended() + if len(spans) != 1 { + t.Fatalf("ended spans = %d, want 1", len(spans)) + } + log, err := spanToTLSLog(spans[0]) + if err != nil { + t.Fatalf("spanToTLSLog: %v", err) + } + contents := make(map[string]string, len(log.Contents)) + for _, item := range log.Contents { + contents[item.Key] = item.Value + } + if contents["Name"] != tlsModelSpanName || contents["Kind"] != "client" || contents["StatusCode"] != "OK" { + t.Fatalf("unexpected envelope identity: %#v", contents) + } + if contents["ParentSpanID"] != "" { + t.Fatalf("root ParentSpanID = %q, want empty", contents["ParentSpanID"]) + } + var attrs map[string]any + if err := json.Unmarshal([]byte(contents["Attributes"]), &attrs); err != nil { + t.Fatalf("decode Attributes: %v", err) + } + if attrs[sem_ai.TLS_APP_TYPE] != tlsAppType || attrs[sem_ai.SESSION_ID] != "session-1" || attrs[sem_ai.GEN_AI_USAGE_INPUT_TOKENS] != float64(11) { + t.Fatalf("unexpected TLS exporter attributes: %#v", attrs) + } + if contents["ServiceName"] != "Eino" { + t.Fatalf("ServiceName = %q, want Eino", contents["ServiceName"]) + } +} + +func TestTLSLogExporterWaitsForProducerCallback(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + defer func() { _ = provider.Shutdown(context.Background()) }() + _, span := provider.Tracer("test").Start(context.Background(), tlsRootSpanName) + span.End() + spans := recorder.Ended() + + exporter := &tlsLogExporter{producer: &fakeTLSLogProducer{}, topicID: "topic", source: "source"} + if err := exporter.ExportSpans(context.Background(), spans); err != nil { + t.Fatalf("ExportSpans() error = %v", err) + } +} + +func TestTLSLogExporterReturnsProducerCallbackFailure(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + defer func() { _ = provider.Shutdown(context.Background()) }() + _, span := provider.Tracer("test").Start(context.Background(), tlsRootSpanName) + span.End() + spans := recorder.Ended() + + exporter := &tlsLogExporter{producer: &fakeTLSLogProducer{result: &producer.Result{ + Attempts: []*producer.Attempt{{ErrorCode: "Forbidden", ErrorMessage: "denied"}}, + }}, topicID: "topic", source: "source"} + if err := exporter.ExportSpans(context.Background(), spans); err == nil { + t.Fatal("ExportSpans() succeeded after a Producer callback failure") + } +} + +func TestTLSRootStateAggregatesModelUsage(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + defer func() { _ = provider.Shutdown(context.Background()) }() + + _, rootSpan := provider.Tracer("test").Start(context.Background(), tlsRootSpanName) + root := &tlsRootState{span: rootSpan} + root.addModelUsage(map[string]any{ + sem_ai.GEN_AI_REQUEST_MODEL: "model-a", + sem_ai.GEN_AI_USAGE_INPUT_TOKENS: 17, + sem_ai.GEN_AI_USAGE_OUTPUT_TOKENS: 8, + sem_ai.GEN_AI_USAGE_TOTAL_TOKENS: 25, + sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_V2: 3, + sem_ai.GEN_AI_USAGE_REASONING_OUTPUT_TOKENS: 4, + }) + rootSpan.End() + + spans := recorder.Ended() + if len(spans) != 1 { + t.Fatalf("ended spans = %d, want 1", len(spans)) + } + attrs := otelAttributesToMap(spans[0].Attributes()) + for key, want := range map[string]any{ + sem_ai.GEN_AI_REQUEST_MODEL: "model-a", + sem_ai.GEN_AI_USAGE_INPUT_TOKENS: int64(17), + sem_ai.GEN_AI_USAGE_OUTPUT_TOKENS: int64(8), + sem_ai.GEN_AI_USAGE_TOTAL_TOKENS: int64(25), + sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_V2: int64(3), + sem_ai.GEN_AI_USAGE_REASONING_OUTPUT_TOKENS: int64(4), + } { + if got := attrs[key]; got != want { + t.Errorf("%s = %#v, want %#v", key, got, want) + } + } +} + +func TestTLSExporterCreatesTraceRootForDirectModel(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + defer func() { _ = provider.Shutdown(context.Background()) }() + + handler := &TLSCallbackHandler{ + Tracer: provider.Tracer(scopeName), + dataParser: NewDefaultDataParser(false), + TLSExporterEnabled: true, + AppName: "Eino", + } + ctx := SetSession(context.Background(), WithSessionID("trace-session")) + ctx = handler.OnStart(ctx, &callbacks.RunInfo{Component: components.ComponentOfChatModel}, &model.CallbackInput{ + Messages: []*schema.Message{{Role: schema.User, Content: "hello"}}, + Config: &model.Config{Model: "demo-model"}, + }) + handler.OnEnd(ctx, &callbacks.RunInfo{Component: components.ComponentOfChatModel}, &model.CallbackOutput{ + Message: &schema.Message{Role: schema.Assistant, Content: "world"}, + TokenUsage: &model.TokenUsage{ + PromptTokens: 3, + CompletionTokens: 2, + TotalTokens: 5, + }, + }) + + spans := recorder.Ended() + if len(spans) != 2 { + t.Fatalf("ended spans = %d, want 2", len(spans)) + } + byName := make(map[string]sdktrace.ReadOnlySpan, len(spans)) + for _, span := range spans { + byName[span.Name()] = span + } + root := byName[tlsRootSpanName] + modelSpan := byName[tlsModelSpanName] + if root == nil || modelSpan == nil { + t.Fatalf("expected %q and %q spans, got %#v", tlsRootSpanName, tlsModelSpanName, byName) + } + if root.Parent().IsValid() { + t.Fatalf("root unexpectedly has parent %s", root.Parent().SpanID()) + } + if modelSpan.Parent().SpanID() != root.SpanContext().SpanID() { + t.Fatalf("model parent = %s, want root %s", modelSpan.Parent().SpanID(), root.SpanContext().SpanID()) + } + attrs := otelAttributesToMap(root.Attributes()) + for key, want := range map[string]any{ + sem_ai.GEN_AI_SPAN_KIND: sem_ai.GEN_AI_SPAN_KIND_CHAIN, + sem_ai.GEN_AI_INPUT: "hello", + sem_ai.GEN_AI_OUTPUT: "world", + sem_ai.GEN_AI_REQUEST_MODEL: "demo-model", + sem_ai.GEN_AI_USAGE_INPUT_TOKENS: int64(3), + sem_ai.GEN_AI_USAGE_OUTPUT_TOKENS: int64(2), + sem_ai.GEN_AI_USAGE_TOTAL_TOKENS: int64(5), + sem_ai.GEN_AI_SESSION_ID: "trace-session", + sem_ai.GEN_AI_RESPONSE_MODEL: "demo-model", + } { + if got := attrs[key]; got != want { + t.Errorf("root %s = %#v, want %#v", key, got, want) + } + } + modelAttrs := otelAttributesToMap(modelSpan.Attributes()) + if got := modelAttrs[sem_ai.GEN_AI_REQUEST_MODEL]; got != "demo-model" { + t.Errorf("model %s = %#v, want demo-model", sem_ai.GEN_AI_REQUEST_MODEL, got) + } + if got := modelAttrs[sem_ai.GEN_AI_RESPONSE_MODEL]; got != "demo-model" { + t.Errorf("model %s = %#v, want demo-model", sem_ai.GEN_AI_RESPONSE_MODEL, got) + } + if _, ok := modelAttrs[sem_ai.GEN_AI_REQUEST_DURATION_MS].(int64); !ok { + t.Errorf("model is missing numeric %s: %#v", sem_ai.GEN_AI_REQUEST_DURATION_MS, modelAttrs[sem_ai.GEN_AI_REQUEST_DURATION_MS]) + } + inputMessagesJSON, ok := attrs[sem_ai.GEN_AI_INPUT_MESSAGES].(string) + if !ok { + t.Errorf("root is missing %s", sem_ai.GEN_AI_INPUT_MESSAGES) + } else { + var messages []struct { + Role string `json:"role"` + Parts []struct { + Type string `json:"type"` + Content string `json:"content"` + } `json:"parts"` + } + if err := json.Unmarshal([]byte(inputMessagesJSON), &messages); err != nil { + t.Fatalf("decode %s: %v", sem_ai.GEN_AI_INPUT_MESSAGES, err) + } + if len(messages) != 1 || messages[0].Role != "user" || len(messages[0].Parts) != 1 || messages[0].Parts[0].Type != "text" || messages[0].Parts[0].Content != "hello" { + t.Fatalf("unexpected normalized input messages: %s", inputMessagesJSON) + } + } +} + +func TestLatestUserMessageContent(t *testing.T) { + tags := map[string]any{ + sem_ai.GEN_AI_PROMPT + ".0.role": "system", + sem_ai.GEN_AI_PROMPT + ".0.content": "system prompt", + sem_ai.GEN_AI_PROMPT + ".1.role": "user", + sem_ai.GEN_AI_PROMPT + ".1.content": "first question", + sem_ai.GEN_AI_PROMPT + ".2.role": "assistant", + sem_ai.GEN_AI_PROMPT + ".2.content": "first answer", + sem_ai.GEN_AI_PROMPT + ".3.role": "user", + sem_ai.GEN_AI_PROMPT + ".3.content": "latest question", + } + if got := latestUserMessageContent(tags); got != "latest question" { + t.Fatalf("latest user message = %q, want latest question", got) + } +} + +func TestTLSExporterParentsToolsUnderLatestModel(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + defer func() { _ = provider.Shutdown(context.Background()) }() + + handler := &TLSCallbackHandler{ + Tracer: provider.Tracer(scopeName), + dataParser: NewDefaultDataParser(false), + TLSExporterEnabled: true, + } + rootInfo := &callbacks.RunInfo{Component: compose.ComponentOfLambda, Name: "eino-agent"} + rootCtx := handler.OnStart(context.Background(), rootInfo, "input") + modelInfo := &callbacks.RunInfo{Component: components.ComponentOfChatModel} + modelCtx := handler.OnStart(rootCtx, modelInfo, &model.CallbackInput{ + Messages: []*schema.Message{{Role: schema.User, Content: "hello"}}, + Config: &model.Config{Model: "demo-model"}, + }) + handler.OnEnd(modelCtx, modelInfo, &model.CallbackOutput{Message: &schema.Message{Role: schema.Assistant, Content: "tool call"}}) + + toolInfo := &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "weather"} + toolCtx := handler.OnStart(rootCtx, toolInfo, &tool.CallbackInput{}) + handler.OnEnd(toolCtx, toolInfo, &tool.CallbackOutput{}) + handler.OnEnd(rootCtx, rootInfo, "done") + + spans := recorder.Ended() + if len(spans) != 3 { + t.Fatalf("ended spans = %d, want 3", len(spans)) + } + byName := make(map[string]sdktrace.ReadOnlySpan, len(spans)) + for _, span := range spans { + byName[span.Name()] = span + } + root := byName[tlsRootSpanName] + modelSpan := byName[tlsModelSpanName] + toolSpan := byName[tlsToolSpanName] + if root == nil || modelSpan == nil || toolSpan == nil { + t.Fatalf("expected root/model/tool spans, got %#v", byName) + } + if modelSpan.Parent().SpanID() != root.SpanContext().SpanID() { + t.Fatalf("model parent = %s, want root %s", modelSpan.Parent().SpanID(), root.SpanContext().SpanID()) + } + if toolSpan.Parent().SpanID() != modelSpan.SpanContext().SpanID() { + t.Fatalf("tool parent = %s, want model %s", toolSpan.Parent().SpanID(), modelSpan.SpanContext().SpanID()) + } + toolAttrs := otelAttributesToMap(toolSpan.Attributes()) + if got := toolAttrs[sem_ai.TOOL_SUCCESS]; got != true { + t.Errorf("tool success = %#v, want true", got) + } + if got := toolAttrs[sem_ai.TOOL_ERROR]; got != false { + t.Errorf("tool error = %#v, want false", got) + } +} + +func TestTLSExporterParentsToolsByToolCallID(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + defer func() { _ = provider.Shutdown(context.Background()) }() + + tracer := provider.Tracer(scopeName) + rootCtx, rootSpan := tracer.Start(context.Background(), tlsRootSpanName) + root := &tlsRootState{span: rootSpan} + + _, firstModel := tracer.Start(rootCtx, tlsModelSpanName) + root.rememberModelSpan(firstModel, "call-weather") + firstModel.End() + _, secondModel := tracer.Start(rootCtx, tlsModelSpanName) + root.rememberModelSpan(secondModel, "call-search") + secondModel.End() + + handler := &TLSCallbackHandler{TLSExporterEnabled: true} + toolCtx := handler.contextWithToolCallParent(rootCtx, &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "weather"}, root, "call-weather") + _, toolSpan := tracer.Start(toolCtx, tlsToolSpanName) + toolSpan.End() + rootSpan.End() + + if toolSpan.SpanContext().TraceID() != firstModel.SpanContext().TraceID() { + t.Fatal("tool span did not preserve the model trace") + } + foundTool := false + for _, span := range recorder.Ended() { + if span.Name() == tlsToolSpanName && span.Parent().SpanID() != firstModel.SpanContext().SpanID() { + t.Fatalf("tool parent = %s, want first model %s", span.Parent().SpanID(), firstModel.SpanContext().SpanID()) + } else if span.Name() == tlsToolSpanName { + foundTool = true + } + } + if !foundTool { + t.Fatal("tool span was not exported") + } +} + +func TestWaitStreamInputStopsOnCancellationAndTimeout(t *testing.T) { + stopCh := make(TLSStreamInputAsyncVal) + canceledCtx, cancel := context.WithCancel(context.WithValue(context.Background(), TLSStreamInputAsyncKey{}, stopCh)) + cancel() + start := time.Now() + waitStreamInput(canceledCtx, time.Second) + if elapsed := time.Since(start); elapsed > 100*time.Millisecond { + t.Fatalf("canceled stream wait took %s", elapsed) + } + + timeoutCtx := context.WithValue(context.Background(), TLSStreamInputAsyncKey{}, stopCh) + start = time.Now() + waitStreamInput(timeoutCtx, 10*time.Millisecond) + if elapsed := time.Since(start); elapsed > 200*time.Millisecond { + t.Fatalf("timed stream wait took %s", elapsed) + } +} + +func TestTLSApplicationAttributes(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + defer func() { _ = provider.Shutdown(context.Background()) }() + _, span := provider.Tracer("test").Start(context.Background(), tlsRootSpanName) + (&TLSCallbackHandler{AppName: "Eino", Release: "v1", TLSExporterEnabled: true}).setTLSApplicationAttributes( + span, + &callbacks.RunInfo{Name: "eino-agent"}, + ) + span.End() + + spans := recorder.Ended() + if len(spans) != 1 { + t.Fatalf("ended spans = %d, want 1", len(spans)) + } + attrs := otelAttributesToMap(spans[0].Attributes()) + for key, want := range map[string]any{ + "app.name": "Eino", + "agent.name": "eino-agent", + "tls.plugin.version": "v1", + } { + if got := attrs[key]; got != want { + t.Errorf("%s = %#v, want %#v", key, got, want) + } + } +} + +func TestValidateTLSLogExporterConfig(t *testing.T) { + cfg := &TLSConfig{ + AppName: "Eino", + TLSExporterEnabled: true, + TLSLogEndpoint: "tls-cn-guilin-boe.volces.com", + TLSLogRegion: "cn-guilin-boe", + TLSLogTopicID: "topic-id", + TLSLogAPIKey: "api-key", + } + if err := ValidateTLSConfig(cfg); err != nil { + t.Fatalf("ValidateTLSConfig() error = %v", err) + } + cfg.TLSLogEndpoint = "" + if err := ValidateTLSConfig(cfg); err == nil { + t.Fatal("ValidateTLSConfig() accepted incomplete TLS log exporter config") + } +} + +func TestLoadTLSConfigFromEnvForTLSExporter(t *testing.T) { + t.Setenv("TLS_EXPORTER_ENABLED", "true") + t.Setenv("TLS_APP_NAME", "Eino") + t.Setenv("TLS_LOG_ENDPOINT", "tls-cn-guilin-boe.volces.com") + t.Setenv("TLS_LOG_REGION", "cn-guilin-boe") + t.Setenv("TLS_LOG_TRACE_TOPIC_ID", "topic-id") + t.Setenv("TLS_LOG_API_KEY", "api-key") + + cfg, err := LoadTLSConfigFromEnv() + if err != nil { + t.Fatalf("LoadTLSConfigFromEnv() error = %v", err) + } + if !cfg.TLSExporterEnabled { + t.Fatal("TLSExporterEnabled = false, want true") + } +} + +func TestNormalizeTLSProducerEndpoint(t *testing.T) { + for input, want := range map[string]string{ + "tls-cn-guilin-boe.volces.com": "https://tls-cn-guilin-boe.volces.com", + "https://tls-cn-guilin-boe.volces.com/": "https://tls-cn-guilin-boe.volces.com", + "http://localhost:8080/": "http://localhost:8080", + "": "", + } { + if got := normalizeTLSProducerEndpoint(input); got != want { + t.Errorf("normalizeTLSProducerEndpoint(%q) = %q, want %q", input, got, want) + } + } +} + +func TestTLSExporterPreservesCurrentEinoMultimodalInputAndOutput(t *testing.T) { + imageURL := "https://example.com/test-image.png" + videoURL := "https://example.com/test-video.mp4" + input := &schema.Message{ + Role: schema.User, + UserInputMultiContent: []schema.MessageInputPart{ + {Type: schema.ChatMessagePartTypeText, Text: "describe the attached media"}, + {Type: schema.ChatMessagePartTypeImageURL, Image: &schema.MessageInputImage{ + MessagePartCommon: schema.MessagePartCommon{URL: &imageURL, MIMEType: "image/png"}, + }}, + {Type: schema.ChatMessagePartTypeVideoURL, Video: &schema.MessageInputVideo{ + MessagePartCommon: schema.MessagePartCommon{URL: &videoURL, MIMEType: "video/mp4"}, + }}, + }, + } + output := &schema.Message{ + Role: schema.Assistant, + AssistantGenMultiContent: []schema.MessageOutputPart{ + {Type: schema.ChatMessagePartTypeText, Text: "the video shows a test scene"}, + {Type: schema.ChatMessagePartTypeVideoURL, Video: &schema.MessageOutputVideo{ + MessagePartCommon: schema.MessagePartCommon{URL: &videoURL, MIMEType: "video/mp4"}, + }}, + }, + } + + parser := defaultDataParser{} + inputTags, err := parser.ParseInput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfChatModel}, &model.CallbackInput{ + Messages: []*schema.Message{input}, + }) + if err != nil { + t.Fatalf("parse multimodal input: %v", err) + } + inputMessages := modelMessagesFromTag(t, inputTags[sem_ai.GEN_AI_INPUT_MESSAGES]) + lensInput := toTLSLensMessages(inputMessages) + if len(lensInput) != 1 || len(lensInput[0].Parts) != 3 { + t.Fatalf("unexpected lens input: %#v", lensInput) + } + if lensInput[0].Parts[0].Type != "text" || lensInput[0].Parts[0].Content != "describe the attached media" { + t.Fatalf("text input part was lost: %#v", lensInput[0].Parts[0]) + } + if lensInput[0].Parts[1].Type != "image_url" || mediaURL(lensInput[0].Parts[1].ImageURL) != imageURL { + t.Fatalf("image input part was lost: %#v", lensInput[0].Parts[1]) + } + if lensInput[0].Parts[2].Type != "video_url" || mediaURL(lensInput[0].Parts[2].VideoURL) != videoURL { + t.Fatalf("video input part was lost: %#v", lensInput[0].Parts[2]) + } + normalizeTLSLensMessages(inputTags) + assertTLSLensValuePayload(t, inputTags[sem_ai.GEN_AI_INPUT], "user", "describe the attached media") + if got := latestUserMessageContent(inputTags); got != "describe the attached media [image: "+imageURL+"] [video: "+videoURL+"]" { + t.Fatalf("session input summary = %q", got) + } + + outputTags, err := parser.ParseOutput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfChatModel}, &model.CallbackOutput{Message: output}) + if err != nil { + t.Fatalf("parse multimodal output: %v", err) + } + outputMessages := modelMessagesFromTag(t, outputTags[sem_ai.GEN_AI_OUTPUT_MESSAGES]) + lensOutput := toTLSLensMessages(outputMessages) + if len(lensOutput) != 1 || len(lensOutput[0].Parts) != 2 { + t.Fatalf("unexpected lens output: %#v", lensOutput) + } + if lensOutput[0].Parts[0].Content != "the video shows a test scene" || mediaURL(lensOutput[0].Parts[1].VideoURL) != videoURL { + t.Fatalf("multimodal model output was lost: %#v", lensOutput[0].Parts) + } + normalizeTLSLensMessages(outputTags) + assertTLSLensValuePayload(t, outputTags[sem_ai.GEN_AI_OUTPUT], "assistant", "the video shows a test scene") +} + +func TestTLSExporterRedactsInlineMediaPayloads(t *testing.T) { + imageData := "image-inline-payload" + audioData := "audio-inline-payload" + videoData := "video-inline-payload" + fileData := "file-inline-payload" + input := &schema.Message{ + Role: schema.User, + UserInputMultiContent: []schema.MessageInputPart{ + {Type: schema.ChatMessagePartTypeImageURL, Image: &schema.MessageInputImage{MessagePartCommon: schema.MessagePartCommon{Base64Data: &imageData, MIMEType: "image/png"}}}, + {Type: schema.ChatMessagePartTypeAudioURL, Audio: &schema.MessageInputAudio{MessagePartCommon: schema.MessagePartCommon{Base64Data: &audioData, MIMEType: "audio/wav"}}}, + {Type: schema.ChatMessagePartTypeVideoURL, Video: &schema.MessageInputVideo{MessagePartCommon: schema.MessagePartCommon{Base64Data: &videoData, MIMEType: "video/mp4"}}}, + {Type: schema.ChatMessagePartTypeFileURL, File: &schema.MessageInputFile{Name: "private.pdf", MessagePartCommon: schema.MessagePartCommon{Base64Data: &fileData, MIMEType: "application/pdf"}}}, + }, + } + output := &schema.Message{ + Role: schema.Assistant, + AssistantGenMultiContent: []schema.MessageOutputPart{ + {Type: schema.ChatMessagePartTypeImageURL, Image: &schema.MessageOutputImage{MessagePartCommon: schema.MessagePartCommon{Base64Data: &imageData, MIMEType: "image/png"}}}, + {Type: schema.ChatMessagePartTypeAudioURL, Audio: &schema.MessageOutputAudio{MessagePartCommon: schema.MessagePartCommon{Base64Data: &audioData, MIMEType: "audio/wav"}}}, + {Type: schema.ChatMessagePartTypeVideoURL, Video: &schema.MessageOutputVideo{MessagePartCommon: schema.MessagePartCommon{Base64Data: &videoData, MIMEType: "video/mp4"}}}, + }, + } + + for name, message := range map[string]*schema.Message{"input": input, "output": output} { + encoded, err := json.Marshal(convertModelMessage(message)) + if err != nil { + t.Fatalf("marshal %s message: %v", name, err) + } + for _, payload := range []string{imageData, audioData, videoData, fileData, "base64_data"} { + if strings.Contains(string(encoded), payload) { + t.Fatalf("%s telemetry payload leaked %q: %s", name, payload, encoded) + } + } + } +} + +func TestTLSExporterPreservesMultimodalAndToolPayloadsInStreams(t *testing.T) { + imageURL := "https://example.com/stream-image.png" + videoURL := "https://example.com/stream-video.mp4" + parser := defaultDataParser{} + + inputReader, inputWriter := schema.Pipe[callbacks.CallbackInput](1) + inputWriter.Send(&model.CallbackInput{Messages: []*schema.Message{{ + Role: schema.User, + UserInputMultiContent: []schema.MessageInputPart{ + {Type: schema.ChatMessagePartTypeText, Text: "stream this media"}, + {Type: schema.ChatMessagePartTypeImageURL, Image: &schema.MessageInputImage{MessagePartCommon: schema.MessagePartCommon{URL: &imageURL}}}, + {Type: schema.ChatMessagePartTypeVideoURL, Video: &schema.MessageInputVideo{MessagePartCommon: schema.MessagePartCommon{URL: &videoURL}}}, + }, + }}}, nil) + inputWriter.Close() + inputTags, err := parser.ParseStreamInput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfChatModel}, inputReader) + if err != nil { + t.Fatalf("parse stream multimodal input: %v", err) + } + streamInput := modelMessagesFromTag(t, inputTags[sem_ai.GEN_AI_INPUT_MESSAGES]) + if len(streamInput) != 1 { + t.Fatalf("unexpected stream input messages: %#v", inputTags[sem_ai.GEN_AI_INPUT_MESSAGES]) + } + lensInput := toTLSLensMessages(streamInput) + if len(lensInput[0].Parts) != 3 || mediaURL(lensInput[0].Parts[1].ImageURL) != imageURL || mediaURL(lensInput[0].Parts[2].VideoURL) != videoURL { + t.Fatalf("stream multimodal parts were lost: %#v", lensInput) + } + if inputTags[sem_ai.GEN_AI_REQUEST_IS_STREAM] != true || inputTags[sem_ai.GEN_AI_IS_STREAMING] != true { + t.Fatalf("stream flags missing from input: %#v", inputTags) + } + normalizeTLSLensMessages(inputTags) + assertTLSLensValuePayload(t, inputTags[sem_ai.GEN_AI_INPUT], "user", "stream this media") + + modelOutputReader, modelOutputWriter := schema.Pipe[callbacks.CallbackOutput](1) + modelOutputWriter.Send(&model.CallbackOutput{Message: &schema.Message{Role: schema.Assistant, Content: "stream response"}}, nil) + modelOutputWriter.Close() + modelOutputTags, err := parser.ParseStreamOutput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfChatModel}, modelOutputReader) + if err != nil { + t.Fatalf("parse stream model output: %v", err) + } + normalizeTLSLensMessages(modelOutputTags) + assertTLSLensValuePayload(t, modelOutputTags[sem_ai.GEN_AI_OUTPUT], "assistant", "stream response") + + toolInputReader, toolInputWriter := schema.Pipe[callbacks.CallbackInput](2) + toolInputWriter.Send(&tool.CallbackInput{ArgumentsInJSON: `{"city":`}, nil) + toolInputWriter.Send(&tool.CallbackInput{ArgumentsInJSON: `"Guilin"}`}, nil) + toolInputWriter.Close() + toolInputTags, err := parser.ParseStreamInput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "weather"}, toolInputReader) + if err != nil { + t.Fatalf("parse stream tool input: %v", err) + } + if got := toolInputTags[sem_ai.GEN_AI_TOOL_CALL_ARGUMENTS]; got != `{"city":"Guilin"}` { + t.Fatalf("stream tool arguments = %#v", got) + } + + toolOutputReader, toolOutputWriter := schema.Pipe[callbacks.CallbackOutput](2) + toolOutputWriter.Send(&tool.CallbackOutput{Response: `{"temperature":`}, nil) + toolOutputWriter.Send(&tool.CallbackOutput{Response: `25}`}, nil) + toolOutputWriter.Close() + toolOutputTags, err := parser.ParseStreamOutput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "weather"}, toolOutputReader) + if err != nil { + t.Fatalf("parse stream tool output: %v", err) + } + if got := toolOutputTags[sem_ai.GEN_AI_TOOL_CALL_RESULT]; got != `{"temperature":25}` { + t.Fatalf("stream tool result = %#v", got) + } +} + +func TestTLSExporterUsesRawToolPayloadsAndLensToolParts(t *testing.T) { + parser := defaultDataParser{} + inputTags, err := parser.ParseInput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "weather"}, &tool.CallbackInput{ + ArgumentsInJSON: `{"city":"Guilin"}`, + }) + if err != nil { + t.Fatalf("parse tool input: %v", err) + } + if got := inputTags[sem_ai.GEN_AI_TOOL_CALL_ARGUMENTS]; got != `{"city":"Guilin"}` { + t.Fatalf("tool arguments = %#v", got) + } + outputTags, err := parser.ParseOutput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfTool, Name: "weather"}, &tool.CallbackOutput{ + Response: `{"temperature":25}`, + }) + if err != nil { + t.Fatalf("parse tool output: %v", err) + } + if got := outputTags[sem_ai.GEN_AI_TOOL_CALL_RESULT]; got != `{"temperature":25}` { + t.Fatalf("tool result = %#v", got) + } + + lensMessages := toTLSLensMessages([]*sem_ai.ModelMessage{ + { + Role: "assistant", + ToolCalls: []*sem_ai.ModelToolCall{{ + ID: "call-weather", + Function: &sem_ai.ModelToolCallFunction{Name: "weather", Arguments: `{"city":"Guilin"}`}, + }}, + }, + {Role: "tool", ToolCallID: "call-weather", Content: `{"temperature":25}`}, + }) + if len(lensMessages) != 2 || len(lensMessages[0].Parts) != 1 || len(lensMessages[1].Parts) != 1 { + t.Fatalf("unexpected lens tool messages: %#v", lensMessages) + } + if call := lensMessages[0].Parts[0]; call.Type != "tool_call" || call.ID != "call-weather" || call.Name != "weather" { + t.Fatalf("tool call part = %#v", call) + } + if response := lensMessages[1].Parts[0]; response.Type != "tool_call_response" || response.ID != "call-weather" || response.Result != `{"temperature":25}` { + t.Fatalf("tool response part = %#v", response) + } +} + +func TestTLSLensMessagesCoalescesStreamedContentParts(t *testing.T) { + messages := toTLSLensMessages([]*sem_ai.ModelMessage{{ + Role: "assistant", + Content: "streamed text", + Parts: []*sem_ai.ModelMessagePart{ + {Type: sem_ai.ModelMessagePartTypeText, Text: "streamed"}, + {Type: sem_ai.ModelMessagePartTypeText, Text: " text"}, + {Type: sem_ai.ModelMessagePartTypeReasoning, Text: "reason"}, + {Type: sem_ai.ModelMessagePartTypeReasoning, Text: "ing"}, + }, + }}) + if len(messages) != 1 || len(messages[0].Parts) != 2 { + t.Fatalf("stream chunks should be coalesced, got %#v", messages) + } + if got := messages[0].Parts[0]; got.Type != "text" || got.Content != "streamed text" { + t.Fatalf("stream text = %#v, want one content part", got) + } + if got := messages[0].Parts[1]; got.Type != "reasoning" || got.Content != "reasoning" { + t.Fatalf("stream reasoning = %#v, want one content part", got) + } +} + +func mediaURL(raw any) string { + encoded, err := json.Marshal(raw) + if err != nil { + return "" + } + var media struct { + URL string `json:"url"` + } + if json.Unmarshal(encoded, &media) != nil { + return "" + } + return media.URL +} + +func assertTLSLensValuePayload(t *testing.T, raw any, wantRole, wantContent string) { + t.Helper() + payload, ok := raw.(tlsLensMessagesPayload) + if !ok { + t.Fatalf("trace detail value type = %T, want tlsLensMessagesPayload", raw) + } + if len(payload.Messages) != 1 || payload.Messages[0].Role != wantRole || len(payload.Messages[0].Parts) == 0 { + t.Fatalf("unexpected trace detail payload: %#v", payload) + } + if got := payload.Messages[0].Parts[0].Content; got != wantContent { + t.Fatalf("trace detail content = %#v, want %q", got, wantContent) + } + encoded, err := json.Marshal(payload) + if err != nil { + t.Fatalf("marshal trace detail payload: %v", err) + } + if strings.Contains(string(encoded), `"stream"`) || strings.Contains(string(encoded), `"choices"`) { + t.Fatalf("trace detail payload leaked callback wrapper: %s", encoded) + } +} + +func modelMessagesFromTag(t *testing.T, raw any) []*sem_ai.ModelMessage { + t.Helper() + if messages, ok := raw.([]*sem_ai.ModelMessage); ok { + return messages + } + encoded, ok := raw.(string) + if !ok { + t.Fatalf("message tag type = %T, want JSON string", raw) + } + var messages []*sem_ai.ModelMessage + if err := json.Unmarshal([]byte(encoded), &messages); err != nil { + t.Fatalf("decode message tag: %v; raw=%s", err, encoded) + } + return messages +} diff --git a/callbacks/tls/tls_handler.go b/callbacks/tls/tls_handler.go new file mode 100644 index 000000000..85a6d2f43 --- /dev/null +++ b/callbacks/tls/tls_handler.go @@ -0,0 +1,1408 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "os" + "runtime/debug" + "strconv" + "strings" + "sync" + "time" + + sem_ai "github.com/cloudwego/eino-ext/callbacks/tls/semconv" + "github.com/cloudwego/eino-ext/libs/acl/opentelemetry" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + otelsemconv "go.opentelemetry.io/otel/semconv/v1.27.0" + "go.opentelemetry.io/otel/trace" +) + +const scopeName = "github.com/cloudwego/eino-ext/callbacks/tls" + +type TLSConfig struct { + // TLSEndpoint is the TLS OTLP endpoint. + // Example: "tls-cn-beijing.volces.com:4317" + TLSEndpoint string + + // AppName is the application name shown in TLS. + AppName string + + // TLSOTLPHeader contains OTLP export headers. + TLSOTLPHeader map[string]string + + // TLSOTLPHeadersStr is the comma-separated OTLP headers string. + TLSOTLPHeadersStr string + + // Release is the version or release identifier. + Release string + + // TLSExporterEnabled emits the span names and attributes expected by the + // TLS LogApp dashboard schema. It is opt-in to preserve the + // existing generic TLS callback behavior for current users. + TLSExporterEnabled bool + + // TLSLog* configures the TLS Producer SendLogs transport used by the TLS + // integration. When TLSLogTopicID is empty, the existing OTLP transport is + // used even if TLSExporterEnabled is enabled. + TLSLogEndpoint string + TLSLogRegion string + TLSLogTopicID string + TLSLogAPIKey string + TLSLogAccessKeyID string + TLSLogAccessKeySecret string +} + +func NewTLSCallbackHandler(config ...*TLSConfig) (handler callbacks.Handler, shutdown func(ctx context.Context) error, err error) { + cfg, err := resolveTLSConfig(config...) + if err != nil { + return nil, nil, err + } + + return buildTLSCallbackHandler(cfg, newOptions()) +} + +func NewTLSCallbackHandlerWithOptions(cfg *TLSConfig, opts ...Option) (handler callbacks.Handler, shutdown func(ctx context.Context) error, err error) { + if cfg == nil { + cfg, err = LoadTLSConfigFromEnv() + if err != nil { + return nil, nil, fmt.Errorf("failed to load TLS config from environment: %w", err) + } + } else if err = ValidateTLSConfig(cfg); err != nil { + return nil, nil, fmt.Errorf("failed to validate TLS config: %w", err) + } + + return buildTLSCallbackHandler(cfg, newOptions(opts...)) +} + +// NewTLSCallbackHandlerFromEnv keeps the original API shape for callers already passing a prepared config. +func NewTLSCallbackHandlerFromEnv(cfg *TLSConfig) (handler callbacks.Handler, shutdown func(ctx context.Context) error, err error) { + return NewTLSCallbackHandlerWithOptions(cfg) +} + +// TLSCallbackHandler implements callbacks.Handler. +type TLSCallbackHandler struct { + OtelProvider *opentelemetry.OtelProvider + AppName string + Release string + Tracer trace.Tracer + dataParser CallbackDataParser + TLSExporterEnabled bool +} + +// TLSHandler extends TLSCallbackHandler with manual span controls. +type TLSHandler struct { + *TLSCallbackHandler +} + +func NewTLSHandler(config ...*TLSConfig) (*TLSHandler, func(ctx context.Context) error, error) { + cfg, err := resolveTLSConfig(config...) + if err != nil { + return nil, nil, err + } + + return NewTLSHandlerWithOptions(cfg) +} + +func NewTLSHandlerWithOptions(cfg *TLSConfig, opts ...Option) (*TLSHandler, func(ctx context.Context) error, error) { + handler, shutdown, err := NewTLSCallbackHandlerWithOptions(cfg, opts...) + if err != nil { + return nil, shutdown, err + } + + tlsHandler, ok := handler.(*TLSCallbackHandler) + if !ok { + return nil, shutdown, errors.New("failed to cast handler to *TLSCallbackHandler") + } + + return &TLSHandler{TLSCallbackHandler: tlsHandler}, shutdown, nil +} + +// NewTLSHandlerFromEnv keeps the original API shape for callers already passing a prepared config. +func NewTLSHandlerFromEnv(cfg *TLSConfig) (*TLSHandler, func(ctx context.Context) error, error) { + return NewTLSHandlerWithOptions(cfg) +} + +type RequestInfo struct { + mu sync.RWMutex + Model string +} + +func (r *RequestInfo) setModel(model string) { + if r == nil || model == "" { + return + } + r.mu.Lock() + r.Model = model + r.mu.Unlock() +} + +func (r *RequestInfo) model() string { + if r == nil { + return "" + } + r.mu.RLock() + defer r.mu.RUnlock() + return r.Model +} + +type TLSStateKey struct{} + +type tlsRootStateKey struct{} + +type TLSState struct { + StartTime time.Time + Span trace.Span + RequestInfo *RequestInfo + IsRootNode bool + root *tlsRootState + // syntheticRoot is populated when a direct Eino model/tool callback has no + // enclosing graph callback. TLS Lens expects an agent.turn root in that + // case, so the callback creates one around the component span. + syntheticRoot trace.Span +} + +// tlsRootState collects turn-level fields that LLM Lens dashboard cards read +// from the agent.turn span, while the individual model spans retain their +// own request-level values. +type tlsRootState struct { + span trace.Span + mu sync.Mutex + + modelRequests int + inputTokens int + outputTokens int + totalTokens int + cacheRead int + reasoning int + lastModelSpan trace.SpanContext + modelSpanByToolCall map[string]trace.SpanContext + hasModelInput bool +} + +// tlsLensMessage is the Codex/Aiden-compatible message envelope consumed by +// the TLS session view. The generic Eino callback model uses a top-level +// Content field; TLS Lens expects text to be contained in parts. +type tlsLensMessage struct { + Role string `json:"role"` + Parts []tlsLensMessagePart `json:"parts,omitempty"` + Name string `json:"name,omitempty"` + ToolCallID string `json:"tool_call_id,omitempty"` +} + +// tlsLensMessagesPayload is the common Input/Output value shape used by the +// TLS trace-detail renderer. Keep the complete, structured transcript in a +// `messages` field for both streaming and non-streaming model callbacks. In +// particular, do not expose Eino's callback transport wrappers (`stream`) or +// the OpenAI-style response wrapper (`choices`) as the primary UI payload. +type tlsLensMessagesPayload struct { + Messages []tlsLensMessage `json:"messages,omitempty"` +} + +type tlsLensMessagePart struct { + Type string `json:"type"` + Content any `json:"content,omitempty"` + ImageURL any `json:"image_url,omitempty"` + AudioURL any `json:"audio_url,omitempty"` + VideoURL any `json:"video_url,omitempty"` + FileURL any `json:"file_url,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Arguments any `json:"arguments,omitempty"` + Result any `json:"result,omitempty"` +} + +func (r *tlsRootState) addModelUsage(tags map[string]any) { + if r == nil || r.span == nil { + return + } + input, hasInput := intTag(tags, sem_ai.GEN_AI_USAGE_INPUT_TOKENS) + output, hasOutput := intTag(tags, sem_ai.GEN_AI_USAGE_OUTPUT_TOKENS) + total, hasTotal := intTag(tags, sem_ai.GEN_AI_USAGE_TOTAL_TOKENS) + cacheRead, hasCacheRead := intTag(tags, sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_V2) + reasoning, hasReasoning := intTag(tags, sem_ai.GEN_AI_USAGE_REASONING_OUTPUT_TOKENS) + if !hasInput && !hasOutput && !hasTotal && !hasCacheRead && !hasReasoning { + return + } + + r.mu.Lock() + defer r.mu.Unlock() + r.modelRequests++ + r.inputTokens += input + r.outputTokens += output + r.totalTokens += total + r.cacheRead += cacheRead + r.reasoning += reasoning + r.span.SetAttributes( + attribute.Int(sem_ai.GEN_AI_USAGE_INPUT_TOKENS, r.inputTokens), + attribute.Int(sem_ai.GEN_AI_USAGE_OUTPUT_TOKENS, r.outputTokens), + attribute.Int(sem_ai.GEN_AI_USAGE_TOTAL_TOKENS, r.totalTokens), + attribute.Int(sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_V2, r.cacheRead), + attribute.Int(sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, r.cacheRead), + attribute.Int(sem_ai.GEN_AI_USAGE_CACHED_TOKENS, r.cacheRead), + attribute.Int(sem_ai.GEN_AI_USAGE_CACHE_CREATE_INPUT_TOKENS, 0), + attribute.Int(sem_ai.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, 0), + attribute.Int(sem_ai.GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, r.reasoning), + ) + model, _ := tags[sem_ai.GEN_AI_REQUEST_MODEL].(string) + if model == "" { + model, _ = tags[sem_ai.GEN_AI_RESPONSE_MODEL].(string) + } + if model != "" { + r.span.SetAttributes(attribute.String(sem_ai.GEN_AI_REQUEST_MODEL, model)) + } +} + +// copyModelPresentation copies the trace-level fields the TraceTableV2 and +// trace-detail views read from the agent.turn span. The first model input is +// the trace input, while the latest model output is the trace output. +func (r *tlsRootState) copyModelPresentation(tags map[string]any) { + if r == nil || r.span == nil || len(tags) == 0 { + return + } + + r.mu.Lock() + defer r.mu.Unlock() + + rootTags := make(map[string]any, 7) + if !r.hasModelInput { + for _, key := range []string{ + sem_ai.GEN_AI_INPUT, + sem_ai.GEN_AI_INPUT_MESSAGES, + sem_ai.GEN_AI_REQUEST_MODEL, + sem_ai.GEN_AI_REQUEST_TOOL_DEFINITIONS, + } { + if value, ok := tags[key]; ok { + rootTags[key] = value + } + } + if _, ok := rootTags[sem_ai.GEN_AI_INPUT]; ok { + r.hasModelInput = true + } + // The full message array remains on gen_ai.input.messages for trace + // drill-down. For the session list, input.value should be readable at + // a glance, so keep only the most recent user-message content there. + if content := latestUserMessageContent(tags); content != "" { + rootTags[sem_ai.GEN_AI_INPUT] = content + } + } + for _, key := range []string{ + sem_ai.GEN_AI_OUTPUT, + sem_ai.GEN_AI_OUTPUT_MESSAGES, + sem_ai.GEN_AI_RESPONSE_MODEL, + sem_ai.GEN_AI_RESPONSE_FINISH_REASON, + } { + if value, ok := tags[key]; ok { + rootTags[key] = value + } + } + // Keep the full provider response on the llm.request span, but make the + // root session row readable just like the Codex and Claude integrations. + if content := latestLensMessageContent(tags[sem_ai.GEN_AI_OUTPUT_MESSAGES], "assistant"); content != "" { + rootTags[sem_ai.GEN_AI_OUTPUT] = content + } + setSpanAttributesFromTags(r.span, rootTags) +} + +func latestUserMessageContent(tags map[string]any) string { + if content := latestLensMessageContent(tags[sem_ai.GEN_AI_INPUT_MESSAGES], "user"); content != "" { + return content + } + const promptPrefix = sem_ai.GEN_AI_PROMPT + "." + const roleSuffix = ".role" + latestIndex := -1 + latestContent := "" + for key, value := range tags { + if !strings.HasPrefix(key, promptPrefix) || !strings.HasSuffix(key, roleSuffix) { + continue + } + messageRole, ok := value.(string) + if !ok || !strings.EqualFold(messageRole, "user") { + continue + } + indexText := strings.TrimSuffix(strings.TrimPrefix(key, promptPrefix), roleSuffix) + index, err := strconv.Atoi(indexText) + if err != nil || index <= latestIndex { + continue + } + content, _ := tags[promptPrefix+indexText+".content"].(string) + if content == "" { + continue + } + latestIndex = index + latestContent = content + } + return latestContent +} + +func latestLensMessageContent(raw any, role string) string { + if raw == nil { + return "" + } + if messages, ok := raw.([]*sem_ai.ModelMessage); ok { + return latestLensMessageContent(toTLSLensMessages(messages), role) + } + encoded, err := json.Marshal(raw) + if err != nil { + return "" + } + var messages []tlsLensMessage + if err := json.Unmarshal(encoded, &messages); err != nil { + return "" + } + for i := len(messages) - 1; i >= 0; i-- { + if !strings.EqualFold(messages[i].Role, role) { + continue + } + if summary := lensMessageSummary(messages[i]); summary != "" { + return summary + } + } + return "" +} + +func lensMessageSummary(message tlsLensMessage) string { + parts := make([]string, 0, len(message.Parts)) + for _, part := range message.Parts { + switch part.Type { + case "text", "reasoning": + if content, ok := part.Content.(string); ok && content != "" { + parts = append(parts, content) + } + case "image_url", "audio_url", "video_url", "file_url": + parts = append(parts, mediaPartSummary(part)) + case "tool_call": + if part.Name != "" { + parts = append(parts, "[tool call: "+part.Name+"]") + } + case "tool_call_response": + if content, ok := part.Result.(string); ok && content != "" { + parts = append(parts, content) + } else { + parts = append(parts, "[tool result]") + } + } + } + return strings.Join(parts, " ") +} + +func mediaPartSummary(part tlsLensMessagePart) string { + label := strings.TrimSuffix(part.Type, "_url") + media := part.ImageURL + if media == nil { + media = part.AudioURL + } + if media == nil { + media = part.VideoURL + } + if media == nil { + media = part.FileURL + } + encoded, err := json.Marshal(media) + if err != nil { + return "[" + label + "]" + } + var payload struct { + URL string `json:"url"` + Name string `json:"name"` + } + if json.Unmarshal(encoded, &payload) != nil { + return "[" + label + "]" + } + if payload.Name != "" { + return "[" + label + ": " + payload.Name + "]" + } + if payload.URL != "" { + return "[" + label + ": " + payload.URL + "]" + } + return "[" + label + "]" +} + +func normalizeTLSLensMessages(tags map[string]any) { + if len(tags) == 0 { + return + } + for _, entry := range []struct { + messagesKey string + valueKey string + }{ + {messagesKey: sem_ai.GEN_AI_INPUT_MESSAGES, valueKey: sem_ai.GEN_AI_INPUT}, + {messagesKey: sem_ai.GEN_AI_OUTPUT_MESSAGES, valueKey: sem_ai.GEN_AI_OUTPUT}, + } { + key := entry.messagesKey + raw, ok := tags[key] + if !ok { + continue + } + // Parser implementations may use their own message slice type. Round + // through the attribute's JSON contract instead of requiring an exact Go + // type, then emit the one TLS Lens understands. + var encoded []byte + if serialized, ok := raw.(string); ok { + encoded = []byte(serialized) + } else { + var err error + encoded, err = json.Marshal(raw) + if err != nil { + continue + } + } + var messages []*sem_ai.ModelMessage + if err := json.Unmarshal(encoded, &messages); err != nil { + continue + } + lensMessages := toTLSLensMessages(messages) + tags[key] = lensMessages + // input.value/output.value drives the trace detail's Input/Output tabs. + // Use the same Lens-compatible transcript as gen_ai.*.messages so both + // streaming and non-streaming spans have one readable representation. + tags[entry.valueKey] = tlsLensMessagesPayload{Messages: lensMessages} + } +} + +func toTLSLensMessages(messages []*sem_ai.ModelMessage) []tlsLensMessage { + converted := make([]tlsLensMessage, 0, len(messages)) + for _, message := range messages { + if message == nil { + continue + } + result := tlsLensMessage{ + Role: message.Role, + Name: message.Name, + ToolCallID: message.ToolCallID, + } + if message.Role == "tool" { + result.Parts = append(result.Parts, tlsLensMessagePart{ + Type: "tool_call_response", + ID: message.ToolCallID, + Result: message.Content, + }) + } else if message.Content != "" && !hasTLSLensPartType(message.Parts, sem_ai.ModelMessagePartTypeText) { + result.Parts = append(result.Parts, tlsLensMessagePart{Type: "text", Content: message.Content}) + } + if message.ReasoningContent != "" && !hasTLSLensPartType(message.Parts, sem_ai.ModelMessagePartTypeReasoning) { + result.Parts = append(result.Parts, tlsLensMessagePart{Type: "reasoning", Content: message.ReasoningContent}) + } + for _, part := range message.Parts { + if lensPart := toTLSLensMessagePart(part); lensPart != nil { + result.Parts = appendTLSLensMessagePart(result.Parts, *lensPart) + } + } + for _, toolCall := range message.ToolCalls { + if toolCall == nil || toolCall.Function == nil { + continue + } + result.Parts = append(result.Parts, tlsLensMessagePart{ + Type: "tool_call", + ID: toolCall.ID, + Name: toolCall.Function.Name, + Arguments: decodeJSONValue(toolCall.Function.Arguments), + }) + } + converted = append(converted, result) + } + return converted +} + +func hasTLSLensPartType(parts []*sem_ai.ModelMessagePart, partType sem_ai.ModelMessagePartType) bool { + for _, part := range parts { + if part != nil && part.Type == partType { + return true + } + } + return false +} + +// appendTLSLensMessagePart folds adjacent streamed text/reasoning chunks into +// one part. This keeps the trace detail compact: a token stream is represented +// as one content value rather than an array entry per token. +func appendTLSLensMessagePart(parts []tlsLensMessagePart, next tlsLensMessagePart) []tlsLensMessagePart { + if len(parts) == 0 || (next.Type != "text" && next.Type != "reasoning") { + return append(parts, next) + } + last := &parts[len(parts)-1] + if last.Type != next.Type { + return append(parts, next) + } + lastContent, lastOK := last.Content.(string) + nextContent, nextOK := next.Content.(string) + if !lastOK || !nextOK { + return append(parts, next) + } + last.Content = lastContent + nextContent + return parts +} + +func toTLSLensMessagePart(part *sem_ai.ModelMessagePart) *tlsLensMessagePart { + if part == nil { + return nil + } + result := &tlsLensMessagePart{Type: string(part.Type)} + switch part.Type { + case sem_ai.ModelMessagePartTypeText, sem_ai.ModelMessagePartTypeReasoning: + result.Content = part.Text + case sem_ai.ModelMessagePartTypeImage: + result.ImageURL = part.ImageURL + case sem_ai.ModelMessagePartTypeAudio: + result.AudioURL = part.AudioURL + case sem_ai.ModelMessagePartTypeVideo: + result.VideoURL = part.VideoURL + case sem_ai.ModelMessagePartTypeFile: + result.FileURL = part.FileURL + default: + return nil + } + return result +} + +func decodeJSONValue(raw string) any { + if raw == "" { + return "" + } + var value any + if json.Unmarshal([]byte(raw), &value) == nil { + return value + } + return raw +} + +func (r *tlsRootState) rememberModelSpan(span trace.Span, toolCallIDs ...string) { + if r == nil || span == nil { + return + } + spanContext := span.SpanContext() + if !spanContext.IsValid() { + return + } + r.mu.Lock() + r.lastModelSpan = spanContext + if len(toolCallIDs) > 0 { + if r.modelSpanByToolCall == nil { + r.modelSpanByToolCall = make(map[string]trace.SpanContext, len(toolCallIDs)) + } + for _, toolCallID := range toolCallIDs { + if toolCallID != "" { + r.modelSpanByToolCall[toolCallID] = spanContext + } + } + } + r.mu.Unlock() +} + +func (r *tlsRootState) modelSpanForToolCall(toolCallID string) trace.SpanContext { + if r == nil { + return trace.SpanContext{} + } + r.mu.Lock() + defer r.mu.Unlock() + if toolCallID != "" { + if spanContext, ok := r.modelSpanByToolCall[toolCallID]; ok && spanContext.IsValid() { + return spanContext + } + } + return r.lastModelSpan +} + +func modelToolCallIDs(message *schema.Message) []string { + if message == nil || len(message.ToolCalls) == 0 { + return nil + } + ids := make([]string, 0, len(message.ToolCalls)) + for _, toolCall := range message.ToolCalls { + if toolCall.ID != "" { + ids = append(ids, toolCall.ID) + } + } + return ids +} + +func modelToolCallIDsFromTags(tags map[string]any) []string { + raw, ok := tags[sem_ai.GEN_AI_OUTPUT_MESSAGES] + if !ok || raw == nil { + return nil + } + var encoded []byte + if serialized, ok := raw.(string); ok { + encoded = []byte(serialized) + } else { + var err error + encoded, err = json.Marshal(raw) + if err != nil { + return nil + } + } + + var messages []struct { + ToolCalls []struct { + ID string `json:"id"` + } `json:"tool_calls"` + Parts []struct { + Type string `json:"type"` + ID string `json:"id"` + } `json:"parts"` + } + if err := json.Unmarshal(encoded, &messages); err != nil { + return nil + } + ids := make([]string, 0) + seen := make(map[string]struct{}) + for _, message := range messages { + for _, toolCall := range message.ToolCalls { + if toolCall.ID != "" { + seen[toolCall.ID] = struct{}{} + } + } + for _, part := range message.Parts { + if part.Type == "tool_call" && part.ID != "" { + seen[part.ID] = struct{}{} + } + } + } + for id := range seen { + ids = append(ids, id) + } + return ids +} + +func intTag(tags map[string]any, key string) (int, bool) { + value, ok := tags[key] + if !ok { + return 0, false + } + switch v := value.(type) { + case int: + return v, true + case int64: + return int(v), true + case float64: + return int(v), true + default: + return 0, false + } +} + +func stringTag(tags map[string]any, key string) string { + if tags == nil { + return "" + } + value, _ := tags[key].(string) + return value +} + +// addMissingTLSModelAttributes completes the dashboard contract when an Eino +// model callback does not repeat its Config in CallbackOutput. The input +// callback already contains the selected model, while request duration is +// known by the handler for both streaming and non-streaming calls. +func addMissingTLSModelAttributes(tags map[string]any, state *TLSState) { + if tags == nil || state == nil { + return + } + model := state.RequestInfo.model() + if stringTag(tags, sem_ai.GEN_AI_REQUEST_MODEL) == "" && model != "" { + tags[sem_ai.GEN_AI_REQUEST_MODEL] = model + } + if stringTag(tags, sem_ai.GEN_AI_RESPONSE_MODEL) == "" { + if model != "" { + tags[sem_ai.GEN_AI_RESPONSE_MODEL] = model + } + } + if _, exists := tags[sem_ai.GEN_AI_REQUEST_DURATION_MS]; !exists && !state.StartTime.IsZero() { + tags[sem_ai.GEN_AI_REQUEST_DURATION_MS] = time.Since(state.StartTime).Milliseconds() + } +} + +type TLSStreamInputAsyncKey struct{} + +type TLSStreamInputAsyncVal chan struct{} + +func buildTLSCallbackHandler(cfg *TLSConfig, opts *options) (callbacks.Handler, func(ctx context.Context) error, error) { + if cfg.usesTLSLogTransport() { + p, err := newTLSLogProvider(cfg) + if err != nil { + return nil, nil, fmt.Errorf("init TLS log provider failed: %w", err) + } + parser := newDefaultDataParserWithConcatFuncs(opts.concatFuncs, opts.enableAggrOutput) + if opts.parser != nil { + parser = opts.parser + } + return &TLSCallbackHandler{ + OtelProvider: p, + AppName: cfg.AppName, + Release: cfg.Release, + Tracer: p.TracerProvider.Tracer(scopeName), + dataParser: parser, + TLSExporterEnabled: true, + }, p.Shutdown, nil + } + + otelOpts := []opentelemetry.Option{ + opentelemetry.WithServiceName(cfg.AppName), + opentelemetry.WithExportEndpoint(cfg.TLSEndpoint), + opentelemetry.WithHeaders(cfg.TLSOTLPHeader), + opentelemetry.WithResourceAttribute(attribute.String("tls.business_type", "gen_ai")), + opentelemetry.WithEnableTracing(true), + opentelemetry.WithEnableMetrics(false), + } + if cfg.Release != "" { + otelOpts = append(otelOpts, opentelemetry.WithResourceAttribute(otelsemconv.ServiceVersionKey.String(cfg.Release))) + } + + p, err := opentelemetry.NewOpenTelemetryProvider(otelOpts...) + if err != nil { + return nil, nil, fmt.Errorf("init opentelemetry provider failed: %w", err) + } + if p == nil { + return nil, nil, errors.New("init opentelemetry provider failed") + } + if p.TracerProvider == nil { + return nil, p.Shutdown, errors.New("tracer provider is nil") + } + + parser := newDefaultDataParserWithConcatFuncs(opts.concatFuncs, opts.enableAggrOutput) + if opts.parser != nil { + parser = opts.parser + } + + return &TLSCallbackHandler{ + OtelProvider: p, + AppName: cfg.AppName, + Release: cfg.Release, + Tracer: p.TracerProvider.Tracer(scopeName), + dataParser: parser, + TLSExporterEnabled: cfg.TLSExporterEnabled, + }, p.Shutdown, nil +} + +func resolveTLSConfig(config ...*TLSConfig) (*TLSConfig, error) { + if len(config) > 0 && config[0] != nil { + if err := ValidateTLSConfig(config[0]); err != nil { + return nil, fmt.Errorf("failed to validate TLS config: %w", err) + } + return config[0], nil + } + + cfg, err := LoadTLSConfigFromEnv() + if err != nil { + return nil, fmt.Errorf("failed to load TLS config from environment: %w", err) + } + + return cfg, nil +} + +func setCommonSpanAttributes(ctx context.Context, span trace.Span, info *callbacks.RunInfo, tlsExporterEnabled bool) bool { + isRootNode := ctx.Value(tlsRootStateKey{}) == nil + spanKind := _eino_run_component_to_span_kind(info.Component) + if isRootNode && spanKind == sem_ai.GEN_AI_SPAN_KIND_TASK { + span.SetAttributes(attribute.String(sem_ai.GEN_AI_SPAN_KIND, sem_ai.GEN_AI_SPAN_KIND_CHAIN)) + } else { + span.SetAttributes(attribute.String(sem_ai.GEN_AI_SPAN_KIND, spanKind)) + } + + span.SetAttributes( + attribute.String(sem_ai.GEN_AI_SYSTEM, "eino"), + attribute.String(sem_ai.GEN_AI_FRAMEWORK, "eino"), + attribute.String(sem_ai.TASK_NAME, info.Name), + attribute.String("runinfo.name", info.Name), + attribute.String("runinfo.type", info.Type), + attribute.String("runinfo.component", string(info.Component)), + ) + if tlsExporterEnabled { + span.SetAttributes( + attribute.String(sem_ai.TLS_APP_TYPE, "eino"), + attribute.String(sem_ai.GEN_AI_PROVIDER_NAME, "eino"), + attribute.String(sem_ai.GEN_AI_OPERATION_NAME, tlsOperationName(info, isRootNode)), + ) + } + setSessionAttributes(ctx, span) + + return isRootNode +} + +func setSessionAttributes(ctx context.Context, span trace.Span) { + session, ok := ctx.Value(tlsSessionOptionKey{}).(*sessionOptions) + if !ok || session == nil { + return + } + + if session.SessionID != "" { + span.SetAttributes(attribute.String(sem_ai.GEN_AI_SESSION_ID, session.SessionID)) + span.SetAttributes(attribute.String(sem_ai.SESSION_ID, session.SessionID)) + span.SetAttributes(attribute.String("gen_ai.conversation.id", session.SessionID)) + span.SetAttributes(attribute.String("session.name", session.SessionID)) + } + if session.UserID != "" { + span.SetAttributes(attribute.String(sem_ai.GEN_AI_USER_ID, session.UserID)) + span.SetAttributes(attribute.String(sem_ai.USER_ID, session.UserID)) + } +} + +func (h *TLSCallbackHandler) setTLSApplicationAttributes(span trace.Span, info *callbacks.RunInfo) { + if h == nil || !h.TLSExporterEnabled || span == nil { + return + } + attrs := make([]attribute.KeyValue, 0, 3) + if h.AppName != "" { + attrs = append(attrs, attribute.String("app.name", h.AppName)) + } + if info != nil && info.Name != "" { + attrs = append(attrs, attribute.String("agent.name", info.Name)) + } + if h.Release != "" { + attrs = append(attrs, attribute.String("tls.plugin.version", h.Release)) + } + if len(attrs) > 0 { + span.SetAttributes(attrs...) + } +} + +const streamInputWaitTimeout = 5 * time.Second + +func waitStreamInput(ctx context.Context, timeout time.Duration) { + if ctx == nil { + ctx = context.Background() + } + if stopCh, ok := ctx.Value(TLSStreamInputAsyncKey{}).(TLSStreamInputAsyncVal); ok { + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case <-stopCh: + case <-ctx.Done(): + log.Printf("stream input did not finish before callback context was canceled") + case <-timer.C: + log.Printf("stream input did not finish within %s; ending span without its remaining input attributes", timeout) + } + } +} + +func endSpan(ctx context.Context, span trace.Span, status codes.Code, description string) { + waitStreamInput(ctx, streamInputWaitTimeout) + span.SetStatus(status, description) + span.End(trace.WithTimestamp(time.Now())) +} + +func (h *TLSCallbackHandler) startSyntheticRootIfNeeded(ctx context.Context, info *callbacks.RunInfo, startTime time.Time) (context.Context, *tlsRootState, trace.Span) { + if h == nil || !h.TLSExporterEnabled || info == nil || ctx.Value(tlsRootStateKey{}) != nil || ctx.Value(TLSStateKey{}) != nil { + return ctx, nil, nil + } + if info.Component != components.ComponentOfChatModel && info.Component != components.ComponentOfTool { + return ctx, nil, nil + } + + rootCtx, rootSpan := h.Tracer.Start(ctx, tlsRootSpanName, + trace.WithSpanKind(trace.SpanKindServer), + trace.WithTimestamp(startTime), + ) + setCommonSpanAttributes(rootCtx, rootSpan, info, true) + rootSpan.SetAttributes( + attribute.String(sem_ai.GEN_AI_SPAN_KIND, sem_ai.GEN_AI_SPAN_KIND_CHAIN), + attribute.String(sem_ai.GEN_AI_OPERATION_NAME, tlsOperationName(info, true)), + ) + h.setTLSApplicationAttributes(rootSpan, info) + root := &tlsRootState{span: rootSpan} + return context.WithValue(rootCtx, tlsRootStateKey{}, root), root, rootSpan +} + +func (h *TLSCallbackHandler) contextWithToolParent(ctx context.Context, info *callbacks.RunInfo, root *tlsRootState) context.Context { + return h.contextWithToolCallParent(ctx, info, root, compose.GetToolCallID(ctx)) +} + +func (h *TLSCallbackHandler) contextWithToolCallParent(ctx context.Context, info *callbacks.RunInfo, root *tlsRootState, toolCallID string) context.Context { + if h == nil || !h.TLSExporterEnabled || info == nil || info.Component != components.ComponentOfTool || root == nil || root.span == nil { + return ctx + } + + modelSpan := root.modelSpanForToolCall(toolCallID) + if !modelSpan.IsValid() { + return ctx + } + currentParent := trace.SpanContextFromContext(ctx) + rootSpan := root.span.SpanContext() + if !currentParent.IsValid() || currentParent.SpanID() == rootSpan.SpanID() { + return trace.ContextWithSpanContext(ctx, modelSpan) + } + return ctx +} + +func endSyntheticRoot(root trace.Span, status codes.Code, description string) { + if root == nil { + return + } + root.SetStatus(status, description) + root.End(trace.WithTimestamp(time.Now())) +} + +func (h *TLSCallbackHandler) OnStart(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context { + if h == nil || info == nil { + return ctx + } + + info = completeRunInfo(info) + ctx = injectToolIDNameMapToCtx(ctx, info, input) + + startTime := time.Now() + ctx, syntheticRootState, syntheticRoot := h.startSyntheticRootIfNeeded(ctx, info, startTime) + if syntheticRootState != nil { + ctx = context.WithValue(ctx, tlsRootStateKey{}, syntheticRootState) + } + root, _ := ctx.Value(tlsRootStateKey{}).(*tlsRootState) + ctx = h.contextWithToolParent(ctx, info, root) + ctx, span := h.Tracer.Start(ctx, h.spanName(ctx, info), trace.WithSpanKind(h.spanKind(ctx, info)), trace.WithTimestamp(startTime)) + isRootNode := setCommonSpanAttributes(ctx, span, info, h.TLSExporterEnabled) + h.setTLSApplicationAttributes(span, info) + if root == nil { + root = &tlsRootState{span: span} + ctx = context.WithValue(ctx, tlsRootStateKey{}, root) + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfTool { + toolCallID := compose.GetToolCallID(ctx) + if toolCallID == "" { + toolCallID = span.SpanContext().SpanID().String() + } + span.SetAttributes( + attribute.String(sem_ai.GEN_AI_TOOL_NAME, getName(info)), + attribute.String(sem_ai.GEN_AI_TOOL_TYPE, info.Type), + attribute.String(sem_ai.GEN_AI_TOOL_CALL_ID, toolCallID), + attribute.Bool(sem_ai.TOOL_ERROR, false), + attribute.Bool(sem_ai.TOOL_SUCCESS, true), + ) + } + + requestInfo := &RequestInfo{} + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + if cbInput := model.ConvCallbackInput(input); cbInput != nil && cbInput.Config != nil { + requestInfo.setModel(cbInput.Config.Model) + } + if modelName := requestInfo.model(); modelName != "" { + span.SetAttributes(attribute.String(sem_ai.GEN_AI_REQUEST_MODEL, modelName)) + } + } + if h.dataParser != nil { + tags, err := h.dataParser.ParseInput(ctx, info, input) + if err != nil { + log.Printf("ParseInput failed, info: %+v, err: %+v", info, err) + } else { + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + normalizeTLSLensMessages(tags) + } + setSpanAttributesFromTags(span, tags) + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + requestInfo.setModel(stringTag(tags, sem_ai.GEN_AI_REQUEST_MODEL)) + root.copyModelPresentation(tags) + } + } + } + + return context.WithValue(ctx, TLSStateKey{}, &TLSState{ + StartTime: startTime, + Span: span, + RequestInfo: requestInfo, + IsRootNode: isRootNode, + root: root, + syntheticRoot: syntheticRoot, + }) +} + +func (h *TLSCallbackHandler) OnEnd(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context { + if h == nil || info == nil { + return ctx + } + + state, ok := ctx.Value(TLSStateKey{}).(*TLSState) + if !ok || state == nil || state.Span == nil { + log.Printf("no state in context, runinfo: %+v", info) + return ctx + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + if cbOutput := model.ConvCallbackOutput(output); cbOutput != nil { + state.root.rememberModelSpan(state.Span, modelToolCallIDs(cbOutput.Message)...) + } + } + + defer func() { + endSpan(ctx, state.Span, codes.Ok, "") + endSyntheticRoot(state.syntheticRoot, codes.Ok, "") + }() + + if h.dataParser != nil { + tags, err := h.dataParser.ParseOutput(ctx, completeRunInfo(info), output) + if err != nil { + log.Printf("ParseOutput failed, info: %+v, err: %+v", info, err) + } else { + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + addMissingTLSModelAttributes(tags, state) + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + normalizeTLSLensMessages(tags) + } + setSpanAttributesFromTags(state.Span, tags) + if h.TLSExporterEnabled && info.Component == components.ComponentOfTool { + state.Span.SetAttributes( + attribute.Bool(sem_ai.TOOL_ERROR, false), + attribute.Bool(sem_ai.TOOL_SUCCESS, true), + ) + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + state.root.copyModelPresentation(tags) + state.root.addModelUsage(tags) + } + } + } + + return ctx +} + +func (h *TLSCallbackHandler) OnError(ctx context.Context, info *callbacks.RunInfo, err error) context.Context { + if h == nil || info == nil { + return ctx + } + + state, ok := ctx.Value(TLSStateKey{}).(*TLSState) + if !ok || state == nil || state.Span == nil { + log.Printf("no state in context, runinfo: %+v", info) + return ctx + } + + if err != nil { + state.Span.RecordError(err) + state.Span.SetAttributes( + attribute.String("error.type", fmt.Sprintf("%T", err)), + attribute.String("error.message", err.Error()), + ) + if h.TLSExporterEnabled && info.Component == components.ComponentOfTool { + state.Span.SetAttributes( + attribute.Bool(sem_ai.TOOL_ERROR, true), + attribute.Bool(sem_ai.TOOL_SUCCESS, false), + ) + } + defer func() { + endSpan(ctx, state.Span, codes.Error, err.Error()) + endSyntheticRoot(state.syntheticRoot, codes.Error, err.Error()) + }() + } else { + defer func() { + endSpan(ctx, state.Span, codes.Error, "") + endSyntheticRoot(state.syntheticRoot, codes.Error, "") + }() + } + + return ctx +} + +func (h *TLSCallbackHandler) OnStartWithStreamInput(ctx context.Context, info *callbacks.RunInfo, input *schema.StreamReader[callbacks.CallbackInput]) context.Context { + if h == nil { + input.Close() + return ctx + } + if info == nil { + input.Close() + return ctx + } + + info = completeRunInfo(info) + startTime := time.Now() + ctx, syntheticRootState, syntheticRoot := h.startSyntheticRootIfNeeded(ctx, info, startTime) + if syntheticRootState != nil { + ctx = context.WithValue(ctx, tlsRootStateKey{}, syntheticRootState) + } + root, _ := ctx.Value(tlsRootStateKey{}).(*tlsRootState) + ctx = h.contextWithToolParent(ctx, info, root) + ctx, span := h.Tracer.Start(ctx, h.spanName(ctx, info), trace.WithSpanKind(h.spanKind(ctx, info)), trace.WithTimestamp(startTime)) + isRootNode := setCommonSpanAttributes(ctx, span, info, h.TLSExporterEnabled) + h.setTLSApplicationAttributes(span, info) + if root == nil { + root = &tlsRootState{span: span} + ctx = context.WithValue(ctx, tlsRootStateKey{}, root) + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfTool { + toolCallID := compose.GetToolCallID(ctx) + if toolCallID == "" { + toolCallID = span.SpanContext().SpanID().String() + } + span.SetAttributes( + attribute.String(sem_ai.GEN_AI_TOOL_NAME, getName(info)), + attribute.String(sem_ai.GEN_AI_TOOL_TYPE, info.Type), + attribute.String(sem_ai.GEN_AI_TOOL_CALL_ID, toolCallID), + attribute.Bool(sem_ai.TOOL_ERROR, false), + attribute.Bool(sem_ai.TOOL_SUCCESS, true), + ) + } + + requestInfo := &RequestInfo{} + stopCh := make(TLSStreamInputAsyncVal) + ctx = context.WithValue(ctx, TLSStreamInputAsyncKey{}, stopCh) + + if h.dataParser != nil { + go func() { + defer func() { + if e := recover(); e != nil { + log.Printf("recover update span panic: %v, runinfo: %+v, stack: %s", e, info, string(debug.Stack())) + } + close(stopCh) + }() + + tags, err := h.dataParser.ParseStreamInput(ctx, info, input) + if err != nil { + log.Printf("ParseStreamInput failed, info: %+v, err: %+v", info, err) + return + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + normalizeTLSLensMessages(tags) + } + setSpanAttributesFromTags(span, tags) + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + requestInfo.setModel(stringTag(tags, sem_ai.GEN_AI_REQUEST_MODEL)) + root.copyModelPresentation(tags) + } + }() + } else { + input.Close() + close(stopCh) + } + + return context.WithValue(ctx, TLSStateKey{}, &TLSState{ + StartTime: startTime, + Span: span, + RequestInfo: requestInfo, + IsRootNode: isRootNode, + root: root, + syntheticRoot: syntheticRoot, + }) +} + +func (h *TLSCallbackHandler) OnEndWithStreamOutput(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[callbacks.CallbackOutput]) context.Context { + if h == nil { + output.Close() + return ctx + } + if info == nil { + output.Close() + return ctx + } + + state, ok := ctx.Value(TLSStateKey{}).(*TLSState) + if !ok || state == nil || state.Span == nil { + log.Printf("no state in context, runinfo: %+v", info) + output.Close() + return ctx + } + + info = completeRunInfo(info) + if h.dataParser != nil { + go func() { + defer func() { + if e := recover(); e != nil { + log.Printf("recover update span panic: %v, runinfo: %+v, stack: %s", e, info, string(debug.Stack())) + } + endSpan(ctx, state.Span, codes.Ok, "") + endSyntheticRoot(state.syntheticRoot, codes.Ok, "") + }() + + tags, err := h.dataParser.ParseStreamOutput(ctx, info, output) + if err != nil { + log.Printf("ParseStreamOutput failed, info: %+v, err: %+v", info, err) + return + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + addMissingTLSModelAttributes(tags, state) + normalizeTLSLensMessages(tags) + } + setSpanAttributesFromTags(state.Span, tags) + if h.TLSExporterEnabled && info.Component == components.ComponentOfTool { + state.Span.SetAttributes( + attribute.Bool(sem_ai.TOOL_ERROR, false), + attribute.Bool(sem_ai.TOOL_SUCCESS, true), + ) + } + if h.TLSExporterEnabled && info.Component == components.ComponentOfChatModel { + state.root.copyModelPresentation(tags) + state.root.addModelUsage(tags) + state.root.rememberModelSpan(state.Span, modelToolCallIDsFromTags(tags)...) + } + }() + } else { + output.Close() + endSpan(ctx, state.Span, codes.Ok, "") + endSyntheticRoot(state.syntheticRoot, codes.Ok, "") + } + + return ctx +} + +func (h *TLSHandler) StartSpan(ctx context.Context, spanName string, tracingTags map[string]any) context.Context { + return h.StartSpanWithKind(ctx, spanName, trace.SpanKindClient, tracingTags) +} + +// StartSpanWithKind starts a manual span with the specified OpenTelemetry span kind. +func (h *TLSHandler) StartSpanWithKind(ctx context.Context, spanName string, spanKind trace.SpanKind, tracingTags map[string]any) context.Context { + if h == nil { + return ctx + } + + startTime := time.Now() + if spanName == "" { + spanName = DefaultSpanName + } + + ctx, span := h.Tracer.Start(ctx, spanName, trace.WithSpanKind(spanKind), trace.WithTimestamp(startTime)) + setSessionAttributes(ctx, span) + setSpanAttributesFromTags(span, tracingTags) + + return context.WithValue(ctx, TLSStateKey{}, &TLSState{ + StartTime: startTime, + Span: span, + RequestInfo: nil, + IsRootNode: true, + }) +} + +func (h *TLSHandler) FinishSpan(ctx context.Context, tracingTags map[string]any) context.Context { + return h.FinishSpanWithError(ctx, tracingTags, nil) +} + +// FinishSpanWithError finishes a manual span and records err when it is non-nil. +func (h *TLSHandler) FinishSpanWithError(ctx context.Context, tracingTags map[string]any, err error) context.Context { + state, ok := ctx.Value(TLSStateKey{}).(*TLSState) + if !ok || state == nil || state.Span == nil { + log.Printf("no state in context") + return ctx + } + + setSpanAttributesFromTags(state.Span, tracingTags) + if err != nil { + state.Span.RecordError(err) + state.Span.SetStatus(codes.Error, err.Error()) + } else { + state.Span.SetStatus(codes.Ok, "") + } + state.Span.End(trace.WithTimestamp(time.Now())) + return ctx +} + +// SetSession stores session attributes in ctx. +func (h *TLSHandler) SetSession(ctx context.Context, sessionID, userID string) context.Context { + return SetSession(ctx, WithSessionID(sessionID), WithUserID(userID)) +} + +func ValidateTLSConfig(cfg *TLSConfig) error { + if cfg == nil { + return errors.New("config cannot be nil") + } + if cfg.AppName == "" { + return errors.New("AppName is required") + } + if cfg.usesTLSLogTransport() { + if cfg.TLSLogEndpoint == "" || cfg.TLSLogRegion == "" || cfg.TLSLogTopicID == "" { + return errors.New("TLSLogEndpoint, TLSLogRegion and TLSLogTopicID are required for the TLS log exporter") + } + if cfg.TLSLogAPIKey == "" && (cfg.TLSLogAccessKeyID == "" || cfg.TLSLogAccessKeySecret == "") { + return errors.New("TLSLogAPIKey or TLSLogAccessKeyID/TLSLogAccessKeySecret is required for the TLS log exporter") + } + return nil + } + if cfg.TLSEndpoint == "" { + return errors.New("TLSEndpoint is required") + } + + if cfg.TLSOTLPHeadersStr != "" { + cfg.TLSOTLPHeader = parseOTLPHeaders(cfg.TLSOTLPHeadersStr) + } + if len(cfg.TLSOTLPHeader) == 0 { + return errors.New("TLSOTLPHeader is required, either directly set or via TLSOTLPHeadersStr") + } + + return nil +} + +func LoadTLSConfigFromEnv() (*TLSConfig, error) { + cfg := &TLSConfig{ + TLSEndpoint: os.Getenv("TLS_ENDPOINT"), + AppName: os.Getenv("TLS_APP_NAME"), + Release: getEnvOrDefault("TLS_AGENT_VERSION", DefaultVersion), + TLSOTLPHeadersStr: os.Getenv("TLS_EXPORTER_OTLP_HEADERS"), + TLSExporterEnabled: parseBoolEnv("TLS_EXPORTER_ENABLED"), + TLSLogEndpoint: os.Getenv("TLS_LOG_ENDPOINT"), + TLSLogRegion: os.Getenv("TLS_LOG_REGION"), + TLSLogTopicID: os.Getenv("TLS_LOG_TRACE_TOPIC_ID"), + TLSLogAPIKey: os.Getenv("TLS_LOG_API_KEY"), + TLSLogAccessKeyID: os.Getenv("TLS_LOG_AK"), + TLSLogAccessKeySecret: os.Getenv("TLS_LOG_SK"), + } + + return cfg, ValidateTLSConfig(cfg) +} + +func parseBoolEnv(key string) bool { + value, err := strconv.ParseBool(os.Getenv(key)) + return err == nil && value +} + +func (h *TLSCallbackHandler) spanName(ctx context.Context, info *callbacks.RunInfo) string { + if h != nil && h.TLSExporterEnabled { + return tlsSpanName(ctx, info) + } + return getSpanName(info) +} + +func (h *TLSCallbackHandler) spanKind(ctx context.Context, info *callbacks.RunInfo) trace.SpanKind { + if h != nil && h.TLSExporterEnabled && tlsSpanName(ctx, info) == tlsRootSpanName { + return trace.SpanKindServer + } + return trace.SpanKindClient +} + +func getEnvOrDefault(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func getSpanName(info *callbacks.RunInfo) string { + spanName := getName(info) + if spanName == "" { + return DefaultSpanName + } + + return spanName +} + +func parseOTLPHeaders(headersStr string) map[string]string { + headers := make(map[string]string) + if headersStr == "" { + return headers + } + + pairs := strings.Split(headersStr, ",") + for _, pair := range pairs { + kv := strings.SplitN(pair, "=", 2) + if len(kv) == 2 { + headers[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1]) + } + } + + return headers +} diff --git a/callbacks/tls/tls_handler_test.go b/callbacks/tls/tls_handler_test.go new file mode 100644 index 000000000..59d039471 --- /dev/null +++ b/callbacks/tls/tls_handler_test.go @@ -0,0 +1,225 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "errors" + "io" + "strings" + "testing" + + "github.com/bytedance/mockey" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" + "go.opentelemetry.io/otel/codes" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +func TestTLSHandlerManualSpans(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) + t.Cleanup(func() { + _ = provider.Shutdown(context.Background()) + }) + + handler := &TLSHandler{ + TLSCallbackHandler: &TLSCallbackHandler{ + Tracer: provider.Tracer(scopeName), + }, + } + + t.Run("uses the requested span kind", func(t *testing.T) { + ctx := handler.StartSpanWithKind(context.Background(), "session/prompt", trace.SpanKindServer, map[string]any{ + "rpc.method": "session/prompt", + }) + handler.FinishSpan(ctx, nil) + + spans := recorder.Ended() + if len(spans) != 1 { + t.Fatalf("expected one ended span, got %d", len(spans)) + } + if got := spans[0].SpanKind(); got != trace.SpanKindServer { + t.Fatalf("expected server span, got %s", got) + } + if got := spans[0].Status().Code; got != codes.Ok { + t.Fatalf("expected OK status, got %s", got) + } + }) + + t.Run("records and reports errors", func(t *testing.T) { + ctx := handler.StartSpan(context.Background(), "session/prompt", nil) + handler.FinishSpanWithError(ctx, map[string]any{"error.type": "internal"}, errors.New("prompt failed")) + + spans := recorder.Ended() + if len(spans) != 2 { + t.Fatalf("expected two ended spans, got %d", len(spans)) + } + span := spans[1] + if got := span.Status().Code; got != codes.Error { + t.Fatalf("expected error status, got %s", got) + } + if got := span.Status().Description; got != "prompt failed" { + t.Fatalf("expected error description %q, got %q", "prompt failed", got) + } + if len(span.Events()) != 1 || span.Events()[0].Name != "exception" { + t.Fatalf("expected an exception event, got %#v", span.Events()) + } + }) +} + +func TestTLSCallback(t *testing.T) { + cbh := &TLSCallbackHandler{ + Tracer: noop.NewTracerProvider().Tracer(scopeName), + dataParser: NewDefaultDataParser(true), + } + callbacks.AppendGlobalHandlers(cbh) + ctx := context.Background() + ctx = SetSession(ctx, WithSessionID("test_session_id"), WithUserID("test_user_id")) + g := compose.NewGraph[string, string]() + err := g.AddLambdaNode("node1", compose.InvokableLambda(func(ctx context.Context, input string) (output string, err error) { + return input, nil + }), compose.WithNodeName("node1")) + if err != nil { + t.Fatal(err) + } + err = g.AddLambdaNode("node2", compose.InvokableLambda(func(ctx context.Context, input string) (output string, err error) { + sb := strings.Builder{} + for i := 0; i < 10; i++ { + sb.WriteString(input) + } + return sb.String(), nil + }), compose.WithNodeName("node2")) + if err != nil { + t.Fatal(err) + } + err = g.AddEdge(compose.START, "node1") + if err != nil { + t.Fatal(err) + } + err = g.AddEdge("node1", "node2") + if err != nil { + t.Fatal(err) + } + err = g.AddEdge("node2", compose.END) + if err != nil { + t.Fatal(err) + } + runner, err := g.Compile(ctx) + if err != nil { + t.Fatal(err) + } + + mockey.PatchConvey("test span", t, func() { + result, err_ := runner.Invoke(ctx, "input") + if err_ != nil { + t.Fatal(err_) + } + if result != "inputinputinputinputinputinputinputinputinputinput" { + t.Fatalf("expect input, but got %s", result) + } + }) + + mockey.PatchConvey("test span stream", t, func() { + streamResult, err_ := runner.Stream(ctx, "input") + if err_ != nil { + t.Fatal(err_) + } + result := "" + for { + chunk, err__ := streamResult.Recv() + if err__ == io.EOF { + break + } + if err__ != nil { + t.Fatal(err_) + } + result += chunk + } + if result != "inputinputinputinputinputinputinputinputinputinput" { + t.Fatalf("expect input, but got %s", result) + } + }) + + mockey.PatchConvey("test generation", t, func() { + ctx1 := cbh.OnStart(ctx, &callbacks.RunInfo{Component: components.ComponentOfChatModel}, &model.CallbackInput{ + Messages: []*schema.Message{{Role: schema.System, Content: "system message"}, {Role: schema.User, Content: "user message"}}, + Config: &model.Config{ + Model: "model", MaxTokens: 1, Temperature: 2, TopP: 3, Stop: []string{"stop"}, + }, + Extra: map[string]interface{}{"key": "value"}, + }) + _, ok := ctx1.Value(TLSStateKey{}).(*TLSState) + if !ok { + t.Fatal("except state exists") + } + cbh.OnEnd(ctx1, &callbacks.RunInfo{Component: components.ComponentOfChatModel}, &model.CallbackOutput{ + Message: &schema.Message{Role: schema.Assistant, Content: "assistant message"}, + TokenUsage: &model.TokenUsage{ + PromptTokens: 1, + CompletionTokens: 2, + TotalTokens: 3, + }, + }) + }) + + mockey.PatchConvey("test generation stream", t, func() { + insr, insw := schema.Pipe[callbacks.CallbackInput](3) + insw.Send(&model.CallbackInput{ + Messages: []*schema.Message{{Role: schema.System, Content: "system "}, {Role: schema.User, Content: ""}}, + }, nil) + insw.Send(&model.CallbackInput{ + Messages: []*schema.Message{{Role: schema.System, Content: "message"}, {Role: schema.User, Content: "user "}}, + Config: &model.Config{ + Model: "model", MaxTokens: 1, Temperature: 2, TopP: 3, Stop: []string{"stop"}, + }, + Extra: map[string]interface{}{"key": "value"}, + }, nil) + insw.Send(&model.CallbackInput{ + Messages: []*schema.Message{{Role: schema.System, Content: ""}, {Role: schema.User, Content: "message"}}, + }, nil) + insw.Close() + outsr, outsw := schema.Pipe[callbacks.CallbackOutput](3) + outsw.Send(&model.CallbackOutput{ + Message: &schema.Message{Role: schema.Assistant, Content: "assistant"}, + }, nil) + outsw.Send(&model.CallbackOutput{ + Message: &schema.Message{Role: schema.Assistant, Content: " "}, + TokenUsage: &model.TokenUsage{ + PromptTokens: 1, + CompletionTokens: 2, + TotalTokens: 3, + }, + }, nil) + outsw.Send(&model.CallbackOutput{ + Message: &schema.Message{Role: schema.Assistant, Content: "message"}, + }, nil) + outsw.Close() + ctx2 := cbh.OnStartWithStreamInput(ctx, &callbacks.RunInfo{Component: components.ComponentOfChatModel}, insr) + _, ok := ctx2.Value(TLSStateKey{}).(*TLSState) + if !ok { + t.Fatal("except state exists") + } + cbh.OnEndWithStreamOutput(ctx2, &callbacks.RunInfo{Component: components.ComponentOfChatModel}, outsr) + }) +} diff --git a/callbacks/tls/trace.go b/callbacks/tls/trace.go new file mode 100644 index 000000000..9929a4dc1 --- /dev/null +++ b/callbacks/tls/trace.go @@ -0,0 +1,983 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tls + +import ( + "context" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + "time" + + sem_ai "github.com/cloudwego/eino-ext/callbacks/tls/semconv" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/embedding" + "github.com/cloudwego/eino/components/indexer" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/components/retriever" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +// CallbackDataParser tag parser for trace +// Implement CallbackDataParser and replace defaultDataParser by WithCallbackDataParser if needed +type CallbackDataParser interface { + ParseInput(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) (map[string]any, error) + ParseOutput(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) (map[string]any, error) + ParseStreamInput(ctx context.Context, info *callbacks.RunInfo, input *schema.StreamReader[callbacks.CallbackInput]) (map[string]any, error) + ParseStreamOutput(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[callbacks.CallbackOutput]) (map[string]any, error) +} + +func NewDefaultDataParser(enableAggrMessageOutput bool) CallbackDataParser { + return &defaultDataParser{concatFuncs: make(map[reflect.Type]any), enableAggrMessageOutput: enableAggrMessageOutput} +} + +func newDefaultDataParserWithConcatFuncs(concatFuncs map[reflect.Type]any, enableAggrMessageOutput bool) CallbackDataParser { + if concatFuncs == nil { + return NewDefaultDataParser(enableAggrMessageOutput) + } + + return &defaultDataParser{concatFuncs: concatFuncs, enableAggrMessageOutput: enableAggrMessageOutput} +} + +type defaultDataParser struct { + concatFuncs map[reflect.Type]any + enableAggrMessageOutput bool +} + +// setSpanAttributesFromTags 将解析后的标签设置到span +func setSpanAttributesFromTags(span trace.Span, tags map[string]any) { + if tags == nil { + return + } + + for key, value := range tags { + switch v := value.(type) { + case string: + span.SetAttributes(attribute.String(key, v)) + case int: + span.SetAttributes(attribute.Int(key, v)) + case int64: + span.SetAttributes(attribute.Int64(key, v)) + case float32: + span.SetAttributes(attribute.Float64(key, float64(v))) + case float64: + span.SetAttributes(attribute.Float64(key, v)) + case bool: + span.SetAttributes(attribute.Bool(key, v)) + case []string: + span.SetAttributes(attribute.StringSlice(key, v)) + case []any: + // 尝试转换为字符串切片 + strSlice := make([]string, 0, len(v)) + for _, item := range v { + if str, ok := item.(string); ok { + strSlice = append(strSlice, str) + } + } + if len(strSlice) > 0 { + span.SetAttributes(attribute.StringSlice(key, strSlice)) + } else { + // 否则序列化为JSON字符串 + if jsonStr, err := json.Marshal(v); err == nil { + span.SetAttributes(attribute.String(key, string(jsonStr))) + } + } + default: + // 其他类型尝试序列化为JSON + if jsonStr, err := json.Marshal(v); err == nil { + span.SetAttributes(attribute.String(key, string(jsonStr))) + } + } + } +} + +func (d defaultDataParser) ParseInput(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) (map[string]any, error) { + if info == nil { + return nil, nil + } + + tags := make(spanTags) + switch info.Component { + case components.ComponentOfChatModel: + cbInput := model.ConvCallbackInput(input) + if cbInput != nil { + // 设置请求参数 + if cbInput.Config != nil { + tags.set(sem_ai.GEN_AI_MODEL_NAME, cbInput.Config.Model) + tags.set(sem_ai.GEN_AI_REQUEST_MODEL, cbInput.Config.Model) + tags.set(sem_ai.GEN_AI_REQUEST_TEMPERATURE, cbInput.Config.Temperature) + tags.set(sem_ai.GEN_AI_REQUEST_MAX_TOKENS, cbInput.Config.MaxTokens) + tags.set(sem_ai.GEN_AI_REQUEST_TOP_P, cbInput.Config.TopP) + if len(cbInput.Config.Stop) > 0 { + tags.set(sem_ai.GEN_AI_REQUEST_STOP, cbInput.Config.Stop) + tags.set(sem_ai.GEN_AI_REQUEST_STOP_SEQUENCES, cbInput.Config.Stop) + } + } + + // 设置消息内容 + if cbInput.Messages != nil { + for i, msg := range cbInput.Messages { + if msg == nil { + continue + } + if content := messageDisplayText(msg); content != "" { + tags.set(fmt.Sprintf("%s.%d.role", sem_ai.GEN_AI_PROMPT, i), string(msg.Role)) + tags.set(fmt.Sprintf("%s.%d.content", sem_ai.GEN_AI_PROMPT, i), content) + } + if multiContentJSON := modelMessagePartsJSON(msg); len(multiContentJSON) > 0 { + for j, part := range multiContentJSON { + if contentJSON, err := json.Marshal(part); err == nil { + tags.set(fmt.Sprintf("%s.%d.content_part.%d", sem_ai.GEN_AI_PROMPT, i, j), string(contentJSON)) + } + } + } + } + } + tags.set(sem_ai.GEN_AI_INPUT, convertModelInput(cbInput)) + tags.set(sem_ai.GEN_AI_INPUT_MESSAGES, iterSlice(cbInput.Messages, convertModelMessage)) + + // 设置工具调用信息 + if len(cbInput.Tools) > 0 { + toolDefinitions := iterSlice(cbInput.Tools, convertTool) + if jsonBytes, err := json.Marshal(toolDefinitions); err == nil { + tags.set(sem_ai.GEN_AI_REQUEST_TOOL_DEFINITIONS, string(jsonBytes)) + } + } + } + case components.ComponentOfPrompt: + cbInput := prompt.ConvCallbackInput(input) + if cbInput != nil { + // 应用提示模板属性 + if kvs := _promptTemplateInput(cbInput); kvs != nil { + for _, kv := range kvs { + tags.set(string(kv.Key), kv.Value.AsInterface()) + } + } + } + + case components.ComponentOfEmbedding: + cbInput := embedding.ConvCallbackInput(input) + if cbInput != nil { + tags.set(sem_ai.GEN_AI_INPUT, cbInput.Texts) + tags.set(sem_ai.EMBEDDING_TEXT, cbInput.Texts) + + if cbInput.Config != nil { + tags.set(sem_ai.GEN_AI_MODEL_NAME, cbInput.Config.Model) + } + } + + case components.ComponentOfRetriever: + cbInput := retriever.ConvCallbackInput(input) + if cbInput != nil { + tags.set(sem_ai.GEN_AI_INPUT, parseAny(ctx, cbInput.Query, false)) + tags.set(sem_ai.GEN_AI_REQUEST_PARAMETERS, convertRetrieverCallOption(cbInput)) + tags.set(sem_ai.RERANKER_QUERY, parseAny(ctx, cbInput.Query, false)) + + // 处理检索器特定参数 + if cbInput.Extra != nil { + if topK, ok := cbInput.Extra["top_k"]; ok { + tags.set(sem_ai.RERANKER_TOP_K, topK) + } + if modelName, ok := cbInput.Extra["model_name"]; ok { + tags.set(sem_ai.RERANKER_MODEL_NAME, modelName) + } + } + } + + case components.ComponentOfIndexer: + cbInput := indexer.ConvCallbackInput(input) + if cbInput != nil { + tags.set(sem_ai.GEN_AI_INPUT, parseAny(ctx, cbInput.Docs, false)) + } + + case components.ComponentOfTool: + cbInput := tool.ConvCallbackInput(input) + if cbInput != nil { + tags.set(sem_ai.GEN_AI_TOOL_NAME, getName(info)) + tags.set(sem_ai.GEN_AI_TOOL_TYPE, info.Type) + toolInput := cbInput.ArgumentsInJSON + tags.set(sem_ai.GEN_AI_INPUT, toolInput) + tags.set(sem_ai.GEN_AI_TOOL_CALL_ARGUMENTS, toolInput) + } + + case compose.ComponentOfLambda: + tags.set(sem_ai.GEN_AI_INPUT, parseAny(ctx, input, false)) + + default: + // 默认设置为任务类型 + tags.set(sem_ai.GEN_AI_INPUT, parseAny(ctx, input, false)) + } + + return tags, nil +} + +// 设置模型配置和token使用情况的公共函数 +func setModelConfigAndTokenUsage(tags spanTags, cbOutput *model.CallbackOutput, setModelName bool) { + // 设置响应模型 + if cbOutput.Config != nil { + tags.set(sem_ai.GEN_AI_RESPONSE_MODEL, cbOutput.Config.Model) + // 可选设置模型名称 + if setModelName { + tags.set(sem_ai.GEN_AI_MODEL_NAME, cbOutput.Config.Model) + } + } + + // 设置token使用情况 + if cbOutput.TokenUsage != nil { + tags.set(sem_ai.GEN_AI_USAGE_TOTAL_TOKENS, cbOutput.TokenUsage.TotalTokens). + set(sem_ai.GEN_AI_USAGE_PROMPT_TOKENS, cbOutput.TokenUsage.PromptTokens). + set(sem_ai.GEN_AI_USAGE_COMPLETION_TOKENS, cbOutput.TokenUsage.CompletionTokens). + set(sem_ai.GEN_AI_USAGE_INPUT_TOKENS, cbOutput.TokenUsage.PromptTokens). + set(sem_ai.GEN_AI_USAGE_OUTPUT_TOKENS, cbOutput.TokenUsage.CompletionTokens) + + // Keep all cost dimensions present (including zero) so dashboard SUM + // queries return 0 instead of an empty result when a provider does not + // report cache or reasoning usage. + cachedTokens := cbOutput.TokenUsage.PromptTokenDetails.CachedTokens + tags.set(sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, cachedTokens). + set(sem_ai.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_V2, cachedTokens). + set(sem_ai.GEN_AI_USAGE_CACHED_TOKENS, cachedTokens). + set(sem_ai.GEN_AI_USAGE_CACHE_CREATE_INPUT_TOKENS, 0). + set(sem_ai.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, 0). + set(sem_ai.GEN_AI_USAGE_REASONING_OUTPUT_TOKENS, cbOutput.TokenUsage.CompletionTokensDetails.ReasoningTokens) + } +} + +// 设置完成原因和完成内容的公共函数 +func setMessageCompletionDetails(tags spanTags, msg *schema.Message) { + // 设置完成原因 + if msg != nil && msg.ResponseMeta != nil && len(msg.ResponseMeta.FinishReason) > 0 { + tags.set(sem_ai.GEN_AI_RESPONSE_FINISH_REASON, msg.ResponseMeta.FinishReason) + } + // 设置推理内容 + if msg != nil && len(msg.ReasoningContent) > 0 { + tags.set(sem_ai.GEN_AI_REASONING_CONTENT, msg.ReasoningContent) + } + // 设置完成内容 + if msg != nil && messageDisplayText(msg) != "" { + tags.set(fmt.Sprintf("%s.%d.role", sem_ai.GEN_AI_COMPLETION, 0), string(msg.Role)) + tags.set(fmt.Sprintf("%s.%d.content", sem_ai.GEN_AI_COMPLETION, 0), messageDisplayText(msg)) + } + if msg != nil { + tags.set(sem_ai.GEN_AI_OUTPUT_MESSAGES, []*sem_ai.ModelMessage{convertModelMessage(msg)}) + } +} + +func (d defaultDataParser) ParseOutput(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) (map[string]any, error) { + if info == nil { + return nil, nil + } + + tags := make(spanTags) + switch info.Component { + case components.ComponentOfChatModel: + cbOutput := model.ConvCallbackOutput(output) + if cbOutput != nil { + // 设置输出内容 + finalOutput := convertModelOutput(cbOutput) + tags.set(sem_ai.GEN_AI_OUTPUT, finalOutput) + + // 使用公共函数设置模型配置和token使用情况 + setModelConfigAndTokenUsage(tags, cbOutput, false) + + // 使用公共函数设置完成原因和完成内容 + setMessageCompletionDetails(tags, cbOutput.Message) + } + + case components.ComponentOfPrompt: + cbOutput := prompt.ConvCallbackOutput(output) + if cbOutput != nil { + // 可以添加提示模板输出相关字段 + } + + case components.ComponentOfEmbedding: + cbOutput := embedding.ConvCallbackOutput(output) + if cbOutput != nil { + // 设置嵌入向量 + tags.set(sem_ai.GEN_AI_OUTPUT, parseAny(ctx, cbOutput.Embeddings, false)) + tags.set(sem_ai.EMBEDDING_EMBEDDINGS, parseAny(ctx, cbOutput.Embeddings, false)) + + // 设置向量大小 + if len(cbOutput.Embeddings) > 0 { + vec := cbOutput.Embeddings[0] + if len(vec) > 0 { + tags.set(sem_ai.GEN_AI_EMBEDDINGS_DIMENSION_COUNT, len(vec)) + } + } + // 设置token使用情况 + if cbOutput.TokenUsage != nil { + tags.set(sem_ai.GEN_AI_USAGE_TOTAL_TOKENS, cbOutput.TokenUsage.TotalTokens). + set(sem_ai.GEN_AI_USAGE_INPUT_TOKENS, cbOutput.TokenUsage.PromptTokens). + set(sem_ai.GEN_AI_USAGE_COMPLETION_TOKENS, cbOutput.TokenUsage.CompletionTokens) + } + + // 设置模型信息 + if cbOutput.Config != nil { + tags.set(sem_ai.GEN_AI_REQUEST_MODEL, cbOutput.Config.Model) + tags.set(sem_ai.GEN_AI_RESPONSE_MODEL, cbOutput.Config.Model) + } + } + + case components.ComponentOfIndexer: + cbOutput := indexer.ConvCallbackOutput(output) + if cbOutput != nil { + // 设置索引文档ID + tags.set(sem_ai.GEN_AI_OUTPUT, parseAny(ctx, cbOutput.IDs, false)) + for i, id := range cbOutput.IDs { + tags.set(fmt.Sprintf("%s.%d", sem_ai.DOCUMENT_ID, i), id) + } + } + + case components.ComponentOfRetriever: + cbOutput := retriever.ConvCallbackOutput(output) + if cbOutput != nil { + // 设置检索结果 + result := convertRetrieverOutput(cbOutput) + tags.set(sem_ai.GEN_AI_OUTPUT, result) + + // 处理检索文档详情 + if cbOutput.Docs != nil { + // 按要求格式解析并配置 GEN_AI_RETRIEVAL_DOCUMENTS + retrievalDocs := make([]map[string]interface{}, 0, len(cbOutput.Docs)) + for _, doc := range cbOutput.Docs { + docMap := map[string]interface{}{ + "document": map[string]interface{}{ + "content": doc.Content, + "metadata": doc.MetaData, + "score": doc.MetaData["_score"], // 假设分数存储在 _score 元数据中 + "id": doc.ID, + }, + } + retrievalDocs = append(retrievalDocs, docMap) + } + tags.set(sem_ai.GEN_AI_RETRIEVAL_DOCUMENTS, retrievalDocs) + } + } + + case components.ComponentOfTool: + cbOutput := tool.ConvCallbackOutput(output) + if cbOutput != nil { + toolOutput := cbOutput.Response + if toolOutput == "" && cbOutput.ToolOutput != nil { + toolOutput = parseAny(ctx, cbOutput.ToolOutput, false) + } + tags.set(sem_ai.GEN_AI_OUTPUT, toolOutput) + tags.set(sem_ai.GEN_AI_TOOL_CALL_RESULT, toolOutput) + } + + case compose.ComponentOfLambda: + tags.set(sem_ai.GEN_AI_OUTPUT, parseAny(ctx, output, false)) + + case compose.ComponentOfToolsNode: + messages, ok := output.([]*schema.Message) + if ok { + tags.set(sem_ai.GEN_AI_OUTPUT, parseAny(ctx, iterSliceWithCtx(ctx, iterSlice(messages, convertModelMessage), addToolName), false)) + } else { + tags.set(sem_ai.GEN_AI_OUTPUT, parseAny(ctx, output, false)) + } + default: + // 默认设置为任务类型 + tags.set(sem_ai.GEN_AI_OUTPUT, parseAny(ctx, output, false)) + } + + return tags, nil +} + +func (d defaultDataParser) ParseStreamInput(ctx context.Context, info *callbacks.RunInfo, input *schema.StreamReader[callbacks.CallbackInput]) (map[string]any, error) { + defer input.Close() + + if info == nil { + return nil, nil + } + + tags := make(spanTags) + // 设置流相关标志 + tags.set(sem_ai.GEN_AI_REQUEST_IS_STREAM, true) + tags.set(sem_ai.GEN_AI_IS_STREAMING, true) + + switch info.Component { + case components.ComponentOfChatModel: + chunks, recvErr := d.ParseDefaultStreamInput(ctx, input) + if recvErr != nil { + return nil, recvErr + } + + // try concat + config, messages, _, concatErr := d.tryConcatInputChunks(convModelCallbackInput(chunks)) + if concatErr != nil { + return nil, concatErr + } + + tags.set(sem_ai.GEN_AI_INPUT, parseAny(ctx, messages, true)) + tags.set(sem_ai.GEN_AI_INPUT_MESSAGES, iterSlice(messages, convertModelMessage)) + // 设置聚合后的消息 + if len(messages) > 0 { + for i, msg := range messages { + if msg != nil && messageDisplayText(msg) != "" { + tags.set(fmt.Sprintf("%s.%d.role", sem_ai.GEN_AI_PROMPT, i), string(msg.Role)) + tags.set(fmt.Sprintf("%s.%d.content", sem_ai.GEN_AI_PROMPT, i), messageDisplayText(msg)) + } + } + } + if config != nil { + tags.set(sem_ai.GEN_AI_REQUEST_MODEL, config.Model) + tags.set(sem_ai.GEN_AI_REQUEST_MAX_TOKENS, config.MaxTokens) + tags.set(sem_ai.GEN_AI_REQUEST_TEMPERATURE, config.Temperature) + tags.set(sem_ai.GEN_AI_REQUEST_TOP_P, config.TopP) + } + case components.ComponentOfTool: + chunks, recvErr := d.ParseDefaultStreamInput(ctx, input) + if recvErr != nil { + return nil, recvErr + } + arguments := make([]string, 0, len(chunks)) + for _, chunk := range chunks { + if cbInput := tool.ConvCallbackInput(chunk); cbInput != nil { + arguments = append(arguments, cbInput.ArgumentsInJSON) + } + } + toolInput := strings.Join(arguments, "") + tags.set(sem_ai.GEN_AI_TOOL_NAME, getName(info)) + tags.set(sem_ai.GEN_AI_TOOL_TYPE, info.Type) + tags.set(sem_ai.GEN_AI_INPUT, toolInput) + tags.set(sem_ai.GEN_AI_TOOL_CALL_ARGUMENTS, toolInput) + default: + chunks, recvErr := d.ParseDefaultStreamInput(ctx, input) + if recvErr != nil { + return nil, recvErr + } + + i, concatErr := d.tryConcatChunks(toAnySlice(chunks)) + if concatErr != nil { + return nil, concatErr + } + + tags.set(sem_ai.GEN_AI_INPUT, parseAny(ctx, i, true)) + } + + return tags, nil +} + +func (d defaultDataParser) ParseStreamOutput(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[callbacks.CallbackOutput]) (map[string]any, error) { + defer output.Close() + + if info == nil { + return nil, nil + } + + tags := make(spanTags) + var ( + hasFirstToken bool + firstTokenTime time.Time + startTime = time.Now() + ) + // 设置流相关标志 + tags.set(sem_ai.GEN_AI_REQUEST_IS_STREAM, true) + tags.set(sem_ai.GEN_AI_IS_STREAMING, true) + switch info.Component { + case components.ComponentOfChatModel: + var chunks []callbacks.CallbackOutput + for { + item, recvErr := output.Recv() + if recvErr != nil { + if recvErr == io.EOF { + break + } + return nil, recvErr + } + + cbOutput := model.ConvCallbackOutput(item) + // 记录首次token时间 + if !hasFirstToken && cbOutput.Message != nil && len(cbOutput.Message.Content) > 0 { + hasFirstToken = true + firstTokenTime = time.Now() + timeToFirstToken := firstTokenTime.Sub(startTime).Milliseconds() + tags.set(sem_ai.GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN, timeToFirstToken) + tags.set(sem_ai.GEN_AI_USER_TIME_TO_FIRST_TOKEN, timeToFirstToken) + } + + chunks = append(chunks, item) + } + + // 转换为模型输出格式 + modelChunks := convModelCallbackOutput(chunks) + + // 合并输出 + usage, message, _, err := d.tryConcatOutputChunks(modelChunks) + if err != nil { + return nil, err + } + + var mergedOutput *model.CallbackOutput + for _, chunk := range modelChunks { + if chunk == nil { + continue + } + + mergedOutput = &model.CallbackOutput{ + Config: chunk.Config, + TokenUsage: usage, + } + if chunk.Config != nil { + break + } + } + if mergedOutput != nil { + setModelConfigAndTokenUsage(tags, mergedOutput, true) + } + if message != nil { + setMessageCompletionDetails(tags, message) + tags.set(sem_ai.GEN_AI_OUTPUT, convertModelOutput(&model.CallbackOutput{ + Message: message, + TokenUsage: usage, + })) + } + case components.ComponentOfTool: + chunks, recvErr := d.ParseDefaultStreamOutput(ctx, output) + if recvErr != nil { + return nil, recvErr + } + responses := make([]string, 0, len(chunks)) + for _, chunk := range chunks { + if cbOutput := tool.ConvCallbackOutput(chunk); cbOutput != nil { + if cbOutput.Response != "" { + responses = append(responses, cbOutput.Response) + } else if cbOutput.ToolOutput != nil { + responses = append(responses, parseAny(ctx, cbOutput.ToolOutput, true)) + } + } + } + toolOutput := strings.Join(responses, "") + tags.set(sem_ai.GEN_AI_OUTPUT, toolOutput) + tags.set(sem_ai.GEN_AI_TOOL_CALL_RESULT, toolOutput) + default: + chunks, recvErr := d.ParseDefaultStreamOutput(ctx, output) + if recvErr != nil { + return nil, recvErr + } + + o, concatErr := d.tryConcatChunks(toAnySlice(chunks)) + if concatErr != nil { + return nil, concatErr + } + + tags.set(sem_ai.GEN_AI_OUTPUT, parseAny(ctx, o, true)) + } + + return tags, nil +} + +func (d defaultDataParser) ParseDefaultStreamInput(ctx context.Context, input *schema.StreamReader[callbacks.CallbackInput]) (chunks []callbacks.CallbackInput, err error) { + for { + item, recvErr := input.Recv() + if recvErr != nil { + if recvErr == io.EOF { + break + } + + return chunks, recvErr + } + + chunks = append(chunks, item) + } + + return chunks, nil +} + +func (d defaultDataParser) ParseDefaultStreamOutput(ctx context.Context, output *schema.StreamReader[callbacks.CallbackOutput]) (chunks []callbacks.CallbackOutput, err error) { + for { + item, recvErr := output.Recv() + if recvErr != nil { + if recvErr == io.EOF { + break + } + + return chunks, recvErr + } + + chunks = append(chunks, item) + } + + return chunks, nil +} + +func (d defaultDataParser) tryConcatInputChunks(chunks []*model.CallbackInput) (config *model.Config, messages []*schema.Message, extra map[string]interface{}, err error) { + var mas [][]*schema.Message + for _, c := range chunks { + if c == nil { + continue + } + if len(c.Messages) > 0 { + mas = append(mas, c.Messages) + } + if len(c.Extra) > 0 { + extra = c.Extra + } + if c.Config != nil { + config = c.Config + } + } + if len(mas) == 0 { + return config, []*schema.Message{}, extra, nil + } + messages, err = concatMessageArray(mas) + if err != nil { + return nil, nil, nil, fmt.Errorf("concat messages failed: %v", err) + } + return config, messages, extra, nil +} + +func (d defaultDataParser) tryConcatOutputChunks(chunks []*model.CallbackOutput) (usage *model.TokenUsage, message *schema.Message, extra map[string]interface{}, err error) { + var mas []*schema.Message + for _, c := range chunks { + if c == nil { + continue + } + if c.TokenUsage != nil { + usage = c.TokenUsage + } + if c.Message != nil { + mas = append(mas, c.Message) + } + if c.Extra != nil { + extra = c.Extra + } + } + if len(mas) == 0 { + return usage, &schema.Message{}, extra, nil + } + message, err = schema.ConcatMessages(mas) + if err != nil { + return nil, nil, nil, fmt.Errorf("concat message failed: %v", err) + } + return usage, message, extra, nil +} + +func (d defaultDataParser) tryConcatChunks(chunks []any) (any, error) { + if len(chunks) == 0 { + return chunks, nil + } + + var first any + for _, chunk := range chunks { + if chunk != nil { + first = chunk + break + } + } + if first == nil { + return chunks, nil + } + + typ := reflect.TypeOf(first) + if fn := d.getConcatFunc(typ); fn != nil { + s := reflect.MakeSlice(reflect.SliceOf(typ), 0, len(chunks)) + for _, chunk := range chunks { + if chunk == nil { + continue + } + s = reflect.Append(s, reflect.ValueOf(chunk)) + } + + val, concatErr := fn(s) + if concatErr != nil { + return nil, concatErr + } + + return val.Interface(), nil + } + + return chunks, nil +} + +func (d defaultDataParser) getConcatFunc(typ reflect.Type) func(reflect.Value) (reflect.Value, error) { + if fn, ok := d.concatFuncs[typ]; ok { + return func(a reflect.Value) (reflect.Value, error) { + rvs := reflect.ValueOf(fn).Call([]reflect.Value{a}) + var err error + if !rvs[1].IsNil() { + err = rvs[1].Interface().(error) + } + return rvs[0], err + } + } + + return nil +} + +// messageDisplayText supplies the compact input/output used by the session +// table. Detailed multimodal structure stays in gen_ai.*.messages. +func messageDisplayText(message *schema.Message) string { + if message == nil { + return "" + } + lensMessages := toTLSLensMessages([]*sem_ai.ModelMessage{convertModelMessage(message)}) + if len(lensMessages) == 0 { + return "" + } + return lensMessageSummary(lensMessages[0]) +} + +func modelMessagePartsJSON(message *schema.Message) []map[string]any { + converted := convertModelMessage(message) + if converted == nil || len(converted.Parts) == 0 { + return nil + } + parts := make([]map[string]any, 0, len(converted.Parts)) + for _, part := range converted.Parts { + if part == nil { + continue + } + encoded, err := json.Marshal(part) + if err != nil { + continue + } + var value map[string]any + if err := json.Unmarshal(encoded, &value); err == nil { + parts = append(parts, value) + } + } + return parts +} + +func parseAny(ctx context.Context, v any, bStream bool) string { + if v == nil { + return "" + } + + switch t := v.(type) { + case []*schema.Message: + return toJson(t, bStream) + + case *schema.Message: + return toJson(t, bStream) + + case string: + if bStream { + return toJson(t, bStream) + } + return t + + case json.Marshaler: + return toJson(v, bStream) + + case map[string]any: + return toJson(t, bStream) + + case []callbacks.CallbackInput: + return parseAny(ctx, toAnySlice(t), bStream) + + case []callbacks.CallbackOutput: + return parseAny(ctx, toAnySlice(t), bStream) + + case []any: + if len(t) > 0 { + if _, ok := t[0].(*schema.Message); ok { + msgs := make([]*schema.Message, 0, len(t)) + for i := range t { + msg, ok := t[i].(*schema.Message) + if ok { + msgs = append(msgs, msg) + } + } + + return parseAny(ctx, msgs, bStream) + } + } + + return toJson(t, bStream) + + default: + return toJson(v, bStream) + } +} + +func toAnySlice[T any](src []T) []any { + resp := make([]any, len(src)) + for i := range src { + resp[i] = src[i] + } + + return resp +} + +// 辅助函数: 处理提示模板 +func _promptTemplateInput(input *prompt.CallbackInput) []attribute.KeyValue { + if len(input.Templates) == 0 { + return nil + } + + var kvs []attribute.KeyValue + + // 处理PromptTemplate内容 + // if templateStr, ok := input.Templates[0].Template.(string); ok && templateStr != "" { + // kvs = append(kvs, attribute.String(sem_ai.GEN_AI_PROMPT_TEMPLATE, templateStr)) + // } + + // if inputVars, ok := input.Variables.(map[string]any); ok && len(inputVars) > 0 { + // varsStr, _ := json.Marshal(inputVars) + // kvs = append(kvs, attribute.String(sem_ai.GEN_AI_PROMPT_VARIABLES, string(varsStr))) + // } + + return kvs +} + +func _llmModelInput(input *model.CallbackInput) []attribute.KeyValue { + if input == nil || len(input.Messages) == 0 { + return nil + } + + var kvs []attribute.KeyValue + inputs := make([]map[string]interface{}, 0) + for i, in := range input.Messages { + if in != nil && len(in.Content) > 0 { + kvs = append(kvs, attribute.String(fmt.Sprintf(sem_ai.GEN_AI_PROMPT+".%d.role", i), string(in.Role))) + kvs = append(kvs, attribute.String(fmt.Sprintf(sem_ai.GEN_AI_PROMPT+".%d.content", i), in.Content)) + } + if in != nil && len(in.MultiContent) > 0 { + multiContentJSON, err := convertMultiContentToJSON(in) + if err == nil && multiContentJSON != nil { + inputs = append(inputs, multiContentJSON...) + } + } + } + + if len(inputs) > 0 { + inputmap := map[string]interface{}{ + "inputs": map[string]interface{}{ + "input": inputs, + }, + } + jsonStr, _ := json.Marshal(inputmap) + kvs = append(kvs, attribute.String(fmt.Sprintf(sem_ai.GEN_AI_INPUT), string(jsonStr))) + } + + for i, tool := range input.Tools { + if tool != nil && len(tool.Name) > 0 { + kvs = append(kvs, attribute.String(fmt.Sprintf(sem_ai.GEN_AI_COMPLETION+".%d"+sem_ai.TOOL_CALL_FUNCTION_NAME, i), + tool.Name)) + kvs = append(kvs, attribute.String(fmt.Sprintf(sem_ai.GEN_AI_COMPLETION+".%d"+sem_ai.TOOL_CALL_FUNCTION_ARGUMENTS, + i), tool.Desc)) + kvs = append(kvs, attribute.String(fmt.Sprintf(sem_ai.GEN_AI_COMPLETION+".%d"+sem_ai.TOOL_CALL_FUNCTION_DESCRIPTION, i), + tool.Desc)) + } + } + + if input.Config != nil { + kvs = append(kvs, attribute.String(sem_ai.GEN_AI_MODEL_NAME, input.Config.Model)) + kvs = append(kvs, attribute.Float64(sem_ai.GEN_AI_REQUEST_TEMPERATURE, float64(input.Config.Temperature))) + kvs = append(kvs, attribute.Float64(sem_ai.GEN_AI_REQUEST_TOP_P, float64(input.Config.TopP))) + } + + return kvs +} + +func _llmModelOutput(input *model.CallbackOutput) []attribute.KeyValue { + if input == nil || input.TokenUsage == nil { + return nil + } + + var kvs []attribute.KeyValue + + if input.TokenUsage != nil { + kvs = append(kvs, attribute.Int(sem_ai.GEN_AI_USAGE_TOTAL_TOKENS, input.TokenUsage.TotalTokens)) + kvs = append(kvs, attribute.Int(sem_ai.GEN_AI_USAGE_COMPLETION_TOKENS, input.TokenUsage.CompletionTokens)) + kvs = append(kvs, attribute.Int(sem_ai.GEN_AI_USAGE_PROMPT_TOKENS, input.TokenUsage.PromptTokens)) + } + + if input.Message != nil && len(input.Message.ReasoningContent) > 0 { + kvs = append(kvs, attribute.String(sem_ai.GEN_AI_RESPONSE_FINISH_REASON, input.Message.ReasoningContent)) + } + + return kvs +} + +func _toolsOutput(input *tool.CallbackOutput) []attribute.KeyValue { + if input == nil || len(input.Response) == 0 { + return nil + } + + var kvs []attribute.KeyValue + + // 设置工具响应相关属性 + // kvs = append(kvs, attribute.String(sem_ai.AGENT_OBSERVATION, input.Response)) + + return kvs +} + +func convertMultiContentToJSON(in *schema.Message) ([]map[string]interface{}, error) { + if in == nil || len(in.MultiContent) == 0 { + return nil, fmt.Errorf("input message is nil or has no multi-content") + } + + inputs := make([]map[string]interface{}, 0, len(in.MultiContent)) + + for _, part := range in.MultiContent { + switch part.Type { + case schema.ChatMessagePartTypeText: + inputs = append(inputs, map[string]interface{}{ + "type": "text", + "text": part.Text, + }) + case schema.ChatMessagePartTypeImageURL: + if part.ImageURL != nil { + inputs = append(inputs, map[string]interface{}{ + "type": "image", + "image_url": map[string]interface{}{ + "url": part.ImageURL.URL, + }, + }) + } + case schema.ChatMessagePartTypeAudioURL: + if part.AudioURL != nil { + inputs = append(inputs, map[string]interface{}{ + "type": "audio", + "audio_url": map[string]interface{}{ + "url": part.AudioURL.URL, + }, + }) + } + case schema.ChatMessagePartTypeVideoURL: + if part.VideoURL != nil { + inputs = append(inputs, map[string]interface{}{ + "type": "video", + "video_url": map[string]interface{}{ + "url": part.VideoURL.URL, + }, + }) + } + case schema.ChatMessagePartTypeFileURL: + if part.FileURL != nil { + inputs = append(inputs, map[string]interface{}{ + "type": "file", + "file_url": map[string]interface{}{ + "url": part.FileURL.URL, + }, + }) + } + } + } + + return inputs, nil +} diff --git a/callbacks/tls/trace_test.go b/callbacks/tls/trace_test.go new file mode 100644 index 000000000..5f52a573d --- /dev/null +++ b/callbacks/tls/trace_test.go @@ -0,0 +1,248 @@ +/* + * Copyright 2026 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "reflect" + "strings" + "testing" + + sem_ai "github.com/cloudwego/eino-ext/callbacks/tls/semconv" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/embedding" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/retriever" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +func TestDefaultDataParserParseInputRetrieverSetsRequestParameters(t *testing.T) { + parser := defaultDataParser{} + + tags, err := parser.ParseInput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfRetriever}, &retriever.CallbackInput{ + Query: "what is tls callback", + TopK: 3, + Filter: "lang=zh", + }) + if err != nil { + t.Fatalf("ParseInput returned error: %v", err) + } + + params, ok := tags[sem_ai.GEN_AI_REQUEST_PARAMETERS].(string) + if !ok { + t.Fatalf("expected %q to be stored as JSON string, got %#v", sem_ai.GEN_AI_REQUEST_PARAMETERS, tags[sem_ai.GEN_AI_REQUEST_PARAMETERS]) + } + if !strings.Contains(params, `"top_k":3`) { + t.Fatalf("expected retriever top_k in request parameters, got %s", params) + } + if !strings.Contains(params, `"filter":"lang=zh"`) { + t.Fatalf("expected retriever filter in request parameters, got %s", params) + } + if _, exists := tags[sem_ai.GEN_AI_SPAN_KIND_RETRIEVER]; exists { + t.Fatalf("unexpected retriever span kind value used as attribute key: %q", sem_ai.GEN_AI_SPAN_KIND_RETRIEVER) + } +} + +func TestDefaultDataParserParseOutputEmbeddingUsesDimensionCount(t *testing.T) { + parser := defaultDataParser{} + + tags, err := parser.ParseOutput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfEmbedding}, &embedding.CallbackOutput{ + Embeddings: [][]float64{{1, 2, 3}}, + }) + if err != nil { + t.Fatalf("ParseOutput returned error: %v", err) + } + + got, ok := tags[sem_ai.GEN_AI_EMBEDDINGS_DIMENSION_COUNT].(int) + if !ok { + t.Fatalf("expected dimension count to be int, got %#v", tags[sem_ai.GEN_AI_EMBEDDINGS_DIMENSION_COUNT]) + } + if got != 3 { + t.Fatalf("expected dimension count 3, got %d", got) + } +} + +func TestDefaultDataParserParseStreamOutputIncludesCompletionDetails(t *testing.T) { + parser := defaultDataParser{} + reader, writer := schema.Pipe[callbacks.CallbackOutput](2) + writer.Send(&model.CallbackOutput{ + Message: &schema.Message{ + Role: schema.Assistant, + Content: "partial", + }, + }, nil) + writer.Send(&model.CallbackOutput{ + Message: &schema.Message{ + Role: schema.Assistant, + Content: " response", + ResponseMeta: &schema.ResponseMeta{ + FinishReason: "stop", + }, + }, + TokenUsage: &model.TokenUsage{ + PromptTokens: 5, + CompletionTokens: 2, + TotalTokens: 7, + }, + }, nil) + writer.Close() + + tags, err := parser.ParseStreamOutput(context.Background(), &callbacks.RunInfo{Component: components.ComponentOfChatModel}, reader) + if err != nil { + t.Fatalf("ParseStreamOutput returned error: %v", err) + } + + if got := tags[sem_ai.GEN_AI_RESPONSE_FINISH_REASON]; got != "stop" { + t.Fatalf("expected finish reason stop, got %#v", got) + } + if got := tags[sem_ai.GEN_AI_COMPLETION+".0.content"]; got != "partial response" { + t.Fatalf("expected aggregated completion content partial response, got %#v", got) + } + + output, ok := tags[sem_ai.GEN_AI_OUTPUT].(string) + if !ok { + t.Fatalf("expected stream output to be serialized JSON, got %#v", tags[sem_ai.GEN_AI_OUTPUT]) + } + if !strings.Contains(output, `"finish_reason":"stop"`) { + t.Fatalf("expected serialized output to include finish_reason, got %s", output) + } + if !strings.Contains(output, `"content":"partial response"`) { + t.Fatalf("expected serialized output to include aggregated content, got %s", output) + } +} + +func TestConvertModelMessagePreservesReasoningAndFilePart(t *testing.T) { + got := convertModelMessage(&schema.Message{ + Role: schema.Assistant, + ReasoningContent: "need to inspect the document first", + MultiContent: []schema.ChatMessagePart{ + { + Type: schema.ChatMessagePartTypeFileURL, + FileURL: &schema.ChatMessageFileURL{ + Name: "spec.pdf", + URL: "https://example.com/spec.pdf", + }, + }, + }, + }) + + if got == nil { + t.Fatal("expected converted message") + } + if got.ReasoningContent != "need to inspect the document first" { + t.Fatalf("expected reasoning content to be preserved, got %q", got.ReasoningContent) + } + if len(got.Parts) != 1 || got.Parts[0] == nil || got.Parts[0].FileURL == nil { + t.Fatalf("expected file part to be preserved, got %#v", got.Parts) + } + if got.Parts[0].FileURL.Name != "spec.pdf" { + t.Fatalf("expected file name spec.pdf, got %q", got.Parts[0].FileURL.Name) + } + if got.Parts[0].FileURL.URL != "https://example.com/spec.pdf" { + t.Fatalf("expected file url to be preserved, got %q", got.Parts[0].FileURL.URL) + } +} + +func TestDefaultDataParserParseOutputToolsNodeAddsToolName(t *testing.T) { + parser := defaultDataParser{} + ctx := context.WithValue(context.Background(), tlsToolIDNameMapKey{}, map[string]string{ + "call-1": "search", + }) + + tags, err := parser.ParseOutput(ctx, &callbacks.RunInfo{Component: compose.ComponentOfToolsNode}, []*schema.Message{ + { + Role: schema.Tool, + ToolCallID: "call-1", + Content: "result", + }, + }) + if err != nil { + t.Fatalf("ParseOutput returned error: %v", err) + } + + output, ok := tags[sem_ai.GEN_AI_OUTPUT].(string) + if !ok { + t.Fatalf("expected tools node output to be serialized JSON, got %#v", tags[sem_ai.GEN_AI_OUTPUT]) + } + if !strings.Contains(output, `"name":"search"`) { + t.Fatalf("expected serialized output to include tool name, got %s", output) + } +} + +func TestDefaultDataParserParseStreamInputUsesCustomConcatFunction(t *testing.T) { + parser := defaultDataParser{ + concatFuncs: map[reflect.Type]any{ + reflect.TypeOf(""): func(chunks []string) (string, error) { + return strings.Join(chunks, ""), nil + }, + }, + } + + reader, writer := schema.Pipe[callbacks.CallbackInput](2) + writer.Send("foo", nil) + writer.Send("bar", nil) + writer.Close() + + tags, err := parser.ParseStreamInput(context.Background(), &callbacks.RunInfo{Component: compose.ComponentOfLambda}, reader) + if err != nil { + t.Fatalf("ParseStreamInput returned error: %v", err) + } + + input, ok := tags[sem_ai.GEN_AI_INPUT].(string) + if !ok { + t.Fatalf("expected stream input to be serialized JSON, got %#v", tags[sem_ai.GEN_AI_INPUT]) + } + if !strings.Contains(input, `"foobar"`) { + t.Fatalf("expected custom concat result in input, got %s", input) + } + if strings.Contains(input, `"foo","bar"`) { + t.Fatalf("expected chunks to be concatenated before serialization, got %s", input) + } +} + +func TestDefaultDataParserParseStreamOutputUsesCustomConcatFunction(t *testing.T) { + parser := defaultDataParser{ + concatFuncs: map[reflect.Type]any{ + reflect.TypeOf(""): func(chunks []string) (string, error) { + return strings.Join(chunks, ""), nil + }, + }, + } + + reader, writer := schema.Pipe[callbacks.CallbackOutput](2) + writer.Send("foo", nil) + writer.Send("bar", nil) + writer.Close() + + tags, err := parser.ParseStreamOutput(context.Background(), &callbacks.RunInfo{Component: compose.ComponentOfLambda}, reader) + if err != nil { + t.Fatalf("ParseStreamOutput returned error: %v", err) + } + + output, ok := tags[sem_ai.GEN_AI_OUTPUT].(string) + if !ok { + t.Fatalf("expected stream output to be serialized JSON, got %#v", tags[sem_ai.GEN_AI_OUTPUT]) + } + if !strings.Contains(output, `"foobar"`) { + t.Fatalf("expected custom concat result in output, got %s", output) + } + if strings.Contains(output, `"foo","bar"`) { + t.Fatalf("expected chunks to be concatenated before serialization, got %s", output) + } +} diff --git a/callbacks/tls/utils.go b/callbacks/tls/utils.go new file mode 100644 index 000000000..8cb31a32b --- /dev/null +++ b/callbacks/tls/utils.go @@ -0,0 +1,310 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + "strings" + + "github.com/bytedance/sonic" + sem_ai "github.com/cloudwego/eino-ext/callbacks/tls/semconv" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +const ( + DefaultVersion = "1.0.1" + DefaultSpanName = "unset" +) + +func getName(info *callbacks.RunInfo) string { + if info == nil { + return "" + } + if len(info.Name) != 0 { + return info.Name + } + return strings.TrimSpace(info.Type + " " + string(info.Component)) +} + +func completeRunInfo(info *callbacks.RunInfo) *callbacks.RunInfo { + if info != nil && len(info.Name) == 0 { + nInfo := *info + nInfo.Name = getName(info) + return &nInfo + } + + return info +} + +func convModelCallbackInput(in []callbacks.CallbackInput) []*model.CallbackInput { + ret := make([]*model.CallbackInput, len(in)) + for i, c := range in { + ret[i] = model.ConvCallbackInput(c) + } + return ret +} + +func extractModelInput(ins []*model.CallbackInput) (config *model.Config, messages []*schema.Message, extra map[string]interface{}, err error) { + var mas [][]*schema.Message + for _, in := range ins { + if in == nil { + continue + } + if len(in.Messages) > 0 { + mas = append(mas, in.Messages) + } + if len(in.Extra) > 0 { + extra = in.Extra + } + if in.Config != nil { + config = in.Config + } + } + if len(mas) == 0 { + return config, []*schema.Message{}, extra, nil + } + messages, err = concatMessageArray(mas) + if err != nil { + return nil, nil, nil, fmt.Errorf("concat messages failed: %v", err) + } + return config, messages, extra, nil +} + +func convModelCallbackOutput(out []callbacks.CallbackOutput) []*model.CallbackOutput { + ret := make([]*model.CallbackOutput, len(out)) + for i, c := range out { + ret[i] = model.ConvCallbackOutput(c) + } + return ret +} + +func extractModelOutput(outs []*model.CallbackOutput) (usage *model.TokenUsage, messages []*schema.Message, extra map[string]interface{}, config *model.Config, err error) { + masMap := make(map[schema.RoleType][]*schema.Message) + for _, out := range outs { + if out == nil { + continue + } + if out.TokenUsage != nil { + usage = out.TokenUsage + } + if out.Message != nil { + if _, ok := masMap[out.Message.Role]; !ok { + masMap[out.Message.Role] = make([]*schema.Message, 0) + } + masMap[out.Message.Role] = append(masMap[out.Message.Role], out.Message) + } + if out.Extra != nil { + extra = out.Extra + } + if out.Config != nil { + config = out.Config + } + } + if len(masMap) == 0 { + return usage, nil, extra, config, nil + } + messages = make([]*schema.Message, 0) + for _, mas := range masMap { + message, err := schema.ConcatMessages(mas) + if err != nil { + log.Printf("concat message failed: %v", err) + } else { + messages = append(messages, message) + } + } + + return usage, messages, extra, config, nil +} + +func concatMessageArray(mas [][]*schema.Message) ([]*schema.Message, error) { + if len(mas) == 0 { + return nil, fmt.Errorf("message array is empty") + } + arrayLen := len(mas[0]) + + ret := make([]*schema.Message, arrayLen) + slicesToConcat := make([][]*schema.Message, arrayLen) + + for _, ma := range mas { + if len(ma) != arrayLen { + return nil, fmt.Errorf("unexpected array length. "+ + "Got %d, expected %d", len(ma), arrayLen) + } + + for i := 0; i < arrayLen; i++ { + m := ma[i] + if m != nil { + slicesToConcat[i] = append(slicesToConcat[i], m) + } + } + } + + for i, slice := range slicesToConcat { + if len(slice) == 0 { + ret[i] = nil + } else if len(slice) == 1 { + ret[i] = slice[0] + } else { + cm, err := schema.ConcatMessages(slice) + if err != nil { + return nil, err + } + + ret[i] = cm + } + } + + return ret, nil +} + +func convSchemaMessage(in []*schema.Message) []*model.CallbackInput { + ret := make([]*model.CallbackInput, len(in)) + for i, c := range in { + ret[i] = model.ConvCallbackInput(c) + } + return ret +} + +type tlsToolIDNameMapKey struct{} + +func injectToolIDNameMapToCtx(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context { + if info == nil || info.Component != compose.ComponentOfToolsNode { + return ctx + } + + message, ok := input.(*schema.Message) + if !ok || message == nil { + return ctx + } + + toolIDNameMap := make(map[string]string, len(message.ToolCalls)) + for _, toolCall := range message.ToolCalls { + toolIDNameMap[toolCall.ID] = toolCall.Function.Name + } + + return context.WithValue(ctx, tlsToolIDNameMapKey{}, toolIDNameMap) +} + +func getToolIDNameMapFromCtx(ctx context.Context) map[string]string { + toolIDNameMap, ok := ctx.Value(tlsToolIDNameMapKey{}).(map[string]string) + if !ok { + return nil + } + + return toolIDNameMap +} + +func toJson(v any, bStream bool) string { + if v == nil { + return fmt.Sprintf("%s", errors.New("try to marshal nil error")) + } + if bStream { + v = map[string]any{"stream": v} + } + b, err := sonic.MarshalString(v) + if err != nil { + return fmt.Sprintf("%s", err.Error()) + } + return b +} + +type spanTags map[string]any + +func (t spanTags) setTags(kv map[string]any) spanTags { + for k, v := range kv { + t.set(k, v) + } + + return t +} + +func (t spanTags) set(key string, value any) spanTags { + if t == nil || value == nil { + return t + } + + if _, found := t[key]; found { + return t + } + + switch k := reflect.TypeOf(value).Kind(); k { + case reflect.Array, + reflect.Interface, + reflect.Map, + reflect.Pointer, + reflect.Slice, + reflect.Struct: + value = toJson(value, false) + default: + + } + + t[key] = value + + return t +} + +func (t spanTags) setIfNotZero(key string, val any) { + if val == nil { + return + } + + rv := reflect.ValueOf(val) + if rv.IsValid() && rv.IsZero() { + return + } + + t.set(key, val) +} + +func (t spanTags) setFromExtraIfNotZero(key string, extra map[string]any) { + if extra == nil { + return + } + + t.setIfNotZero(key, extra[key]) +} + +// _eino_run_component_to_span_kind 转换 component 到 tls 可以识别的 span_type +// - 当前框架相比于之前缺失的后续需要补齐, 当前按照`TASK`处理 +// - compose 相关概念的 component 概念(Chain/Graph/...), 当前也先按照`TASK`处理 +func _eino_run_component_to_span_kind(c components.Component) string { + switch c { + case components.ComponentOfChatModel: + return sem_ai.GEN_AI_SPAN_KIND_LLM + + case components.ComponentOfEmbedding: + return sem_ai.GEN_AI_SPAN_KIND_EMBEDDING + + case components.ComponentOfRetriever: + return sem_ai.GEN_AI_SPAN_KIND_RETRIEVER + + case components.ComponentOfTool: + return sem_ai.GEN_AI_SPAN_KIND_TOOL + + default: + return sem_ai.GEN_AI_SPAN_KIND_TASK + } +} diff --git a/callbacks/tls/utils_test.go b/callbacks/tls/utils_test.go new file mode 100644 index 000000000..146a23347 --- /dev/null +++ b/callbacks/tls/utils_test.go @@ -0,0 +1,274 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +import ( + "errors" + "testing" + + "github.com/bytedance/mockey" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/schema" + "github.com/smartystreets/goconvey/convey" +) + +func Test_getName(t *testing.T) { + mockey.PatchConvey("Test getName with non-empty Name", t, func() { + info := &callbacks.RunInfo{Name: "testName", Type: "testType", Component: components.ComponentOfEmbedding} + actual := getName(info) + convey.So(actual, convey.ShouldEqual, "testName") + }) + mockey.PatchConvey("Test getName with empty Name", t, func() { + info := &callbacks.RunInfo{Name: "", Type: "testType", Component: components.ComponentOfEmbedding} + actual := getName(info) + convey.So(actual, convey.ShouldEqual, "testType "+string(components.ComponentOfEmbedding)) + }) + mockey.PatchConvey("Test getName with empty Name and Type", t, func() { + info := &callbacks.RunInfo{Name: "", Type: "", Component: components.ComponentOfEmbedding} + actual := getName(info) + convey.So(actual, convey.ShouldEqual, string(components.ComponentOfEmbedding)) + }) + mockey.PatchConvey("Test getName with empty Name and Component", t, func() { + info := &callbacks.RunInfo{Name: "", Type: "testType", Component: ""} + actual := getName(info) + convey.So(actual, convey.ShouldEqual, "testType") + }) + mockey.PatchConvey("Test getName with all empty fields", t, func() { + info := &callbacks.RunInfo{Name: "", Type: "", Component: ""} + actual := getName(info) + convey.So(actual, convey.ShouldEqual, "") + }) +} + +func Test_extractModelInput(t *testing.T) { + mockey.PatchConvey("Test extractModelInput with nil inputs", t, func() { + actualConfig, actualMessages, actualExtra, actualErr := extractModelInput(nil) + convey.So(actualConfig, convey.ShouldBeNil) + convey.So(actualMessages, convey.ShouldBeEmpty) + convey.So(actualExtra, convey.ShouldBeNil) + convey.So(actualErr, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test extractModelInput with empty inputs", t, func() { + actualConfig, actualMessages, actualExtra, actualErr := extractModelInput([]*model.CallbackInput{}) + convey.So(actualConfig, convey.ShouldBeNil) + convey.So(actualMessages, convey.ShouldBeEmpty) + convey.So(actualExtra, convey.ShouldBeNil) + convey.So(actualErr, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test extractModelInput with valid inputs", t, func() { + inputs := []*model.CallbackInput{ + { + Messages: []*schema.Message{ + {Role: "user", Content: "Hello"}, + }, + Config: &model.Config{ + Model: "gpt-3.5-turbo", + }, + Extra: map[string]interface{}{ + "key1": "value1", + }, + }, + { + Messages: []*schema.Message{ + {Role: "assistant", Content: "Hi there!"}, + }, + Extra: map[string]interface{}{ + "key2": "value2", + }, + }, + } + + expectedConfig := &model.Config{ + Model: "gpt-3.5-turbo", + } + expectedMessages := []*schema.Message{ + {Role: "user", Content: "Hello"}, + {Role: "assistant", Content: "Hi there!"}, + } + expectedExtra := map[string]interface{}{ + "key2": "value2", + } + + mockey.Mock(concatMessageArray).Return(expectedMessages, nil).Build() + actualConfig, actualMessages, actualExtra, actualErr := extractModelInput(inputs) + convey.So(actualConfig, convey.ShouldEqual, expectedConfig) + convey.So(actualMessages, convey.ShouldEqual, expectedMessages) + convey.So(actualExtra, convey.ShouldEqual, expectedExtra) + convey.So(actualErr, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test extractModelInput with concatMessageArray failure", t, func() { + inputs := []*model.CallbackInput{ + { + Messages: []*schema.Message{ + {Role: "user", Content: "Hello"}, + }, + Config: &model.Config{ + Model: "gpt-3.5-turbo", + }, + Extra: map[string]interface{}{ + "key1": "value1", + }, + }, + { + Messages: []*schema.Message{ + {Role: "assistant", Content: "Hi there!"}, + }, + Extra: map[string]interface{}{ + "key2": "value2", + }, + }, + } + + mockey.Mock(concatMessageArray).Return(nil, errors.New("concat error")).Build() + actualConfig, actualMessages, actualExtra, actualErr := extractModelInput(inputs) + convey.So(actualConfig, convey.ShouldBeNil) + convey.So(actualMessages, convey.ShouldBeNil) + convey.So(actualExtra, convey.ShouldBeNil) + convey.So(actualErr, convey.ShouldNotBeNil) + }) +} + +func Test_extractModelOutput(t *testing.T) { + mockey.PatchConvey("Test with nil outputs", t, func() { + usage, messages, extra, config, err := extractModelOutput(nil) + convey.So(usage, convey.ShouldBeNil) + convey.So(messages, convey.ShouldBeNil) + convey.So(extra, convey.ShouldBeNil) + convey.So(config, convey.ShouldBeNil) + convey.So(err, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test with empty outputs", t, func() { + usage, messages, extra, config, err := extractModelOutput([]*model.CallbackOutput{}) + convey.So(usage, convey.ShouldBeNil) + convey.So(messages, convey.ShouldBeNil) + convey.So(extra, convey.ShouldBeNil) + convey.So(config, convey.ShouldBeNil) + convey.So(err, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test with single nil output", t, func() { + usage, messages, extra, config, err := extractModelOutput([]*model.CallbackOutput{nil}) + convey.So(usage, convey.ShouldBeNil) + convey.So(messages, convey.ShouldBeNil) + convey.So(extra, convey.ShouldBeNil) + convey.So(config, convey.ShouldBeNil) + convey.So(err, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test with single valid output", t, func() { + expectedUsage := &model.TokenUsage{PromptTokens: 10, CompletionTokens: 20, TotalTokens: 30} + expectedMessage := &schema.Message{Role: "user", Content: "Hello"} + expectedExtra := map[string]interface{}{"key": "value"} + expectedConfig := &model.Config{Model: "gpt-3.5-turbo", MaxTokens: 100, Temperature: 0.7, TopP: 0.9, Stop: []string{"\n"}} + output := &model.CallbackOutput{ + Message: expectedMessage, + Config: expectedConfig, + TokenUsage: expectedUsage, + Extra: expectedExtra, + } + + usage, messages, extra, config, err := extractModelOutput([]*model.CallbackOutput{output}) + convey.So(usage, convey.ShouldEqual, expectedUsage) + convey.So(messages, convey.ShouldHaveLength, 1) + convey.So(messages[0], convey.ShouldEqual, expectedMessage) + convey.So(extra, convey.ShouldEqual, expectedExtra) + convey.So(config, convey.ShouldEqual, expectedConfig) + convey.So(err, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test with multiple valid outputs", t, func() { + expectedUsage := &model.TokenUsage{PromptTokens: 10, CompletionTokens: 20, TotalTokens: 30} + expectedMessage1 := &schema.Message{Role: "user", Content: "Hello"} + expectedMessage2 := &schema.Message{Role: "assistant", Content: "Hi there"} + expectedExtra := map[string]interface{}{"key": "value"} + expectedConfig := &model.Config{Model: "gpt-3.5-turbo", MaxTokens: 100, Temperature: 0.7, TopP: 0.9, Stop: []string{"\n"}} + output1 := &model.CallbackOutput{ + Message: expectedMessage1, + Config: expectedConfig, + TokenUsage: expectedUsage, + Extra: expectedExtra, + } + output2 := &model.CallbackOutput{ + Message: expectedMessage2, + Config: expectedConfig, + TokenUsage: expectedUsage, + Extra: expectedExtra, + } + + usage, messages, extra, config, err := extractModelOutput([]*model.CallbackOutput{output1, output2}) + convey.So(usage, convey.ShouldEqual, expectedUsage) + convey.So(messages, convey.ShouldHaveLength, 2) + convey.So(messages[0], convey.ShouldBeIn, []*schema.Message{expectedMessage1, expectedMessage2}) + convey.So(messages[1], convey.ShouldBeIn, []*schema.Message{expectedMessage1, expectedMessage2}) + convey.So(extra, convey.ShouldEqual, expectedExtra) + convey.So(config, convey.ShouldEqual, expectedConfig) + convey.So(err, convey.ShouldBeNil) + }) +} + +func Test_concatMessageArray(t *testing.T) { + mockey.PatchConvey("Test empty input", t, func() { + mas := [][]*schema.Message{} + actual, err := concatMessageArray(mas) + convey.So(actual, convey.ShouldBeNil) + convey.So(err, convey.ShouldNotBeNil) + }) + + mockey.PatchConvey("Test single message array with correct length", t, func() { + mas := [][]*schema.Message{ + {&schema.Message{Role: "user", Content: "Hello"}}, + } + expected := []*schema.Message{ + {Role: "user", Content: "Hello"}, + } + actual, err := concatMessageArray(mas) + convey.So(actual, convey.ShouldEqual, expected) + convey.So(err, convey.ShouldBeNil) + }) + + mockey.PatchConvey("Test different lengths of message arrays", t, func() { + mas := [][]*schema.Message{ + {&schema.Message{Role: "user", Content: "Hello"}}, + {&schema.Message{Role: "user", Content: "How are you?"}, &schema.Message{Role: "assistant", Content: "I'm good, thanks!"}}, + } + actual, err := concatMessageArray(mas) + convey.So(actual, convey.ShouldBeNil) + convey.So(err, convey.ShouldNotBeNil) + }) +} + +func Test_convSchemaMessage(t *testing.T) { + mockey.PatchConvey("Test convSchemaMessage with empty input", t, func() { + input := []*schema.Message{} + actual := convSchemaMessage(input) + convey.So(actual, convey.ShouldBeEmpty) + }) + + mockey.PatchConvey("Test convSchemaMessage with nil input", t, func() { + input := []*schema.Message{nil} + expected := []*model.CallbackInput{nil} + mockey.Mock(model.ConvCallbackInput).Return(nil).Build() + actual := convSchemaMessage(input) + convey.So(actual, convey.ShouldResemble, expected) + }) +} diff --git a/components/model/ark/chat_completion_api.go b/components/model/ark/chat_completion_api.go index ce1a02c3b..7b29f68eb 100644 --- a/components/model/ark/chat_completion_api.go +++ b/components/model/ark/chat_completion_api.go @@ -883,6 +883,9 @@ func (cm *completionAPIChatModel) toModelCallbackUsage(respMeta *schema.Response CachedTokens: usage.PromptTokenDetails.CachedTokens, }, TotalTokens: usage.TotalTokens, + CompletionTokensDetails: fmodel.CompletionTokensDetails{ + ReasoningTokens: usage.CompletionTokensDetails.ReasoningTokens, + }, } } diff --git a/components/model/ark/chat_completion_api_test.go b/components/model/ark/chat_completion_api_test.go index 8e83478fb..5995659ff 100644 --- a/components/model/ark/chat_completion_api_test.go +++ b/components/model/ark/chat_completion_api_test.go @@ -34,6 +34,28 @@ import ( "github.com/cloudwego/eino/schema" ) +func TestCompletionAPIToModelCallbackUsage(t *testing.T) { + cm := &completionAPIChatModel{} + usage := cm.toModelCallbackUsage(&schema.ResponseMeta{Usage: &schema.TokenUsage{ + CompletionTokens: 7, + PromptTokens: 11, + TotalTokens: 18, + PromptTokenDetails: schema.PromptTokenDetails{ + CachedTokens: 3, + }, + CompletionTokensDetails: schema.CompletionTokensDetails{ + ReasoningTokens: 4, + }, + }}) + + assert.NotNil(t, usage) + assert.Equal(t, 11, usage.PromptTokens) + assert.Equal(t, 7, usage.CompletionTokens) + assert.Equal(t, 18, usage.TotalTokens) + assert.Equal(t, 3, usage.PromptTokenDetails.CachedTokens) + assert.Equal(t, 4, usage.CompletionTokensDetails.ReasoningTokens) +} + func TestChatCompletionAPIStream(t *testing.T) { PatchConvey("test Stream", t, func() { ctx := context.Background() diff --git a/components/tool/duckduckgo/ddgsearch/client.go b/components/tool/duckduckgo/ddgsearch/client.go index ac52199bb..61e660215 100644 --- a/components/tool/duckduckgo/ddgsearch/client.go +++ b/components/tool/duckduckgo/ddgsearch/client.go @@ -187,7 +187,7 @@ func (d *DDGS) sendRequestWithRetry(ctx context.Context, req *http.Request, para // getVQD retrieves the VQD token required for search requests func (d *DDGS) getVQD(ctx context.Context, query string) (string, error) { - endpoint := "https://duckduckgo.com" + endpoint := baseURL // Create request with query parameter req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) diff --git a/components/tool/duckduckgo/ddgsearch/client_test.go b/components/tool/duckduckgo/ddgsearch/client_test.go index 5bf552768..fca9a6dd8 100644 --- a/components/tool/duckduckgo/ddgsearch/client_test.go +++ b/components/tool/duckduckgo/ddgsearch/client_test.go @@ -24,6 +24,23 @@ import ( "time" ) +func useTestEndpoints(t *testing.T, serverURL string) { + t.Helper() + + originalBaseURL := baseURL + originalSearchURL := searchURL + originalNewsURL := newsURL + baseURL = serverURL + searchURL = serverURL + "/d.js" + newsURL = serverURL + "/news.js" + + t.Cleanup(func() { + baseURL = originalBaseURL + searchURL = originalSearchURL + newsURL = originalNewsURL + }) +} + func TestNew(t *testing.T) { tests := []struct { name string @@ -87,6 +104,7 @@ func TestDDGS_Search(t *testing.T) { } })) defer server.Close() + useTestEndpoints(t, server.URL) // Create client with test server URL client, err := New(&Config{ @@ -97,9 +115,6 @@ func TestDDGS_Search(t *testing.T) { t.Fatal(err) } - // Set test endpoints - searchURL = server.URL + "/d.js" - tests := []struct { name string params *SearchParams @@ -183,6 +198,7 @@ func TestDDGS_SearchCache(t *testing.T) { } })) defer server.Close() + useTestEndpoints(t, server.URL) // Create client with cache enabled client, err := New(&Config{ @@ -194,9 +210,6 @@ func TestDDGS_SearchCache(t *testing.T) { t.Fatal(err) } - // Set test endpoints - searchURL = server.URL + "/d.js" - // First search request params := &SearchParams{ Query: "test query", diff --git a/components/tool/duckduckgo/ddgsearch/search_news_test.go b/components/tool/duckduckgo/ddgsearch/search_news_test.go index 3ce6f5df1..eaac9a3ce 100644 --- a/components/tool/duckduckgo/ddgsearch/search_news_test.go +++ b/components/tool/duckduckgo/ddgsearch/search_news_test.go @@ -18,97 +18,88 @@ package ddgsearch import ( "context" - "fmt" + "net/http" + "net/http/httptest" "testing" "time" ) func TestDDGS_News(t *testing.T) { - // Create a client with custom configuration for testing - cfg := &Config{ - Headers: map[string]string{ - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", - }, - Timeout: 30 * time.Second, - MaxRetries: 3, - } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/": + if got := r.URL.Query().Get("q"); got == "" { + t.Error("VQD request is missing the search query") + } + _, _ = w.Write([]byte(``)) + case "/news.js": + query := r.URL.Query() + if got := query.Get("vqd"); got != "test-vqd" { + t.Errorf("news request vqd = %q, want test-vqd", got) + } + if got := query.Get("t"); got != "n" { + t.Errorf("news request t = %q, want n", got) + } + if query.Get("q") == "artificial intelligence" && query.Get("df") != string(TimeRangeDay) { + t.Errorf("news request df = %q, want %q", query.Get("df"), TimeRangeDay) + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{ + "results": [ + {"date": 1704067200, "title": "First result", "excerpt": "first excerpt", "url": "https://example.com/first", "image": "https://example.com/first.png", "source": "Example"}, + {"date": 1704153600, "title": "Second result", "excerpt": "second excerpt", "url": "https://example.com/second", "image": "https://example.com/second.png", "source": "Example"}, + {"date": 1704240000, "title": "Third result", "excerpt": "third excerpt", "url": "https://example.com/third", "image": "https://example.com/third.png", "source": "Example"} + ] + }`)) + default: + http.NotFound(w, r) + } + })) + defer server.Close() + useTestEndpoints(t, server.URL) - client, err := New(cfg) + client, err := New(&Config{ + Headers: map[string]string{"User-Agent": "test"}, + Timeout: time.Second, + MaxRetries: 1, + }) if err != nil { - t.Fatalf("Failed to create client: %v", err) + t.Fatalf("New() error = %v", err) } - // Create context with timeout - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - tests := []struct { - name string - params *NewsParams - want func(*NewsResponse) error - wantErr bool + name string + params *NewsParams + wantCount int + wantErr bool }{ { - name: "basic_search", + name: "basic search", params: &NewsParams{ Query: "technology news", Region: RegionUS, SafeSearch: SafeSearchModerate, MaxResults: 5, }, - want: func(resp *NewsResponse) error { - if len(resp.Results) == 0 { - return fmt.Errorf("expected results, got none") - } - for i, r := range resp.Results { - if r.Title == "" { - return fmt.Errorf("result %d has empty title", i) - } - if r.URL == "" { - return fmt.Errorf("result %d has empty URL", i) - } - if r.Source == "" { - return fmt.Errorf("result %d has empty source", i) - } - if r.Date == "" { - return fmt.Errorf("result %d has empty date", i) - } - } - return nil - }, - wantErr: false, + wantCount: 3, }, { - name: "empty_query", - params: &NewsParams{ - Query: "", - }, - want: nil, + name: "empty query", + params: &NewsParams{}, wantErr: true, }, { - name: "search_with_max_results", + name: "max results", params: &NewsParams{ Query: "technology", Region: RegionUS, SafeSearch: SafeSearchModerate, MaxResults: 2, }, - want: func(resp *NewsResponse) error { - if len(resp.Results) > 2 { - return fmt.Errorf("expected at most 2 results, got %d", len(resp.Results)) - } - // Print sample results for debugging - t.Logf("Sample results for %q:", "search with max results") - for i, r := range resp.Results { - t.Logf(" %d. %s (%s) - %s", i+1, r.Title, r.Source, r.Date) - } - return nil - }, - wantErr: false, + wantCount: 2, }, { - name: "search_with_time_range", + name: "time range", params: &NewsParams{ Query: "artificial intelligence", Region: RegionUS, @@ -116,88 +107,28 @@ func TestDDGS_News(t *testing.T) { TimeRange: TimeRangeDay, MaxResults: 3, }, - want: func(resp *NewsResponse) error { - if len(resp.Results) == 0 { - return fmt.Errorf("expected results, got none") - } - // Verify date is within the last day - now := time.Now() - for i, r := range resp.Results { - date, err := time.Parse(time.RFC3339, r.Date) - if err != nil { - return fmt.Errorf("result %d has invalid date format: %s", i, r.Date) - } - if now.Sub(date) > 24*time.Hour { - return fmt.Errorf("result %d date %s is older than 24 hours", i, r.Date) - } - } - return nil - }, - wantErr: false, - }, - { - name: "search_with_region", - params: &NewsParams{ - Query: "news", - Region: RegionJP, - SafeSearch: SafeSearchModerate, - MaxResults: 3, - }, - want: func(resp *NewsResponse) error { - if len(resp.Results) == 0 { - return fmt.Errorf("expected results, got none") - } - return nil - }, - wantErr: false, + wantCount: 3, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var resp *NewsResponse - var err error - - // Retry logic for flaky tests - for retries := 0; retries < 3; retries++ { - if retries > 0 { - t.Logf("Retry %d for %s", retries, tt.name) - time.Sleep(time.Second * time.Duration(retries)) - } - - resp, err = client.News(ctx, tt.params) - - if (err != nil) == tt.wantErr { - break // Test passed - } - - if err != nil { - t.Logf("Retry %d failed: %v", retries+1, err) - continue - } - - if tt.want != nil { - if err := tt.want(resp); err == nil { - break // Test passed - } else { - t.Logf("Retry %d failed: %v", retries+1, err) - } - } - } - + response, err := client.News(context.Background(), tt.params) if (err != nil) != tt.wantErr { - t.Errorf("DDGS.News() error = %v, wantErr %v", err, tt.wantErr) + t.Fatalf("News() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { return } - - if !tt.wantErr && tt.want != nil { - if err := tt.want(resp); err != nil { - t.Errorf("DDGS.News() validation failed: %v", err) - } + if got := len(response.Results); got != tt.wantCount { + t.Fatalf("News() returned %d results, want %d", got, tt.wantCount) + } + if response.Results[0].Date != "2024-01-01T00:00:00Z" { + t.Errorf("first result date = %q, want RFC3339 timestamp", response.Results[0].Date) + } + if response.Results[0].Title != "First result" || response.Results[0].Source != "Example" { + t.Errorf("first result = %#v, want parsed fixture data", response.Results[0]) } }) - - // Add delay between tests to avoid rate limiting - time.Sleep(time.Second) } } diff --git a/libs/acl/opentelemetry/go.mod b/libs/acl/opentelemetry/go.mod index 5a3f981b5..05c1dec29 100644 --- a/libs/acl/opentelemetry/go.mod +++ b/libs/acl/opentelemetry/go.mod @@ -12,6 +12,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 go.opentelemetry.io/otel/sdk v1.34.0 go.opentelemetry.io/otel/sdk/metric v1.34.0 + google.golang.org/grpc v1.69.4 ) require ( @@ -35,7 +36,6 @@ require ( golang.org/x/text v0.26.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/grpc v1.69.4 // indirect google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/libs/acl/opentelemetry/option.go b/libs/acl/opentelemetry/option.go index 578d214fb..9cc8599a6 100644 --- a/libs/acl/opentelemetry/option.go +++ b/libs/acl/opentelemetry/option.go @@ -39,9 +39,10 @@ type config struct { enableTracing bool enableMetrics bool - exportInsecure bool - exportEndpoint string - exportHeaders map[string]string + exportInsecure bool + exportTLSInsecure bool + exportEndpoint string + exportHeaders map[string]string resource *resource.Resource sdkTracerProvider *sdktrace.TracerProvider @@ -156,6 +157,13 @@ func WithInsecure() Option { }) } +// WithTLSInsecure disables client transport security for the exporter's gRPC with tls +func WithTLSInsecure() Option { + return option(func(cfg *config) { + cfg.exportTLSInsecure = true + }) +} + // WithSampler configures sampler func WithSampler(sampler sdktrace.Sampler) Option { return option(func(cfg *config) { diff --git a/libs/acl/opentelemetry/option_test.go b/libs/acl/opentelemetry/option_test.go index 4ae2c5a16..763244197 100644 --- a/libs/acl/opentelemetry/option_test.go +++ b/libs/acl/opentelemetry/option_test.go @@ -271,6 +271,16 @@ func Test_WithInsecure(t *testing.T) { }) } +func Test_WithTLSInsecure(t *testing.T) { + cfg := &config{} + + mockey.PatchConvey("Test WithTLSInsecure", t, func() { + option := WithTLSInsecure() + option.apply(cfg) + convey.So(cfg.exportTLSInsecure, convey.ShouldBeTrue) + }) +} + func Test_WithSampler(t *testing.T) { mockey.PatchConvey("Test WithSampler", t, func() { sampler := sdktrace.TraceIDRatioBased(0.5) diff --git a/libs/acl/opentelemetry/provider.go b/libs/acl/opentelemetry/provider.go index e79c35790..e387380d2 100644 --- a/libs/acl/opentelemetry/provider.go +++ b/libs/acl/opentelemetry/provider.go @@ -28,6 +28,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" + "google.golang.org/grpc/credentials" ) type OtelProvider struct { @@ -83,6 +84,8 @@ func NewOpenTelemetryProvider(opts ...Option) (*OtelProvider, error) { } if cfg.exportInsecure { traceClientOpts = append(traceClientOpts, otlptracegrpc.WithInsecure()) + } else if cfg.exportTLSInsecure { + traceClientOpts = append(traceClientOpts, otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, ""))) } // trace provider @@ -119,6 +122,8 @@ func NewOpenTelemetryProvider(opts ...Option) (*OtelProvider, error) { } if cfg.exportInsecure { metricsClientOpts = append(metricsClientOpts, otlpmetricgrpc.WithInsecure()) + } else if cfg.exportTLSInsecure { + metricsClientOpts = append(metricsClientOpts, otlpmetricgrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, ""))) } meterProvider = cfg.meterProvider