Add Kimi AI filter plugin - #182
Merged
Merged
Conversation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012batzHd21Na8eJFgc1x2hY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
FilterKimi, a new independent AI filter plugin for Moonshot AI's Kimi API (default modelkimi-k3), following the exact architecture of the existing service-specific AI filters (FilterOpenAI,FilterClaude,FilterGemini,FilterSakuraAI). Kimi's Chat Completions protocol happens to be OpenAI-compatible, but this is deliberately its own plugin rather than a mode ofFilterOpenAI— matching howFilterSakuraAI(also OpenAI-compatible) is already handled.Behavior
plugins/filter/kimi.rb, classAutomatic::Plugin::FilterKimi.https://api.moonshot.ai/v1/chat/completions(not a Recipe setting),OPEN_TIMEOUT = 10,READ_TIMEOUT = 300— mirrorssakura_ai.rb/open_ai.rbexactly.token,model,prompt— validated up front, each missing/empty one raisesArgumentErrorbefore any request.{"model", "messages": [{"role": "system", "content": <prompt>}, {"role": "user", "content": <description>}]}— no sampling, reasoning, or tool-use parameters are ever sent.choices[0].finish_reason == "stop"and non-emptychoices[0].message.content;reasoning_content, when Kimi sends one, is never read, logged, or written anywhere.Error(not retried) and never overwrites the description.TemporaryErrorand retried up to the configuredretrycount withintervalseconds between attempts; other failures raiseErrorimmediately. Exhausting retries raisesErrorand ends the run.title,link, anddateare never touched — onlydescriptionchanges, and only on success.Compatibility
Gemfileandautomatic.gemspecuntouched) — onlyjson,net/http,openssl,urifrom the standard library, same as the other AI filters.open_ai.rb,claude.rb,gemini.rb,sakura_ai.rb) was modified.VERSIONremains26.09;doc/VERSIONS' existing unreleasedv26.09 (Release Date: TBD)entry gets one new bullet (no new version section created).Security
Authorization: Bearer <token>, and never appears in a log line or in any raised exception's message — verified by a dedicated spec.OpenSSL::SSL::VERIFY_PEER), matching every other AI filter.YOUR_KIMI_API_KEY) appear anywhere in the diff — no real-looking key.Documentation
doc/PLUGINS.md: newFilterKimi — Supported (external)catalogue entry (section 6.3), following theFilterSakuraAIentry's structure; updated the "changing service is changing the one entry" line to includeFilterKimi.doc/AI_TUTORIAL.md: added aFilterKimiexample alongsideFilterOpenAI/FilterClaude/FilterGeminiin the "change the service by changing one line" section.doc/DEPLOYMENT.md: addedFilterKimito the "Supported (external), no gem" table row alongside the other AI filters.doc/VERSIONS: one new bullet under the existing unreleasedv26.09entry.Tests / Validation
spec/plugins/filter/kimi_spec.rb(24 examples), modeled onsakura_ai_spec.rb's deterministicNet::HTTP.stub(:start)double — no real network access, no real credential. Covers: missing-settingArgumentErrors validated before any request; description replacement with title/link/order/count preserved; blank-description skip; exact request shape (endpoint, bearer header, JSON body, system/user roles, absence of any sampling/reasoning/tool parameter); success requiringfinish_reason == "stop"plus non-empty content;reasoning_contentnever leaking into the description; permanent failures (invalid JSON, missing choice/message, non-"stop" finish reason, empty content) leaving the description unchanged; retry on network failure/429/5xx and no retry on other 4xx; exhausted retries raisingError; and the token never appearing in logs or exception messages.Validation run in this checkout (Ruby 3.3.6):
bundle exec rspec spec/plugins/filter/kimi_spec.rb— pass, 24 examples, 0 failures.spec/**/*_spec.rb, the patternrake specuses) — pass, 447 examples, 0 failures, 0 new skips (only the repository's own pre-existing optional-gem skips: nokogiri, sanitize, fluent-logger, dalli, activerecord). The Kimi spec runs as part of this, not skipped.gem build automatic.gemspec— pass, buildsautomatic-26.09.gem.bundle exec ruby -Ilib -e "require 'automatic'"— pass, exit 0.bundle exec bin/automatic --version/--help— pass, both exit 0; version prints26.09.git diff --check— pass, no output.Gemfile/automatic.gemspecuntouched.YOUR_KIMI_API_KEYand the repository's own existing header contact convention.Note: this branch (
claude/dreamy-sagan-omgzle) is used instead of the originally-specifiedfeature/filter-kimidue to this session's git operating constraints; basemaster, headclaude/dreamy-sagan-omgzle.🤖 Generated with Claude Code
https://claude.ai/code/session_012batzHd21Na8eJFgc1x2hY
Generated by Claude Code