C# 50行でOpenAI Codex CLIを任意のLLMに繋ぐコンセプト画像

OpenAI Codex CLIをClaude・Gemini・Llamaの上で動かす — C# 50行で

OpenAIのCodex CLIは優れたエディタエージェントUXを提供します(shellツール、apply_patch、plan tracking がすべて揃っています)。問題は、2026年2月時点でOpenAI Responses APIだけをサポートしていることです。Chat Completionサポートは削除されており(codex-rs/model-provider-info/src/lib.rsのWireApi enumにはResponsesのみが残っています)、Chat Completionのみをサポートするエンドポイント(Ollama、LM Studio、お気に入りのLlama runner)はそのまま閉ざされてしまいます。本記事は.NET 10のfile-basedプログラムとMicrosoft.Extensions.AIのIChatClient抽象化を活用し、50行のC#一ファイルでResponses互換サーバーを立て、OpenRouterを介してCodex CLIを任意のモデル上で動作させた過程をまとめます。 はじめに Codex CLIはResponsesを話すどんなサーバーとも気持ちよく対話します。model_provider configブロックがまさにこのために存在します。つまり、好きなモデルでバックされたResponses互換HTTPエンドポイントを立てさえすれば、Codexは汎用フロントエンドになり、頭脳はユーザーが選べます。 最近私が気に入っているトリックは次のとおりです。Microsoft.Extensions.AIのベンダー中立なIChatClient抽象化の上で、OpenAI Chat CompletionサーバーとResponses APIサーバーを同時に動かす50行のC#スクリプトを起動します。バックエンドはOpenRouterに向けます(APIキー1つで Claude、Gemini、Llama、GPTなど数百のモデルを使えます)。そしてCodexに対して、OpenAIではなくこのローカルスクリプトと対話するよう設定します。 最終的な結果は、OpenAI Codex CLIがAnthropicのClaude 3.5 Sonnetの上で動作する状態です(あるいはその日に使いたい別のモデルの上でも)。 構成要素 私が自分で公開しているCadenza.AgentというMSBuild SDKを使います。単一の.csファイルを実行可能なエージェントサーバーに変換するSDKで、.NET 10のfile-basedプログラム向け単一ファイルスクリプティングSDKファミリーの一部です(dotnet run script.csと同じ発想ですが、より豊富なTier-1 API(Tool、UseOllama、UseOpenAi、Runなど)を提供します)。Agentバリアントは次を公開します。 POST /v1/chat/completions — Aider / Continue / Cursor / Copilot BYOK / sgpt 向け POST /v1/responses — Codex CLI 向け どちらもユーザーが構成した同じIChatClientでバックされます。バックエンドを変えても wireフォーマットはそのままです。 LLM側はOpenRouterを使います。OpenAIのChat Completion wireフォーマットを別のbase URLでそのまま提供するので、Microsoft.Extensions.AI.OpenAIのChatClientをそのまま挿して使えます。環境変数1つ、任意のモデル。 Codexの設定はCODEX_HOME環境変数のトリックを活用します。~/.codex/config.tomlを編集する代わりに、Codexが指すディレクトリをサンプル専用に別途作っておけば、そこから新しいconfig.tomlを読みます。これでユーザーのグローバル設定に一切触れない、自己完結型のサンプルが作れます。 スクリプト バックエンドのすべて、ファイル1つです。 #!/usr/bin/env dotnet run #:sdk Cadenza.Agent@1.0.14 using System.ClientModel; using OpenAI; var apiKey = Env.Get("OPENROUTER_API_KEY") ?? throw new InvalidOperationException("OPENROUTER_API_KEY env var missing"); var model = Env.Get("OPENROUTER_MODEL") ?? "anthropic/claude-3.5-sonnet"; ServedModelName = "cadenza-codex-openrouter"; // サンプル専用のCodex homeディレクトリを作成。 var codexHome = Path.Combine(Env.Cwd, ".cadenza-codex-openrouter"); MakeDir(codexHome); var catalogPath = Path.Combine(codexHome, "cadenza-catalog.json").Replace('\\', '/'); var configToml = $""" model = "cadenza-codex-openrouter" model_provider = "cadenza" model_catalog_json = "{catalogPath}" [model_providers.cadenza] name = "Cadenza.Agent (OpenRouter-backed)" base_url = "http://localhost:8080/v1" wire_api = "responses" env_key = "CADENZA_API_KEY" stream_idle_timeout_ms = 300000 """; WriteText(Path.Combine(codexHome, "config.toml"), configToml); // Catalog JSON: Codex に提供するモデル id を宣言して "Defaulting to // fallback metadata" 警告を防ぎます。フィールドは codex-rs/protocol/src/ // openai_models.rs の ModelInfo スキーマ基準 — すべてのキーが必須です。 var catalogJson = """ { "models": [{ "slug": "cadenza-codex-openrouter", "display_name": "Cadenza (OpenRouter)", "description": "OpenRouter-backed agent served by Cadenza.Agent", "supported_reasoning_levels": [], "shell_type": "default", "visibility": "list", "supported_in_api": true, "priority": 50, "availability_nux": null, "upgrade": null, "base_instructions": "", "supports_reasoning_summaries": false, "support_verbosity": false, "default_verbosity": null, "apply_patch_tool_type": "freeform", "truncation_policy": { "mode": "tokens", "limit": 8192 }, "supports_parallel_tool_calls": true, "context_window": 200000, "max_context_window": 200000, "auto_compact_token_limit": 180000, "effective_context_window_percent": 95, "experimental_supported_tools": [] }] } """; WriteText(Path.Combine(codexHome, "cadenza-catalog.json"), catalogJson); WriteLine($"Codex config generated at: {codexHome}"); WriteLine("In another terminal, run:"); WriteLine($" $env:CODEX_HOME = \"{codexHome}\""); WriteLine($" $env:CADENZA_API_KEY = \"any-non-empty-string\""); WriteLine($" codex"); // OpenRouter を LLM バックエンドとして接続。 var openAiOptions = new OpenAIClientOptions { Endpoint = new Uri("https://openrouter.ai/api/v1") }; var chatClient = new OpenAI.Chat.ChatClient(model, new ApiKeyCredential(apiKey), openAiOptions) .AsIChatClient(); UseChatClient(chatClient); await Run(); これがすべてです。プロジェクトファイルも、.csprojも、Program.csもありません。一番上の#:sdkディレクティブが.NET 10のfile-basedプログラムシステムに「このスクリプトはCadenza.Agent SDKを使う」と知らせ、SDKがHTTPサーバー、Responses wireフォーマット、すべてのパッケージ参照を引き込みつつ、Tool、UseOllama、UseChatClient、Runを名前空間なしですぐ呼べる名前として公開します。 ...

2026年5月27日 · 3 分 ·  rkttu
C#とPythonが出会う機械学習インターロップの概念図

C#からHugging Faceモデルを呼ぶ:DotNetPy 0.6.0でWhisper・sentence-transformers・Stable Diffusionを動かす

週末に小さなC#ライブラリ DotNetPy の0.6.0をリリースしました。CPythonのC APIを直接呼び出して、.NETアプリの中でPythonを動かすインターロップライブラリです。本記事は0.6.0に含まれる3つの機械学習サンプル(sentence-transformersによる意味検索、Whisperによる音声認識、Stable Diffusion Turboによる画像生成)をどうまとめたか、そしてその過程でPEP 703 free-threaded CPythonをどのように検証したかを記録したものです。 出発点:手元にあるのはC#だけ、モデルはHugging Faceにある 数か月ごとに同じパターンが繰り返されます。字幕用にWhisperが必要、検索用にsentence-transformerが必要、ときにはStable Diffusionのようなモデルを使いたい。しかし手元にあるのはC#一つだけ。こんなとき、よく取られる回避策にはそれぞれ決定的な欠点があります。 ONNXに変換する。 画像系やエンコーダ系のモデルにはよく合いますが、新しいアーキテクチャやdiffusionパイプラインでは、変換そのものが別プロジェクトになります。 Pythonマイクロサービスとして立ち上げる。 プロセスが2つ、デプロイのシナリオが2つ、そしてホットパスにネットワークホップが1つ追加されます。 外部APIを呼び出す。 コストがかかり、インターネット接続が必要で、データが箱の外に出ます。 pythonnet や CSnakes を使う。 堅実な選択肢ですが、pythonnetはまだNative AOTをサポートしておらず、CSnakesはSource Generatorベースのワークフローを強制します。さらに、両ライブラリともfree-threaded CPythonビルドに対する公開された検証結果をまだ出していません。 私はもう少し薄い選択肢が欲しかったのです。C#コードの中にPythonスニペットを文字列としてインラインで書き、配列をそのまま渡し、JSON形式で結果を受け取り、全体がAOTコンパイルされて単一バイナリになる 形です。それがDotNetPyの目標であり、以下の3つのサンプルはすべてGPUのない普通のWindows 11ノートPCで最初から最後まで動作します。 サンプル1 — sentence-transformers による意味検索 最初のサンプルは小さなコーパスを埋め込み、クエリをエンコードして、最も類似する上位K件の文を返します。戻り値はDotNetPyValueで、内部的にはJSONドキュメントをラップしたもので、GetString()・GetInt32()・GetDouble()、そしてパスベースのプロパティアクセスを通じて.NETの世界に戻ってきます。 using DotNetPy; using DotNetPy.Uv; using var project = PythonProject.CreateBuilder() .WithProjectName("dotnetpy-ml-embeddings") .WithPythonVersion("==3.12.*") .AddDependencies( "sentence-transformers==2.7.0", "transformers==4.40.2", "torch>=2.2,<2.5") .Build(); await project.InitializeAsync(); var executor = project.GetExecutor(); executor.Execute(@" import numpy as np from sentence_transformers import SentenceTransformer model = SentenceTransformer('all-MiniLM-L6-v2') "); var corpus = new[] { "Python is a popular programming language for data science.", "C# and .NET are great for building enterprise applications.", "Rust offers memory safety without garbage collection.", "Pizza is delicious with various toppings.", // … }; var query = "Tell me about programming languages"; using var hits = executor.ExecuteAndCapture(@" corpus_emb = model.encode(corpus, normalize_embeddings=True) query_emb = model.encode([query], normalize_embeddings=True)[0] sims = corpus_emb @ query_emb top_idx = np.argsort(-sims)[:3] result = [ {'rank': int(rank + 1), 'score': float(sims[i]), 'text': corpus[int(i)]} for rank, i in enumerate(top_idx) ] ", new Dictionary<string, object?> { { "corpus", corpus }, { "query", query } }); foreach (var hit in hits!.RootElement.EnumerateArray()) { Console.WriteLine($" {hit.GetProperty("rank").GetInt32()}. " + $"[{hit.GetProperty("score").GetDouble():F3}] " + $"{hit.GetProperty("text").GetString()}"); } 実際の出力はこうなります。 ...

2026年5月11日 · 4 分 ·  rkttu
クラウドプラットフォームアーキテクチャのコンセプトイメージ

RoleEntryPointからFoundryCBAgentまで — .NET開発者が見たMicrosoft Foundry Hosted Agentのアーキテクチャ

この記事は2026年3月時点で、パブリックプレビュー状態にあるMicrosoft Foundry Hosted Agentの設計を、.NETアーキテクチャの系譜の中で分析します。現時点でプロダクション利用を推奨する記事ではなく、エンタープライズAIアーキテクトが注目すべき設計の方向性に焦点を当てています。 エージェントをプロダクションに載せるということ エンタープライズでAIエージェントをプロダクションにデプロイしようとする際、現在の選択肢は2つです。 宣言的エージェント。 Microsoft Foundryポータルでプロンプトとツールを組み合わせて作るno-code方式です。素早く作成できますが、複雑な分岐ロジックやマルチステップワークフロー、外部システムとの精密な連携をコードで制御できません。一定レベルを超えた瞬間に限界に突き当たります。 セルフホストコンテナ。 エージェントロジックをコードで直接記述し、コンテナに入れてデプロイする方式です。自由度は完全ですが、HTTPサーバー構成、認証、会話状態管理、スケーリング、モニタリング、バージョン管理をすべて自前で実装する必要があります。そして決定的に、コンテナ内でエージェントが何をしているか プラットフォームには一切わかりません。 LLMをどう呼び出したか、ツールをどの順序で使ったか、なぜそのレスポンスを生成したかがブラックボックスです。 Hosted Agentはこの間の空白を埋めます。コードレベルの自由度を維持しつつ、エージェントの動作がプラットフォームに構造的に透明に公開される第3の道です。 Windows Azureを覚えていますか 現在のMicrosoft Azureは、2010年2月に Windows Azure という名前で商用リリースされました。2014年3月にMicrosoft Azureにリブランディングされるまで、このプラットフォームのアイデンティティは現在とはかなり異なっていました。 Windows Azureは本質的に PaaSプラットフォーム でした。VMを直接操作するIaaSは当初存在せず、2012年になってようやくVirtual Machinesが追加されました。出発点はCloud Services(Web Role、Worker Role)、SQL Azure(現在のAzure SQL Database)、Storage Servicesのようなマネージドサービスでした。開発者がインフラを意識せずコードと設定ファイルだけでデプロイすることが核心の約束であり、これは当時AWSがEC2中心のIaaSで市場を切り開いていたのとは根本的に異なるアプローチでした。 2014年のリブランディング後、AzureはAWSを強く意識しながらIaaS機能を急速に拡充しました。VM、VNET、Load Balancer、そして後のAKSまで。市場で「クラウド=仮想マシンを借りて使うこと」という認識が支配的だった時期にIaaSを強化したのは商業的に正しい判断でしたが、その過程でWindows Azure時代のPaaS中心の哲学は多少希薄化しました。 しかしMicrosoftの本来の強みは常にPaaSとSaaSにありました。.NET Framework、Visual Studio、SQL Server、Office 365、SharePoint — この会社は開発者とインフォメーションワーカーに マネージドプラットフォーム 上で働かせることに30年以上の歴史を持っています。Windows Azure時代のCloud Servicesはその哲学のクラウド拡張であり、その後のApp Service、Azure Functions、Container Appsも同じ文脈にあります。 この背景を理解すると、Hosted Agentがなぜこの形で設計されたかが明確になります。MicrosoftはAIエージェントという新しいワークロードに対して、IaaS的アプローチ(セルフホストコンテナを直接管理)ではなくPaaS的アプローチ(プラットフォームがホスティングし、開発者はロジックに集中)を選択したのです。これはAWSがBedrock Agentsで取る方向とも、GoogleがVertex AI Agent Builderで取る方向とも異なります。MicrosoftのPaaS DNAがAI時代に再び発現したと見ることができます。 どこかで見たパターン .NET開発者であれば、そしてAzureの歴史を共に歩んできた開発者であれば、Hosted Agentのアーキテクチャに強いデジャヴを感じるでしょう。 Windows Azure Cloud Servicesの時代を思い出してみましょう。あの時も「開発者はビジネスロジックだけを書き、プラットフォームが提供するホストプロセスがそのロジックを包んでインフラに載せる」というのが核心の約束でした。RoleEntryPointを継承し、OnStart()、Run()、OnStop()を実装すれば、WaWorkerHost.exeがそのコードをロードして実行し、Fabric Controllerがインスタンスのプロビジョニングとヘルスモニタリングを担当していました。 Hosted Agentの構造は、このパターンをAIエージェントドメインに再解釈したものです。 ...

2026年3月17日 · 3 分 ·  rkttu
単一のC#ファイルで実行されるコードのコンセプトイメージ

.NETの新ジャンル:NuGet-Free Single File C#コーディングの時代

C#がスクリプト言語のように軽くなるのではなく、スクリプト言語が羨むほど速くなるということです。 はじめに .NET 10で導入されたdotnet run file.cs——いわゆる** file-based app**——は、.csprojファイルなしで単一の.csファイルだけでC#コードを実行できる機能です。しかし、現在この機能の実行速度は初回実行基準でWindowsで約1.5秒、WSL2で約0.8秒レベルです。Pythonのpython script.pyが50ms前後であることと比較すると、まだ「スクリプティング」と呼ぶには物足りない水準です。 しかし、現在.NETエコシステムで同時に進行中の2つの大きな変化が、この状況を根本的に変える可能性があります: dotnet run file.csのビルド最適化 — MSBuildをバイパスしてRoslynを直接呼び出す戦略 Runtime Async (async2) — async/awaitをランタイムレベルで処理し、ステートマシンのオーバーヘッドを除去 この2つが組み合わさると、** NuGetパッケージなしでBCLだけで書くシングルファイルC#プログラム** が独立したコーディングジャンルとして確立される可能性があります。この記事では、その技術的基盤と将来像を描きます。 File-Based Appの現状:MSBuildというボトルネック 本質:.csproj + .cs = 1つの.cs File-based appはC#スクリプト(.csx)とは根本的に異なります。.csxは別のスクリプトホストがランタイムで解釈しますが、file-based appは** コンパイル時に仮想.csprojに変換** されて正規のビルドパイプラインを通ります。出力は通常のプロジェクトと同一のmanaged DLLです。 // これらのディレクティブが.csprojの内容になる #:package System.CommandLine@2.0.0 // ここからが実際のコード Console.WriteLine("Hello, file-based app!"); 問題:MSBuildの重さ dotnet run hello.csを実行すると内部で起こること: ステップ 所要時間(概算) 説明 CLIロード ~200ms .NETランタイムJIT、CLIコマンドディスパッチ MSBuildエンジンロード ~200ms ビルドエンジン初期化 SDK targets評価 ~300ms 数百の.props/.targetsファイルの順次評価 NuGet restore ~100ms+ パッケージ依存性解決(キャッシュ済みの場合) Roslynコンパイル ~200ms 実際のC# → IL変換 実行 ~50ms 結果のDLL実行 合計約1.5秒 のうち実際の「コンパイル+実行」は約250msに過ぎません。残りはすべてMSBuild関連のオーバーヘッドです。 ...

2026年3月16日 · 4 分 ·  rkttu
虫眼鏡でコードを検査するコンセプトイメージ

AIが見たことのない.NET UIフレームワークでIDEを作るとき、エージェントはどうやってAPIを把握するのか

2026-02-08 — LibraStudio開発記 #1 背景 なぜまた別の.NET IDEなのか .NETエコシステムにおけるIDEの選択肢は、実はそれほど広くありません。Visual Studioは強力ですがWindows専用であり、Community Editionでさえ商用利用に制約があります。VS Code + C# Dev Kitの組み合わせも優れていますが、C# Dev Kitがプロプライエタリライセンスである点は変わりません。結局、重要な部分でベンダーロックインが発生し、その上に構築したツールチェーンとワークフロー全体が特定ベンダーの意思決定に依存してしまいます。 かつてはSharpDevelopがWindowsで、MonoDevelop(Xamarin Studio)がクロスプラットフォームでその役割を担っていました。しかしSharpDevelopは2017年に開発が停止し、MonoDevelopはXamarinに統合された後、事実上スタンドアロンIDEとしての役目を終えました。それ以降、最新の.NET(Core以降)開発環境を適切にサポートする** オープンソースでリベラルライセンスのクロスプラットフォームIDE** は登場していません。 この点がずっと惜しいと感じていました。そこでLibraStudioを始めました。ElectronやVS Codeベースではなく、純粋な.NETネイティブIDEを目指します。一人でVisual Studioレベルのものを作ろうというわけではありません。しかし2026年にはAIベースのコードエディタの力を借りることができます。AIエージェントにフレームワークAPIの探索、ボイラープレートの生成、繰り返しの実装を委任すれば、一人でカバーできる範囲は過去とはまったく異なります。 UIフレームワークにはAprillz.MewUIを使用しています。NativeAOTに対応し、XAMLなしでC#コードだけでUIを構築する軽量フレームワークです。このフレームワークは、私と同じく韓国の代表的な.NET開発者コミュニティであるDotNetDev(.NET Dev、https://forum.dotnetdev.kr/)で活動しているソン・ヨンジェ氏のオープンソースプロジェクトです。 AIが知らない新生フレームワークという難関 MewUIはまったく新しいコンセプトの新生UIフレームワークです。XAMLベースのWPFやAvaloniaUIとは異なり、純粋なC# fluent APIでUIを構築する独自の設計を持っています。当然、公式ドキュメントが豊富ではなく、Stack Overflowに関連する質問が蓄積されてもいません。 これに.NETというプラットフォーム自体の問題も重なります。大規模言語モデルの学習データにおいて、.NET/C#はJavaScriptやPythonに比べて相対的に学習頻度が低いです。メジャーフレームワークであるWPFやWinFormsでさえ不正確なコードを生成することがあるのに、学習データにほとんど含まれていないMewUIのAPIを正確に当てる可能性は極めて低いです。 このような状況でAIコーディングエージェントに「最小限のタブベースのテキストエディタを作ってください」と依頼すると、何が起こるでしょうか。 一般的なアプローチ:推測と反復 通常のAIコーディングエージェントのワークフローは次のとおりです: 学習データから類似のAPIを思い出してコードを書きます ビルドします エラーが出たらエラーメッセージを見て修正します 2〜3を繰り返します この方式は、学習データに豊富に含まれているメジャーフレームワーク(WPF、React、SwiftUIなど)ではうまく機能します。しかし、MewUIのようにまったく新しいコンセプトの新生フレームワーク――しかも相対的に学習頻度が低い.NETベース――では、** 推測の精度が極めて低くなります**。メソッド名、パラメータの順序、オーバーロードの有無、イベントシグネチャ――すべてが外れる可能性があります。 別のアプローチ:アセンブリを直接覗く 今回の作業では、私自身が開発したHandMirror MCPを使用しました。HandMirrorは、NuGetパッケージの** コンパイル済みアセンブリを直接検査する** MCP(Model Context Protocol)サーバーです。 Webドキュメントを検索する代わりに、実際の.dllを分析し、次の情報を返します。注目すべきは、HandMirrorが一般的な.NETリフレクション(System.Reflection)ではなくMono.Cecilを使用している点です。Cecilは.NETアセンブリのメタデータをランタイムロードなしに直接読み取るため、検査対象アセンブリの.NETランタイムバージョンに影響されません。.NET Framework 4.x用ライブラリでも.NET 10対象でも同様に分析できます: すべての名前空間と型の一覧 各型のコンストラクタ、プロパティ、メソッド、イベントのシグネチャ Extension methodがどの名前空間にあるか 継承階層構造 実際に得られた情報 Aprillz.MewUI v0.9.1を検査した結果: 178個のパブリック型、14個の名前空間 MultiLineTextBoxがTextBaseを継承し、Text、Placeholder、AcceptTab、Wrap、IsReadOnlyなどのプロパティを持つこと TabControl.SelectionChangedがAction<int>であること(ドキュメントがなければAction<TabItem>と推測していたでしょう――実際そうでした) FileDialog.OpenFile()がOpenFileDialogOptionsを受け取り、Title、Filter、Ownerなどのプロパティを持つこと Menu.Item()とContextMenu.Item()のオーバーロードが異なること――Menu.Itemにはショートカット文字列パラメータがない ObservableValue<T>がSubscribe()、NotifyChanged()、Set()メソッドを持つこと この情報だけで、エディタのコアコードをほぼ正確に記述できました。 結果:初回ビルドでエラー3個 コード全体を書き上げた後、初回ビルドで発生したエラーはわずか3個でした: ...

2026年2月8日 · 2 分 ·  rkttu
オープンソースプロジェクト間の協力とコードポーティングを象徴するイメージ

AI時代のオープンソース貢献:HwpLibSharpポーティングプロジェクトから学んだこと

AI時代のオープンソース貢献:HwpLibSharpポーティングプロジェクトから学んだこと Microsoft MVPとして活動して17年になります。その間、.NETコミュニティで最も多く受けた質問の一つが「C#でHWPファイルをどう扱えばいいですか?」でした。韓国のハンコム社の公式ライブラリはWindowsとCOMベースであり、クロスプラットフォーム.NET環境では事実上、解決策がありませんでした。 そんな中、@neolord0さんのhwplibを見つけました。Javaで書かれた、純粋にHWPファイルフォーマットをパースするオープンソースライブラリです。「これを.NETに移植すればコミュニティに貢献できる」とすぐに思いましたが、簡単なことではありませんでした。コードベースが膨大な上に、今も継続的にアップデートされていたからです。 2026年、AIコーディングアシスタントと共にこの作業を始めました。 一回限りのポーティングではなく「同期化」 一見、HWPは頻繁に変わるフォーマットではないため、一度ポーティングすれば手を加える必要はないと思いがちです。しかし、あらゆる技術は変化し続けます。元のプロジェクトが今も活発にメンテナンスされているのはもちろん、.NET技術自体も進化しているため、一回限りの作業では不十分です。 従来のフォーク(fork)は、時間が経つほど原本との距離が開きます。最終的に「自分たちのバージョン」と「オリジナル」がそれぞれ別の道を歩むことになります。当然の流れですが、それでも別のアプローチを選びました。単なるフォークではなく、** 原本と共に生き続ける「移植された実装(Ported Implementation)」** というアイデンティティを明確にしたのです。 元プロジェクト: hwplib (Java) ↓ 定期的な同期 ポーティングプロジェクト: HwpLibSharp (C#) ↓ .NET特化の改善 エコシステム拡張 そのため、READMEにもこう記載しました。 「本プロジェクトの意思決定および判断の優先権は、オリジナルプロジェクトの作者である @neolord0 氏の意思を優先します。」 これは礼儀のために書いた文章ではありません。2つのプロジェクトが長期的に共存するためには、誰が方向性を決めるのかを最初から明確にする必要があると判断したからです。 AIと共に行ったポーティング作業 正直に言えば、AIコーディングアシスタントなしでこのプロジェクトを進めていたら、初期ポーティングだけで6ヶ月以上かかっていたでしょう。アップストリーム同期は到底手が出せず、結局放置された「もう一つのレガシー」になっていたはずです。 しかし、AIと一緒に作業することで、まったく異なるアプローチが可能になりました。 AIがうまくできたこと 構文変換はほぼ完璧でした。Javaのgetter/setterをC#プロパティに変換し、命名規則をC#コンベンションに合わせ、nullチェックをNullable参照型に変換する作業は、ほとんど自動化できました。 ライブラリのマッピングでもAIの助けは大きかったです。Apache POIをOpenMcdfに置き換える際、「Javaでこのライブラリが果たす役割を.NETでは何で代替できるか」を素早く見つけてくれました。元プロジェクトがアップデートされるたびに変更点を追跡してC#バージョンに反映する反復作業でも、ヒューマンエラーを大幅に削減してくれました。 Upstream変更の自動追跡 この過程で特に効果的だったのは、AIエージェントを活用した** upstream変更の自動追跡** 方式でした。元のhwplibプロジェクトに新しいコミットが入るたびに、AIエージェントが変更されたJavaソースファイルと対応するC#ソースファイルを比較分析し、実装上の差異を検出します。 この同期を体系的に管理するために、ポーティングされたすべてのC#ソースファイルの先頭に、元のJavaファイルとの対応関係を明示するヒントヘッダーを残しました。 // ===================================================================== // Java Original: kr/dogfoot/hwplib/util/compressors/Compressor.java // Repository: https://github.com/neolord0/hwplib // ===================================================================== このヘッダーがあれば、AIエージェントは「このC#ファイルの元はJava側のどのファイルか」を即座に把握できます。upstreamでCompressor.javaが変更されると、AIが対応するC#ファイルを見つけてdiffを分析し、不足している変更点や実装上の差異をレポートしてくれます。人間が数百のファイルを一つずつ照合する必要なく、AIが「この部分が原本と異なっているので確認が必要です」と教えてくれる方式です。 実際にこの方式を導入した後、upstream同期にかかる時間が従来比80%以上短縮されました。以前は変更ログを読んで関連ファイルを一つずつ探しながら手動で反映していたのが、今ではAIが変更リストと影響範囲を自動的に整理してくれるようになりました。 AIができなかったこと 一方、HWPファイルのSection-Paragraph-Control構造、各コントロールの意味、韓国語ワープロ文書ならではの特性といったドメイン知識は、完全に自分の役割でした。AIがもっともらしく提案したコードが実際に正しいかどうかの検証は、やはり人間がやるべき仕事です。 戦略的な意思決定も同様でした。Native AOTをサポートするか、Blazor WebAssemblyでどう動作させるか、どの.NETバージョンまでサポートするか。こうした判断には.NETエコシステム全般への理解と、実際のユーザー環境に対する感覚が必要です。ライセンス文言、原作者との関係設定、韓国の開発者コミュニティの文脈を反映する作業も、AIが下書きを作り、自分が仕上げるという方法で進めました。 .NETエコシステムに合わせた再設計 単にコードを移すだけで終わりではありませんでした。.NET開発者が自然に使えるよう、原本プロジェクトの哲学と意図を損なわない範囲でAPIを整える作業も必要でした。 // Blazor WebAssemblyサポート(ファイルシステムなしでストリームから) var hwpFile = HWPReader.FromStream(memoryStream); // Native AOT互換、URLからの非同期ロード var hwpFile = await HWPReader.FromUrlAsync(url); これらの機能はオリジナルのJavaバージョンには存在しません。しかし.NET開発者なら当然期待するものです。おかげで、Azure FunctionsでHWPを処理したり、Blazorでブラウザ上にHWPをレンダリングすることも可能になりました。 ...

2026年2月7日 · 1 分 ·  rkttu
JavaからNETへのコード移植を表現した抽象的なイメージ

Java hwplibを.NETに移植する:AIと共に歩んだオープンソース移植の旅

始まりは単純な好奇心から 「HWPファイルを.NETで直接扱えたらいいのに…」 こんな考えをした.NET開発者は私だけではないでしょう。HWPファイルは韓国で公共機関を中心に今でも広く使われている文書形式ですが、.NETエコシステムではこれを適切に扱えるオープンソースライブラリがありませんでした。 これまで.NETでHWPファイルを扱うには、Windows OS限定でアレアハングルをインストールすると付属するHWP ActiveXコントロールのCOMタイプライブラリを呼び出して制御する程度しかありませんでしたが、残念ながらこれさえもサポートが終了し、今は道が閉ざされた状態です。 そんな中、Javaで書かれたhwplibを発見しました。neolord0さんが継続的にメンテナンスしてきたこのライブラリは、HWPファイルの読み書きを完成度高くサポートしていました。 以前なら、このようなライブラリを移植することは、強い使命感と目的意識がなければなかなか手を出せる作業ではありませんでした。しかし、高性能なAIモデルが多く登場し、今なら新しい挑戦ができるのではないかという好奇心がありました。 こうして始まった3週間の旅を共有します。 数字で見るプロジェクト 本格的な話の前に、Javaバージョンのプロジェクトの規模を数字でまとめてみましょう。 項目 内容 元プロジェクト neolord0/hwplib (Java) 移植プロジェクト rkttu/libhwpsharp (C#) ターゲットフレームワーク .NET Standard 2.0, .NET Framework 4.7.2, .NET 8 総コミット数 54個 開発期間 2025-12-16 ~ 2026-01-08(約3週間) 初期移植コード 641ファイル、50,010行 641ファイル、5万行。正直、最初にこの数字を見たときは「これは可能なのか?」という疑問が湧きました。 初日:AIと共に行った大規模コード変換 5万行を1日で? 2025年12月16日、プロジェクトを開始しました。通常の状況であれば、5万行のJavaコードをC#に変換するには数ヶ月かかったでしょう。しかし、AIコーディングアシスタントがゲームチェンジャーでした。 AIにJavaファイルを渡し、C#に変換してもらいました。もちろん、機械的な変換だけでは不十分でした。JavaとC#は似ているように見えますが、微妙な違いが多いからです。 // Java public byte getValue() { return value; } public void setValue(byte value) { this.value = value; } // C# public byte Value { get; set; } このようなパターン変換はAIがうまく処理してくれました。しかし、本当の問題は別にありました。 最大の難関:Apache POIからOpenMcdfへ OLE2複合ドキュメントの沼 HWPファイルはMicrosoftのOLE2複合ドキュメント形式(Compound File Binary Format)に基づいています。簡単に言えば、1つのファイルの中に複数の「ストリーム」がフォルダ構造のように格納されている形式です。 JavaではApache POIライブラリがこの形式を扱いますが、.NETには直接対応するものがありません。代わりにOpenMcdfというライブラリを使う必要がありましたが、残念ながらApache POIとはAPIが完全に異なります。 ...

2026年1月8日 · 2 分 ·  rkttu
TableClothプロジェクト カタログビルダーアップデート

TableClothカタログビルダー、.NET 10とFBAで現代化

TableClothプロジェクトとは TableClothは、韓国のインターネットバンキング環境で必要なセキュリティプラグインをWindows Sandbox環境で安全に使用できるようにするオープンソースプロジェクトです。様々な金融機関のウェブサイトで要求されるセキュリティプログラムを隔離された環境で実行し、ホストシステムの安全を確保します。 カタログリポジトリとは TableClothCatalogリポジトリは、TableClothプロジェクトで参照する金融機関サイト別のセキュリティプログラムリストを保管する保存場所です。銀行、証券会社、保険会社などのウェブサイトで要求されるセキュリティプラグイン情報が体系的に整理されており、カタログビルダーツールはこの情報を基にTableClothアプリで使用可能な形式に加工します。 最近のカタログビルダーアップデート 最近、このリポジトリに重要なアップデートが適用されました。今回のコミットでは、カタログビルダーツールが.NET 10にアップグレードされ、プロジェクト構造が大幅に簡素化されました。 主な変更点 .NET 10アップグレード # 変更前 dotnet-version: 8.0.x # 変更後 dotnet-version: 10.0.x ビルドパイプラインが.NET 8から.NET 10にアップグレードされました。これにより、最新ランタイムのパフォーマンス向上と新しい言語機能を活用できるようになりました。 File-Based App(FBA)方式への移行 最も目立つ変化はプロジェクト構造の簡素化です。従来は別途の.csprojプロジェクトファイルとソリューションファイル(.sln)を使用していましたが、今回のアップデートでこれらのファイルが削除され、単一のC#スクリプトファイル(catalogutil.cs)に統合されました。 #!/usr/bin/env dotnet #:package IronSoftware.System.Drawing@2025.9.3 #:property PublishAot=false using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using System.Collections.Concurrent; // ... これが.NETの** File-Based App(FBA)** 方式です。FBAは以下のような利点を提供します: プロジェクトファイル不要:.csprojなしで単一の.csファイルでアプリケーション実行可能 インラインパッケージ参照:#:packageディレクティブでNuGetパッケージを直接参照 インラインビルドプロパティ設定:#:propertyディレクティブでビルドプロパティを設定 シバン(Shebang)サポート:#!/usr/bin/env dotnetでUnixスタイルの実行をサポート Graceful Shutdownサポート 新しいバージョンでは、プロセス終了時により安定した処理が可能になりました: // Graceful shutdown タイムアウト var gracefulShutdownTimeout = TimeSpan.FromSeconds(5); Console.CancelKeyPress += (sender, e) => { if (cts.IsCancellationRequested) { // 2回目のCtrl+C:強制終了 Console.Out.WriteLine("Info: Force exit requested. Terminating immediately..."); e.Cancel = false; return; } Console.Out.WriteLine($"Info: Cancellation requested. Shutting down gracefully (timeout: {gracefulShutdownTimeout.TotalSeconds}s)..."); Console.Out.WriteLine("Info: Press Ctrl+C again to force exit."); e.Cancel = true; // 即時終了を防止 cts.CancelAfter(gracefulShutdownTimeout); cts.Cancel(); }; 主な特徴: ...

2025年12月6日 · 1 分 ·  rkttu