[Provider compatibility] ollama-native rejects Codex exec histories with more than one output per call, breaking routed compaction

クローズ
#6,574 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
75/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
ollama, typescript
領域
api

調査の方向性

Start with buildNativeMessages() in the ollama-native adapter and compare how the anthropic and google adapters handle extra or late tool results. Run the provided offline reproduction and review the three existing tests named in the issue that encode the current refusal. Done means previously issued calls with additional or late outputs no longer cause the request to fail, while results for unknown call IDs can remain errors.

索引モデルが issue の本文から書いたものです。

説明

provider provider-compatibility
Client or integration

Codex App

Provider or upstream service

ollama-cloud (provider adapter: "ollama-native"), observed with ollama-cloud/deepseek-v4.1-flash. The failing function serves every routed Ollama model.

OpenCodex version

Observed on 2.75.0. Reproduced offline on dev 33185c2cc (2.78.0).

Endpoint or capability

POST /v1/responses: remote compaction turns routed by compactionRouting, and any ordinary routed turn that replays Codex code-mode exec history.

Current behaviour

One Codex code-mode exec call can leave more than one custom_tool_call_output with the same call_id in the history:

  • When the cell outlives yield_time_ms, Codex records the yield marker (Script running with cell ID N ...), and each notify() call adds another output for the same call_id.
  • A still-running cell can record its notify() output after later items, for example after a wait function call and its function_call_output.

buildNativeMessages() rejects both shapes before anything is sent upstream (400, failureStage: headers-only):

  • adjacent outputs: ollama-native duplicate tool result for <id>
  • a late output after another call's batch: ollama-native tool result <id> has no originating call

The damage is largest with compactionRouting. When compaction for every session goes to an Ollama Cloud model, a session whose history contains such an exec call can no longer compact. Codex reports Error running remote compact task, and a retry fails the same way because the history does not change. On one installation, 3 of 49 routed compaction attempts over two days failed like this. The other 46 succeeded, with inputs of 340k to 590k tokens.

The anthropic, google and openai-chat adapters build a request from the same parsed history. Anthropic and Google pair the first result with its call and carry the extra outputs as explicit orphan-result text.

Expected behaviour

ollama-native should not fail a request just because Codex recorded more than one output for one exec call. Two acceptable behaviours:

  • Merge additional outputs for a call into its result, as #3750 did for Kiro in the adjacent case.
  • Keep the first result paired and carry extra or late outputs as explicit user-side text, which is what the anthropic and google adapters already do.

A result whose call id never appeared in the history can stay an error if that is the intended policy. A late or additional output for a call that did appear earlier should not fail the request.

Minimal redacted request or reproduction

Run from the repository root on dev (no network, no credentials):

import { parseRequest } from "./src/responses/parser";
import { createOllamaNativeAdapter } from "./src/adapters/ollama-native";
import { createAnthropicAdapter } from "./src/adapters/anthropic";

const user = (text: string) => ({ type: "message", role: "user", content: [{ type: "input_text", text }] });
const execCall = (id: string) => ({ type: "custom_tool_call", call_id: id, name: "exec", input: "notify('progress')" });
const execOut = (id: string, output: string) => ({ type: "custom_tool_call_output", call_id: id, output });
const waitCall = (id: string) => ({ type: "function_call", call_id: id, name: "wait", arguments: JSON.stringify({ cell_id: "31" }) });
const waitOut = (id: string, output: string) => ({ type: "function_call_output", call_id: id, output });
const tools = [
  { type: "custom", name: "exec", description: "Run JavaScript" },
  { type: "function", name: "wait", description: "Wait on a yielded exec cell",
    parameters: { type: "object", properties: { cell_id: { type: "string" } }, required: ["cell_id"] } },
];
const yieldMarker = "Script running with cell ID 31\nWall time 31.0 seconds\nOutput:\n";
const shapes: Record<string, unknown[]> = {
  control: [user("start"), execCall("call_A"), execOut("call_A", "done"), user("continue")],
  adjacent: [user("start"), execCall("call_A"), execOut("call_A", yieldMarker), execOut("call_A", "progress 1"), user("continue")],
  late: [user("start"), execCall("call_A"), execOut("call_A", yieldMarker),
    waitCall("call_B"), waitOut("call_B", "Script completed\nOutput:\n"), execOut("call_A", "progress 2"), user("continue")],
};
const base = { authMode: "key", apiKey: "test-key", liveModels: false, models: ["deepseek-v4.1-flash"] } as any;
for (const [name, input] of Object.entries(shapes)) {
  const parsed = parseRequest({ model: "deepseek-v4.1-flash", stream: true, input, tools });
  for (const [label, adapter] of [
    ["ollama-native", createOllamaNativeAdapter({ ...base, adapter: "ollama-native", baseUrl: "https://ollama.com/v1" })],
    ["anthropic", createAnthropicAdapter({ ...base, adapter: "anthropic", baseUrl: "https://api.anthropic.com" })],
  ] as const) {
    try { await adapter.buildRequest(structuredClone(parsed)); console.log(name, label, "ok"); }
    catch (e: any) { console.log(name, label, "THROWS", e.message); }
  }
}

Output on 33185c2cc. The google and openai-chat adapters also return ok for every shape.

control ollama-native ok
control anthropic ok
adjacent ollama-native THROWS ollama-native duplicate tool result for call_A
adjacent anthropic ok
late ollama-native THROWS ollama-native tool result call_A has no originating call
late anthropic ok

The real history that failed had both shapes in one call: yield marker, notify() output, a wait call and result, then another notify() output for the first call.

Actual response or error
Error running remote compact task: {"error":{"message":"ollama-native duplicate tool result for toolu_<redacted>","type":"invalid_request_error","code":"invalid_request_error"}}

Usage log for the routed attempt: status 400, adapter ollama-native, failureStage headers-only, failureCause payload-rejected, sendCount 0.

Upstream documentation

No public Ollama specification covers more than one tool message per tool_call_id. The concrete client requirement is the Codex code-mode exec output sequence (notify() and yielded cells), the same requirement as #3734.

Suggested mapping or implementation notes

In buildNativeMessages():

  • A toolResult for a call in the open batch that already has a result: append its content to that result in input order and keep error status if any part is an error (the #3750 rule).
  • A toolResult whose call id was issued in an earlier, already settled batch: emit it as explicit user-side text with a marker, as the anthropic adapter does ([tool_result without adjacent tool_use: ...]), instead of throwing.
  • Keep the error for ids that never appeared in the history, if that policy should stay.

The existing tests a duplicate result for the same call is still refused, duplicate results remain rejected while commentary holds an unresolved batch and a result cannot cross a subsequent tool-call batch encode the current refusal, so this is a deliberate policy change for those cases.

Additional context and attachments
  • #3731 / #3734: the same Codex shape on Kiro. #3750 fixed adjacent outputs there; a late output after another call's result is still rejected by that rule.
  • #4842: an ollama-native replay-ordering fix for injected developer messages.
  • Workaround in use: avoid notify() in code-mode cells, or route compaction to a provider whose adapter accepts this history.
Checks
  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.
主要言語
TypeScript
スター
16.9k
フォーク
1.3k
平均マージ
16時間 44分
マージ済み PR(30日)
689

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

lidge-jun/opencodex のほかの issue

lidge-jun/opencodex の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。