AgentSkillsCN

finfocus-plugin

使用 pluginsdk 与 gRPC 协议构建 FinFocus 成本插件的操作指南。当您需要创建新插件、实现 CostSourceService 方法、搭建插件基础框架、调试插件通信、理解插件生命周期,或与录音机参考插件协同工作时,可使用此技能。触发条件包括:“创建插件”、“实现 GetProjectedCost”、“插件 gRPC”、“pluginsdk”、“插件开发”、“录音机插件”,或任何涉及 FinFocus 插件架构的工作任务。

SKILL.md
--- frontmatter
name: finfocus-plugin
description: >
  Guide for building FinFocus cost plugins using the pluginsdk and gRPC protocol.
  Use when creating a new plugin, implementing CostSourceService methods, setting up
  plugin scaffolding, debugging plugin communication, understanding the plugin lifecycle,
  or working with the recorder reference plugin. Triggers on: "create a plugin",
  "implement GetProjectedCost", "plugin gRPC", "pluginsdk", "plugin development",
  "recorder plugin", or any task involving finfocus plugin architecture.

FinFocus Plugin Development

Plugin Architecture

Plugins communicate with finfocus-core via gRPC using protocol buffers from finfocus-spec. Each plugin is a standalone binary that implements the CostSourceService.

text
Core CLI -> gRPC -> Plugin Binary
                    (CostSourceService)

Two launcher types:

  • ProcessLauncher (TCP) - Plugin listens on a port
  • StdioLauncher (stdin/stdout) - Plugin communicates over stdio

Quick Start: New Plugin

  1. Create Go module with finfocus-spec dependency
  2. Embed pluginsdk.BasePlugin for common functionality
  3. Implement required gRPC methods
  4. Build binary and install to ~/.finfocus/plugins/<name>/<version>/
go
import (
    "github.com/rshade/finfocus-spec/sdk/go/pluginsdk"
    pbc "github.com/rshade/finfocus-spec/sdk/go/proto/finfocus/v1"
)

type MyPlugin struct {
    *pluginsdk.BasePlugin
    logger zerolog.Logger
    mu     sync.Mutex
}

func (p *MyPlugin) Name() string { return "my-plugin" }

Required Methods

Every plugin must implement these CostSourceService methods:

MethodPurpose
GetProjectedCostEstimate future costs from resource descriptors
GetActualCostReturn historical costs for a time range
GetRecommendationsReturn cost optimization recommendations
GetPluginInfoReturn plugin metadata (name, version, providers)
ShutdownClean up resources on termination

Request Validation

Use pluginsdk validation helpers before processing:

go
func (p *MyPlugin) GetProjectedCost(ctx context.Context, req *pbc.GetProjectedCostRequest) (*pbc.GetProjectedCostResponse, error) {
    p.mu.Lock()
    defer p.mu.Unlock()

    if err := pluginsdk.ValidateProjectedCostRequest(req); err != nil {
        return nil, status.Errorf(codes.InvalidArgument, "validation: %v", err)
    }
    // Process request...
}

Plugin Installation

Plugins install to ~/.finfocus/plugins/<name>/<version>/:

bash
# Build and install
make build-recorder
make install-recorder  # -> ~/.finfocus/plugins/recorder/0.1.0/

# Verify
./bin/finfocus plugin list
./bin/finfocus plugin validate

Environment Variables

Plugins receive these from core via pluginsdk constants:

ConstantEnv VarPurpose
pluginsdk.EnvPortFINFOCUS_PLUGIN_PORTgRPC port
pluginsdk.EnvLogLevelFINFOCUS_LOG_LEVELLog verbosity
pluginsdk.EnvLogFormatFINFOCUS_LOG_FORMATjson or text
pluginsdk.EnvLogFileFINFOCUS_LOG_FILELog file path
pluginsdk.EnvTraceIDFINFOCUS_TRACE_IDDistributed trace ID

Trace ID Propagation

go
// Server-side: extract trace ID from incoming gRPC metadata
server := grpc.NewServer(
    grpc.UnaryInterceptor(pluginsdk.TracingUnaryServerInterceptor()),
)

// In handlers: get trace ID from context
traceID := pluginsdk.TraceIDFromContext(ctx)

Reference Implementation

The recorder plugin at plugins/recorder/ demonstrates all patterns. See references/recorder-reference.md for the complete implementation walkthrough.

Plugin Communication Details

See references/pluginsdk-api.md for the full pluginsdk API, proto message types, and gRPC patterns.

Testing Plugins

bash
go test ./plugins/recorder/...                       # Unit tests
go test ./test/integration/recorder_test.go          # Integration
go test -bench=BenchmarkRecorder ./plugins/recorder/... # Perf (<10ms)

Common Pitfalls

  • Always use sync.Mutex for thread safety in gRPC handlers
  • Return status.Errorf(codes.InvalidArgument, ...) for validation errors
  • Call cmd.Wait() after Kill() to prevent zombie processes
  • 10-second timeout with 100ms retry delays for plugin startup
  • Platform-specific binary detection (Unix permissions vs Windows .exe)