@ego-z/contracts 0.15.9 → 0.15.11
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 +1 -1
- package/src/ask.d.ts +36 -1
- package/src/draft-conv.d.ts +64 -3
- package/src/index.d.ts +4 -0
- package/src/questions.d.ts +120 -0
- package/src/stream.d.ts +37 -0
package/package.json
CHANGED
package/src/ask.d.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Intent, JsonSchemaDefinition, ResponseFormat, TokenUsage } from './envelope';
|
|
9
|
-
import type { InitContextItem, InitContextUi } from './draft-conv';
|
|
9
|
+
import type { InitContextItem, InitContextItemAccepted, InitContextUi } from './draft-conv';
|
|
10
|
+
import type { AskQuestionSet } from './questions';
|
|
10
11
|
|
|
11
12
|
// ============================================================================
|
|
12
13
|
// /ask — request
|
|
@@ -320,6 +321,21 @@ export interface AskResponseData {
|
|
|
320
321
|
* consumers that ignore it are unaffected (Path-B native-approvals path).
|
|
321
322
|
*/
|
|
322
323
|
pendingActions?: Array<{ approvalId: string; toolName: string; summary: string }>;
|
|
324
|
+
/**
|
|
325
|
+
* A structured set of questions the agent is asking this turn, for the
|
|
326
|
+
* consumer to render as a picker instead of leaving the user to re-type a
|
|
327
|
+
* choice from a numbered list in `answer`.
|
|
328
|
+
*
|
|
329
|
+
* Present only on a turn that actually asked; at most one set per turn.
|
|
330
|
+
* Same species as `pendingActions` — an out-of-band card the consumer
|
|
331
|
+
* renders and resolves separately — and, like it, additive: a consumer
|
|
332
|
+
* that ignores this field still gets a coherent `answer`, because the
|
|
333
|
+
* questions are also stated in the reply text.
|
|
334
|
+
*
|
|
335
|
+
* The answer comes back as an ordinary `AskRequestBody.message`; there is
|
|
336
|
+
* no structured answer channel. See `questions.d.ts` for why.
|
|
337
|
+
*/
|
|
338
|
+
questionSet?: AskQuestionSet;
|
|
323
339
|
/**
|
|
324
340
|
* Per-end-user learning observability (Phase 15 / exURM). Present only when
|
|
325
341
|
* learning was ACTIVE for the turn (tenant opt-in + `externalUserId` present
|
|
@@ -330,4 +346,23 @@ export interface AskResponseData {
|
|
|
330
346
|
*/
|
|
331
347
|
memoryInjected?: boolean;
|
|
332
348
|
memoryProposed?: boolean;
|
|
349
|
+
/**
|
|
350
|
+
* What EgoZ actually accepted from `AskRequestBody.contextItems` this
|
|
351
|
+
* turn, in the order the agent saw them.
|
|
352
|
+
*
|
|
353
|
+
* Present whenever the request carried `contextItems` — including as an
|
|
354
|
+
* empty array, meaning "you sent a list and nothing in it was accepted".
|
|
355
|
+
*
|
|
356
|
+
* ⚠️ ABSENT MEANS THE REQUEST CARRIED NO `contextItems`. It never means
|
|
357
|
+
* "none were accepted". A consumer that reads absence as rejection
|
|
358
|
+
* reports a failure that did not happen; one that reads an empty array as
|
|
359
|
+
* absence reports a success that did not either.
|
|
360
|
+
*
|
|
361
|
+
* Render this — not the list you sent — anywhere you show the user what
|
|
362
|
+
* was shared with the agent. See `InitContextItemAccepted` for why the
|
|
363
|
+
* two can differ and how to join them.
|
|
364
|
+
*
|
|
365
|
+
* Additive: consumers that ignore it are unaffected.
|
|
366
|
+
*/
|
|
367
|
+
contextItemsAccepted?: InitContextItemAccepted[];
|
|
333
368
|
}
|
package/src/draft-conv.d.ts
CHANGED
|
@@ -93,13 +93,74 @@ export interface InitContextItem {
|
|
|
93
93
|
label?: string;
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* A short description of the item. Optional
|
|
97
|
-
*
|
|
98
|
-
*
|
|
96
|
+
* A short description of the item. Optional — but keep it to what the
|
|
97
|
+
* thing IS, not its full record. Everything here displaces conversation
|
|
98
|
+
* from the context window.
|
|
99
|
+
*
|
|
100
|
+
* ⚠️ AN OVER-LONG EXCERPT IS DROPPED, NOT TRUNCATED, and the item still
|
|
101
|
+
* rides without it. Unlike every other cap here, which truncates or
|
|
102
|
+
* rejects.
|
|
103
|
+
*
|
|
104
|
+
* Because you resolve the item yourself, this is sometimes prose
|
|
105
|
+
* (`"£45, in stock"`) and sometimes an ADDRESS the agent will act on
|
|
106
|
+
* (`"projects/api/deploy.sh"`). Shortening prose loses a few words;
|
|
107
|
+
* shortening an address does not produce an invalid address, it produces
|
|
108
|
+
* a **different valid one**, which the agent then reads and reports on
|
|
109
|
+
* with no error anywhere. Nothing server-side can tell the two apart —
|
|
110
|
+
* `kind` is opaque and never resolved — so one behaviour covers both and
|
|
111
|
+
* it is the safe one.
|
|
112
|
+
*
|
|
113
|
+
* `contextItemsAccepted` on the response tells you when it happened.
|
|
99
114
|
*/
|
|
100
115
|
excerpt?: string;
|
|
101
116
|
}
|
|
102
117
|
|
|
118
|
+
/**
|
|
119
|
+
* One entry in the response's `contextItemsAccepted` receipt — what EgoZ
|
|
120
|
+
* actually took from a `contextItems` you sent.
|
|
121
|
+
*
|
|
122
|
+
* ── Why a receipt exists at all ──────────────────────────────────────────
|
|
123
|
+
*
|
|
124
|
+
* Acceptance is LOSSY BY DESIGN, in three ways you cannot observe from the
|
|
125
|
+
* caller: an over-long list is truncated from the end, an item with no
|
|
126
|
+
* `label` is dropped individually, and an over-long `excerpt` is dropped
|
|
127
|
+
* while its item rides on. Each is the right behaviour — none of them should
|
|
128
|
+
* fail a user's turn — but each means the list you sent is not necessarily
|
|
129
|
+
* the list the agent saw.
|
|
130
|
+
*
|
|
131
|
+
* So if you display "what was shared with the agent" anywhere, render THIS,
|
|
132
|
+
* not the list you sent. Rendering your own list produces a display that is
|
|
133
|
+
* confident and wrong in exactly the cases that matter.
|
|
134
|
+
*
|
|
135
|
+
* ⚠️ AN ITEM MISSING FROM THE RECEIPT WAS NOT ACCEPTED. Absence is the only
|
|
136
|
+
* channel that reports the list truncation and the labelless drop, so a
|
|
137
|
+
* consumer that treats this as best-effort or partially populated silently
|
|
138
|
+
* goes back to claiming delivery it cannot verify.
|
|
139
|
+
*
|
|
140
|
+
* Match entries to your own items on `kind` + `id`. Do NOT fall back to
|
|
141
|
+
* matching on `label` — labels are deliberately not unique, so that join is
|
|
142
|
+
* usually right, and "usually right" is the failure this field exists to
|
|
143
|
+
* remove. Render an unmatched entry as unmatched instead.
|
|
144
|
+
*/
|
|
145
|
+
export interface InitContextItemAccepted {
|
|
146
|
+
/** Echo of the item's `kind`. Still never rendered into the prompt. */
|
|
147
|
+
kind?: string;
|
|
148
|
+
/** Echo of the item's `id`. */
|
|
149
|
+
id?: string;
|
|
150
|
+
/** The label the agent saw. Always present — a labelless item is dropped. */
|
|
151
|
+
label: string;
|
|
152
|
+
/**
|
|
153
|
+
* The item was accepted but its `excerpt` was not — it exceeded the cap
|
|
154
|
+
* and was dropped rather than truncated (see `InitContextItem.excerpt`).
|
|
155
|
+
*
|
|
156
|
+
* Absent means the excerpt survived, or there was never one to carry.
|
|
157
|
+
* Distinguishing those two is not what this flag is for: it exists so a
|
|
158
|
+
* "shared with the agent" display can say the agent got a NAME and no
|
|
159
|
+
* detail, instead of implying it got both.
|
|
160
|
+
*/
|
|
161
|
+
excerptOmitted?: boolean;
|
|
162
|
+
}
|
|
163
|
+
|
|
103
164
|
/** One open window in the caller's UI. */
|
|
104
165
|
export interface InitContextOpenWindow {
|
|
105
166
|
/**
|
package/src/index.d.ts
CHANGED
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
* `AskResponseData`.
|
|
27
27
|
* - `stream.d.ts` — full `AskStreamEvent` discriminated union +
|
|
28
28
|
* every variant.
|
|
29
|
+
* - `questions.d.ts` — the structured question set a turn can carry on
|
|
30
|
+
* `AskResponseData` / `AskStreamDoneEvent`:
|
|
31
|
+
* `AskQuestionSet`, `AskQuestion`, `AskAnswer`.
|
|
29
32
|
* - `tool.d.ts` — `ToolWire`, `ToolCreateBody`, `ToolUpdateBody`,
|
|
30
33
|
* `ToolListResponseData`, `ToolGetResponseData`,
|
|
31
34
|
* `ToolPreview`, vocabularies (`ToolType`,
|
|
@@ -91,6 +94,7 @@
|
|
|
91
94
|
export * from './envelope';
|
|
92
95
|
export * from './ask';
|
|
93
96
|
export * from './stream';
|
|
97
|
+
export * from './questions';
|
|
94
98
|
export * from './tool';
|
|
95
99
|
export * from './rag';
|
|
96
100
|
export * from './api-key';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ego-z/contracts — the structured question set a turn can put on the wire.
|
|
3
|
+
*
|
|
4
|
+
* When the agent needs the user to CHOOSE rather than to read, it emits one of
|
|
5
|
+
* these instead of a numbered list in prose. The consumer renders a picker and
|
|
6
|
+
* feeds the selection back as the next turn's `message`.
|
|
7
|
+
*
|
|
8
|
+
* ── Why this is a wire type and not a prompt convention ──────────────────
|
|
9
|
+
*
|
|
10
|
+
* The obvious implementation is a markup token the model is taught to emit
|
|
11
|
+
* (`<Questions>…`) and the consumer parses. That works exactly as long as one
|
|
12
|
+
* provider's prompt keeps honouring it — it is an AUTHORING shape, and it
|
|
13
|
+
* dies on a provider move, silently, in the consumer.
|
|
14
|
+
*
|
|
15
|
+
* So the parse belongs to EgoZ and the wire carries the result. However a
|
|
16
|
+
* provider expresses the intent — a tool call today, a JSON mode response
|
|
17
|
+
* tomorrow — it is normalised here before it leaves the backend, and no
|
|
18
|
+
* consumer ever sees provider-shaped output. Moving providers rewrites one
|
|
19
|
+
* adapter rather than every renderer.
|
|
20
|
+
*
|
|
21
|
+
* ── Ids are minted by the emitter, never derived from labels ─────────────
|
|
22
|
+
*
|
|
23
|
+
* A consumer that keys an answer by its label breaks the first time a
|
|
24
|
+
* provider emits `**#b49c54**` where it used to emit `#b49c54`. Every id here
|
|
25
|
+
* comes from the emission side: a tool's own domain ids where a tool supplied
|
|
26
|
+
* the options, EgoZ-minted otherwise. The model never supplies one — a
|
|
27
|
+
* model-minted id looks stable and isn't.
|
|
28
|
+
*
|
|
29
|
+
* ── The answer does NOT come back through this type ──────────────────────
|
|
30
|
+
*
|
|
31
|
+
* There is no answer field on `AskRequestBody`, deliberately. The model has to
|
|
32
|
+
* READ the pick, and only `message` reaches the prompt, so the answer travels
|
|
33
|
+
* as ordinary message text; the ids ride on `metadata` for the consumer's own
|
|
34
|
+
* correlation. That split is why a question set stays answerable long after
|
|
35
|
+
* the turn that produced it — the text means the same thing tomorrow.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One set of questions the agent is asking this turn — the whole card.
|
|
40
|
+
*
|
|
41
|
+
* At most one per turn. A turn that would produce a second set is refused at
|
|
42
|
+
* emission rather than overwriting the first, so a consumer can treat "a set
|
|
43
|
+
* arrived" and "the set for this turn" as the same thing.
|
|
44
|
+
*/
|
|
45
|
+
export interface AskQuestionSet {
|
|
46
|
+
/**
|
|
47
|
+
* Correlates an answer back to the turn that asked.
|
|
48
|
+
*
|
|
49
|
+
* **Opaque.** No ordering, no derivability from content, and NOT stable
|
|
50
|
+
* across turns: a retried turn produces a different set with different
|
|
51
|
+
* ids, because it is a different emission. A consumer that persists a set
|
|
52
|
+
* keys on this and never assumes two turns share it.
|
|
53
|
+
*/
|
|
54
|
+
id: string;
|
|
55
|
+
/** Heading for the whole set, e.g. "A few details before I book that". */
|
|
56
|
+
title?: string;
|
|
57
|
+
/**
|
|
58
|
+
* The language EgoZ actually resolved for this turn — not the one the
|
|
59
|
+
* caller requested.
|
|
60
|
+
*
|
|
61
|
+
* REQUIRED, and it is doing more work than it looks like: it is the only
|
|
62
|
+
* thing that can set text direction. `dir="auto"` infers direction from
|
|
63
|
+
* the first strong character and therefore gets a Hebrew question whose
|
|
64
|
+
* answers are hex colours exactly backwards. Optionality here would mean
|
|
65
|
+
* "guess the direction", which is the bug rather than the fallback.
|
|
66
|
+
*/
|
|
67
|
+
locale: string;
|
|
68
|
+
/** At least one. Rendered in order. */
|
|
69
|
+
questions: AskQuestion[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One question within a set. */
|
|
73
|
+
export interface AskQuestion {
|
|
74
|
+
/** Opaque, emitter-minted — see `AskQuestionSet.id`. */
|
|
75
|
+
id: string;
|
|
76
|
+
/**
|
|
77
|
+
* What is being asked, e.g. "Which shade?".
|
|
78
|
+
*
|
|
79
|
+
* Must stand alone. It is the only context an answer's `label` gets, both
|
|
80
|
+
* in the UI and in the sentence the consumer composes when the user picks.
|
|
81
|
+
*/
|
|
82
|
+
title: string;
|
|
83
|
+
/**
|
|
84
|
+
* The offered answers, in display order. Never empty — a question with
|
|
85
|
+
* nothing to choose between is asked in prose, not in a card, and is
|
|
86
|
+
* rejected at emission.
|
|
87
|
+
*/
|
|
88
|
+
answers: AskAnswer[];
|
|
89
|
+
/**
|
|
90
|
+
* Whether this question also accepts a free-text answer — the "something
|
|
91
|
+
* else" slot. Per-question, not per-set: a set can mix a chip question
|
|
92
|
+
* with one that invites a note.
|
|
93
|
+
*
|
|
94
|
+
* It EXTENDS `answers`, never replaces it. Absent means chips only.
|
|
95
|
+
*/
|
|
96
|
+
allowCustom?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Whether more than one answer may be selected. Absent means single-select.
|
|
99
|
+
*
|
|
100
|
+
* Set by whoever holds the options and knows if they compose — never
|
|
101
|
+
* inferred by a consumer from how the question is phrased.
|
|
102
|
+
*/
|
|
103
|
+
multi?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One selectable answer. */
|
|
107
|
+
export interface AskAnswer {
|
|
108
|
+
/** Opaque, emitter-minted — see `AskQuestionSet.id`. */
|
|
109
|
+
id: string;
|
|
110
|
+
/**
|
|
111
|
+
* What the user sees, already in `AskQuestionSet.locale`.
|
|
112
|
+
*
|
|
113
|
+
* Must be meaningful on its own, because it is what ends up in the next
|
|
114
|
+
* turn's message text: `#b49c54` reads fine, `"Option 2"` does not — that
|
|
115
|
+
* one only means anything against a rendered list the model never sees.
|
|
116
|
+
*/
|
|
117
|
+
label: string;
|
|
118
|
+
/** Optional secondary line, e.g. a price or a duration. */
|
|
119
|
+
description?: string;
|
|
120
|
+
}
|
package/src/stream.d.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { Intent, TokenUsage } from './envelope';
|
|
15
|
+
import type { AskQuestionSet } from './questions';
|
|
16
|
+
import type { InitContextItemAccepted } from './draft-conv';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Discriminated union of every event the streaming `/ask` endpoint emits.
|
|
@@ -323,6 +325,26 @@ export interface AskStreamDoneEvent {
|
|
|
323
325
|
* Approve/Reject card without parsing the final `data` payload separately.
|
|
324
326
|
*/
|
|
325
327
|
pendingActions?: Array<{ approvalId: string; toolName: string; summary: string }>;
|
|
328
|
+
/**
|
|
329
|
+
* A structured set of questions the agent asked this turn. Same semantics
|
|
330
|
+
* + shape as `AskResponseData.questionSet` — surfaced on the terminal
|
|
331
|
+
* event so streaming consumers can render the picker without parsing the
|
|
332
|
+
* final `data` payload separately.
|
|
333
|
+
*
|
|
334
|
+
* ── Why the terminal frame and not a mid-stream event ────────────────
|
|
335
|
+
*
|
|
336
|
+
* A card that offers choices is only actionable once the turn is settled,
|
|
337
|
+
* and consumers already gate it that way — the Console suppresses its
|
|
338
|
+
* choice row until the stream completes. A dedicated mid-stream variant
|
|
339
|
+
* would be a channel every consumer discards until `done` anyway, bought
|
|
340
|
+
* at the price of a new member of this union (non-breaking for producers,
|
|
341
|
+
* breaking for consumers that exhaustively switch on `event.type`).
|
|
342
|
+
*
|
|
343
|
+
* The cost, chosen rather than overlooked: a turn that terminates with
|
|
344
|
+
* `error` after producing a set loses the card. `pendingActions` carries
|
|
345
|
+
* the identical hole; the retry re-runs the turn.
|
|
346
|
+
*/
|
|
347
|
+
questionSet?: AskQuestionSet;
|
|
326
348
|
/**
|
|
327
349
|
* Per-end-user learning observability (Phase 15 / exURM). Same semantics as
|
|
328
350
|
* `AskResponseData.{memoryInjected,memoryProposed}` — surfaced on the
|
|
@@ -331,6 +353,21 @@ export interface AskStreamDoneEvent {
|
|
|
331
353
|
*/
|
|
332
354
|
memoryInjected?: boolean;
|
|
333
355
|
memoryProposed?: boolean;
|
|
356
|
+
/**
|
|
357
|
+
* What EgoZ accepted from this turn's `contextItems`. Same semantics +
|
|
358
|
+
* shape as `AskResponseData.contextItemsAccepted` — surfaced on the
|
|
359
|
+
* terminal event so streaming consumers can render "what was shared"
|
|
360
|
+
* without parsing the final payload separately.
|
|
361
|
+
*
|
|
362
|
+
* On the terminal frame rather than mid-stream for the same reason as
|
|
363
|
+
* `questionSet`: it describes the settled turn, and a consumer would
|
|
364
|
+
* discard every earlier variant anyway.
|
|
365
|
+
*
|
|
366
|
+
* ⚠️ Absent means the request carried no `contextItems` — never that none
|
|
367
|
+
* were accepted. An empty array is the second thing, and they must not be
|
|
368
|
+
* collapsed by anything relaying this frame.
|
|
369
|
+
*/
|
|
370
|
+
contextItemsAccepted?: InitContextItemAccepted[];
|
|
334
371
|
}
|
|
335
372
|
|
|
336
373
|
export interface AskStreamErrorEvent {
|