{
  "sha": "4d30a920ee36593c9657b4d0ae87c5002e366e71",
  "message": "Integrate OpenAI GPT-6 Luna with MAX reasoning",
  "author": {
    "login": "nnneeeooo-nnneeeooo",
    "name": "",
    "email": ""
  },
  "committer": {
    "login": "web-flow",
    "name": "",
    "email": ""
  },
  "created_at": "2026-10-04T10:08:01Z",
  "files": [
    {
      "filename": ".github/workflows/bench.yml",
      "previous_filename": "",
      "status": "modified",
      "additions": 12,
      "deletions": 1,
      "changes": 13
    },
    {
      "filename": ".github/workflows/briefing.yml",
      "previous_filename": "",
      "status": "modified",
      "additions": 3,
      "deletions": 1,
      "changes": 4
    },
    {
      "filename": ".github/workflows/copilot.yml",
      "previous_filename": "",
      "status": "modified",
      "additions": 3,
      "deletions": 1,
      "changes": 4
    },
    {
      "filename": ".github/workflows/hourly.yml",
      "previous_filename": "",
      "status": "modified",
      "additions": 7,
      "deletions": 1,
      "changes": 8
    },
    {
      "filename": ".github/workflows/manual-draft.yml",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 0,
      "changes": 2
    },
    {
      "filename": "config/model_prices.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 0,
      "changes": 1
    },
    {
      "filename": "pipeline/build.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 0,
      "changes": 1
    },
    {
      "filename": "pipeline/manual_draft.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 0,
      "changes": 2
    },
    {
      "filename": "pipeline/model_config.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 16,
      "deletions": 3,
      "changes": 19
    },
    {
      "filename": "pipeline/providers.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 88,
      "deletions": 10,
      "changes": 98
    },
    {
      "filename": "pipeline/write.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 7,
      "deletions": 6,
      "changes": 13
    },
    {
      "filename": "tests/test_manual_draft.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 46,
      "deletions": 6,
      "changes": 52
    },
    {
      "filename": "tests/test_providers_payloads.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 87,
      "deletions": 3,
      "changes": 90
    },
    {
      "filename": "tests/test_usage.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 7,
      "deletions": 0,
      "changes": 7
    },
    {
      "filename": "tests/test_write_offline.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 5,
      "deletions": 3,
      "changes": 8
    }
  ],
  "diff": "@@ -11,6 +11,9 @@ on:\\n       gemini_model:\\n         description: \"Override AVWIRE_GEMINI_MODEL for this bench\"\\n         required: false\\n+      openai_model:\\n+        description: \"Override AVWIRE_OPENAI_MODEL for this bench\"\\n+        required: false\\n       nvidia_model:\\n         description: \"Override AVWIRE_NVIDIA_MODEL for this bench\"\\n         required: false\\n@@ -39,11 +42,16 @@ jobs:\\n \\n       - name: List models available to each key\\n         env:\\n+          OPENAI_API_KEY: ${{ secrets.OPENAI_API }}\\n           GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}\\n           NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY || secrets.API_1_NVIDIA_NEMOTRON_3_ULTRA_550B_A55B }}\\n           OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}\\n           WECHAT_API_KEY: ${{ secrets.DeepSeekV4Flash_API }}\\n         run: |\\n+          if [ -n \"$OPENAI_API_KEY\" ]; then\\n+            echo \"== OpenAI models (GPT-6 Luna) ==\"\\n+            curl -sf https://api.openai.com/v1/models -H \"Authorization: Bearer $OPENAI_API_KEY\" | python -c 'import json,sys; [print(m[\"id\"]) for m in json.load(sys.stdin).get(\"data\",[]) if \"gpt-6-luna\" in m.get(\"id\", \"\")]' || echo \"(model list unavailable)\"\\n+          fi\\n           if [ -n \"$OPENCODE_API_KEY\" ]; then\\n             echo \"== OpenCode Console models ==\"\\n             curl -sf https://console.opencode.ai/inference/openai/v1/models \\\\n@@ -72,21 +80,24 @@ jobs:\\n \\n       - name: Bench each provider on identical pending groups\\n         env:\\n+          OPENAI_API_KEY: ${{ secrets.OPENAI_API }}\\n           ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\\n           GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}\\n           NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY || secrets.API_1_NVIDIA_NEMOTRON_3_ULTRA_550B_A55B }}\\n           OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}\\n           WECHAT_API_KEY: ${{ secrets.DeepSeekV4Flash_API }}\\n           AVWIRE_MODEL: ${{ vars.AVWIRE_MODEL }}\\n+          AVWIRE_OPENAI_MODEL: ${{ inputs.openai_model || vars.AVWIRE_OPENAI_MODEL }}\\n           AVWIRE_GEMINI_MODEL: ${{ inputs.gemini_model || vars.AVWIRE_GEMINI_MODEL }}\\n           AVWIRE_NVIDIA_MODEL: ${{ inputs.nvidia_model || vars.AVWIRE_NVIDIA_MODEL }}\\n           AVWIRE_OPENCODE_MODEL: ${{ inputs.opencode_model || vars.AVWIRE_OPENCODE_MODEL }}\\n           AVWIRE_WECHAT_MODEL: ${{ inputs.wechat_model || vars.AVWIRE_WECHAT_MODEL }}\\n         run: |\\n           ok=\"\"\\n           failed=\"\"\\n-          for p in wechat opencode gemini nvidia anthropic; do\\n+          for p in openai wechat opencode gemini nvidia anthropic; do\\n             case \"$p\" in\\n+              openai)    key=\"$OPENAI_API_KEY\" ;;\\n               wechat)    key=\"$WECHAT_API_KEY\" ;;\\n               opencode)   key=\"$OPENCODE_API_KEY\" ;;\\n               gemini)    key=\"$GEMINI_API_KEY\" ;;\\n@@ -76,14 +76,16 @@ jobs:\\n       - name: Assemble briefing and build site\\n         env:\\n           # Existing model keys support one sourced search and one overview.\\n+          OPENAI_API_KEY: ${{ secrets.OPENAI_API }}\\n           GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}\\n           NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY || secrets.API_1_NVIDIA_NEMOTRON_3_ULTRA_550B_A55B }}\\n           OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}\\n           OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}\\n           WECHAT_API_KEY: ${{ secrets.DeepSeekV4Flash_API }}\\n           ADSBLOL_API_KEY: ${{ secrets.ADSBLOL_API_KEY }} # optional\\n           AVWIRE_WECHAT_MODEL: ${{ vars.AVWIRE_WECHAT_MODEL }}\\n-          AVWIRE_PROVIDER_ORDER: gemini:gemini-3.6-flash,gemini:gemini-3.5-flash,nvidia:nvidia/nemotron-3-ultra-550b-a55b,wechat:Deepseek-v4-flash,openrouter:nvidia/nemotron-3-ultra-550b-a55b:free,nvidia:nvidia/nemotron-3-super-120b-a12b\\n+          AVWIRE_OPENAI_MODEL: ${{ vars.AVWIRE_OPENAI_MODEL }}\\n+          AVWIRE_PROVIDER_ORDER: openai:gpt-6-luna,gemini:gemini-3.6-flash,gemini:gemini-3.5-flash,nvidia:nvidia/nemotron-3-ultra-550b-a55b,wechat:Deepseek-v4-flash,openrouter:nvidia/nemotron-3-ultra-550b-a55b:free,nvidia:nvidia/nemotron-3-super-120b-a12b\\n           BRIEFING_LLM_INTRO: \"true\"\\n           BRIEFING_MAX_ITEMS: ${{ vars.BRIEFING_MAX_ITEMS }}\\n           BRIEFING_MAX_ITEMS_PER_SECTION: ${{ vars.BRIEFING_MAX_ITEMS_PER_SECTION }}\\n@@ -33,7 +33,9 @@ jobs:\\n           AI_DAILY_REQUEST_LIMIT: ${{ vars.AI_DAILY_REQUEST_LIMIT || '50' }}\\n           AI_EXTERNAL_SEARCH_DAILY_LIMIT: ${{ vars.AI_EXTERNAL_SEARCH_DAILY_LIMIT || '10' }}\\n           AI_DAILY_BUDGET_LIMIT_USD: ${{ vars.AI_DAILY_BUDGET_LIMIT_USD || '5' }}\\n-          AVWIRE_PROVIDER_ORDER: ${{ vars.AVWIRE_PROVIDER_ORDER }}\\n+          AVWIRE_PROVIDER_ORDER: openai:gpt-6-luna,${{ vars.AVWIRE_PROVIDER_ORDER }}\\n+          OPENAI_API_KEY: ${{ secrets.OPENAI_API }}\\n+          AVWIRE_OPENAI_MODEL: ${{ vars.AVWIRE_OPENAI_MODEL }}\\n           OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}\\n           ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\\n           GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}\\n@@ -12,6 +12,8 @@ on:\\n       - \"pipeline/flightwatch.py\"\\n       - \"pipeline/flightnews.py\"\\n       - \"pipeline/write.py\"\\n+      - \"pipeline/providers.py\"\\n+      - \"pipeline/model_config.py\"\\n       - \"pipeline/provider_health.py\"\\n       - \"pipeline/article_schema.py\"\\n       - \"pipeline/build.py\"\\n@@ -50,6 +52,8 @@ jobs:\\n           python tests/test_grounded.py\\n           python tests/test_pla.py\\n           python tests/test_usage.py\\n+          python tests/test_providers_payloads.py\\n+          python tests/test_manual_draft.py\\n           python tests/test_fulltext.py\\n           python tests/test_glossary.py\\n           python tests/test_evidence_binding.py\\n@@ -135,6 +139,7 @@ jobs:\\n \\n       - name: Run pipeline\\n         env:\\n+          OPENAI_API_KEY: ${{ secrets.OPENAI_API }}\\n           ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\\n           GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}\\n           # one NIM key serves every model on the platform; the long name is\\n@@ -147,10 +152,11 @@ jobs:\\n           ADSBLOL_API_KEY: ${{ secrets.ADSBLOL_API_KEY }} # optional\\n           # optional repo variables\\n           AVWIRE_MODEL: ${{ vars.AVWIRE_MODEL }}\\n+          AVWIRE_OPENAI_MODEL: ${{ vars.AVWIRE_OPENAI_MODEL }}\\n           AVWIRE_GEMINI_MODEL: ${{ vars.AVWIRE_GEMINI_MODEL }}\\n           AVWIRE_NVIDIA_MODEL: ${{ vars.AVWIRE_NVIDIA_MODEL }}\\n           AVWIRE_WECHAT_MODEL: ${{ vars.AVWIRE_WECHAT_MODEL }}\\n-          AVWIRE_PROVIDER_ORDER: gemini:gemini-3.6-flash,gemini:gemini-3.5-flash,wechat:Deepseek-v4-flash,nvidia:nvidia/nemotron-3-ultra-550b-a55b,openrouter:nvidia/nemotron-3-ultra-550b-a55b:free,nvidia:nvidia/nemotron-3-super-120b-a12b\\n+          AVWIRE_PROVIDER_ORDER: openai:gpt-6-luna,gemini:gemini-3.6-flash,gemini:gemini-3.5-flash,nvidia:nvidia/nemotron-3-ultra-550b-a55b,wechat:Deepseek-v4-flash,openrouter:nvidia/nemotron-3-ultra-550b-a55b:free,nvidia:nvidia/nemotron-3-super-120b-a12b\\n           # freshness window (hours); code default 120, validated 24-336\\n           NEWS_MAX_AGE_HOURS: ${{ vars.NEWS_MAX_AGE_HOURS }}\\n           PYTHONUNBUFFERED: '1'\\n@@ -28,11 +28,13 @@ jobs:\\n \\n       - name: Process encrypted drafting jobs\\n         env:\\n+          OPENAI_API_KEY: ${{ secrets.OPENAI_API }}\\n           GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}\\n           NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY || secrets.API_1_NVIDIA_NEMOTRON_3_ULTRA_550B_A55B }}\\n           OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}\\n           OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}\\n           WECHAT_API_KEY: ${{ secrets.DeepSeekV4Flash_API }}\\n+          AVWIRE_OPENAI_MODEL: ${{ vars.AVWIRE_OPENAI_MODEL }}\\n           AVWIRE_MANUAL_TOKEN: ${{ secrets.AVWIRE_MANUAL_TOKEN }}\\n         run: python pipeline/manual_draft.py --process-pending\\n \\n@@ -2,6 +2,7 @@\\n   \"_comment\": \"理論價值試算用的參考牌價,單位 USD / 100萬 tokens。kind: reference=公開牌價或市場加權價, estimate=估計值(該模型無明確公開牌價)。本站全部走免費額度,實際支出為 $0;此表僅供『若按牌價計費值多少錢』的參考,可自行修改。鍵是模型 id 的子字串比對。nonApiReference 只列方案內模型工作的官方 API 對照單價；gptFamilyReference 是 GPT-5 系列官方標準 API 牌價目錄；兩者都不計入 API 用量或理論總額。\",\\n   \"usdToTwd\": 31.5,\\n   \"models\": {\\n+    \"gpt-6-luna\": {\"in\": 0.1, \"out\": 0.5, \"kind\": \"reference\", \"note\": \"OpenAI standard API list price, 2026-10; cached input $0.01/M\"},\\n     \"gpt-5.6-sol\": {\"in\": 5.0, \"out\": 30.0, \"kind\": \"reference\", \"note\": \"OpenAI standard API list price, 2026-07; cached input $0.50/M\"},\\n     \"nemotron-3-ultra\": {\"in\": 0.423, \"out\": 2.61, \"kind\": \"reference\", \"note\": \"OpenRouter weighted avg, 2026-06\"},\\n     \"nemotron-3-super\": {\"in\": 0.2, \"out\": 0.8, \"kind\": \"estimate\", \"note\": \"Nemotron family <= $0.40 input; estimated\"},\\n@@ -1082,6 +1082,7 @@ def iso_timestamp(dt) -> str:\\n \\n # Article-footer model credit: writer id -> short public model name.\\n _WRITER_MODELS = (\\n+    (\"gpt-6-luna\", \"GPT-6 Luna\"),\\n     (\"gpt-5.6-sol\", \"GPT-5.6 Sol\"),\\n     (\"nemotron-3-ultra\", \"Nemotron 3 Ultra\"),\\n     (\"nemotron-3-super\", \"Nemotron 3 Super\"),\\n@@ -42,6 +42,7 @@\\n from providers import (  # noqa: E402\\n     GeminiProvider,\\n     NvidiaProvider,\\n+    OpenAIProvider,\\n     OpenCodeProvider,\\n     OpenRouterProvider,\\n     WechatProvider,\\n@@ -567,6 +568,7 @@ def _providers_for(payload: dict) -> list:\\n             \"nvidia\": NvidiaProvider,\\n             \"wechat\": WechatProvider,\\n             \"opencode\": OpenCodeProvider,\\n+            \"openai\": OpenAIProvider,\\n             \"openrouter\": OpenRouterProvider,\\n         }.get(platform)\\n         if cls is None:\\n@@ -15,6 +15,10 @@\\n     \"nvidia:mistralai/mistral-medium-3.5-128b\",\\n )\\n \\n+OPENAI_MODEL_ORDER = (\\n+    \"openai:gpt-6-luna\",\\n+)\\n+\\n WECHAT_MODEL_ORDER = (\\n     \"wechat:Deepseek-v4-flash\",\\n )\\n@@ -47,6 +51,7 @@\\n # Complete model catalog used by the manual drafting workbench and historical\\n # usage dashboard.\\n MODEL_ORDER = (\\n+    OPENAI_MODEL_ORDER[0],\\n     OPENCODE_MODEL_ORDER[0],\\n     OPENCODE_MODEL_ORDER[1],\\n     DIRECT_MODEL_ORDER[0],\\n@@ -65,10 +70,10 @@\\n     *OPENROUTER_MODEL_ORDER[2:],\\n )\\n \\n-# Automated runs use only routes that completed at least one valid article in\\n-# recent production telemetry. Retired/unavailable routes stay selectable in\\n-# the manual workbench but no longer add doomed HTTP 410 calls to every story.\\n+# GPT-6 Luna is the requested automatic primary; remaining routes are the\\n+# current production fallbacks that completed valid articles in telemetry.\\n AUTOMATIC_MODEL_ORDER = (\\n+    OPENAI_MODEL_ORDER[0],\\n     DIRECT_MODEL_ORDER[0],\\n     DIRECT_MODEL_ORDER[1],\\n     DIRECT_MODEL_ORDER[4],\\n@@ -80,6 +85,7 @@\\n DEFAULT_PROVIDER_ORDER = \",\".join(AUTOMATIC_MODEL_ORDER)\\n \\n MODEL_DISPLAY_NAMES = {\\n+    \"openai:gpt-6-luna\": \"OpenAI · GPT-6 Luna\",\\n     \"wechat:Deepseek-v4-flash\":\\n         \"WeChat Coding Plan · DeepSeek V4 Flash\",\\n     \"opencode:claude-sonnet-4-6\":\\n@@ -140,6 +146,13 @@ def manual_reasoning_profile(provider: str, model: str, tier: str) -> dict:\\n     \"\"\"\\n     if tier not in REASONING_TIERS:\\n         tier = \"standard\"\\n+    if provider == \"openai\":\\n+        effort = {\"fast\": \"low\", \"standard\": \"medium\",\\n+                  \"deep\": \"max\"}[tier]\\n+        return {\\n+            \"wire\": {\"reasoning\": {\"effort\": effort}},\\n+            \"effective\": f\"reasoning.effort={effort}\",\\n+        }\\n     if provider == \"openrouter\":\\n         effort = {\"fast\": \"low\", \"standard\": \"medium\",\\n                   \"deep\": \"high\"}[tier]\\n@@ -1,13 +1,14 @@\\n \"\"\"LLM provider adapters for the write stage.\\n \\n-Six interchangeable writers behind one interface:\\n+Seven interchangeable writers behind one interface:\\n \\n     anthropic  Anthropic Messages API with native structured outputs\\n     gemini     Google Gemini API (REST) with responseJsonSchema\\n     nvidia     NVIDIA NIM (integrate.api.nvidia.com, OpenAI-compatible REST),\\n                JSON enforced by prompt + local extraction\\n     openrouter OpenRouter Chat Completions (OpenAI-compatible REST),\\n                JSON enforced by prompt + local extraction\\n+    openai     OpenAI Responses API with strict JSON Schema output\\n     opencode   OpenCode Console gateway, routing each model through its\\n                native Responses, Messages, Gemini or Chat Completions API\\n     wechat     WeChat Coding Plan OpenAI-compatible Chat Completions gateway\\n@@ -72,6 +73,7 @@\\n # profile fall back to conservative generic settings.\\n NVIDIA_DEFAULT_MODEL = \"nvidia/nemotron-3-ultra-550b-a55b\"\\n GEMINI_DEFAULT_MODEL = \"gemini-3.6-flash\"\\n+OPENAI_DEFAULT_MODEL = \"gpt-6-luna\"\\n OPENROUTER_DEFAULT_MODEL = \"nvidia/nemotron-3-ultra-550b-a55b:free\"\\n OPENCODE_DEFAULT_MODEL = \"claude-sonnet-4-6\"\\n OPENCODE_BASE_URL = \"https://console.opencode.ai/inference\"\\n@@ -1159,6 +1161,22 @@ def _record_usage(self, data: dict) -> None:\\n             usage.get(\"output_tokens\") or usage.get(\"completion_tokens\") or 0)\\n         self.usage[\"usageEvents\"] += 1\\n \\n+    def _responses_endpoint(self) -> str:\\n+        return f\"{OPENCODE_BASE_URL}/openai/v1/responses\"\\n+\\n+    def _responses_api_key(self) -> str:\\n+        return os.environ[\"OPENCODE_API_KEY\"]\\n+\\n+    def _responses_format(self, schema: dict) -> dict:\\n+        return {\\n+            \"type\": \"json_schema\",\\n+            \"name\": \"skytical_draft\",\\n+            \"schema\": schema,\\n+        }\\n+\\n+    def _responses_output_tokens(self, repair: bool) -> int:\\n+        return 8192 if repair else 16384\\n+\\n     @staticmethod\\n     def _schema_instruction(schema: dict) -> str:\\n         return (\\n@@ -1175,19 +1193,14 @@ def _responses_call(self, system_prompt: str, user_prompt: str,\\n                 {\"role\": \"system\", \"content\": system_prompt},\\n                 {\"role\": \"user\", \"content\": user_prompt},\\n             ],\\n-            \"max_output_tokens\": 8192 if repair else 16384,\\n+            \"max_output_tokens\": self._responses_output_tokens(repair),\\n             \"reasoning\": {\"effort\": self._effort(repair)},\\n-            \"text\": {\"format\": {\\n-                \"type\": \"json_schema\",\\n-                \"name\": \"skytical_draft\",\\n-                \"schema\": schema,\\n-            }},\\n+            \"text\": {\"format\": self._responses_format(schema)},\\n         }\\n-        key = os.environ[\"OPENCODE_API_KEY\"]\\n         response = self._post_json(\\n-            f\"{OPENCODE_BASE_URL}/openai/v1/responses\",\\n+            self._responses_endpoint(),\\n             payload,\\n-            {\"Authorization\": f\"Bearer {key}\",\\n+            {\"Authorization\": f\"Bearer {self._responses_api_key()}\",\\n              \"Content-Type\": \"application/json\"},\\n             repair,\\n         )\\n@@ -1356,12 +1369,77 @@ def draft(self, system_prompt: str, user_prompt: str, schema: dict):\\n                 ) from repair_exc\\n \\n \\n+class OpenAIProvider(OpenCodeProvider):\\n+    \"\"\"Direct OpenAI Responses API writer using the repository's REST stack.\"\"\"\\n+\\n+    name = \"openai\"\\n+\\n+    def __init__(self, model=None, reasoning_tier=None) -> None:\\n+        selected_model = (\\n+            model or os.environ.get(\"AVWIRE_OPENAI_MODEL\")\\n+            or OPENAI_DEFAULT_MODEL\\n+        )\\n+        super().__init__(selected_model, reasoning_tier)\\n+\\n+    def available(self) -> bool:\\n+        return bool(os.environ.get(\"OPENAI_API_KEY\"))\\n+\\n+    def _responses_endpoint(self) -> str:\\n+        return \"https://api.openai.com/v1/responses\"\\n+\\n+    def _responses_api_key(self) -> str:\\n+        return os.environ[\"OPENAI_API_KEY\"]\\n+\\n+    def _check_status(self, response) -> None:\\n+        if response.status_code in (401, 403):\\n+            raise ProviderAuthError(f\"HTTP {response.status_code}\")\\n+        if response.status_code == 402:\\n+            raise ProviderQuotaError(\"HTTP 402 (credits exhausted?)\")\\n+        if response.status_code == 429:\\n+            body = str(getattr(response, \"text\", \"\") or \"\").lower()\\n+            if any(code in body for code in (\\n+                    \"insufficient_quota\", \"billing_hard_limit\",\\n+                    \"exceeded your current quota\")):\\n+                raise ProviderQuotaError(\"HTTP 429 (quota exhausted)\")\\n+            raise ProviderError(\"HTTP 429 rate limit\")\\n+        if response.status_code == 404:\\n+            raise ProviderError(\\n+                f\"model not found: {self.model} \"\\n+                \"(not available to this API key?)\")\\n+        if response.status_code >= 400:\\n+            raise ProviderError(f\"HTTP {response.status_code}\")\\n+\\n+    def _responses_format(self, schema: dict) -> dict:\\n+        return {\\n+            \"type\": \"json_schema\",\\n+            \"name\": \"skytical_draft\",\\n+            \"schema\": schema,\\n+            \"strict\": True,\\n+        }\\n+\\n+    def _responses_output_tokens(self, repair: bool) -> int:\\n+        # GPT-6 Luna counts reasoning tokens against this cap. Leave room for\\n+        # substantial MAX reasoning and the final bilingual JSON response.\\n+        return 48_000\\n+\\n+    def _effort(self, repair: bool) -> str:\\n+        if self.reasoning_tier:\\n+            profile = manual_reasoning_profile(\\n+                self.name, self.model, self.reasoning_tier)\\n+            effort = profile[\"wire\"][\"reasoning\"][\"effort\"]\\n+            self.reasoning_effective = profile[\"effective\"]\\n+            return effort\\n+        self.reasoning_effective = \"reasoning.effort=max\"\\n+        return \"max\"\\n+\\n+\\n _REGISTRY = {\\n     AnthropicProvider.name: AnthropicProvider,\\n     GeminiProvider.name: GeminiProvider,\\n     NvidiaProvider.name: NvidiaProvider,\\n     WechatProvider.name: WechatProvider,\\n     OpenRouterProvider.name: OpenRouterProvider,\\n+    OpenAIProvider.name: OpenAIProvider,\\n     OpenCodeProvider.name: OpenCodeProvider,\\n }\\n \\n@@ -2,11 +2,11 @@\\n \\n For each pending story group (capped at MAX_GROUPS_PER_RUN per run) one\\n LLM call drafts the article, flash and optional incident row. Providers are\\n-tried in AVWIRE_PROVIDER_ORDER (opencode / anthropic / gemini / nvidia /\\n-openrouter; see providers.py): the first configured provider is the primary\\n-writer and the rest are fallbacks. A provider that hits an auth/quota error or\\n-repeated transport outage is disabled for the rest of the run and the next one\\n-takes over.\\n+tried in AVWIRE_PROVIDER_ORDER (openai / opencode / anthropic / gemini /\\n+nvidia / openrouter / wechat; see providers.py): the first configured\\n+provider is the primary writer and the rest are fallbacks. A provider that\\n+hits an auth/quota error or repeated transport outage is disabled for the rest\\n+of the run and the next one takes over.\\n \\n Each draft carries an editorial status: \"publish\" and \"publish_brief\" go\\n straight to the site and \"reject\" is dropped.  The former manual-review\\n@@ -2712,7 +2712,8 @@ def main() -> None:\\n         print(f\"write: persisted provider cooldowns active: {details}\")\\n     major_deferred = 0\\n     if not providers:\\n-        print(\"write: no LLM API key set (OPENCODE_API_KEY / \"\\n+        print(\"write: no LLM API key set (OPENAI_API_KEY / \"\\n+              \"OPENCODE_API_KEY / \"\\n               \"ANTHROPIC_API_KEY / GEMINI_API_KEY / NVIDIA_API_KEY / \"\\n               \"OPENROUTER_API_KEY / WECHAT_API_KEY); \"\\n               \"skipping article generation\")\\n@@ -178,7 +178,7 @@ def test_publication_detection_prompt_and_bundle():\\n         \"zh\": {\\n             \"title\": \"機場與航空公司營運更新\",\\n             \"summary\": \"主管機關說明航空公司與機場的最新營運狀況。\",\\n-            \"body\": [\"航空主管機關公布最新營運說明。\" * 12\\n+            \"body\": [\"航空主管機關發布最新營運說明。\" * 12\\n                      for _ in range(write.MIN_BODY_PARAGRAPHS)],\\n         },\\n         \"en\": {\\n@@ -195,7 +195,7 @@ def test_publication_detection_prompt_and_bundle():\\n             ],\\n         },\\n         \"flash\": {\\n-            \"zh\": \"主管機關公布航空營運更新。\",\\n+            \"zh\": \"主管機關發布航空營運更新。\",\\n             \"en\": \"The authority published an aviation operations update.\",\\n             \"hot\": False,\\n         },\\n@@ -342,6 +342,10 @@ def test_reasoning_tiers():\\n         \"nvidia\", \"nvidia/nemotron-3-ultra-550b-a55b\", \"deep\")[\"wire\"]\\n         == {\"reasoning_effort\": \"max\"},\\n         \"Nemotron Ultra deep maps to maximum reasoning\")\\n+    check(manual_reasoning_profile(\\n+        \"openai\", \"gpt-6-luna\", \"deep\")[\"wire\"]\\n+        == {\"reasoning\": {\"effort\": \"max\"}},\\n+        \"GPT-6 Luna deep maps to maximum reasoning\")\\n     check(manual_reasoning_profile(\\n         \"nvidia\", \"z-ai/glm-5.2\", \"deep\")[\"effective\"]\\n         == \"provider_default\",\\n@@ -359,9 +363,10 @@ def test_reasoning_tiers():\\n def test_openrouter_manual_provider_order():\\n     saved = {\\n         key: os.environ.get(key)\\n-        for key in (\"GEMINI_API_KEY\", \"NVIDIA_API_KEY\",\\n+        for key in (\"OPENAI_API_KEY\", \"GEMINI_API_KEY\", \"NVIDIA_API_KEY\",\\n                     \"OPENROUTER_API_KEY\", \"OPENCODE_API_KEY\")\\n     }\\n+    os.environ.pop(\"OPENAI_API_KEY\", None)\\n     os.environ.pop(\"GEMINI_API_KEY\", None)\\n     os.environ.pop(\"NVIDIA_API_KEY\", None)\\n     os.environ.pop(\"OPENCODE_API_KEY\", None)\\n@@ -384,10 +389,11 @@ def test_openrouter_manual_provider_order():\\n def test_opencode_manual_provider_order():\\n     saved = {\\n         key: os.environ.get(key)\\n-        for key in (\"GEMINI_API_KEY\", \"NVIDIA_API_KEY\",\\n+        for key in (\"OPENAI_API_KEY\", \"GEMINI_API_KEY\", \"NVIDIA_API_KEY\",\\n                     \"OPENROUTER_API_KEY\", \"OPENCODE_API_KEY\")\\n     }\\n-    for key in (\"GEMINI_API_KEY\", \"NVIDIA_API_KEY\", \"OPENROUTER_API_KEY\"):\\n+    for key in (\"OPENAI_API_KEY\", \"GEMINI_API_KEY\", \"NVIDIA_API_KEY\",\\n+                \"OPENROUTER_API_KEY\"):\\n         os.environ.pop(key, None)\\n     os.environ[\"OPENCODE_API_KEY\"] = \"oc_sk_test-not-a-real-key\"\\n     try:\\n@@ -405,6 +411,31 @@ def test_opencode_manual_provider_order():\\n           \"manual reasoning tier reaches every OpenCode model\")\\n \\n \\n+def test_openai_manual_provider_order():\\n+    saved = {\\n+        key: os.environ.get(key)\\n+        for key in (\"OPENAI_API_KEY\", \"GEMINI_API_KEY\", \"NVIDIA_API_KEY\",\\n+                    \"OPENROUTER_API_KEY\", \"OPENCODE_API_KEY\")\\n+    }\\n+    for key in (\"GEMINI_API_KEY\", \"NVIDIA_API_KEY\", \"OPENROUTER_API_KEY\",\\n+                \"OPENCODE_API_KEY\"):\\n+        os.environ.pop(key, None)\\n+    os.environ[\"OPENAI_API_KEY\"] = \"test-openai-key-not-real\"\\n+    try:\\n+        rows = manual_draft._providers_for(\\n+            manual_draft.validate_payload(sample_payload()))\\n+    finally:\\n+        for key, value in saved.items():\\n+            if value is None:\\n+                os.environ.pop(key, None)\\n+            else:\\n+                os.environ[key] = value\\n+    check([row.label for row in rows] == [\"openai:gpt-6-luna\"],\\n+          \"manual workbench exposes direct GPT-6 Luna selection\")\\n+    check(all(row.reasoning_tier == \"deep\" for row in rows),\\n+          \"manual default deep tier reaches GPT-6 Luna as MAX\")\\n+\\n+\\n class FakeProvider:\\n     def __init__(self, label, answer):\\n         self.label = label\\n@@ -696,6 +727,8 @@ def test_static_security_contract():\\n               / \"hourly.yml\").read_text(encoding=\"utf-8\")\\n     briefing = (ROOT / \".github\" / \"workflows\"\\n                 / \"briefing.yml\").read_text(encoding=\"utf-8\")\\n+    copilot = (ROOT / \".github\" / \"workflows\"\\n+               / \"copilot.yml\").read_text(encoding=\"utf-8\")\\n     check(\"noindex,nofollow,noarchive,nosnippet\" in html\\n           and \"Content-Security-Policy\" in html,\\n           \"private page is noindex and has a restrictive CSP\")\\n@@ -811,6 +844,7 @@ def test_static_security_contract():\\n         and \"actualUsd: 0\" in js,\\n         \"true manual mode records estimated tokens with zero actual API spend\")\\n     check(\"secrets.AVWIRE_MANUAL_TOKEN\" in workflow\\n+          and \"secrets.OPENAI_API\" in workflow\\n           and \"secrets.OPENROUTER_API_KEY\" in workflow\\n           and \"secrets.OPENCODE_API_KEY\" in workflow\\n           and \"secrets.DeepSeekV4Flash_API\" in workflow\\n@@ -821,7 +855,7 @@ def test_static_security_contract():\\n     configured_order = (\\n         f\"AVWIRE_PROVIDER_ORDER: {','.join(AUTOMATIC_MODEL_ORDER)}\")\\n     check(configured_order in hourly and configured_order in briefing,\\n-          \"automatic workflows use the proven production fallback order\")\\n+          \"automatic workflows use GPT-6 Luna and the configured fallbacks\")\\n     check(\"secrets.OPENROUTER_API_KEY\" in hourly\\n           and \"secrets.OPENROUTER_API_KEY\" in briefing\\n           and \"sk-or-v1-\" in hourly and \"sk-or-v1-\" in briefing,\\n@@ -830,6 +864,11 @@ def test_static_security_contract():\\n           and \"secrets.OPENCODE_API_KEY\" in briefing\\n           and \"oc_sk_\" in hourly and \"oc_sk_\" in briefing,\\n           \"automatic workflows receive and scan for the OpenCode secret\")\\n+    check(\"secrets.OPENAI_API\" in hourly\\n+          and \"secrets.OPENAI_API\" in briefing\\n+          and \"secrets.OPENAI_API\" in copilot\\n+          and \"secrets.OPENAI_API\" in workflow,\\n+          \"all model workflows map the OPENAI_API secret into runtime\")\\n \\n \\n def main():\\n@@ -842,6 +881,7 @@ def main():\\n         test_reasoning_tiers,\\n         test_openrouter_manual_provider_order,\\n         test_opencode_manual_provider_order,\\n+        test_openai_manual_provider_order,\\n         test_fallback_and_telemetry,\\n         test_translation_generate_path,\\n         test_manual_time_detection_generate_path,\\n@@ -139,6 +139,7 @@ def fake_post(url, json=None, timeout=None, headers=None):\\n \\n def test_model_priority_defaults():\\n     expected = (\\n+        \"openai:gpt-6-luna\",\\n         \"opencode:claude-sonnet-4-6\",\\n         \"opencode:gpt-5.5\",\\n         \"gemini:gemini-3.6-flash\",\\n@@ -171,6 +172,7 @@ def test_model_priority_defaults():\\n     check(providers.MODEL_ORDER == expected,\\n           \"model priority order matches the configured product order\")\\n     active = (\\n+        \"openai:gpt-6-luna\",\\n         \"gemini:gemini-3.6-flash\",\\n         \"gemini:gemini-3.5-flash\",\\n         \"nvidia:nvidia/nemotron-3-ultra-550b-a55b\",\\n@@ -180,9 +182,11 @@ def test_model_priority_defaults():\\n     )\\n     check(AUTOMATIC_MODEL_ORDER == active\\n           and providers.DEFAULT_ORDER == \",\".join(active),\\n-          \"automatic fallback default contains only proven live routes\")\\n+          \"GPT-6 Luna is primary and current production routes remain fallbacks\")\\n     check(providers.GEMINI_DEFAULT_MODEL == \"gemini-3.6-flash\",\\n           \"Gemini 3.6 Flash is the default model\")\\n+    check(providers.OPENAI_DEFAULT_MODEL == \"gpt-6-luna\",\\n+          \"GPT-6 Luna is the direct OpenAI default model\")\\n     check(providers.NVIDIA_DEFAULT_MODEL\\n           == \"nvidia/nemotron-3-ultra-550b-a55b\",\\n           \"Nemotron Ultra is the highest-priority NVIDIA default\")\\n@@ -193,11 +197,11 @@ def test_model_priority_defaults():\\n           \"Claude Sonnet 4.6 is the highest-priority OpenCode default\")\\n     check(providers.WECHAT_DEFAULT_MODEL == \"Deepseek-v4-flash\",\\n           \"DeepSeek V4 Flash is the WeChat Coding Plan default\")\\n-    check(expected[10:13] == (\\n+    check(expected[11:14] == (\\n         \"nvidia:nvidia/nemotron-3-ultra-550b-a55b\",\\n         \"wechat:Deepseek-v4-flash\",\\n         \"openrouter:nvidia/nemotron-3-ultra-550b-a55b:free\",\\n-    ) and expected[14:16] == (\\n+    ) and expected[15:17] == (\\n         \"nvidia:nvidia/nemotron-3-super-120b-a12b\",\\n         \"openrouter:nvidia/nemotron-3-super-120b-a12b:free\",\\n     ), \"complete catalog preserves owner model order\")\\n@@ -532,6 +536,85 @@ def fake_post(url, json=None, timeout=None, headers=None):\\n     print(\"test_opencode_protocols_and_payloads: done\")\\n \\n \\n+def test_openai_responses_api():\\n+    \"\"\"Direct OpenAI uses MAX reasoning and strict Responses JSON output.\"\"\"\\n+    saved = {\\n+        key: os.environ.get(key)\\n+        for key in (\"OPENAI_API_KEY\", \"AVWIRE_PROVIDER_ORDER\",\\n+                    \"AVWIRE_OPENAI_MODEL\")\\n+    }\\n+    os.environ.pop(\"OPENAI_API_KEY\", None)\\n+    provider = providers.OpenAIProvider()\\n+    check(not provider.available(), \"OpenAI is unavailable without its key\")\\n+    os.environ[\"OPENAI_API_KEY\"] = \"test-openai-key-not-real\"\\n+    os.environ[\"AVWIRE_PROVIDER_ORDER\"] = \"openai:gpt-6-luna\"\\n+    try:\\n+        provider = providers.OpenAIProvider()\\n+        captured = []\\n+\\n+        def fake_post(url, json=None, timeout=None, headers=None):\\n+            captured.append((url, json, headers))\\n+            return FakeResponse({\\n+                \"status\": \"completed\",\\n+                \"output_text\": GOOD,\\n+                \"usage\": {\"input_tokens\": 41, \"output_tokens\": 17},\\n+            }, headers={\"x-request-id\": \"req-openai-safe\",\\n+                        \"authorization\": \"must-not-be-captured\"})\\n+\\n+        result = with_post(\\n+            fake_post, lambda: provider.draft(\"sys\", \"user\", SCHEMA))\\n+        url, payload, headers = captured[0]\\n+        check(provider.available() and result == {\"ok\": 1},\\n+              \"OpenAI Responses draft succeeds with configured key\")\\n+        check(url == \"https://api.openai.com/v1/responses\"\\n+              and payload[\"model\"] == \"gpt-6-luna\",\\n+              \"GPT-6 Luna uses the direct OpenAI Responses endpoint\")\\n+        check(payload[\"reasoning\"] == {\"effort\": \"max\"}\\n+              and provider.reasoning_effective == \"reasoning.effort=max\",\\n+              \"automatic GPT-6 Luna calls request MAX reasoning\")\\n+        check(payload[\"max_output_tokens\"] == 48_000,\\n+              \"MAX reasoning has an output cap that reserves reasoning room\")\\n+        check(payload[\"text\"][\"format\"] == {\\n+                  \"type\": \"json_schema\", \"name\": \"skytical_draft\",\\n+                  \"schema\": SCHEMA, \"strict\": True,\\n+              } and headers[\"Authorization\"]\\n+              == \"Bearer test-openai-key-not-real\",\\n+              \"OpenAI sends strict schema output with bearer authentication\")\\n+        check(provider.usage == {\\n+                  \"inputTokens\": 41, \"outputTokens\": 17,\\n+                  \"usageEvents\": 1,\\n+              } and provider.request_log[0][\"headers\"]\\n+              == {\"x-request-id\": \"req-openai-safe\"},\\n+              \"usage is counted and credentials are excluded from telemetry\")\\n+        try:\\n+            provider._check_status(FakeResponse({}, status=429))\\n+            check(False, \"OpenAI rate-limit 429 should request provider fallback\")\\n+        except providers.ProviderQuotaError:\\n+            check(False, \"OpenAI rate limit must not disable its provider\")\\n+        except providers.ProviderError:\\n+            check(True, \"OpenAI rate limit preserves provider availability\")\\n+        try:\\n+            provider._check_status(FakeResponse(\\n+                {\"error\": {\"code\": \"insufficient_quota\"}}, status=429))\\n+            check(False, \"OpenAI insufficient quota should disable provider\")\\n+        except providers.ProviderQuotaError:\\n+            check(True, \"OpenAI insufficient quota disables this provider run\")\\n+        rows = providers.build_providers()\\n+        check([row.label for row in rows] == [\"openai:gpt-6-luna\"],\\n+              \"provider registry builds the configured OpenAI model\")\\n+        deep = providers.OpenAIProvider(\\n+            \"gpt-6-luna\", reasoning_tier=\"deep\")\\n+        check(deep._effort(repair=False) == \"max\",\\n+              \"manual deep reasoning maps to OpenAI MAX\")\\n+    finally:\\n+        for key, value in saved.items():\\n+            if value is None:\\n+                os.environ.pop(key, None)\\n+            else:\\n+                os.environ[key] = value\\n+    print(\"test_openai_responses_api: done\")\\n+\\n+\\n def test_nvidia_reasoning_trace_never_parsed():\\n     \"\"\"reasoning_content is ignored; empty final content is a failure.\"\"\"\\n     def fake_post(url, json=None, timeout=None, headers=None):\\n@@ -676,6 +759,7 @@ def main():\\n         test_model_priority_defaults,\\n         test_wechat_protocol_and_payloads,\\n         test_opencode_protocols_and_payloads,\\n+        test_openai_responses_api,\\n         test_openrouter_models_and_payloads,\\n         test_nvidia_model_profiles,\\n         test_nvidia_reasoning_trace_never_parsed,\\n@@ -385,6 +385,13 @@ def json(self):\\n check(\"GPT-5.6 Sol API usage would use the official $5/$30 rates\",\\n       gpt_api_rows[0][\"usd\"] == 35.0\\n       and gpt_api_rows[0][\"kind\"] == \"reference\")\\n+gpt6_rows, _ = build.usage_rows(\\n+    {\"models\": {\"openai:gpt-6-luna\":\\n+                {\"calls\": 1, \"inputTokens\": 1_000_000,\\n+                 \"outputTokens\": 1_000_000, \"unknownCalls\": 0}}}, prices)\\n+check(\"GPT-6 Luna API usage uses the official $0.10/$0.50 rates\",\\n+      gpt6_rows[0][\"usd\"] == 0.6\\n+      and gpt6_rows[0][\"kind\"] == \"reference\")\\n check(\"totals aggregate across models\",\\n       totals[\"calls\"] == 8 and totals[\"usd\"] > 0)\\n rows2, _ = build.usage_rows(\\n@@ -485,8 +485,8 @@ def test_no_api_key_end_to_end():\\n     pending_before = (tmp2 / \"pending.json\").read_bytes()\\n \\n     env = dict(os.environ)\\n-    for key in (\"ANTHROPIC_API_KEY\", \"GEMINI_API_KEY\", \"NVIDIA_API_KEY\",\\n-                \"OPENROUTER_API_KEY\", \"OPENCODE_API_KEY\"):\\n+    for key in (\"OPENAI_API_KEY\", \"ANTHROPIC_API_KEY\", \"GEMINI_API_KEY\",\\n+                \"NVIDIA_API_KEY\", \"OPENROUTER_API_KEY\", \"OPENCODE_API_KEY\"):\\n         env.pop(key, None)\\n     env[\"AVWIRE_DATA_DIR\"] = str(tmp2)\\n     env[\"PYTHONIOENCODING\"] = \"utf-8\"\\n@@ -751,7 +751,8 @@ def test_model_chain_and_routing_policy():\\n     \"\"\"Model order, run-local platform circuits and primary retry policy.\"\"\"\\n     # --- order tokens: same platform may appear with different models -----\\n     saved = {k: os.environ.get(k) for k in (\\n-        \"AVWIRE_PROVIDER_ORDER\", \"NVIDIA_API_KEY\", \"GEMINI_API_KEY\",\\n+        \"AVWIRE_PROVIDER_ORDER\", \"OPENAI_API_KEY\", \"NVIDIA_API_KEY\",\\n+        \"GEMINI_API_KEY\",\\n         \"OPENROUTER_API_KEY\", \"OPENCODE_API_KEY\", \"ANTHROPIC_API_KEY\")}\\n     os.environ[\"AVWIRE_PROVIDER_ORDER\"] = (\\n         \"nvidia:nvidia/nemotron-3-ultra-550b-a55b, \"\\n@@ -762,6 +763,7 @@ def test_model_chain_and_routing_policy():\\n     os.environ[\"GEMINI_API_KEY\"] = \"test-key-not-real\"\\n     os.environ[\"OPENROUTER_API_KEY\"] = \"test-key-not-real\"\\n     os.environ.pop(\"OPENCODE_API_KEY\", None)\\n+    os.environ.pop(\"OPENAI_API_KEY\", None)\\n     os.environ.pop(\"ANTHROPIC_API_KEY\", None)\\n     try:\\n         labels = [p.label for p in providers.build_providers()]"
}
