Class: Chocomint::Server

Inherits:
Object
  • Object
show all
Includes:
EditGitHandlers, EditHandlers
Defined in:
lib/chocomint/server.rb

Overview

監督完結型の Anthropic Messages API 互換プロキシサーバー。

POST /v1/messages でユーザー要求を受け取り、Orchestrator に委譲して ツール実行・機械検証・意味検証・再試行・監査ログまで完結させ、 保証済みの結果を Anthropic 形式のレスポンスとして返す。

通常の LLM API との違い: 「提案」ではなく「実行が保証された結果」を返す。

Constant Summary collapse

PORT_FALLBACK_RANGE =

指定ポートが使用中なら、この範囲内で連番の空きポートを探す (0 なら無制限に探索しない)。

20
PAGE_SIZE =
100

Constants included from EditGitHandlers

EditGitHandlers::GIT_NOT_FOUND

Constants included from EditHandlers

EditHandlers::ANSI_ESCAPE, EditHandlers::ASSET_CONTENT_TYPES, EditHandlers::BINARY_SNIFF_BYTES, EditHandlers::CHAT_CONTEXT_LIMIT, EditHandlers::COMPACT_INPUT_LIMIT, EditHandlers::EDIT_HTML_TEMPLATE, EditHandlers::EXCLUDED_TREE_DIRS, EditHandlers::MAX_LISTED_FILES, EditHandlers::TARGET_SUMMARY_LIMIT, EditHandlers::TOOL_LABELS

Instance Method Summary collapse

Methods included from EditGitHandlers

#discard_paths, #git_body_paths, #git_capture, #git_current_branch, #git_remote_url, #git_repo?, #git_run, #git_tracked?, #handle_edit_git_branches, #handle_edit_git_checkout, #handle_edit_git_commit, #handle_edit_git_discard, #handle_edit_git_init, #handle_edit_git_push, #handle_edit_git_remote_set, #handle_edit_git_stage, #handle_edit_git_stage_all, #handle_edit_git_status, #handle_edit_git_unstage, #handle_edit_git_unstage_all, #normalize_path, #parse_git_status, #push_target_missing?, #require_git_repo!, #same_path?, #unstage_paths, #with_git

Methods included from EditHandlers

#binary_content?, #build_chat_request, #chat_file_context, #chat_instruction?, #console_ws_config, #deep_scrub, #edit_chat_reply, #edit_chat_result, #edit_html, #edit_json, #edit_method_guard, #edit_root, #fs_body_path, #handle_edit, #handle_edit_asset, #handle_edit_chat, #handle_edit_chat_stream, #handle_edit_compact, #handle_edit_file, #handle_edit_files, #handle_edit_fs_delete, #handle_edit_fs_mkdir, #handle_edit_fs_rename, #handle_edit_fs_touch, #handle_edit_save, #handle_edit_static, #humanize_chat_error, #list_workspace_empty_dirs, #list_workspace_files, #normalize_fs_path, #relative_root, #result_field, #run_chat_stream, #serve_edit_static, #strip_ansi, #tool_label, #tool_output_text, #tool_target_summary, #with_fs

Constructor Details

#initialize(planner, host: "127.0.0.1", port: 9210, logger: $stderr, sqlite_path: nil, path_guard: nil, base_dir: Dir.pwd, max_file_bytes: 10_485_760, ws_port: nil, ws_token: nil, chat_client: nil) ⇒ Server

planner: Planner インスタンス (run(request, expectations:) -> Planner::Result)。 edit UI 用に PathGuard / base_dir / vendor / console 情報も受け取る (任意)。 chat_client: 会話 (ツール不要の質問) を判定・応答する ChatClient (任意)。 未指定なら /edit チャットは従来どおり全指示を Planner に流す。



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/chocomint/server.rb', line 28

def initialize(planner, host: "127.0.0.1", port: 9210, logger: $stderr,
               sqlite_path: nil, path_guard: nil, base_dir: Dir.pwd,
               max_file_bytes: 10_485_760, ws_port: nil, ws_token: nil,
               chat_client: nil)
  @planner = planner
  @chat_client = chat_client
  @host = host
  @port = port
  @logger = logger
  @sqlite_path = sqlite_path
  @path_guard = path_guard
  @base_dir = base_dir
  @max_file_bytes = max_file_bytes
  @ws_port = ws_port
  @ws_token = ws_token
  @vendor_dir = File.expand_path("../../public/vendor", __dir__)
  @edit_static_dir = File.expand_path("../../public/edit", __dir__)
end

Instance Method Details

#process(body_hash) ⇒ Object

テスト・組み込み用: Rack 非依存で 1 リクエストを処理する純粋関数。 body_hash(パース済み) -> [status, response_hash]



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/chocomint/server.rb', line 97

def process(body_hash)
  request = extract_request(body_hash)
  return [400, error_body("invalid_request_error", "no user message found")] if request.nil?

  expectations = body_hash["expectations"] || body_hash.dig("metadata", "expectations")
  result = @planner.run(request, expectations: expectations)
  [200, success_body(result)]
rescue Chocomint::InvalidProposalError, Chocomint::UnknownToolError => e
  [422, error_body("invalid_request_error", e.message)]
rescue Chocomint::RetryLimitExceededError => e
  [502, error_body("execution_failed", e.message)]
rescue Chocomint::Error => e
  [500, error_body("api_error", e.message)]
end

#render_logs(rows, total: nil, page: 0, trace_id: nil) ⇒ Object

監査ログをブラウザ表示するための HTML を組み立てる純粋関数。 rows: SqliteLogger#recent 相当の Hash 配列(当該ページ分) total: 全件数(trace_id 指定時はその trace の件数) page: 現在のページ(0 始まり) trace_id: フィルタ中の trace_id(無ければ nil) -> [status, content_type, body]



120
121
122
123
124
# File 'lib/chocomint/server.rb', line 120

def render_logs(rows, total: nil, page: 0, trace_id: nil)
  total ||= rows.size
  [200, "text/html; charset=utf-8",
   logs_html(rows, total: total, page: page, trace_id: trace_id)]
end

#startObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/chocomint/server.rb', line 50

def start
  server = bind_server_with_fallback

  server.mount_proc("/v1/messages") { |req, res| handle_messages(req, res) }
  server.mount_proc("/health") { |_req, res| ok_json(res, { "status" => "ok" }) }
  server.mount_proc("/logs") { |req, res| handle_logs(req, res) }
  server.mount_proc("/logs/detail") { |req, res| handle_log_detail(req, res) }
  server.mount_proc("/logs/llm") { |req, res| handle_llm_logs(req, res) }

  # AI エディタ (/edit)。PathGuard が注入されているときのみ有効化する。
  if @path_guard
    server.mount_proc("/edit") { |req, res| handle_edit(req, res) }
    server.mount_proc("/edit/assets") { |req, res| handle_edit_asset(req, res) }
    server.mount_proc("/edit/static") { |req, res| handle_edit_static(req, res) }
    server.mount_proc("/edit/files") { |req, res| handle_edit_files(req, res) }
    server.mount_proc("/edit/file") { |req, res| handle_edit_file(req, res) }
    server.mount_proc("/edit/save") { |req, res| handle_edit_save(req, res) }
    server.mount_proc("/edit/chat") { |req, res| handle_edit_chat(req, res) }
    server.mount_proc("/edit/compact") { |req, res| handle_edit_compact(req, res) }
    # ファイル操作 (右クリックメニュー): 削除 / 名前変更 / 追加。
    server.mount_proc("/edit/fs/delete") { |req, res| handle_edit_fs_delete(req, res) }
    server.mount_proc("/edit/fs/rename") { |req, res| handle_edit_fs_rename(req, res) }
    server.mount_proc("/edit/fs/mkdir") { |req, res| handle_edit_fs_mkdir(req, res) }
    server.mount_proc("/edit/fs/touch") { |req, res| handle_edit_fs_touch(req, res) }
    # Git 操作 (ステータスバー / WORKING DIRECTORY パネル)。
    server.mount_proc("/edit/git/status") { |req, res| handle_edit_git_status(req, res) }
    server.mount_proc("/edit/git/init") { |req, res| handle_edit_git_init(req, res) }
    server.mount_proc("/edit/git/stage") { |req, res| handle_edit_git_stage(req, res) }
    server.mount_proc("/edit/git/unstage") { |req, res| handle_edit_git_unstage(req, res) }
    server.mount_proc("/edit/git/stage_all") { |req, res| handle_edit_git_stage_all(req, res) }
    server.mount_proc("/edit/git/unstage_all") { |req, res| handle_edit_git_unstage_all(req, res) }
    server.mount_proc("/edit/git/discard") { |req, res| handle_edit_git_discard(req, res) }
    server.mount_proc("/edit/git/commit") { |req, res| handle_edit_git_commit(req, res) }
    server.mount_proc("/edit/git/push") { |req, res| handle_edit_git_push(req, res) }
    server.mount_proc("/edit/git/remote") { |req, res| handle_edit_git_remote_set(req, res) }
    server.mount_proc("/edit/git/branches") { |req, res| handle_edit_git_branches(req, res) }
    server.mount_proc("/edit/git/checkout") { |req, res| handle_edit_git_checkout(req, res) }
  end

  trap("INT") { server.shutdown }
  @logger.puts("chocomint proxy listening on http://#{@host}:#{@port}/v1/messages")
  @logger.puts("chocomint editor available at http://#{@host}:#{@port}/edit") if @path_guard
  server.start
end