mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
7a04bdfeba
* feat(kit): isolate viper config per Kit instance + add NewAgent (#40) - Give each kit.New()/NewAgent() call an isolated *viper.Viper store so multiple Kit instances in one process no longer clobber each other's config; runtime mutators (SetModel, SetThinkingLevel) touch only the owning instance, making subagent spawning and multi-Kit embedding race-free - Thread the per-instance store through internal/config, internal/models (ProviderConfig.ConfigStore), internal/kitsetup, and the extension runner, with a nil -> process-global fallback so the CLI is unaffected - Share the global store when Options.CLI != nil to preserve cobra flag bindings (also opted in for internal/acpserver) - Remove viperInitMu; preserve the tri-state IsSet precedence contract and sdkDefaultMaxTokens floor - Add ergonomic NewAgent + functional options (WithModel, WithStreaming, Ephemeral, etc.); NewAgent defaults streaming on, opt out via WithStreaming(false). New(ctx, *Options) behavior is unchanged - Add config-isolation regression test and NewAgent/option coverage; document NewAgent and per-instance isolation in README Fixes #40 * docs(sdk): document NewAgent options and per-instance config isolation - Add "Functional options (NewAgent)" and "Per-instance config isolation" sections to the docs site SDK overview, with an options table and a "when to use which" constructor comparison - Cross-reference NewAgent from the SDK options page and correct the now per-instance ProviderAPIKey precedence wording - Document NewAgent + With* helpers and config isolation in pkg/kit/README and list NewAgent/Option in the API reference - Show the NewAgent constructor in the SDK examples getting-started snippet * fix(kit): correct config loading and isolate ACP sessions - Isolate each ACP session's config store instead of sharing the global viper, preventing per-session SetModel/SetThinkingLevel races; seed the root-command flag values (model, thinking-level, provider URL/key) so `kit acp -m <model>` is still honored - Run initConfig for isolated SDK stores by gating on opts.CLI instead of v.GetString("model"), which setSDKDefaults always populates and thus skipped .kit.yml / KIT_* loading for SDK callers - Configure KIT_* env overrides unconditionally in initConfig so passing an explicit config file no longer disables environment variable support - Wrap config unmarshal/validate errors with %w to preserve the error chain * fix(kit): make Options.Streaming a *bool to honor unset - Change Options.Streaming from bool to *bool so a zero-valued Options no longer forces stream=false; New only sets the key when non-nil, letting streaming resolve through the precedence chain (env -> config -> default true). This also fixes the CLI path, which never set the field - Mirror the existing sampling-parameter pointer pattern instead of adding a separate StreamingSet sentinel, keeping Options internally consistent - Update WithStreaming/NewAgent, subagent, and ACP callers to the pointer form; add regression tests for the nil-default and explicit opt-out paths - Update SDK docs (README, pkg/kit/README, options page) with the ptrBool helper and *bool semantics * fix(kit): inherit parent provider config in subagents - Copy the parent's effective provider/runtime config (API key, URL, TLS, thinking level, max-tokens, samplers) onto child Options in Kit.Subagent. After the per-instance viper isolation, the child's isolated store only re-loaded .kit.yml / KIT_*, silently dropping config the parent set via programmatic Options or runtime setters like SetThinkingLevel - Preserve the IsSet tri-state for max-tokens and samplers so per-model defaults still apply on the child when the parent left them unset - Add TestInheritProviderConfig covering propagation, unset keys, and nil-safety
89 lines
3.9 KiB
Go
89 lines
3.9 KiB
Go
package kit
|
|
|
|
import "context"
|
|
|
|
// Option configures a [Kit] created via [NewAgent]. Options are applied in
|
|
// order to an [Options] value, so later options override earlier ones. The
|
|
// type is a plain func(*Options), so callers can define their own options
|
|
// without depending on any internal type.
|
|
type Option func(*Options)
|
|
|
|
// NewAgent creates a Kit using an ergonomic functional-options API. It is a
|
|
// thin, additive front door over [New]: the supplied options are applied to a
|
|
// fresh [Options] value which is then passed to [New]. For advanced
|
|
// configuration not covered by the With* helpers (MCPConfig,
|
|
// InProcessMCPServers, session backends, MCP task tuning, etc.) construct an
|
|
// [Options] explicitly and call [New].
|
|
//
|
|
// Streaming defaults to enabled. Pass WithStreaming(false) to disable it.
|
|
//
|
|
// Example:
|
|
//
|
|
// k, err := kit.NewAgent(ctx,
|
|
// kit.WithModel("anthropic/claude-sonnet-4-5-20250929"),
|
|
// kit.WithSystemPrompt("You are a helpful assistant."),
|
|
// kit.WithMaxTokens(8192),
|
|
// kit.Ephemeral(),
|
|
// )
|
|
func NewAgent(ctx context.Context, opts ...Option) (*Kit, error) {
|
|
// Streaming defaults to true for the ergonomic constructor — this is the
|
|
// natural expectation for interactive agents. WithStreaming(false) overrides it.
|
|
streamOn := true
|
|
o := &Options{Streaming: &streamOn}
|
|
for _, fn := range opts {
|
|
fn(o)
|
|
}
|
|
return New(ctx, o)
|
|
}
|
|
|
|
// WithModel sets the model in "provider/model" format
|
|
// (e.g. "anthropic/claude-sonnet-4-5-20250929").
|
|
func WithModel(m string) Option { return func(o *Options) { o.Model = m } }
|
|
|
|
// WithSystemPrompt sets the system prompt. The value may be inline text or a
|
|
// path to a file whose contents are loaded as the prompt.
|
|
func WithSystemPrompt(p string) Option { return func(o *Options) { o.SystemPrompt = p } }
|
|
|
|
// WithStreaming enables or disables streaming responses. [NewAgent] enables
|
|
// streaming by default, so pass WithStreaming(false) to opt out.
|
|
func WithStreaming(b bool) Option {
|
|
return func(o *Options) { o.Streaming = &b }
|
|
}
|
|
|
|
// WithMaxTokens sets the maximum output tokens per LLM response. A value of 0
|
|
// lets the precedence chain (env → config → per-model → SDK floor) resolve a
|
|
// value; a non-zero value pins it and suppresses automatic right-sizing.
|
|
func WithMaxTokens(n int) Option { return func(o *Options) { o.MaxTokens = n } }
|
|
|
|
// WithThinkingLevel sets the reasoning effort for models that support extended
|
|
// thinking. Valid values: "off", "none", "minimal", "low", "medium", "high".
|
|
// An empty string lets the precedence chain resolve a level.
|
|
func WithThinkingLevel(level string) Option { return func(o *Options) { o.ThinkingLevel = level } }
|
|
|
|
// WithTools sets the agent's tool set, replacing the default core tools. When
|
|
// no tools are provided the default set is used.
|
|
func WithTools(t ...Tool) Option { return func(o *Options) { o.Tools = t } }
|
|
|
|
// WithExtraTools adds tools alongside the core/MCP/extension tools rather than
|
|
// replacing them.
|
|
func WithExtraTools(t ...Tool) Option { return func(o *Options) { o.ExtraTools = t } }
|
|
|
|
// WithProviderAPIKey overrides the API key used to authenticate with the model
|
|
// provider.
|
|
func WithProviderAPIKey(key string) Option { return func(o *Options) { o.ProviderAPIKey = key } }
|
|
|
|
// WithProviderURL overrides the provider endpoint URL. Useful for
|
|
// OpenAI-compatible proxies (LiteLLM, vLLM, Azure OpenAI, etc.).
|
|
func WithProviderURL(url string) Option { return func(o *Options) { o.ProviderURL = url } }
|
|
|
|
// WithConfigFile sets an explicit config file path, overriding the default
|
|
// .kit.yml search.
|
|
func WithConfigFile(path string) Option { return func(o *Options) { o.ConfigFile = path } }
|
|
|
|
// WithDebug enables SDK debug logging.
|
|
func WithDebug() Option { return func(o *Options) { o.Debug = true } }
|
|
|
|
// Ephemeral configures an in-memory session with no persistence (equivalent to
|
|
// Options.NoSession = true).
|
|
func Ephemeral() Option { return func(o *Options) { o.NoSession = true } }
|