Class: Ask::AppServer::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/app_server/config.rb

Overview

Configuration loader for ask-app-server.

Reads from a JSON config file, then merges environment variable overrides. Config file search order (first found wins):

1. ASK_APP_SERVER_CONFIG env var (explicit path)
2. ./.ask-app-server.json (project-local)
3. ~/.ask-app-server/config.json (user-global)

Environment variable overrides:

ASK_APP_SERVER_MODEL       — model identifier (e.g., "opencode_go/deepseek-v4-flash")
ASK_APP_SERVER_PERMISSIONS — permission mode (on_request, never)
DEBUG                      — debug logging

Example config file (~/.ask-app-server/config.json):

{
"model": "opencode_go/deepseek-v4-flash",
"tools": ["bash", "read", "write", "edit", "glob", "grep"],
"permissions": {
  "mode": "on_request",
  "blocked_tools": ["write", "edit", "bash", "destroy"],
  "timeout": 300
},
"system_prompt": "You are a helpful AI coding assistant.",
"session": { "timeout": 600 },
"custom_models": {
  "deepseek-v4-flash": {
    "provider": "opencode_go",
    "context": 1000000,
    "output": 384000
  }
}
}

Constant Summary collapse

DEFAULTS =
{
  model: "opencode_go/deepseek-v4-flash",
  tools: %w[bash read write edit glob grep].freeze,
  permissions: {
    mode: :on_request,
    blocked_tools: %w[write edit bash destroy].freeze,
    timeout: 300
  }.freeze,
  system_prompt: nil,
  session: {
    timeout: 600
  }.freeze,
  custom_models: {}.freeze
}.freeze
CONFIG_FILE_PATHS =
[
  -> { ENV["ASK_APP_SERVER_CONFIG"] },
  -> { File.expand_path(".ask-app-server.json") },
  -> { File.expand_path("~/.ask-app-server/config.json") }
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_path: nil) ⇒ Config

Returns a new instance of Config.



63
64
65
66
67
# File 'lib/ask/app_server/config.rb', line 63

def initialize(config_path: nil)
  @source_path = nil
  @data = load_config(config_path)
  @registered = false
end

Instance Attribute Details

#source_pathObject (readonly)

Returns the value of attribute source_path.



61
62
63
# File 'lib/ask/app_server/config.rb', line 61

def source_path
  @source_path
end

Instance Method Details

#blocked_toolsObject

List of tool names that require permission.



111
112
113
# File 'lib/ask/app_server/config.rb', line 111

def blocked_tools
  @data.dig(:permissions, :blocked_tools)&.map(&:to_s) || DEFAULTS.dig(:permissions, :blocked_tools)
end

#custom_modelsObject

Custom model definitions from config.



150
151
152
# File 'lib/ask/app_server/config.rb', line 150

def custom_models
  @data[:custom_models] || {}
end

#debug?Boolean

Whether debug logging is enabled.

Returns:

  • (Boolean)


145
146
147
# File 'lib/ask/app_server/config.rb', line 145

def debug?
  ENV["DEBUG"] == "1"
end

#modelObject

Resolved model identifier (may include provider prefix).



70
71
72
# File 'lib/ask/app_server/config.rb', line 70

def model
  ENV.fetch("ASK_APP_SERVER_MODEL", @data[:model])
end

#model_idObject

The model ID (without provider prefix).



87
88
89
# File 'lib/ask/app_server/config.rb', line 87

def model_id
  parsed_model[1]
end

#model_providerObject

The provider slug (nil if not specified).



92
93
94
# File 'lib/ask/app_server/config.rb', line 92

def model_provider
  parsed_model[0]
end

#parsed_modelObject

Parse model into [provider, model_id]. Supports "provider/model" format and bare model names.



76
77
78
79
80
81
82
83
84
# File 'lib/ask/app_server/config.rb', line 76

def parsed_model
  raw = model
  if raw.include?("/")
    parts = raw.split("/", 2)
    [parts[0], parts[1]]
  else
    [nil, raw]
  end
end

#permission_modeObject

Permission mode symbol (:on_request, :never).



102
103
104
105
106
107
108
# File 'lib/ask/app_server/config.rb', line 102

def permission_mode
  env_mode = ENV["ASK_APP_SERVER_PERMISSIONS"]
  return env_mode.to_sym if env_mode

  raw = @data.dig(:permissions, :mode)
  raw ? raw.to_sym : DEFAULTS.dig(:permissions, :mode)
end

#permission_timeoutObject

Permission request timeout in seconds.



116
117
118
# File 'lib/ask/app_server/config.rb', line 116

def permission_timeout
  @data.dig(:permissions, :timeout) || DEFAULTS.dig(:permissions, :timeout)
end

#register_models!Object

Register custom models into Ask::ModelCatalog so ask-agent can find them. Safe to call multiple times — models are registered only once.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/ask/app_server/config.rb', line 156

def register_models!
  return if @registered
  @registered = true

  custom_models.each do |model_id, cfg|
    provider = cfg[:provider] || cfg["provider"]
    context = cfg[:context] || cfg["context"] || 4096
    output = cfg[:output] || cfg["output"] || 4096

    # Build a ModelInfo-compatible struct and register it
    model_info = OpenStruct.new(
      id: model_id.to_s,
      provider: provider.to_s,
      chat?: true,
      context: context,
      output: output
    )

    # Use the singleton's register if available
    if Ask::ModelCatalog.respond_to?(:instance)
      catalog = Ask::ModelCatalog.instance
      catalog.register(model_info) if catalog.respond_to?(:register)
    end

    if debug?
      warn "[ask-app-server] Registered custom model: #{provider}/#{model_id} (#{context}ctx)"
    end
  end
end

#session_timeoutObject

Session timeout in seconds.



126
127
128
# File 'lib/ask/app_server/config.rb', line 126

def session_timeout
  @data.dig(:session, :timeout) || DEFAULTS.dig(:session, :timeout)
end

#state_configObject

State persistence configuration. Returns nil if no state config is set (use in-memory defaults).



132
133
134
# File 'lib/ask/app_server/config.rb', line 132

def state_config
  @data[:state]
end

#state_sqlite_pathObject

Path for SQLite state database (if configured).



137
138
139
140
141
142
# File 'lib/ask/app_server/config.rb', line 137

def state_sqlite_path
  cfg = state_config
  return nil unless cfg

  cfg[:sqlite_path] || cfg["sqlite_path"] || cfg[:path] || cfg["path"]
end

#system_promptObject

System prompt for the agent.



121
122
123
# File 'lib/ask/app_server/config.rb', line 121

def system_prompt
  @data[:system_prompt]
end

#to_hObject

All config as a hash (for display).



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/ask/app_server/config.rb', line 187

def to_h
  prov, mod = parsed_model
  {
    model: mod,
    provider: prov,
    tools: tools,
    permissions: {
      mode: permission_mode,
      blocked_tools: blocked_tools,
      timeout: permission_timeout
    },
    system_prompt: system_prompt,
    session: { timeout: session_timeout },
    custom_models: custom_models.keys,
    source: source_path || "(defaults)"
  }
end

#toolsObject

List of tool names to load.



97
98
99
# File 'lib/ask/app_server/config.rb', line 97

def tools
  @data[:tools] || DEFAULTS[:tools]
end