dsh-draw 0.2.16 → 0.2.18

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.
@@ -15,6 +15,9 @@ export type DrawLocaleKey =
15
15
  | 'result.regenerate'
16
16
  | 'result.regenerating'
17
17
  | 'result.failed'
18
+ | 'row.title'
19
+ | 'row.preparing'
20
+ | 'row.running'
18
21
  | 'tab'
19
22
  | 'tab.engines'
20
23
  | 'tab.preferred'
@@ -40,6 +43,9 @@ export const en: Record<DrawLocaleKey, string> = {
40
43
  'result.regenerate': 'Regenerate',
41
44
  'result.regenerating': 'Regenerating…',
42
45
  'result.failed': 'Regenerate failed',
46
+ 'row.title': 'Tool call',
47
+ 'row.preparing': 'Preparing tool call',
48
+ 'row.running': 'Running',
43
49
  'tab': 'Image generation',
44
50
  'tab.engines': 'Engines',
45
51
  'tab.preferred': 'preferred',
@@ -66,6 +72,9 @@ export const zh: Record<DrawLocaleKey, string> = {
66
72
  'result.regenerate': '重新生成',
67
73
  'result.regenerating': '正在重新生成…',
68
74
  'result.failed': '重新生成失败',
75
+ 'row.title': '工具调用',
76
+ 'row.preparing': '正在准备调用',
77
+ 'row.running': '运行中',
69
78
  'tab': '图像生成',
70
79
  'tab.engines': '引擎',
71
80
  'tab.preferred': '首选',
@@ -51,6 +51,88 @@ export type ToolCallBlock = RunningToolCallBlock | SettledToolResultBlock
51
51
  /** Settled tool-result node only. */
52
52
  export type ToolResultNode = SettledToolResultBlock
53
53
 
54
+ /**
55
+ * The Host's tool-call-view stage discriminant. The Host splits the owner
56
+ * currency into `preparing` / `start` / `result`, each carrying its own stage
57
+ * block, and dispatches the SAME keyed entry in every stage — so a keyed card
58
+ * receives a preparing owner whose block has no `kind` and no `argsRaw`.
59
+ *
60
+ * {@link TOOL_CALL_PHASES} is the single source of truth for this union: the
61
+ * literal type is derived from the array, so a value and its type can never
62
+ * drift apart, and `scripts/verify-host-contract.mjs` compares the array
63
+ * against the Host's own `ToolCallPhaseProps` declaration.
64
+ */
65
+ export const TOOL_CALL_PHASES = ['preparing', 'start', 'result'] as const
66
+
67
+ /** One stage of the Host's tool-call view. */
68
+ export type ToolCallPhase = (typeof TOOL_CALL_PHASES)[number]
69
+
70
+ /**
71
+ * Classify a frozen tool block into its Host stage, mirroring the Host's own
72
+ * `toolCallPhase`: a settled node wins on `kind`, and the running family splits
73
+ * on `phase`. `ToolCallBlock` is structurally narrower than the Host union
74
+ * (`RunningToolCallBlock` declares no `phase` field), so the running arm is
75
+ * read through a structural probe rather than a declared member.
76
+ *
77
+ * @param block - the frozen tool block the owner delivered.
78
+ * @returns the stage this block belongs to.
79
+ */
80
+ export function toolCallPhase(block: ToolCallBlock): ToolCallPhase {
81
+ if ('kind' in block) return 'result'
82
+ return (block as { phase?: unknown }).phase === 'preparing' ? 'preparing' : 'start'
83
+ }
84
+
85
+ /** How a card presents one stage of its own keyed tool call. */
86
+ export interface PresentedToolCallPhase {
87
+ /** The Host stage this presentation covers. */
88
+ phase: ToolCallPhase
89
+ /**
90
+ * Whether this stage carries a settled result to present, or whether the
91
+ * card must render its own in-flight row for a call still arriving.
92
+ */
93
+ state: 'result' | 'in-flight'
94
+ }
95
+
96
+ /**
97
+ * Project the card's one appearance per Host stage.
98
+ *
99
+ * Only the `result` stage carries the engine/quota facts and the regenerate
100
+ * action. The earlier stages have no arguments and no result, and the keyed
101
+ * slot is OCCUPIED for them — the Host's own generic row is unreachable
102
+ * (the renderer falls back only for an empty cell) — so the card owning an
103
+ * in-flight row is what keeps the row visible while the call streams.
104
+ *
105
+ * A stage outside the declared vocabulary is reported loudly instead of being
106
+ * silently swept into `preparing`; the `switch` is exhaustive over
107
+ * {@link ToolCallPhase}, so a new stage fails the build before it can reach
108
+ * this default.
109
+ *
110
+ * @param phase - the stage classified from the owner's block.
111
+ * @returns the presentation this card owes that stage.
112
+ */
113
+ export function presentToolCallPhase(phase: ToolCallPhase): PresentedToolCallPhase {
114
+ switch (phase) {
115
+ case 'result':
116
+ return { phase, state: 'result' }
117
+ case 'preparing':
118
+ case 'start':
119
+ return { phase, state: 'in-flight' }
120
+ default:
121
+ return exhaustiveToolCallPhase(phase)
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Compile-time completeness check for the stage vocabulary: a new Host stage
127
+ * added to {@link ToolCallPhase} without a branch above fails the build here.
128
+ *
129
+ * @param phase - the unhandled stage.
130
+ * @returns never; throws at runtime.
131
+ */
132
+ function exhaustiveToolCallPhase(phase: never): never {
133
+ throw new Error(`unhandled tool-call phase: ${JSON.stringify(phase)}`)
134
+ }
135
+
54
136
  /** One image of the presented result card. */
55
137
  export interface PresentedImage {
56
138
  attachmentId: string
@@ -78,11 +160,19 @@ export interface PresentedDrawResult {
78
160
  /**
79
161
  * Project one settled `image_generate` tool block onto the card model.
80
162
  *
163
+ * The stage guard is explicit, not incidental: only the Host's `result` stage
164
+ * carries `kind`, and the preparing stage's block is an ordinary object, so
165
+ * an `undefined` return is the presenter's answer for every non-result stage
166
+ * as well as for a foreign tool, an error result, or a window-truncated call
167
+ * head. The card pairs that answer with {@link toolCallPhase} to render its
168
+ * in-flight row instead of an empty node.
169
+ *
81
170
  * @param block - the frozen tool-call block (running or settled).
82
171
  * @returns the presented model, or `undefined` when the block is not a settled
83
172
  * image_generate result.
84
173
  */
85
174
  export function presentDrawResult(block: ToolCallBlock): PresentedDrawResult | undefined {
175
+ if (toolCallPhase(block) !== 'result') return undefined
86
176
  if (!('kind' in block) || block.kind !== 'tool-result' || block.isError) return undefined
87
177
  if (block.call?.name !== 'image_generate') return undefined
88
178
  const value = (block.meta as { engine?: unknown; model?: unknown; fallbackUsed?: unknown; images?: unknown; quota?: unknown; limits?: unknown } | undefined) ?? {}
@@ -31,6 +31,11 @@ export function installDrawStyles(): () => void {
31
31
  .dshdraw-figure img { max-width: 100%; border-radius: 6px; border: 1px solid var(--dsh-border, #d0d7de); }
32
32
  .dshdraw-figure figcaption { font-size: 11px; opacity: 0.75; overflow-wrap: anywhere; }
33
33
  .dshdraw-meta { font-size: 12px; opacity: 0.85; display: flex; flex-wrap: wrap; gap: 12px; }
34
+ .dshdraw-inflight { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 13px; min-height: 20px; }
35
+ .dshdraw-inflight .dshdraw-sep { opacity: 0.45; }
36
+ .dshdraw-inflight-title { font-weight: 500; }
37
+ .dshdraw-inflight-summary { opacity: 0.85; overflow-wrap: anywhere; }
38
+ .dshdraw-inflight-state { font-size: 12px; opacity: 0.7; }
34
39
  .dshdraw-actions { display: flex; gap: 8px; }
35
40
  .dshdraw-button {
36
41
  border: 1px solid var(--dsh-border, #d0d7de); border-radius: 6px; background: transparent;
package/src/version.ts CHANGED
@@ -8,4 +8,4 @@
8
8
  */
9
9
 
10
10
  /** Plugin version; must equal the `version` field in `package.json`. */
11
- export const PLUGIN_VERSION = '0.2.16'
11
+ export const PLUGIN_VERSION = '0.2.18'