fix(zed): switch to language_models.openai_compatible for Agent panel visibility

The Zed Agent panel (new ACP-based UI) only discovers providers registered
under language_models.openai_compatible.<name>, not language_models.openai.
Each named entry appears as its own provider in the LLM Providers list.

- language_models.openai_compatible.harness-sync-gateway holds gateway URL + models
- agent.default_model.provider = 'harness-sync-gateway' (was 'openai')
- Model entries include capabilities {tools,images,parallel_tool_calls}
- Export common.GatewayProviderID so zed.go can reference it
- Fix staticcheck QF1001 (De Morgan's law) in io.go
- Fix struct field alignment in File (80→72 bytes)
- gofmt remaining files
This commit is contained in:
2026-05-23 21:12:47 +01:00
parent fd97c60df2
commit bde3128dba
27 changed files with 345 additions and 117 deletions
+19 -26
View File
@@ -43,6 +43,19 @@ func defaultHome() string {
// Name returns the harness identifier.
func (a *Adapter) Name() string { return name }
// Capabilities declares what claude-code harness-sync manages.
// Claude Code has its own Anthropic Max subscription — provider/model config is skipped.
func (a *Adapter) Capabilities() adapter.HarnessCapabilities {
return adapter.HarnessCapabilities{
ManagesProviders: false,
ManagesModels: false,
ManagesMCP: true,
ManagesSkills: true,
ManagesInstructions: true,
HasBuiltInSub: true,
}
}
// Detect returns true when ~/.claude exists.
func (a *Adapter) Detect() bool {
_, err := os.Stat(filepath.Join(a.home, ".claude"))
@@ -50,9 +63,8 @@ func (a *Adapter) Detect() bool {
}
// Render produces the FileSet that applies the canonical bundle to claude-code.
// settings.json is MERGED into any existing file so user-managed keys
// settings.json is MERGED into any existing file so user-managed keys
// (hooks, permissions, env, …) are preserved.
// Claude Code manages its own subscription and settings.json — harness-sync
// only manages skills/agents symlinks, CLAUDE.md, and MCP servers.
func (a *Adapter) Render(b *canonical.Bundle) (*adapter.FileSet, error) {
fs := adapter.NewFileSet()
base := filepath.Join(a.home, ".claude")
@@ -78,29 +90,6 @@ func (a *Adapter) Render(b *canonical.Bundle) (*adapter.FileSet, error) {
Content: []byte(instructions),
})
// settings.json holds the gateway env vars + model override.
// Only emit when a gateway URL is configured.
if b.Profile.Gateway.URL != "" {
settingsPath := filepath.Join(base, "settings.json")
settingsExisting, _ := os.ReadFile(settingsPath)
settingsOverlay := map[string]any{
"model": b.Profile.Gateway.DefaultModel,
"env": map[string]any{
"ANTHROPIC_BASE_URL": b.Profile.Gateway.URL,
"ANTHROPIC_AUTH_TOKEN": b.Profile.Gateway.Token,
},
}
settingsMerged, err := common.MergeJSONKeys(settingsExisting, settingsOverlay)
if err != nil {
return nil, err
}
fs.Add(adapter.File{
Dest: settingsPath,
Kind: adapter.RenderedFile,
Content: settingsMerged,
})
}
// Claude Code reads MCP servers from two files on disk:
// * ~/.claude.json (live, written by `claude mcp add`)
// * ~/.claude/mcp_servers.json (older / fallback location)
@@ -133,6 +122,10 @@ func (a *Adapter) Render(b *canonical.Bundle) (*adapter.FileSet, error) {
Dest: livePath,
Kind: adapter.RenderedFile,
Content: liveMerged,
// NoMerge: .claude.json is a live state file (Claude Code rewrites it
// constantly). The JSON merge above already reconciles at the key level;
// a git 3-way merge on top causes perpetual conflicts.
NoMerge: true,
})
}
@@ -94,7 +94,9 @@ func TestRenderMergesLiveClaudeJSON(t *testing.T) {
assert.Equal(t, "stdio", fp["type"])
}
func TestClaudeCodeRenderSetsAnthropicBaseURLInSettings(t *testing.T) {
func TestClaudeCodeRenderNeverWritesSettings(t *testing.T) {
// Claude Code uses its own Anthropic Max subscription.
// harness-sync must NEVER write settings.json even when a gateway is configured.
home := t.TempDir()
ad := New(WithHome(home))
b := &canonical.Bundle{
@@ -113,16 +115,7 @@ func TestClaudeCodeRenderSetsAnthropicBaseURLInSettings(t *testing.T) {
fs.ForEach(func(f adapter.File) { seen[f.Dest] = f })
settingsDest := filepath.Join(home, ".claude", "settings.json")
require.Contains(t, seen, settingsDest)
assert.Equal(t, adapter.RenderedFile, seen[settingsDest].Kind)
var parsed map[string]any
require.NoError(t, json.Unmarshal(seen[settingsDest].Content, &parsed))
assert.Equal(t, "claude-sonnet-4-6", parsed["model"])
env, ok := parsed["env"].(map[string]any)
require.True(t, ok, "env must be a map")
assert.Equal(t, "https://gw", env["ANTHROPIC_BASE_URL"])
assert.Equal(t, "tok", env["ANTHROPIC_AUTH_TOKEN"])
assert.NotContains(t, seen, settingsDest, "settings.json must never be written — claude-code has its own subscription")
}
func TestClaudeCodeRenderNoGatewayNoSettings(t *testing.T) {