Coverage for sources/agentsmgr/renderers/opencode.py: 27%
37 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-23 02:37 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-23 02:37 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
4#============================================================================#
5# #
6# Licensed under the Apache License, Version 2.0 (the "License"); #
7# you may not use this file except in compliance with the License. #
8# You may obtain a copy of the License at #
9# #
10# http://www.apache.org/licenses/LICENSE-2.0 #
11# #
12# Unless required by applicable law or agreed to in writing, software #
13# distributed under the License is distributed on an "AS IS" BASIS, #
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15# See the License for the specific language governing permissions and #
16# limitations under the License. #
17# #
18#============================================================================#
21''' OpenCode renderer implementation.
23 Provides path resolution and targeting mode validation for OpenCode,
24 which supports both per-user and per-project configuration.
25'''
28from . import __
29from .base import RENDERERS, ExplicitTargetMode, RendererBase
32class OpencodeRenderer( RendererBase ):
33 ''' Renderer for OpenCode coder.
35 Supports both per-user and per-project configuration modes.
36 Per-user mode respects OPENCODE_CONFIG environment variable
37 with fallback to configuration overrides and XDG-like default.
38 '''
40 name = 'opencode'
41 modes_available = frozenset( ( 'per-user', 'per-project' ) )
42 mode_default = 'per-project'
43 memory_filename = 'AGENTS.md'
45 _LOCATIONS_MAP = __.immut.Dictionary( {
46 'agents': 'agent',
47 'commands': 'command',
48 } )
50 def calculate_directory_location( self, item_type: str ) -> str:
51 ''' Returns singular directory names for OpenCode configuration.
53 OpenCode expects singular directory names (agent, command) rather
54 than the plural forms used by other coders.
55 '''
56 return self._LOCATIONS_MAP.get( item_type, item_type )
58 def get_template_flavor( self, item_type: str ) -> str:
59 ''' Determines template flavor for OpenCode.
61 OpenCode shares markdown command format with Claude but uses
62 its own agent format, so returns 'claude' for commands and
63 'opencode' for agents.
64 '''
65 if item_type == 'commands':
66 return 'claude'
67 return 'opencode'
69 def resolve_base_directory(
70 self,
71 mode: ExplicitTargetMode,
72 target: __.Path,
73 configuration: __.cabc.Mapping[ str, __.typx.Any ],
74 environment: __.cabc.Mapping[ str, str ],
75 ) -> __.Path:
76 ''' Resolves base output directory for OpenCode.
78 For per-project mode, returns .opencode in project root.
79 For per-user mode, respects precedence: OPENCODE_CONFIG
80 environment variable, configuration file override, or
81 XDG-like ~/.config/opencode default.
82 '''
83 self.validate_mode( mode )
84 if mode == 'per-project':
85 return target / ".auxiliary/configuration/coders/opencode"
86 if mode == 'per-user':
87 return self._resolve_user_directory( configuration, environment )
88 raise __.TargetModeNoSupport( self.name, mode )
90 def _resolve_user_directory(
91 self,
92 configuration: __.cabc.Mapping[ str, __.typx.Any ],
93 environment: __.cabc.Mapping[ str, str ],
94 ) -> __.Path:
95 ''' Resolves per-user directory following precedence rules.
97 Precedence order:
98 1. OPENCODE_CONFIG environment variable (directory containing
99 opencode.json settings file)
100 2. Configuration file override (directory for this coder)
101 3. XDG-like default ~/.config/opencode
102 '''
103 if 'OPENCODE_CONFIG' in environment:
104 directory = __.Path( environment[ 'OPENCODE_CONFIG' ] )
105 return directory.expanduser( )
106 coder_configuration = self._extract_coder_configuration(
107 configuration )
108 if 'directory' in coder_configuration:
109 directory = __.Path( coder_configuration[ 'directory' ] )
110 return directory.expanduser( )
111 return __.Path.home( ) / '.config' / 'opencode'
113 def _extract_coder_configuration(
114 self, configuration: __.cabc.Mapping[ str, __.typx.Any ]
115 ) -> __.cabc.Mapping[ str, __.typx.Any ]:
116 ''' Extracts configuration for this specific coder.
118 Looks for coder entry in configuration coders array by name.
119 '''
120 coders = configuration.get( 'coders', ( ) )
121 for coder in coders:
122 if coder.get( 'name' ) == self.name:
123 return coder
124 return { }
127RENDERERS[ 'opencode' ] = OpencodeRenderer( )