mirror of
https://github.com/lukaszraczylo/harness-sync.git
synced 2026-10-12 00:45:22 +00:00
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user