Module: EasyAI::Config::LocalConfig

Defined in:
lib/easyai/config/local_config.rb

Overview

本地配置门面:~/.easyai/config.json 的唯一读写入口

Defined Under Namespace

Classes: Error, IncompatibleVersionError, NotFoundError, ParseError, PlatformNotFoundError, ToolNotConfiguredError

Constant Summary collapse

SCHEMA_VERSION =
'2.0.0'
PLATFORM_DISPLAY_NAMES =
{
  'claude_official' => 'Claude 官方',
  'deepseek' => 'DeepSeek',
  'glm' => 'GLM(Coding Plan)',
  'kimi' => 'Kimi',
  'longcat' => 'LongCat(美团)',
  'minimax' => 'MiniMax',
  'openai_official' => 'ChatGPT Subscribe'
}.freeze
DISABLED_PLATFORMS =

已屏蔽的平台:保留模板与 config 数据,仅暂不对外提供(setup 不列出、runtime 不可选)。 codex/deepseek:新版 codex 移除了 wire_api="chat",而 DeepSeek 暂无 Responses API (/responses 实测 404),接入即失败,故先屏蔽。待 DeepSeek 支持 Responses 或接入协议 转换代理后,从本表删除该项即可恢复(profile 模板与 KNOWN_PLATFORMS 条目均未删除)。

{
  'codex' => %w[deepseek]
}.freeze

Class Method Summary collapse

Class Method Details

.available_platforms(tool) ⇒ Object



86
87
88
89
90
91
92
93
94
# File 'lib/easyai/config/local_config.rb', line 86

def available_platforms(tool)
  return [] unless exists?

  cfg = load
  platforms = cfg.dig(tool, 'platforms')
  platforms.is_a?(Hash) ? platforms.keys : []
rescue Error
  []
end

.config_pathObject

注意:必须每次动态 expand_path,避免常量在加载期 freeze 路径, 让 spec 中切换 ENV 后定位失败



23
24
25
# File 'lib/easyai/config/local_config.rb', line 23

def config_path
  File.expand_path('~/.easyai/config.json')
end

.delete_platform(tool, platform) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/easyai/config/local_config.rb', line 153

def delete_platform(tool, platform)
  return unless exists?

  cfg = load
  platforms = cfg.dig(tool, 'platforms')
  return cfg unless platforms.is_a?(Hash) && platforms.key?(platform)

  platforms.delete(platform)
  cfg.delete(tool) if platforms.empty?
  save(cfg)
  cfg
end

.disabled_platform?(tool, platform) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
# File 'lib/easyai/config/local_config.rb', line 82

def disabled_platform?(tool, platform)
  Array(DISABLED_PLATFORMS[tool]).include?(platform)
end

.exists?Boolean

Returns:

  • (Boolean)


27
28
29
# File 'lib/easyai/config/local_config.rb', line 27

def exists?
  File.file?(config_path)
end

.loadObject

Raises:



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/easyai/config/local_config.rb', line 31

def load
  path = config_path
  raise NotFoundError, "未找到本地配置文件 #{path},请运行 easyai setup" unless File.file?(path)

  raw = File.read(path)
  data = begin
    JSON.parse(raw)
  rescue JSON::ParserError => e
    raise ParseError,
          "配置解析失败 #{path}: #{e.message};可运行 easyai setup --reset 重新生成"
  end

  version = data['version']
  unless version == SCHEMA_VERSION
    raise IncompatibleVersionError,
          "配置版本 #{version.inspect} 不被支持,期望 #{SCHEMA_VERSION}"
  end

  data
end

.platform_display_name(key) ⇒ Object



70
71
72
# File 'lib/easyai/config/local_config.rb', line 70

def platform_display_name(key)
  PLATFORM_DISPLAY_NAMES.fetch(key, key)
end

.resolve_platform(tool:, platform: nil, verbose: false, extra: {}) {|platform_display_name(selected), selected| ... } ⇒ Object

解析平台配置。选定平台后会 yield 平台名(如果给了 block), 让上层(AIToolBase)负责打印 UI 提示,保持 LocalConfig 的输出最小化。 extra:调用方注入的虚拟平台 { key => data }(如 codex 的 ChatGPT Subscribe), 与 config.json 中真实平台合并参与选择;选中虚拟平台时返回其 data。

Yields:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/easyai/config/local_config.rb', line 100

def resolve_platform(tool:, platform: nil, verbose: false, extra: {})
  cfg = load
  tool_cfg = cfg[tool]
  platforms = tool_cfg.is_a?(Hash) ? tool_cfg['platforms'] : nil
  platforms = {} unless platforms.is_a?(Hash)
  extra = {} unless extra.is_a?(Hash)

  # 显式指定了已屏蔽平台:直接拒绝(extra 虚拟平台不受屏蔽影响)
  if platform && !platform.empty? && disabled_platform?(tool, platform) && !extra.key?(platform)
    raise PlatformNotFoundError, "工具 #{tool} 的平台 #{platform} 已被屏蔽(暂不可用)"
  end

  # 屏蔽清单中的真实平台不参与选择(保留 config 数据,仅隐藏)
  visible = platforms.reject { |name, _| disabled_platform?(tool, name) }
  # 数据层:extra(虚拟平台,如 ChatGPT Subscribe)覆盖同名真实平台,保留其 marker
  merged = visible.merge(extra)
  if merged.empty?
    raise ToolNotConfiguredError,
          "工具 #{tool} 未配置任何平台,请运行 easyai setup --tool=#{tool}"
  end

  # 顺序层:extra 虚拟平台始终排在选择列表最前(如【ChatGPT Subscribe】恒为第一项)
  available = extra.keys + (visible.keys - extra.keys)
  selected =
    if platform && !platform.empty?
      unless available.include?(platform)
        raise PlatformNotFoundError,
              "工具 #{tool} 未配置平台 #{platform}(可用:#{available.join(', ')}"
      end

      platform
    elsif available.size == 1
      only = available.first
      puts "使用平台 #{platform_display_name(only)}" if verbose
      only
    else
      interactive_select(tool, available)
    end

  yield(platform_display_name(selected), selected) if block_given?
  merged[selected]
end

.save(cfg) ⇒ Object



52
53
54
55
56
57
58
# File 'lib/easyai/config/local_config.rb', line 52

def save(cfg)
  path = config_path
  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, JSON.pretty_generate(cfg))
  File.chmod(0o600, path) unless Gem.win_platform?
  path
end

.upsert_platform(tool, platform, data) ⇒ Object



143
144
145
146
147
148
149
150
151
# File 'lib/easyai/config/local_config.rb', line 143

def upsert_platform(tool, platform, data)
  cfg = exists? ? load : { 'version' => SCHEMA_VERSION }
  cfg['version'] ||= SCHEMA_VERSION
  cfg[tool] ||= { 'platforms' => {} }
  cfg[tool]['platforms'] ||= {}
  cfg[tool]['platforms'][platform] = data
  save(cfg)
  cfg
end