mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Add a partial answer message phase (#51241)
## Why Assistant answer text may be followed by more output or tools. A distinct `partial_answer` phase lets callers represent this separately from commentary and a terminal `final_answer`. ## What changed - Add `MessagePhase::PartialAnswer` and expose `partial_answer` in the app-server schemas and generated TypeScript and Python types. - Keep the TUI working indicator visible after partial answers and exclude them from the thread history's final-answer summary field. - Route partial answers to the speakable channel in realtime V3 BEM-tag mode. ## Testing Add coverage for phase preservation through serialization, input-message conversion, and SSE item start/completion events. Extend TUI snapshot coverage to verify that partial answers keep the working indicator visible while final answers hide it. GitOrigin-RevId: 1ae3ad9264b6e36ae1cbe6b93df3d345ab4460e6
This commit is contained in:
+9
-2
@@ -2213,7 +2213,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -2223,7 +2223,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -3015,7 +3015,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -3025,7 +3025,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
+9
-2
@@ -15361,7 +15361,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -15371,7 +15371,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
+9
-2
@@ -11373,7 +11373,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -11383,7 +11383,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
+9
-2
@@ -650,7 +650,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -660,7 +660,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -650,7 +650,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -660,7 +660,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
+9
-2
@@ -389,7 +389,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -399,7 +399,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -798,7 +798,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -808,7 +808,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -902,7 +902,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -912,7 +912,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -650,7 +650,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -660,7 +660,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -824,7 +824,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -834,7 +834,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
+9
-2
@@ -824,7 +824,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -834,7 +834,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -824,7 +824,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -834,7 +834,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -451,7 +451,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -461,7 +461,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -918,7 +918,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -928,7 +928,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -824,7 +824,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -834,7 +834,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -902,7 +902,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -912,7 +912,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
+9
-2
@@ -824,7 +824,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -834,7 +834,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -798,7 +798,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -808,7 +808,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -824,7 +824,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -834,7 +834,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
+9
-2
@@ -798,7 +798,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -808,7 +808,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -798,7 +798,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -808,7 +808,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
@@ -798,7 +798,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"description": "Classifies assistant text as commentary, a partial answer, or a terminal answer.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
|
||||
@@ -808,7 +808,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's terminal answer text for the current turn.",
|
||||
"description": "Stable answer text that may be followed by more assistant output or tools.",
|
||||
"enum": [
|
||||
"partial_answer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The assistant's declared terminal answer text for the current turn.",
|
||||
"enum": [
|
||||
"final_answer"
|
||||
],
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -3,9 +3,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Classifies an assistant message as interim commentary or final answer text.
|
||||
* Classifies assistant text as commentary, a partial answer, or a terminal answer.
|
||||
*
|
||||
* Providers do not emit this consistently, so callers must treat `None` as
|
||||
* "phase unknown" and keep compatibility behavior for legacy models.
|
||||
*/
|
||||
export type MessagePhase = "commentary" | "final_answer";
|
||||
export type MessagePhase = "commentary" | "partial_answer" | "final_answer";
|
||||
|
||||
@@ -734,6 +734,46 @@ mod tests {
|
||||
Duration::from_millis(1000)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn partial_answer_phase_survives_item_start_and_completion() {
|
||||
let item = json!({
|
||||
"id": "msg_partial",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [],
|
||||
"phase": "partial_answer"
|
||||
});
|
||||
let added = json!({"type": "response.output_item.added", "item": item});
|
||||
let delta = json!({"type": "response.output_text.delta", "delta": "First result."});
|
||||
let mut done_item = item;
|
||||
done_item["content"] = json!([{"type": "output_text", "text": "First result."}]);
|
||||
let done = json!({"type": "response.output_item.done", "item": done_item});
|
||||
let completed = json!({
|
||||
"type": "response.completed",
|
||||
"response": {"id": "resp1", "end_turn": false}
|
||||
});
|
||||
let stream = [added, delta, done, completed]
|
||||
.into_iter()
|
||||
.map(|event| format!("data: {event}\n\n"))
|
||||
.collect::<String>();
|
||||
|
||||
let events = collect_events(&[stream.as_bytes()]).await;
|
||||
assert_matches!(events.as_slice(), [
|
||||
Ok(ResponseEvent::OutputItemAdded(ResponseItem::Message {
|
||||
phase: Some(MessagePhase::PartialAnswer), content: start_content, ..
|
||||
})),
|
||||
Ok(ResponseEvent::OutputTextDelta(text)),
|
||||
Ok(ResponseEvent::OutputItemDone(ResponseItem::Message {
|
||||
phase: Some(MessagePhase::PartialAnswer), content: done_content, ..
|
||||
})),
|
||||
Ok(ResponseEvent::Completed { end_turn: Some(false), .. }),
|
||||
] if start_content.is_empty()
|
||||
&& text == "First result."
|
||||
&& done_content == &vec![codex_protocol::models::ContentItem::OutputText {
|
||||
text: "First result.".to_string()
|
||||
}]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn parses_items_and_completed() {
|
||||
let item1 = json!({
|
||||
|
||||
@@ -928,6 +928,8 @@ impl RealtimeConversationManager {
|
||||
if handoff.client_managed_handoffs {
|
||||
return Ok(());
|
||||
}
|
||||
// TODO: Preserve explicit PartialAnswer phases through completed and streamed BEM
|
||||
// routing, with integration coverage asserting the speakable channel.
|
||||
let phase = if handoff.routes_handoff_by_bem() {
|
||||
match bem_message_phase(
|
||||
&output_text,
|
||||
@@ -942,6 +944,8 @@ impl RealtimeConversationManager {
|
||||
} else {
|
||||
phase
|
||||
};
|
||||
// TODO: Treat PartialAnswer as nonterminal for V1 handoffs, omitting the final-message
|
||||
// prefix for streamed, completed-item, and standalone output.
|
||||
let is_commentary = matches!(phase, Some(MessagePhase::Commentary));
|
||||
let active_handoff = handoff.stream.lock().await.active_handoff.clone();
|
||||
let output = match active_handoff {
|
||||
@@ -2272,7 +2276,9 @@ fn v3_output_writer(
|
||||
CodexResponseHandoffMode::Thinking => None,
|
||||
CodexResponseHandoffMode::Commentary => Some(RealtimeContextAppendChannel::Commentary),
|
||||
CodexResponseHandoffMode::BemTags => match phase {
|
||||
Some(MessagePhase::FinalAnswer) => Some(RealtimeContextAppendChannel::Speakable),
|
||||
Some(MessagePhase::PartialAnswer | MessagePhase::FinalAnswer) => {
|
||||
Some(RealtimeContextAppendChannel::Speakable)
|
||||
}
|
||||
Some(MessagePhase::Commentary) => Some(RealtimeContextAppendChannel::Commentary),
|
||||
None => Some(RealtimeContextAppendChannel::Speakable),
|
||||
},
|
||||
|
||||
@@ -2750,6 +2750,8 @@ async fn try_run_sampling_request(
|
||||
cancellation_token: cancellation_token.child_token(),
|
||||
};
|
||||
|
||||
// TODO: Allow mailbox preemption after PartialAnswer and keep delivery open
|
||||
// in the completed-item deferral predicates.
|
||||
let preempt_for_mailbox_mail = match &item {
|
||||
ResponseItem::Message { role, phase, .. } => {
|
||||
role == "assistant" && matches!(phase, Some(MessagePhase::Commentary))
|
||||
|
||||
@@ -937,7 +937,7 @@ pub const DEFAULT_IMAGE_DETAIL: ImageDetail = ImageDetail::High;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// Classifies an assistant message as interim commentary or final answer text.
|
||||
/// Classifies assistant text as commentary, a partial answer, or a terminal answer.
|
||||
///
|
||||
/// Providers do not emit this consistently, so callers must treat `None` as
|
||||
/// "phase unknown" and keep compatibility behavior for legacy models.
|
||||
@@ -947,7 +947,9 @@ pub enum MessagePhase {
|
||||
/// Additional tool calls or assistant output may follow before turn
|
||||
/// completion.
|
||||
Commentary,
|
||||
/// The assistant's terminal answer text for the current turn.
|
||||
/// Stable answer text that may be followed by more assistant output or tools.
|
||||
PartialAnswer,
|
||||
/// The assistant's declared terminal answer text for the current turn.
|
||||
FinalAnswer,
|
||||
}
|
||||
|
||||
@@ -2518,27 +2520,38 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_input_message_conversion_preserves_phase() {
|
||||
let item = ResponseItem::from(ResponseInputItem::Message {
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "still working".to_string(),
|
||||
}],
|
||||
phase: Some(MessagePhase::Commentary),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::Message {
|
||||
fn response_input_message_conversion_preserves_phase() -> Result<()> {
|
||||
for phase in [
|
||||
None,
|
||||
Some(MessagePhase::Commentary),
|
||||
Some(MessagePhase::PartialAnswer),
|
||||
Some(MessagePhase::FinalAnswer),
|
||||
] {
|
||||
let input = ResponseInputItem::Message {
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "answer text".to_string(),
|
||||
}],
|
||||
phase: phase.clone(),
|
||||
};
|
||||
let wire = serde_json::to_value(input)?;
|
||||
let item = ResponseItem::from(serde_json::from_value::<ResponseInputItem>(wire)?);
|
||||
let expected = ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "still working".to_string(),
|
||||
text: "answer text".to_string(),
|
||||
}],
|
||||
phase: Some(MessagePhase::Commentary),
|
||||
phase,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}
|
||||
);
|
||||
};
|
||||
assert_eq!(item, expected);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<ResponseItem>(serde_json::to_value(item)?)?,
|
||||
expected
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -520,7 +520,7 @@ WHERE thread_id = ?
|
||||
.map_err(thread_history_error)?;
|
||||
}
|
||||
ThreadItem::AgentMessage {
|
||||
phase: Some(MessagePhase::Commentary) | None,
|
||||
phase: Some(MessagePhase::Commentary | MessagePhase::PartialAnswer) | None,
|
||||
..
|
||||
}
|
||||
| ThreadItem::HookPrompt { .. }
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/status_and_layout.rs
|
||||
expression: normalize_snapshot_paths(terminal.backend().vt100().screen().contents())
|
||||
---
|
||||
|
||||
|
||||
• 1
|
||||
|
||||
• Working (0s • esc to interrupt)
|
||||
|
||||
|
||||
› Ask Codex to do anything
|
||||
|
||||
gpt-5 default · /tmp/project
|
||||
@@ -482,7 +482,7 @@ impl ChatWidget {
|
||||
Some(MessagePhase::FinalAnswer) | None => {
|
||||
!self.input_queue.pending_steers.is_empty()
|
||||
}
|
||||
Some(MessagePhase::Commentary) => true,
|
||||
Some(MessagePhase::Commentary | MessagePhase::PartialAnswer) => true,
|
||||
};
|
||||
self.maybe_restore_status_indicator_after_stream_idle();
|
||||
}
|
||||
|
||||
@@ -2205,42 +2205,51 @@ async fn streaming_final_answer_keeps_task_running_state() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn single_line_final_answer_hides_working_status_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
async fn answer_phase_controls_working_status_snapshot() {
|
||||
for (phase, working_visible, snapshot) in [
|
||||
(
|
||||
MessagePhase::FinalAnswer,
|
||||
false,
|
||||
"single_line_final_answer_hides_working_status",
|
||||
),
|
||||
(
|
||||
MessagePhase::PartialAnswer,
|
||||
true,
|
||||
"single_line_partial_answer_keeps_working_status",
|
||||
),
|
||||
] {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
|
||||
complete_user_message(&mut chat, "user-1", "count to 1");
|
||||
chat.on_task_started();
|
||||
complete_assistant_message(
|
||||
&mut chat,
|
||||
"msg-final-single-line",
|
||||
"1",
|
||||
Some(MessagePhase::FinalAnswer),
|
||||
);
|
||||
complete_user_message(&mut chat, "user-1", "count to 1");
|
||||
chat.on_task_started();
|
||||
complete_assistant_message(&mut chat, "msg-final-single-line", "1", Some(phase));
|
||||
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
assert!(!chat.bottom_pane.status_indicator_visible());
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
assert_eq!(chat.bottom_pane.status_indicator_visible(), working_visible);
|
||||
|
||||
let width: u16 = 40;
|
||||
let vt_height: u16 = 10;
|
||||
let ui_height = chat.desired_height(width);
|
||||
let viewport = Rect::new(0, vt_height - ui_height - 1, width, ui_height);
|
||||
let backend = VT100Backend::new(width, vt_height);
|
||||
let mut terminal = crate::custom_terminal::Terminal::with_options(backend).expect("terminal");
|
||||
terminal.set_viewport_area(viewport);
|
||||
let width: u16 = 40;
|
||||
let vt_height: u16 = 10;
|
||||
let ui_height = chat.desired_height(width);
|
||||
let viewport = Rect::new(0, vt_height - ui_height - 1, width, ui_height);
|
||||
let backend = VT100Backend::new(width, vt_height);
|
||||
let mut terminal =
|
||||
crate::custom_terminal::Terminal::with_options(backend).expect("terminal");
|
||||
terminal.set_viewport_area(viewport);
|
||||
|
||||
for lines in drain_insert_history(&mut rx) {
|
||||
crate::insert_history::insert_history_lines(&mut terminal, lines)
|
||||
.expect("insert history lines");
|
||||
for lines in drain_insert_history(&mut rx) {
|
||||
crate::insert_history::insert_history_lines(&mut terminal, lines)
|
||||
.expect("insert history lines");
|
||||
}
|
||||
|
||||
terminal
|
||||
.draw(|frame| chat.render(frame.area(), frame.buffer_mut()))
|
||||
.expect("draw final answer");
|
||||
assert_chatwidget_snapshot!(
|
||||
snapshot,
|
||||
normalize_snapshot_paths(terminal.backend().vt100().screen().contents())
|
||||
);
|
||||
}
|
||||
|
||||
terminal
|
||||
.draw(|frame| chat.render(frame.area(), frame.buffer_mut()))
|
||||
.expect("draw final answer");
|
||||
assert_chatwidget_snapshot!(
|
||||
"single_line_final_answer_hides_working_status",
|
||||
normalize_snapshot_paths(terminal.backend().vt100().screen().contents())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -2750,6 +2750,7 @@ class MergeStrategy(Enum):
|
||||
|
||||
class MessagePhase(Enum):
|
||||
commentary = "commentary"
|
||||
partial_answer = "partial_answer"
|
||||
final_answer = "final_answer"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user