feat: add kimi a7m overlay workflow and remote43 validation

This commit is contained in:
phamnazage-jpg
2026-05-26 07:50:43 +08:00
parent 497e5d91b4
commit 83a05b4889
174 changed files with 3424 additions and 122 deletions

View File

@@ -0,0 +1,87 @@
package main
import (
"context"
"flag"
"fmt"
"io"
"strings"
"sub2api-cn-relay-manager/internal/overlay"
"sub2api-cn-relay-manager/internal/pack"
)
type applyHostOverlayFunc func(context.Context, applyHostOverlayCLIRequest) (overlay.ApplyResult, error)
type applyHostOverlayCLIRequest struct {
PackDir string
ProviderID string
HostVersion string
SourceDir string
OutputDir string
TargetHost string
OverlayID string
}
func parseApplyHostOverlayCLIArgs(args []string) (applyHostOverlayCLIRequest, error) {
fs := flag.NewFlagSet("apply-host-overlay", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var req applyHostOverlayCLIRequest
fs.StringVar(&req.PackDir, "pack-dir", "", "")
fs.StringVar(&req.ProviderID, "provider-id", "", "")
fs.StringVar(&req.HostVersion, "host-version", "", "")
fs.StringVar(&req.SourceDir, "source-dir", "", "")
fs.StringVar(&req.OutputDir, "output-dir", "", "")
fs.StringVar(&req.TargetHost, "target-host", "", "")
fs.StringVar(&req.OverlayID, "overlay-id", "", "")
if err := fs.Parse(args); err != nil {
return applyHostOverlayCLIRequest{}, err
}
switch {
case strings.TrimSpace(req.PackDir) == "":
return applyHostOverlayCLIRequest{}, fmt.Errorf("--pack-dir is required")
case strings.TrimSpace(req.ProviderID) == "":
return applyHostOverlayCLIRequest{}, fmt.Errorf("--provider-id is required")
case strings.TrimSpace(req.HostVersion) == "":
return applyHostOverlayCLIRequest{}, fmt.Errorf("--host-version is required")
case strings.TrimSpace(req.SourceDir) == "":
return applyHostOverlayCLIRequest{}, fmt.Errorf("--source-dir is required")
}
return req, nil
}
func runApplyHostOverlay(ctx context.Context, req applyHostOverlayCLIRequest) (overlay.ApplyResult, error) {
loadedPack, err := pack.LoadDir(req.PackDir)
if err != nil {
return overlay.ApplyResult{}, err
}
providerManifest, err := findProvider(loadedPack, req.ProviderID)
if err != nil {
return overlay.ApplyResult{}, err
}
targetHost := strings.TrimSpace(req.TargetHost)
if targetHost == "" {
targetHost = loadedPack.Manifest.TargetHost
}
resolvedOverlays, err := pack.ResolveApplicableHostOverlays(providerManifest, targetHost, req.HostVersion)
if err != nil {
return overlay.ApplyResult{}, err
}
if len(resolvedOverlays) == 0 {
return overlay.ApplyResult{}, fmt.Errorf("no host overlays matched provider %q for host %q version %q", req.ProviderID, targetHost, req.HostVersion)
}
filteredOverlays, err := overlay.FilterOverlays(resolvedOverlays, req.OverlayID)
if err != nil {
return overlay.ApplyResult{}, err
}
return overlay.Apply(ctx, overlay.ApplyRequest{
PackDir: loadedPack.Dir,
SourceDir: req.SourceDir,
OutputDir: req.OutputDir,
Overlays: filteredOverlays,
})
}

View File

@@ -0,0 +1,115 @@
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"strings"
"testing"
)
func TestRunApplyHostOverlayAppliesResolvedPackOverlay(t *testing.T) {
sourceDir := t.TempDir()
if err := os.MkdirAll(filepath.Join(sourceDir, "backend"), 0o755); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "backend", "hello.txt"), []byte("hello\n"), 0o644); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
packDir := createApplyHostOverlayPackFixture(t)
result, err := runApplyHostOverlay(context.Background(), applyHostOverlayCLIRequest{
PackDir: packDir,
ProviderID: "kimi-a7m",
HostVersion: "0.1.129",
SourceDir: sourceDir,
})
if err != nil {
t.Fatalf("runApplyHostOverlay() error = %v", err)
}
body, err := os.ReadFile(filepath.Join(result.OutputDir, "backend", "hello.txt"))
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(body) != "patched\n" {
t.Fatalf("patched file = %q, want %q", string(body), "patched\n")
}
if _, err := os.Stat(result.MetadataFilePath); err != nil {
t.Fatalf("Stat(metadata) error = %v", err)
}
}
func createApplyHostOverlayPackFixture(t *testing.T) string {
t.Helper()
packDir := t.TempDir()
files := map[string]string{
"pack.json": `{
"pack_id": "openai-cn-pack",
"version": "1.1.3",
"vendor": "YourTeam",
"target_host": "sub2api",
"min_host_version": "0.1.126",
"max_host_version": "0.2.x",
"providers_dir": "providers",
"checksum_file": "checksums.txt"
}`,
"providers/kimi-a7m.json": `{
"provider_id": "kimi-a7m",
"display_name": "Kimi A7M OpenAI Compatible",
"base_url": "https://kimi.a7m.com.cn/v1",
"platform": "openai",
"account_type": "apikey",
"default_models": ["kimi-k2.6"],
"smoke_test_model": "kimi-k2.6",
"host_overlays": [
{
"overlay_id": "sub2api-stock-v0129-kimi-a7m",
"display_name": "sub2api stock v0.1.129 Kimi A7M overlay",
"target_host": "sub2api",
"min_host_version": "0.1.129",
"max_host_version": "0.1.129",
"apply_mode": "patch",
"patch_path": "overlays/kimi-a7m-sub2api-v0.1.129.patch",
"notes_path": "overlays/kimi-a7m-sub2api-v0.1.129.md",
"reason": "stock host still routes chat traffic into unsupported Responses path"
}
],
"group_template": {"name": "g", "rate_multiplier": 1},
"channel_template": {"name": "c", "model_mapping": {"kimi-k2.6": "kimi-k2.6"}},
"plan_template": {"name": "p", "price": 1, "validity_days": 30, "validity_unit": "day"},
"import": {"supports_multi_key": true, "supports_strict": true, "supports_partial": true}
}`,
"overlays/kimi-a7m-sub2api-v0.1.129.patch": strings.Join([]string{
"diff --git a/backend/hello.txt b/backend/hello.txt",
"--- a/backend/hello.txt",
"+++ b/backend/hello.txt",
"@@ -1 +1 @@",
"-hello",
"+patched",
"",
}, "\n"),
"overlays/kimi-a7m-sub2api-v0.1.129.md": "# overlay\n",
}
checksumLines := make([]string, 0, len(files))
for relativePath, body := range files {
absolutePath := filepath.Join(packDir, relativePath)
if err := os.MkdirAll(filepath.Dir(absolutePath), 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", absolutePath, err)
}
if err := os.WriteFile(absolutePath, []byte(body), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", absolutePath, err)
}
sum := sha256.Sum256([]byte(body))
checksumLines = append(checksumLines, hex.EncodeToString(sum[:])+" "+relativePath)
}
if err := os.WriteFile(filepath.Join(packDir, "checksums.txt"), []byte(strings.Join(checksumLines, "\n")+"\n"), 0o644); err != nil {
t.Fatalf("WriteFile(checksums.txt) error = %v", err)
}
return packDir
}

View File

@@ -93,7 +93,7 @@ func TestBatchImportCLI(t *testing.T) {
"--access-mode", "self_service",
"--probe-api-key", "gateway-key",
"--confirm-timeout", "15s",
}, nil, nil, nil, nil, nil, nil, func(_ context.Context, req batchImportCLIRequest) (batchImportCLIResult, error) {
}, nil, nil, nil, nil, nil, nil, nil, func(_ context.Context, req batchImportCLIRequest) (batchImportCLIResult, error) {
batchImportCalled = true
if req.HostID != "host-1" {
t.Fatalf("HostID = %q, want host-1", req.HostID)

View File

@@ -81,7 +81,7 @@ type rollbackSummary struct {
func main() {
if err := execute(context.Background(), log.Writer(), os.Args[1:], func(context.Context) (config.StartupConfig, error) {
return config.LoadStartupFromEnv()
}, runInstallPack, runImportProvider, runPreviewProvider, runRollbackProvider, runReconcileProvider, runBatchImport); err != nil {
}, runInstallPack, runImportProvider, runPreviewProvider, runRollbackProvider, runReconcileProvider, runApplyHostOverlay, runBatchImport); err != nil {
log.Fatalf("run cli: %v", err)
}
}
@@ -96,6 +96,7 @@ func execute(
previewProvider previewProviderFunc,
rollbackProvider rollbackProviderFunc,
reconcileProvider reconcileProviderFunc,
applyHostOverlay applyHostOverlayFunc,
batchImport batchImportFunc,
) error {
if len(args) > 0 && args[0] == "batch-import" {
@@ -171,6 +172,22 @@ func execute(
_, err = fmt.Fprintf(output, "status=%s\nmissing_count=%d\nextra_count=%d\nprobe_failures=%d\naccess_status=%s\n", result.Status, result.MissingCount, result.ExtraCount, result.ProbeFailureCount, result.AccessStatus)
return err
}
if len(args) > 0 && args[0] == "apply-host-overlay" {
req, err := parseApplyHostOverlayCLIArgs(args[1:])
if err != nil {
return err
}
result, err := applyHostOverlay(ctx, req)
if err != nil {
return err
}
overlayIDs := make([]string, 0, len(result.AppliedOverlays))
for _, hostOverlay := range result.AppliedOverlays {
overlayIDs = append(overlayIDs, hostOverlay.OverlayID)
}
_, err = fmt.Fprintf(output, "output_dir=%s\napplied_overlays=%d\noverlay_ids=%s\nmetadata_file=%s\n", result.OutputDir, len(result.AppliedOverlays), strings.Join(overlayIDs, ","), result.MetadataFilePath)
return err
}
cfg, err := loadConfig(ctx)
if err != nil {
@@ -410,10 +427,16 @@ func runPreviewProvider(ctx context.Context, req previewCLIRequest) (provision.P
}
service := provision.NewPreviewService(client)
hostVersion, err := client.GetHostVersion(ctx)
if err != nil {
return provision.PreviewReport{}, err
}
return service.PreviewImport(ctx, provision.PreviewRequest{
Provider: providerManifest,
Mode: req.Mode,
Keys: req.Keys,
TargetHost: loadedPack.Manifest.TargetHost,
HostVersion: hostVersion,
Provider: providerManifest,
Mode: req.Mode,
Keys: req.Keys,
})
}

View File

@@ -8,6 +8,8 @@ import (
"testing"
"sub2api-cn-relay-manager/internal/config"
"sub2api-cn-relay-manager/internal/overlay"
"sub2api-cn-relay-manager/internal/pack"
"sub2api-cn-relay-manager/internal/provision"
"sub2api-cn-relay-manager/internal/reconcile"
"sub2api-cn-relay-manager/internal/store/sqlite"
@@ -33,7 +35,7 @@ func TestExecuteWritesConfigSummaryAfterBootstrap(t *testing.T) {
SQLiteDSN: "file:test.db?_foreign_keys=on",
},
}, nil
}, nil, nil, nil, nil, nil, nil)
}, nil, nil, nil, nil, nil, nil, nil)
if err != nil {
t.Fatalf("execute() returned error: %v", err)
}
@@ -61,7 +63,7 @@ func TestExecuteReturnsBootstrapError(t *testing.T) {
err := execute(context.Background(), &bytes.Buffer{}, nil, func(context.Context) (config.StartupConfig, error) {
return config.StartupConfig{}, wantErr
}, nil, nil, nil, nil, nil, nil)
}, nil, nil, nil, nil, nil, nil, nil)
if !errors.Is(err, wantErr) {
t.Fatalf("execute() error = %v, want %v", err, wantErr)
}
@@ -75,7 +77,7 @@ func TestExecuteReturnsWriteError(t *testing.T) {
Server: config.ServerConfig{ListenAddr: ":9292"},
Database: config.DatabaseConfig{SQLiteDSN: "file:test.db"},
}, nil
}, nil, nil, nil, nil, nil, nil)
}, nil, nil, nil, nil, nil, nil, nil)
if !errors.Is(err, wantErr) {
t.Fatalf("execute() error = %v, want %v", err, wantErr)
}
@@ -99,7 +101,7 @@ func TestExecuteInstallPackWritesSummary(t *testing.T) {
HostVersion: "0.1.126",
Providers: []sqlite.Provider{{ProviderID: "deepseek"}},
}, nil
}, nil, nil, nil, nil, nil)
}, nil, nil, nil, nil, nil, nil)
if err != nil {
t.Fatalf("execute() install-pack error = %v", err)
}
@@ -134,7 +136,7 @@ func TestExecuteImportProviderWritesSummary(t *testing.T) {
AccessStatus: provision.AccessStatusSelfServiceReady,
Accounts: []provision.AccountImportResult{{}, {}},
}, nil
}, nil, nil, nil, nil)
}, nil, nil, nil, nil, nil)
if err != nil {
t.Fatalf("execute() import error = %v", err)
}
@@ -171,7 +173,7 @@ func TestExecutePreviewProviderWritesSummary(t *testing.T) {
"plan": {Action: provision.PreviewActionConflict},
},
}, nil
}, nil, nil, nil)
}, nil, nil, nil, nil)
if err != nil {
t.Fatalf("execute() preview error = %v", err)
}
@@ -199,7 +201,7 @@ func TestExecuteRollbackProviderWritesSummary(t *testing.T) {
t.Fatalf("unexpected rollback request: %+v", req)
}
return rollbackSummary{Accounts: 2, Plans: 1, Channels: 1, Groups: 1}, nil
}, nil, nil)
}, nil, nil, nil)
if err != nil {
t.Fatalf("execute() rollback error = %v", err)
}
@@ -228,7 +230,7 @@ func TestExecuteReconcileProviderWritesSummary(t *testing.T) {
t.Fatalf("unexpected reconcile request: %+v", req)
}
return reconcile.Result{Status: "drifted", MissingCount: 1, ExtraCount: 2, ProbeFailureCount: 1, AccessStatus: provision.AccessStatusBroken}, nil
}, nil)
}, nil, nil)
if err != nil {
t.Fatalf("execute() reconcile error = %v", err)
}
@@ -241,6 +243,48 @@ func TestExecuteReconcileProviderWritesSummary(t *testing.T) {
}
}
func TestExecuteApplyHostOverlayWritesSummary(t *testing.T) {
var output bytes.Buffer
applyCalled := false
err := execute(context.Background(), &output, []string{
"apply-host-overlay",
"--pack-dir", "/tmp/pack",
"--provider-id", "kimi-a7m",
"--host-version", "0.1.129",
"--source-dir", "/tmp/sub2api-src",
}, nil, nil, nil, nil, nil, nil, func(_ context.Context, req applyHostOverlayCLIRequest) (overlay.ApplyResult, error) {
applyCalled = true
if req.ProviderID != "kimi-a7m" || req.HostVersion != "0.1.129" {
t.Fatalf("unexpected apply-host-overlay request: %+v", req)
}
return overlay.ApplyResult{
OutputDir: "/tmp/sub2api-src-patched-sub2api-stock-v0129-kimi-a7m",
AppliedOverlays: []pack.HostOverlay{{
OverlayID: "sub2api-stock-v0129-kimi-a7m",
}},
MetadataFilePath: "/tmp/sub2api-src-patched-sub2api-stock-v0129-kimi-a7m/.sub2api-cn-relay-manager-overlay.json",
}, nil
}, nil)
if err != nil {
t.Fatalf("execute() apply-host-overlay error = %v", err)
}
if !applyCalled {
t.Fatal("execute() did not invoke applyHostOverlay")
}
got := output.String()
if !strings.Contains(got, "applied_overlays=1") || !strings.Contains(got, "overlay_ids=sub2api-stock-v0129-kimi-a7m") {
t.Fatalf("execute() apply-host-overlay output = %q, want overlay summary", got)
}
}
func TestParseApplyHostOverlayCLIArgsRequiresSourceDir(t *testing.T) {
_, err := parseApplyHostOverlayCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "kimi-a7m", "--host-version", "0.1.129"})
if err == nil {
t.Fatal("parseApplyHostOverlayCLIArgs() error = nil, want validation error")
}
}
func TestParseInstallPackCLIArgsRequiresHostBaseURL(t *testing.T) {
_, err := parseInstallPackCLIArgs([]string{"--pack-path", "/tmp/openai-pack.zip"})
if err == nil {