@ego-z/contracts 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ego-z/contracts",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Wire-format type contracts shared between EgoZ backend, SDK, MCP and console. Type-only — no runtime artifacts.",
5
5
  "types": "./src/index.d.ts",
6
6
  "exports": {
package/src/ask.d.ts CHANGED
@@ -157,6 +157,28 @@ export interface AskRequestBody {
157
157
  export interface AskStreamRequestBody extends AskRequestBody {
158
158
  stream?: true | 'minimal';
159
159
 
160
+ /**
161
+ * How `delta.phase` is decided — orthogonal to `stream`, which controls
162
+ * *which* events are emitted rather than *when* text is released.
163
+ *
164
+ * - `'buffered'` (default when omitted) → an iteration's text is held
165
+ * until its phase is known, so every delta lands correctly labelled.
166
+ * The terminal answer arrives as a single delta.
167
+ * - `'live'` → text streams token-by-token as `phase: 'pending'`,
168
+ * and an `iteration.end` event classifies the block once the
169
+ * iteration closes.
170
+ *
171
+ * Omit it and the wire is byte-identical to a pre-0.14.0 backend — this
172
+ * flag is the only thing that can introduce `'pending'` deltas or
173
+ * `iteration.end` events, so existing consumers cannot encounter either.
174
+ *
175
+ * Prefer the SDK's `askStream(request, { live: true })` option over
176
+ * setting this by hand: an SDK old enough to lack the option is also
177
+ * old enough to lack the reducer support, and routing it through the
178
+ * option makes that version skew impossible to express.
179
+ */
180
+ phaseMode?: 'buffered' | 'live';
181
+
160
182
  /**
161
183
  * Opt-in deep-execution trace for the Console "Your Agent" playground.
162
184
  *
package/src/stream.d.ts CHANGED
@@ -26,6 +26,7 @@ export type AskStreamEvent =
26
26
  | AskStreamRagSearchingEvent
27
27
  | AskStreamRagRetrievedEvent
28
28
  | AskStreamDeltaEvent
29
+ | AskStreamIterationEndEvent
29
30
  | AskStreamToolCallingEvent
30
31
  | AskStreamToolResultEvent
31
32
  | AskStreamLLMDebugEvent
@@ -55,6 +56,67 @@ export interface AskStreamDeltaEvent {
55
56
  type: 'delta';
56
57
  /** Token (or token-like fragment) appended to the running answer. */
57
58
  content: string;
59
+ /**
60
+ * Whether this delta is part of the model's pre-tool narration
61
+ * ('thinking' — text emitted by an iteration that also asked for a
62
+ * tool call) or the terminal reply ('final' — the last iteration's
63
+ * text, no further tool calls follow).
64
+ *
65
+ * Consumers can use this to render two channels: a secondary
66
+ * "thinking out loud" strip and the primary answer body.
67
+ *
68
+ * **Buffered mode (default).** An iteration's text is held until its
69
+ * nature is known, so the label is always correct when it lands: a
70
+ * 'thinking' iteration streams live token-by-token once its first
71
+ * tool_call arrives, and the 'final' delta is flushed as one event at
72
+ * iteration end. The trade is that the terminal answer arrives whole
73
+ * rather than typing out.
74
+ *
75
+ * **Live mode** (`AskStreamRequestBody.phaseMode: 'live'`). Every text
76
+ * delta streams immediately as `'pending'` and the enclosing block is
77
+ * classified by the `iteration.end` event that closes it. No delta is
78
+ * ever re-labelled — the text a reader has seen never changes, only
79
+ * the container around it resolves. This is materially different from
80
+ * the post-hoc *correction* design that was rejected: that one asserted
81
+ * a phase and later retracted it; this one asserts nothing until it
82
+ * knows.
83
+ *
84
+ * Optional so a consumer pinned to an older contract version still
85
+ * parses the frame. **Absent means `'final'`** — that is the legacy
86
+ * reading for a backend predating this field, and it is why an
87
+ * undecided delta must carry `'pending'` explicitly rather than
88
+ * omitting the field: `askStreamReducer` is pure and never sees the
89
+ * request, so it cannot otherwise tell "old backend" from "not yet
90
+ * decided".
91
+ */
92
+ phase?: 'thinking' | 'final' | 'pending';
93
+ }
94
+
95
+ /**
96
+ * Closes one orchestrator iteration and classifies the block of `'pending'`
97
+ * deltas it produced. **Live mode only** — buffered streams never emit it,
98
+ * so a consumer that does not opt in sees a byte-identical wire.
99
+ *
100
+ * Emitted at iteration end, *before* any `tool.calling` for that iteration,
101
+ * so a consumer's timeline always settles before the next thing moves.
102
+ *
103
+ * `phase` is authoritative: it reports whether the orchestrator is actually
104
+ * going on to dispatch tools (`'thinking'`) or this was the terminal answer
105
+ * (`'final'`). It is derived from the same condition that drives the loop,
106
+ * not from a guess made while the text was still arriving.
107
+ */
108
+ export interface AskStreamIterationEndEvent {
109
+ type: 'iteration.end';
110
+ /** Zero-based index in the orchestrator's tool loop. */
111
+ iteration: number;
112
+ /** What the just-closed block of `'pending'` deltas turned out to be. */
113
+ phase: 'thinking' | 'final';
114
+ /**
115
+ * Wall-clock duration of this iteration's LLM call, measured server-side.
116
+ * Lets a consumer render "Thought for 1.2s" from the real number rather
117
+ * than timing arrival, which would include transport and buffering.
118
+ */
119
+ durationMs: number;
58
120
  }
59
121
 
60
122
  export interface AskStreamToolCallingEvent {