Skip to content

feat(sdk): expose generation and provider params on Options#7

Merged
ezynda3 merged 7 commits into
masterfrom
feat/sdk-options-overrides
Apr 17, 2026
Merged

feat(sdk): expose generation and provider params on Options#7
ezynda3 merged 7 commits into
masterfrom
feat/sdk-options-overrides

Conversation

@ezynda3

@ezynda3 ezynda3 commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Let SDK consumers configure Kit fully in-code without reaching into
viper's global state or shipping a .kit.yml.

Why

Before this PR, downstream apps that embed Kit had to do this:

viper.Set("max-tokens", 16384) // leaky, undiscoverable
host, _ := kit.New(ctx, &kit.Options{Model: "..."})

Now:

host, _ := kit.New(ctx, &kit.Options{
    Model:     "anthropic/claude-sonnet-4-5-20250929",
    MaxTokens: 16384,
})

What's in

New Options fields

Generation parameters

Field Type Notes
MaxTokens int 0 = auto-resolve; non-zero suppresses auto right-sizing
ThinkingLevel string "off", "low", "medium", "high"
Temperature *float32 pointer so explicit 0.0 ≠ unset
TopP *float32
TopK *int32
FrequencyPenalty *float32 OpenAI-family
PresencePenalty *float32 OpenAI-family

Provider configuration

Field Type Notes
ProviderAPIKey string overrides config / env
ProviderURL string override endpoint (LiteLLM, vLLM, Azure, self-hosted)
TLSSkipVerify bool only effective when true

Precedence (highest → lowest)

  1. Options.X
  2. KIT_X env var
  3. .kit.yml
  4. Per-model defaults (modelSettings / customModels.params)
  5. Provider-level defaults
  6. SDK last-resort floor (MaxTokens = 4096)

Sampling params that remain nil at step 6 are left out of the provider call
entirely, so the LLM library applies its own default.

Correctness fix (2nd commit)

The initial implementation regressed viper.IsSet() semantics: the
original setSDKDefaults() called viper.SetDefault for every param,
which makes IsSet() return true. That silently suppressed per-model
defaults (ApplyModelSettings) and auto right-sizing (rightSizeMaxTokens)
for every SDK-created Kit — and for every CLI run, since cmd/root.go
also routes through kit.New. Effective MaxTokens for
claude-sonnet-4-5 was pinned at 4096 instead of 32768.

Fix:

  • Drop SetDefault for all IsSet-sensitive keys (max-tokens,
    temperature, top-p, top-k, frequency-penalty,
    presence-penalty, thinking-level). Keep only model,
    system-prompt, stream, num-gpu-layers, main-gpu.
  • Apply the 4096 MaxTokens floor directly on the *ProviderConfig
    struct in kit.New() when nothing else resolved a value. Keeps
    viper.IsSet("max-tokens") == false so right-sizing and per-model
    maxTokens still fire.

Empirically verified: SDK Kit.MaxTokens() for claude-sonnet-4-5
before fix = 4096, after fix = 32768.

Tests

  • TestNewWithGenerationOptions/{MaxTokens,ThinkingLevel,Temperature}
    assert each Options field propagates end-to-end.
  • TestNewPreservesIsSetSemantics (regression) — asserts that when no
    generation Options field is set, viper.IsSet() returns false for all
    seven keys. Uses SkipConfig: true to isolate from ~/.kit.yml.
  • TestNewWithProviderOptions/{succeeds,Options beats viper,ProviderURL}
    including a subtest that writes a placeholder key to viper first, then
    confirms Options.ProviderAPIKey wins.
  • resetViper() helper so subtests don't bleed state.

Backwards compatibility

  • ✅ All existing Options fields unchanged.
  • go test -race ./... clean.
  • go vet ./... clean.
  • ✅ CLI behavior unchanged (CLI passes these via cobra flags, not Options).
  • ✅ Tmux + non-interactive smoke tests pass.

Docs

Added or updated in the same commit:

  • README.md — expanded Options code block, fixed --max-tokens default
    (was 4096, actually 8192 with auto right-sizing).
  • skills/kit-sdk/SKILL.md — new fields + cheat-sheet table.
  • www/pages/sdk/options.md — restructured into grouped sections, added
    Precedence section.
  • www/pages/sdk/overview.md — new "Generation & provider overrides" section.
  • www/pages/configuration.md — precedence summary pointing at SDK options.

Suggested release: minor bump (v0.55.0) — purely additive API.

Test plan

go test -race ./...
go test -v -run 'TestNewWith|TestNewPreservesIsSet' ./pkg/kit/
go vet ./...

Summary by CodeRabbit

Release Notes

  • New Features

    • Added generation parameter options to SDK (MaxTokens, ThinkingLevel, Temperature, TopP, TopK, FrequencyPenalty, PresencePenalty)
    • Added provider configuration options to SDK (ProviderAPIKey, ProviderURL, TLSSkipVerify)
    • Updated default --max-tokens to 8192 with auto-scaling up to 32768
  • Documentation

    • Added configuration precedence guide showing resolution order across CLI, SDK options, environment variables, and config files
    • Enhanced SDK options reference with new generation and provider parameters

ezynda3 added 2 commits April 17, 2026 11:24
Adds programmatic overrides on kit.Options for the model/provider knobs
that were previously only reachable through viper.Set() — letting SDK
consumers (web apps, services, embedded agents) configure kit fully
in-code without polluting global viper state or shipping .kit.yml.

Generation parameters:
  - MaxTokens         int      (max output tokens per response)
  - ThinkingLevel     string   (off/low/medium/high)
  - Temperature       *float32
  - TopP              *float32
  - TopK              *int32
  - FrequencyPenalty  *float32
  - PresencePenalty   *float32

Sampling params use pointer types so explicit 0 is distinguishable from
unset; nil leaves provider/per-model defaults in place.

Provider configuration:
  - ProviderAPIKey    string
  - ProviderURL       string
  - TLSSkipVerify     bool

Implementation just pushes Options values into viper inside New(),
so all existing downstream code (BuildProviderConfig, SetModel,
modelSettings lookups, runtime model switching) picks them up
uniformly without any new code paths. Tests added for MaxTokens,
ThinkingLevel, and ProviderAPIKey.
Previously setSDKDefaults() registered viper.SetDefault for max-tokens,
temperature, top-p, top-k, frequency/presence-penalty, and thinking-level.
viper.SetDefault makes IsSet() return true, which silently suppressed
per-model defaults (ApplyModelSettings) and automatic right-sizing
(rightSizeMaxTokens) for every SDK-created Kit — and for CLI runs too,
since cmd/root.go routes through kit.New. Effective max-tokens for
claude-sonnet-4-5 was pinned at 4096 instead of 32768.

- Drop SetDefault for all IsSet-sensitive keys; keep only model,
  system-prompt, stream, num-gpu-layers, main-gpu.
- Apply a 4096 max-tokens floor directly on the *models.ProviderConfig
  struct in kit.New() when nothing else resolved a value. Keeps
  viper.IsSet("max-tokens") == false so rightSizeMaxTokens and
  per-model maxTokens overrides still fire.
- Update Options.MaxTokens / ThinkingLevel godoc to describe the real
  precedence chain.
- Strengthen tests: add Temperature subtest; add
  TestNewPreservesIsSetSemantics regression covering all seven keys;
  split TestNewWithProviderOptions into three subtests including
  Options-beats-viper-state and ProviderURL propagation; add
  resetViper helper so subtests don't bleed state.
- Document the new SDK fields (MaxTokens, ThinkingLevel, Temperature,
  TopP, TopK, FrequencyPenalty, PresencePenalty, ProviderAPIKey,
  ProviderURL, TLSSkipVerify) in README, skills/kit-sdk, and the www
  configuration / sdk/options / sdk/overview pages, including a
  dedicated precedence table.
@mark-iii-labs-huly

Copy link
Copy Markdown

Connected to Huly®: KIT-7

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@ezynda3 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 38 minutes and 21 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 38 minutes and 21 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed0d7a63-1f40-408d-9771-db4288dabd8f

📥 Commits

Reviewing files that changed from the base of the PR and between ecf95b5 and 4e82fac.

📒 Files selected for processing (8)
  • README.md
  • internal/ui/fileutil/processor_test.go
  • pkg/kit/config.go
  • pkg/kit/kit.go
  • pkg/kit/kit_test.go
  • skills/kit-sdk/SKILL.md
  • www/pages/configuration.md
  • www/pages/sdk/options.md
📝 Walkthrough

Walkthrough

The kit SDK now supports programmatic configuration of generation parameters (MaxTokens, ThinkingLevel, sampling controls) and provider settings (API key, URL, TLS) directly via the Options struct, with a documented resolution precedence chain that includes CLI flags, SDK Options, environment variables, config files, and provider defaults.

Changes

Cohort / File(s) Summary
Core SDK Implementation
pkg/kit/kit.go, pkg/kit/config.go
Added new public fields to Options struct for generation parameters (MaxTokens, ThinkingLevel, Temperature, TopP, TopK, FrequencyPenalty, PresencePenalty) and provider configuration (ProviderAPIKey, ProviderURL, TLSSkipVerify). Updated New() to conditionally write these into viper and apply a last-resort sdkDefaultMaxTokens of 4096 when both provider and SDK MaxTokens are zero. Removed viper defaults for most generation keys to maintain "explicit vs. unset" distinction.
Test Coverage
pkg/kit/kit_test.go
Added comprehensive test cases validating propagation of new Options fields through viper state and agent configuration, including MaxTokens, ThinkingLevel, sampling parameter pointers, and provider overrides. Introduced test helpers resetViper() and upper() for configuration isolation and environment variable name mapping.
Documentation Updates
README.md, skills/kit-sdk/SKILL.md, www/pages/configuration.md, www/pages/sdk/options.md, www/pages/sdk/overview.md
Extended SDK examples with new generation and provider option fields, added precedence documentation clarifying the resolution order among CLI flags, SDK Options, environment variables, config files, and provider defaults, and introduced a "Generation & provider overrides" guide for SDK consumers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops of config, fields take flight,
SDK options now set just right,
Generation, providers aligned,
Precedence chains carefully designed,
A rabbit's code hops clean and tight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: exposing generation and provider parameters as fields on the Options struct for programmatic configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sdk-options-overrides

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
pkg/kit/kit_test.go (1)

230-257: _ = err swallows creation failure silently.

The subtest deliberately tolerates kit.New failing (model validation, provider handshake) and only asserts the viper state. That's a reasonable scope, but _ = err reads as a lint suppression without intent. Consider a short t.Logf so a future reader (or CI flake investigation) can tell whether the override assertion is being exercised pre- or post-failure:

Suggested tweak
-		if host != nil {
-			defer func() { _ = host.Close() }()
-		}
-		_ = err
+		if host != nil {
+			defer func() { _ = host.Close() }()
+		}
+		if err != nil {
+			t.Logf("kit.New returned error (tolerated): %v", err)
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/kit/kit_test.go` around lines 230 - 257, Replace the silent suppression
of the kit.New error with a test log so failures are visible during CI
debugging: instead of `_ = err` call t.Logf (e.g., t.Logf("kit.New error: %v",
err)) after creating host so the test still tolerates creation failure but
records whether kit.New failed, leaving the existing host cleanup and the
viper.ProviderAPIKey assertion intact (look for kit.New,
kit.Options.ProviderAPIKey, and viper.GetString in this subtest).
pkg/kit/kit.go (1)

1245-1255: Minor: the opts.MaxTokens == 0 conjunct is redundant.

If opts.MaxTokens > 0 the earlier viper.Set("max-tokens", opts.MaxTokens) at line 1126 guarantees providerConfig.MaxTokens != 0 after BuildProviderConfig. So providerConfig.MaxTokens == 0 alone is sufficient. Not a bug — harmless defensive coding — flagging only for readability.

Optional simplification
-		if providerConfig.MaxTokens == 0 && opts.MaxTokens == 0 {
+		if providerConfig.MaxTokens == 0 {
 			providerConfig.MaxTokens = sdkDefaultMaxTokens
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/kit/kit.go` around lines 1245 - 1255, The condition in
BuildProviderConfig (the block that applies sdkDefaultMaxTokens) uses a
redundant conjunct opts.MaxTokens == 0; simplify the guard to check only
providerConfig.MaxTokens == 0 and then set providerConfig.MaxTokens =
sdkDefaultMaxTokens when true, keeping sdkDefaultMaxTokens as the fallback; this
removes the unnecessary dependency on opts.MaxTokens while preserving the
current behavior.
README.md (1)

549-589: LGTM — SDK example and precedence notes are accurate.

Matches the kit.Options godoc and the test-verified behavior (TestNewWithGenerationOptions, TestNewWithProviderOptions). Worth noting the ptr helper isn't shown — consider a 2-line func ptr[T any](v T) *T { return &v } snippet nearby so copy-pasters don't hit a compile error, though skills/kit-sdk/SKILL.md already defines ptrFloat32.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 549 - 589, Add a short generic ptr helper so the
README example compiles (the example uses ptr for Temperature) — implement a
small two-line helper named ptr (returns a pointer to a value) and place it
adjacent to the code sample, or alternatively add a short note pointing readers
to the existing ptrFloat32 helper in skills/kit-sdk/SKILL.md; keep references to
kit.Options and the tests (TestNewWithGenerationOptions,
TestNewWithProviderOptions) intact so readers can correlate behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/kit/kit_test.go`:
- Around line 150-195: The test TestNewPreservesIsSetSemantics contains an
unnecessary KIT_* env-var skip guard and use of upper() because SkipConfig: true
prevents InitConfig() from registering viper env handling; remove the
os.Getenv(envVar) check and the upper() dependency from the test
(TestNewPreservesIsSetSemantics) so the test only asserts viper.IsSet for
SDK-set keys, and fix InitConfig() to call
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) in addition to the
existing viper.SetEnvPrefix("KIT") and viper.AutomaticEnv() so hyphenated keys
like "max-tokens" map to KIT_MAX_TOKENS when config init is enabled.

In `@pkg/kit/kit.go`:
- Around line 1119-1158: The code mutates viper's global state inside kit.New
via viper.Set (e.g., when handling Options fields like MaxTokens, Temperature,
ProviderAPIKey), causing values to leak between Kit instances; add a clear godoc
comment on the Options type and kit.New explaining that Options are applied by
mutating the global viper store (via viper.Set) and therefore Kit is not
isolated across multiple New() calls, referencing kit.New, Options, viper.Set,
viperInitMu, resetViper, and downstream readers like SetModel/GetThinkingLevel;
include a short migration note advising consumers to call resetViper() between
constructions or to avoid creating multiple Kits in the same process, and add a
TODO suggesting a future refactor to use a per-call viper.New() instance to
fully isolate config.

---

Nitpick comments:
In `@pkg/kit/kit_test.go`:
- Around line 230-257: Replace the silent suppression of the kit.New error with
a test log so failures are visible during CI debugging: instead of `_ = err`
call t.Logf (e.g., t.Logf("kit.New error: %v", err)) after creating host so the
test still tolerates creation failure but records whether kit.New failed,
leaving the existing host cleanup and the viper.ProviderAPIKey assertion intact
(look for kit.New, kit.Options.ProviderAPIKey, and viper.GetString in this
subtest).

In `@pkg/kit/kit.go`:
- Around line 1245-1255: The condition in BuildProviderConfig (the block that
applies sdkDefaultMaxTokens) uses a redundant conjunct opts.MaxTokens == 0;
simplify the guard to check only providerConfig.MaxTokens == 0 and then set
providerConfig.MaxTokens = sdkDefaultMaxTokens when true, keeping
sdkDefaultMaxTokens as the fallback; this removes the unnecessary dependency on
opts.MaxTokens while preserving the current behavior.

In `@README.md`:
- Around line 549-589: Add a short generic ptr helper so the README example
compiles (the example uses ptr for Temperature) — implement a small two-line
helper named ptr (returns a pointer to a value) and place it adjacent to the
code sample, or alternatively add a short note pointing readers to the existing
ptrFloat32 helper in skills/kit-sdk/SKILL.md; keep references to kit.Options and
the tests (TestNewWithGenerationOptions, TestNewWithProviderOptions) intact so
readers can correlate behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9e96f2e-e58d-4a40-b2c2-ed93b222773e

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb20f5 and ecf95b5.

📒 Files selected for processing (8)
  • README.md
  • pkg/kit/config.go
  • pkg/kit/kit.go
  • pkg/kit/kit_test.go
  • skills/kit-sdk/SKILL.md
  • www/pages/configuration.md
  • www/pages/sdk/options.md
  • www/pages/sdk/overview.md

Comment thread pkg/kit/kit_test.go
Comment thread pkg/kit/kit.go
ezynda3 added 5 commits April 17, 2026 11:59
The SDK last-resort MaxTokens floor is applied in kit.New() when
Options.MaxTokens, KIT_MAX_TOKENS, .kit.yml, and per-model defaults
are all unset. It was 4096 (inherited from the old setSDKDefaults
viper default) while the CLI --max-tokens cobra default is 8192.

Bump the floor to 8192 so SDK and CLI callers start from the same
base value before rightSizeMaxTokens runs, then update README,
skills/kit-sdk/SKILL.md, and www/pages/{configuration,sdk/options}.md
to match.
- InitConfig now installs a viper env key replacer so keys like
  "max-tokens" bind to KIT_MAX_TOKENS under AutomaticEnv; previously
  hyphenated keys silently missed their documented env overrides.
- Simplify TestNewPreservesIsSetSemantics: with SkipConfig: true no env
  bindings are registered, so the os.Getenv guard and upper() helper
  were dead weight. Remove both and drop the unused helper.
The SDK applies Options by calling viper.Set on viper's process-global
store, which means two Kits constructed in the same process are not
isolated from each other: the second New overwrites the first's keys,
and downstream readers (SetModel, GetThinkingLevel, BuildProviderConfig)
observe the most recent value.

- Add a 'Global viper state warning' block to the Options godoc
  explaining the leak, the zero-value-does-not-clear gotcha, and
  pointing at viper.Reset() as the migration workaround.
- Add a matching warning to the New godoc so consumers discover the
  constraint from either entry point.
- Detach the viperInitMu godoc (previously lodged inside New's comment
  block) and clarify that the mutex only guards the construction
  window, not instance isolation.
- Add a TODO noting the proper fix: refactor to a per-call viper.New()
  instance so each Kit owns its own config store.
TestDetectMediaType/.go fails on CI images (Ubuntu mime-support) where
/etc/mime.types registers '.go → text/x-go', because mime.TypeByExtension
reads those files at init. The test intended to exercise the 'unknown
extension falls through to text/plain' branch but used a real extension,
making the assertion environment-dependent.

Replace '.go' with '.kitsyntheticext', an invented extension that no
system MIME database registers. The fallback path is now exercised
deterministically on any host.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant