Start starts the toolset with single-flight semantics. Concurrent callers block until the start attempt completes. If start fails, a future call will retry. If the underlying toolset doesn't implement Startable, this is a no-op.
(ctx context.Context)
| 115 | // If start fails, a future call will retry. |
| 116 | // If the underlying toolset doesn't implement Startable, this is a no-op. |
| 117 | func (s *StartableToolSet) Start(ctx context.Context) (err error) { |
| 118 | s.mu.Lock() |
| 119 | defer s.mu.Unlock() |
| 120 | |
| 121 | recovering := false |
| 122 | if s.started { |
| 123 | if reporter, ok := As[startReporter](s.ToolSet); !ok || reporter.IsStarted() { |
| 124 | return nil |
| 125 | } |
| 126 | s.started = false |
| 127 | recovering = true |
| 128 | } |
| 129 | |
| 130 | // Span the toolset startup — MCP handshake, OAuth probes, |
| 131 | // tool discovery, etc. can take seconds to minutes and the |
| 132 | // "tools loading…" UI was previously unattributable. Only |
| 133 | // fires when the toolset has work to do (Restartable on a |
| 134 | // recovering run, or Startable on a cold start); cheap |
| 135 | // toolsets without either skip the span entirely. |
| 136 | // |
| 137 | // Unwrap once so the kind attribute names the underlying toolset |
| 138 | // (e.g. *mcp.Toolset, *builtin.ShellTool) instead of the |
| 139 | // *tools.namedToolSet wrapper that every toolset gets in the |
| 140 | // registry — same pattern DescribeToolSet uses. |
| 141 | inner := s.ToolSet |
| 142 | if u, ok := inner.(Unwrapper); ok { |
| 143 | inner = u.Unwrap() |
| 144 | } |
| 145 | if restarter, hasRestarter := As[Restartable](s.ToolSet); recovering && hasRestarter { |
| 146 | ctx, span := otel.Tracer("github.com/docker/docker-agent/pkg/tools").Start( |
| 147 | ctx, |
| 148 | "toolset.start", |
| 149 | trace.WithSpanKind(trace.SpanKindInternal), |
| 150 | trace.WithAttributes(attribute.String("cagent.toolset.kind", fmt.Sprintf("%T", inner))), |
| 151 | ) |
| 152 | defer func() { |
| 153 | if err != nil { |
| 154 | span.RecordError(err) |
| 155 | span.SetStatus(codes.Error, err.Error()) |
| 156 | } |
| 157 | span.End() |
| 158 | }() |
| 159 | if err := restarter.Restart(ctx); err != nil { |
| 160 | s.startStreak.fail() |
| 161 | s.recoveryStreak.fail() |
| 162 | return err |
| 163 | } |
| 164 | } else if startable, ok := As[Startable](s.ToolSet); ok { |
| 165 | ctx, span := otel.Tracer("github.com/docker/docker-agent/pkg/tools").Start( |
| 166 | ctx, |
| 167 | "toolset.start", |
| 168 | trace.WithSpanKind(trace.SpanKindInternal), |
| 169 | trace.WithAttributes(attribute.String("cagent.toolset.kind", fmt.Sprintf("%T", inner))), |
| 170 | ) |
| 171 | defer func() { |
| 172 | if err != nil { |
| 173 | span.RecordError(err) |
| 174 | span.SetStatus(codes.Error, err.Error()) |
nothing calls this directly
no test coverage detected