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.
package/lib/index.js CHANGED
@@ -1391,7 +1391,7 @@ var EngineRouter = class {
1391
1391
  * @module dsh-draw/version
1392
1392
  */
1393
1393
  /** Plugin version; must equal the `version` field in `package.json`. */
1394
- const PLUGIN_VERSION = "0.2.16";
1394
+ const PLUGIN_VERSION = "0.2.18";
1395
1395
  //#endregion
1396
1396
  //#region src/service.ts
1397
1397
  /**
@@ -1,8 +1,14 @@
1
1
  /**
2
- * The keyed `image_generate` tool result card: engine/quota facts and the
3
- * regenerate action. The images themselves are the attachment content blocks
4
- * the shell already renders from the tool result, so the card only adds the
5
- * accounting line and the action — it never duplicates image transport.
2
+ * The keyed `image_generate` tool view: engine/quota facts and the regenerate
3
+ * action once the call settles, and an in-flight row of its own while the
4
+ * arguments are still arriving. The images themselves are the attachment
5
+ * content blocks the shell already renders from the tool result, so the card
6
+ * only adds the accounting line and the action — it never duplicates image
7
+ * transport.
8
+ *
9
+ * The Host dispatches this keyed entry in EVERY stage of the call, and a keyed
10
+ * cell that is occupied never reaches the owner fallback — so the earlier
11
+ * stages cannot be answered with nothing without blanking the row.
6
12
  *
7
13
  * The `tool.call.toolview` SlotMap member is declared locally (mirroring the
8
14
  * harness's own ui-tool contract declaration, which its package index does not
@@ -14,17 +20,32 @@ import { type ReactElement } from 'react';
14
20
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
15
21
  import { type ToolCallBlock } from './present.js';
16
22
  /**
17
- * Standard owner currency for one keyed tool view (mirror of the harness
18
- * ui-tool contract `ToolCallOwnerProps`, which the package index does not
19
- * re-export — keep the shape identical so the declarations merge).
23
+ * Stage-specific tool data, mirror of the harness ui-tool contract
24
+ * `ToolCallPhaseProps`. The Host dispatches the SAME keyed entry in every
25
+ * stage, so the card's own contract must discriminate on `phase` exactly as
26
+ * the Host's does — a narrower declaration here hides the preparing owner
27
+ * rather than rejecting it (the slot is occupied for every stage).
28
+ */
29
+ export type ToolCallPhaseProps = {
30
+ readonly phase: 'preparing';
31
+ readonly block: ToolCallBlock;
32
+ } | {
33
+ readonly phase: 'start';
34
+ readonly block: ToolCallBlock;
35
+ } | {
36
+ readonly phase: 'result';
37
+ readonly block: ToolCallBlock;
38
+ };
39
+ /**
40
+ * Common owner currency declared beside the phase share, mirror of the
41
+ * harness ui-tool contract `ToolCallCommonProps` (which the package index
42
+ * does not re-export — keep the shape identical so the declarations merge).
20
43
  */
21
- export interface ToolCallOwnerProps {
22
- /** Tool call identity, stable across running and settled forms. */
44
+ export interface ToolCallCommonProps {
45
+ /** Tool call identity, stable across all stages. */
23
46
  callId: string;
24
47
  /** Wire tool name and keyed dispatch value. */
25
48
  toolName: string;
26
- /** Frozen running call or settled result node. */
27
- block: ToolCallBlock;
28
49
  /** Session workspace root for relative summaries. */
29
50
  cwd?: string;
30
51
  /** Open a tool argument path through the host. */
@@ -32,6 +53,11 @@ export interface ToolCallOwnerProps {
32
53
  /** Inspect this call in the trajectory view when available. */
33
54
  inspect?: () => void;
34
55
  }
56
+ /**
57
+ * Standard owner currency for one keyed tool view: the common share plus the
58
+ * stage share, mirroring the harness ui-tool contract `ToolCallOwnerProps`.
59
+ */
60
+ export type ToolCallOwnerProps = ToolCallCommonProps & ToolCallPhaseProps;
35
61
  declare module '@deepseek-ai/dsh-client-ui-slots' {
36
62
  interface SlotMap {
37
63
  /** Keyed atomic tool-call view, dispatched by the wire tool name. */
@@ -50,9 +76,13 @@ export interface DrawResultCardInjected {
50
76
  /** Full component props assembled by the tool-view slot renderer. */
51
77
  export type DrawResultCardProps = PropsRuntime<'tool.call.toolview'> & PropsLocale<'draw'> & InjectFace<DrawResultCardInjected>;
52
78
  /**
53
- * The keyed tool result card.
54
- * @param props - owner currency, locale, and injected bindings.
55
- * @returns the card element.
79
+ * The keyed tool view. Dispatched by the wire tool name in EVERY stage of the
80
+ * call, so the card owes a presentation to the in-flight stages too: returning
81
+ * an empty node would leave the row blank, because an occupied keyed cell
82
+ * never reaches the Host's own generic row.
83
+ *
84
+ * @param props - owner currency (common + stage shares), locale, and injected bindings.
85
+ * @returns the in-flight row, or the settled result card.
56
86
  */
57
87
  export declare function DrawResultCard(props: DrawResultCardProps): ReactElement;
58
88
  //# sourceMappingURL=DrawResultCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DrawResultCard.d.ts","sourceRoot":"","sources":["../../../src/client/DrawResultCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAEpE;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,KAAK,EAAE,aAAa,CAAA;IACpB,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kDAAkD;IAClD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf,qEAAqE;QACrE,oBAAoB,EAAE;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,KAAK,EAAE,SAAS,CAAC;YAAC,KAAK,EAAE,kBAAkB,CAAA;SAAE,CAAA;KACrF;CACF;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7D;AAED,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAC7B,YAAY,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,MAAM,CAAC,GACnB,UAAU,CAAC,sBAAsB,CAAC,CAAA;AAEtC;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAqCvE"}
1
+ {"version":3,"file":"DrawResultCard.d.ts","sourceRoot":"","sources":["../../../src/client/DrawResultCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,EAAoC,KAAK,aAAa,EAAsB,MAAM,cAAc,CAAA;AAEvG;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GAC9D;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAA;AAE/D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kDAAkD;IAClD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAAA;AAEzE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf,qEAAqE;QACrE,oBAAoB,EAAE;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,KAAK,EAAE,SAAS,CAAC;YAAC,KAAK,EAAE,kBAAkB,CAAA;SAAE,CAAA;KACrF;CACF;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7D;AAED,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAC7B,YAAY,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,MAAM,CAAC,GACnB,UAAU,CAAC,sBAAsB,CAAC,CAAA;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAiBvE"}
@@ -1,8 +1,14 @@
1
1
  /**
2
- * The keyed `image_generate` tool result card: engine/quota facts and the
3
- * regenerate action. The images themselves are the attachment content blocks
4
- * the shell already renders from the tool result, so the card only adds the
5
- * accounting line and the action — it never duplicates image transport.
2
+ * The keyed `image_generate` tool view: engine/quota facts and the regenerate
3
+ * action once the call settles, and an in-flight row of its own while the
4
+ * arguments are still arriving. The images themselves are the attachment
5
+ * content blocks the shell already renders from the tool result, so the card
6
+ * only adds the accounting line and the action — it never duplicates image
7
+ * transport.
8
+ *
9
+ * The Host dispatches this keyed entry in EVERY stage of the call, and a keyed
10
+ * cell that is occupied never reaches the owner fallback — so the earlier
11
+ * stages cannot be answered with nothing without blanking the row.
6
12
  *
7
13
  * The `tool.call.toolview` SlotMap member is declared locally (mirroring the
8
14
  * harness's own ui-tool contract declaration, which its package index does not
@@ -11,19 +17,49 @@
11
17
  * @module dsh-draw/client/DrawResultCard
12
18
  */
13
19
  import { createElement as h, useState } from 'react';
14
- import { presentDrawResult } from './present.js';
20
+ import { presentDrawResult, toolCallPhase } from './present.js';
15
21
  /**
16
- * The keyed tool result card.
17
- * @param props - owner currency, locale, and injected bindings.
18
- * @returns the card element.
22
+ * The keyed tool view. Dispatched by the wire tool name in EVERY stage of the
23
+ * call, so the card owes a presentation to the in-flight stages too: returning
24
+ * an empty node would leave the row blank, because an occupied keyed cell
25
+ * never reaches the Host's own generic row.
26
+ *
27
+ * @param props - owner currency (common + stage shares), locale, and injected bindings.
28
+ * @returns the in-flight row, or the settled result card.
19
29
  */
20
30
  export function DrawResultCard(props) {
31
+ // The owner's discriminant and the block are two statements of the same fact,
32
+ // and the block is the one the presenter reads — so the card classifies the
33
+ // stage from the block, through the same function the presenter uses. The
34
+ // owner's arm only breaks the tie in the impossible case of a `result` owner
35
+ // carrying a block that is not a settled node.
36
+ const phase = toolCallPhase(props.block);
37
+ if (phase !== 'result') {
38
+ const stage = props.phase === 'result' ? phase : props.phase;
39
+ return h('div', { className: 'dshdraw-inflight', 'aria-busy': 'true', 'data-phase': stage }, h('span', { className: 'dshdraw-inflight-title' }, props.t('row.title')), h('span', { className: 'dshdraw-sep', 'aria-hidden': 'true' }, '·'), h('span', { className: 'dshdraw-inflight-summary' }, props.toolName), h('span', { className: 'dshdraw-inflight-state' }, props.t(inFlightKey(stage))));
40
+ }
41
+ return h(DrawResultCardBody, props);
42
+ }
43
+ /** Locale key naming the stage of a call that has not settled yet. */
44
+ function inFlightKey(phase) {
45
+ return phase === 'preparing' ? 'row.preparing' : 'row.running';
46
+ }
47
+ /**
48
+ * The settled result card: engine/quota facts and the regenerate action.
49
+ *
50
+ * @param props - the owner at its `result` stage, locale, and injected bindings.
51
+ * @returns the card element.
52
+ */
53
+ function DrawResultCardBody(props) {
21
54
  const { block, regenerate, t } = props;
22
55
  const presented = presentDrawResult(block);
23
56
  const [busy, setBusy] = useState(false);
24
57
  const [failed, setFailed] = useState(false);
25
- if (presented === undefined)
58
+ if (presented === undefined) {
59
+ // The Host handed this card a settled node it does not own (a foreign call
60
+ // head after window truncation, or an error result) — nothing to add.
26
61
  return h('div', null);
62
+ }
27
63
  const runRegenerate = async () => {
28
64
  if (presented.args === undefined)
29
65
  return;
@@ -6,7 +6,7 @@
6
6
  * @module dsh-draw/client/locales
7
7
  */
8
8
  /** Dictionary key union. */
9
- export type DrawLocaleKey = 'result.title' | 'result.fallback' | 'result.quota' | 'result.engine' | 'result.regenerate' | 'result.regenerating' | 'result.failed' | 'tab' | 'tab.engines' | 'tab.preferred' | 'tab.disabled' | 'tab.keyRef' | 'tab.credentialSet' | 'tab.credentialMissing' | 'tab.setKey' | 'tab.removeKey' | 'tab.probe' | 'tab.quotaCalls' | 'tab.quotaBytes' | 'tab.health' | 'tab.healthOk' | 'tab.healthError';
9
+ export type DrawLocaleKey = 'result.title' | 'result.fallback' | 'result.quota' | 'result.engine' | 'result.regenerate' | 'result.regenerating' | 'result.failed' | 'row.title' | 'row.preparing' | 'row.running' | 'tab' | 'tab.engines' | 'tab.preferred' | 'tab.disabled' | 'tab.keyRef' | 'tab.credentialSet' | 'tab.credentialMissing' | 'tab.setKey' | 'tab.removeKey' | 'tab.probe' | 'tab.quotaCalls' | 'tab.quotaBytes' | 'tab.health' | 'tab.healthOk' | 'tab.healthError';
10
10
  /** English dictionary. */
11
11
  export declare const en: Record<DrawLocaleKey, string>;
12
12
  /** Chinese dictionary. */
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,4BAA4B;AAC5B,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,mBAAmB,GACnB,qBAAqB,GACrB,eAAe,GACf,KAAK,GACL,aAAa,GACb,eAAe,GACf,cAAc,GACd,YAAY,GACZ,mBAAmB,GACnB,uBAAuB,GACvB,YAAY,GACZ,eAAe,GACf,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,YAAY,GACZ,cAAc,GACd,iBAAiB,CAAA;AAErB,0BAA0B;AAC1B,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAuB5C,CAAA;AAED,0BAA0B;AAC1B,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAuB5C,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,4BAA4B;AAC5B,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,mBAAmB,GACnB,qBAAqB,GACrB,eAAe,GACf,WAAW,GACX,eAAe,GACf,aAAa,GACb,KAAK,GACL,aAAa,GACb,eAAe,GACf,cAAc,GACd,YAAY,GACZ,mBAAmB,GACnB,uBAAuB,GACvB,YAAY,GACZ,eAAe,GACf,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,YAAY,GACZ,cAAc,GACd,iBAAiB,CAAA;AAErB,0BAA0B;AAC1B,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CA0B5C,CAAA;AAED,0BAA0B;AAC1B,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CA0B5C,CAAA"}
@@ -14,6 +14,9 @@ export const en = {
14
14
  'result.regenerate': 'Regenerate',
15
15
  'result.regenerating': 'Regenerating…',
16
16
  'result.failed': 'Regenerate failed',
17
+ 'row.title': 'Tool call',
18
+ 'row.preparing': 'Preparing tool call',
19
+ 'row.running': 'Running',
17
20
  'tab': 'Image generation',
18
21
  'tab.engines': 'Engines',
19
22
  'tab.preferred': 'preferred',
@@ -39,6 +42,9 @@ export const zh = {
39
42
  'result.regenerate': '重新生成',
40
43
  'result.regenerating': '正在重新生成…',
41
44
  'result.failed': '重新生成失败',
45
+ 'row.title': '工具调用',
46
+ 'row.preparing': '正在准备调用',
47
+ 'row.running': '运行中',
42
48
  'tab': '图像生成',
43
49
  'tab.engines': '引擎',
44
50
  'tab.preferred': '首选',
@@ -47,6 +47,59 @@ export interface SettledToolResultBlock {
47
47
  export type ToolCallBlock = RunningToolCallBlock | SettledToolResultBlock;
48
48
  /** Settled tool-result node only. */
49
49
  export type ToolResultNode = SettledToolResultBlock;
50
+ /**
51
+ * The Host's tool-call-view stage discriminant. The Host splits the owner
52
+ * currency into `preparing` / `start` / `result`, each carrying its own stage
53
+ * block, and dispatches the SAME keyed entry in every stage — so a keyed card
54
+ * receives a preparing owner whose block has no `kind` and no `argsRaw`.
55
+ *
56
+ * {@link TOOL_CALL_PHASES} is the single source of truth for this union: the
57
+ * literal type is derived from the array, so a value and its type can never
58
+ * drift apart, and `scripts/verify-host-contract.mjs` compares the array
59
+ * against the Host's own `ToolCallPhaseProps` declaration.
60
+ */
61
+ export declare const TOOL_CALL_PHASES: readonly ['preparing', 'start', 'result'];
62
+ /** One stage of the Host's tool-call view. */
63
+ export type ToolCallPhase = (typeof TOOL_CALL_PHASES)[number];
64
+ /**
65
+ * Classify a frozen tool block into its Host stage, mirroring the Host's own
66
+ * `toolCallPhase`: a settled node wins on `kind`, and the running family splits
67
+ * on `phase`. `ToolCallBlock` is structurally narrower than the Host union
68
+ * (`RunningToolCallBlock` declares no `phase` field), so the running arm is
69
+ * read through a structural probe rather than a declared member.
70
+ *
71
+ * @param block - the frozen tool block the owner delivered.
72
+ * @returns the stage this block belongs to.
73
+ */
74
+ export declare function toolCallPhase(block: ToolCallBlock): ToolCallPhase;
75
+ /** How a card presents one stage of its own keyed tool call. */
76
+ export interface PresentedToolCallPhase {
77
+ /** The Host stage this presentation covers. */
78
+ phase: ToolCallPhase;
79
+ /**
80
+ * Whether this stage carries a settled result to present, or whether the
81
+ * card must render its own in-flight row for a call still arriving.
82
+ */
83
+ state: 'result' | 'in-flight';
84
+ }
85
+ /**
86
+ * Project the card's one appearance per Host stage.
87
+ *
88
+ * Only the `result` stage carries the engine/quota facts and the regenerate
89
+ * action. The earlier stages have no arguments and no result, and the keyed
90
+ * slot is OCCUPIED for them — the Host's own generic row is unreachable
91
+ * (the renderer falls back only for an empty cell) — so the card owning an
92
+ * in-flight row is what keeps the row visible while the call streams.
93
+ *
94
+ * A stage outside the declared vocabulary is reported loudly instead of being
95
+ * silently swept into `preparing`; the `switch` is exhaustive over
96
+ * {@link ToolCallPhase}, so a new stage fails the build before it can reach
97
+ * this default.
98
+ *
99
+ * @param phase - the stage classified from the owner's block.
100
+ * @returns the presentation this card owes that stage.
101
+ */
102
+ export declare function presentToolCallPhase(phase: ToolCallPhase): PresentedToolCallPhase;
50
103
  /** One image of the presented result card. */
51
104
  export interface PresentedImage {
52
105
  attachmentId: string;
@@ -78,6 +131,13 @@ export interface PresentedDrawResult {
78
131
  /**
79
132
  * Project one settled `image_generate` tool block onto the card model.
80
133
  *
134
+ * The stage guard is explicit, not incidental: only the Host's `result` stage
135
+ * carries `kind`, and the preparing stage's block is an ordinary object, so
136
+ * an `undefined` return is the presenter's answer for every non-result stage
137
+ * as well as for a foreign tool, an error result, or a window-truncated call
138
+ * head. The card pairs that answer with {@link toolCallPhase} to render its
139
+ * in-flight row instead of an empty node.
140
+ *
81
141
  * @param block - the frozen tool-call block (running or settled).
82
142
  * @returns the presented model, or `undefined` when the block is not a settled
83
143
  * image_generate result.
@@ -1 +1 @@
1
- {"version":3,"file":"present.d.ts","sourceRoot":"","sources":["../../../src/client/present.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"present.d.ts","sourceRoot":"","sources":["../../../src/client/present.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;;;;;GAQG;AAEH,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;CACb;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,oEAAoE;IACpE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC9C,+EAA+E;IAC/E,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,sBAAsB,CAAA;AAEzE,qCAAqC;AACrC,MAAM,MAAM,cAAc,GAAG,sBAAsB,CAAA;AAEnD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,YAAI,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAA;AAEzE,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7D;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAGjE;AAED,gEAAgE;AAChE,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,KAAK,EAAE,aAAa,CAAA;IACpB;;;OAGG;IACH,KAAK,EAAE,QAAQ,GAAG,WAAW,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,sBAAsB,CAUjF;AAaD,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,YAAY,EAAE,OAAO,CAAA;IACrB,mCAAmC;IACnC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAA;IACjC,8CAA8C;IAC9C,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,wBAAwB;IACxB,MAAM,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IACpD,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CAC1C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,mBAAmB,GAAG,SAAS,CAwCvF;AAED,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACtC,KAAK,EAAE;QAAE,wBAAwB,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAA;IACvE,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,GAAE,MAAM,MAAiB,GAAG,kBAAkB,CAqB/G"}
@@ -6,14 +6,88 @@
6
6
  *
7
7
  * @module dsh-draw/client/present
8
8
  */
9
+ /**
10
+ * The Host's tool-call-view stage discriminant. The Host splits the owner
11
+ * currency into `preparing` / `start` / `result`, each carrying its own stage
12
+ * block, and dispatches the SAME keyed entry in every stage — so a keyed card
13
+ * receives a preparing owner whose block has no `kind` and no `argsRaw`.
14
+ *
15
+ * {@link TOOL_CALL_PHASES} is the single source of truth for this union: the
16
+ * literal type is derived from the array, so a value and its type can never
17
+ * drift apart, and `scripts/verify-host-contract.mjs` compares the array
18
+ * against the Host's own `ToolCallPhaseProps` declaration.
19
+ */
20
+ export const TOOL_CALL_PHASES = ['preparing', 'start', 'result'];
21
+ /**
22
+ * Classify a frozen tool block into its Host stage, mirroring the Host's own
23
+ * `toolCallPhase`: a settled node wins on `kind`, and the running family splits
24
+ * on `phase`. `ToolCallBlock` is structurally narrower than the Host union
25
+ * (`RunningToolCallBlock` declares no `phase` field), so the running arm is
26
+ * read through a structural probe rather than a declared member.
27
+ *
28
+ * @param block - the frozen tool block the owner delivered.
29
+ * @returns the stage this block belongs to.
30
+ */
31
+ export function toolCallPhase(block) {
32
+ if ('kind' in block)
33
+ return 'result';
34
+ return block.phase === 'preparing' ? 'preparing' : 'start';
35
+ }
36
+ /**
37
+ * Project the card's one appearance per Host stage.
38
+ *
39
+ * Only the `result` stage carries the engine/quota facts and the regenerate
40
+ * action. The earlier stages have no arguments and no result, and the keyed
41
+ * slot is OCCUPIED for them — the Host's own generic row is unreachable
42
+ * (the renderer falls back only for an empty cell) — so the card owning an
43
+ * in-flight row is what keeps the row visible while the call streams.
44
+ *
45
+ * A stage outside the declared vocabulary is reported loudly instead of being
46
+ * silently swept into `preparing`; the `switch` is exhaustive over
47
+ * {@link ToolCallPhase}, so a new stage fails the build before it can reach
48
+ * this default.
49
+ *
50
+ * @param phase - the stage classified from the owner's block.
51
+ * @returns the presentation this card owes that stage.
52
+ */
53
+ export function presentToolCallPhase(phase) {
54
+ switch (phase) {
55
+ case 'result':
56
+ return { phase, state: 'result' };
57
+ case 'preparing':
58
+ case 'start':
59
+ return { phase, state: 'in-flight' };
60
+ default:
61
+ return exhaustiveToolCallPhase(phase);
62
+ }
63
+ }
64
+ /**
65
+ * Compile-time completeness check for the stage vocabulary: a new Host stage
66
+ * added to {@link ToolCallPhase} without a branch above fails the build here.
67
+ *
68
+ * @param phase - the unhandled stage.
69
+ * @returns never; throws at runtime.
70
+ */
71
+ function exhaustiveToolCallPhase(phase) {
72
+ throw new Error(`unhandled tool-call phase: ${JSON.stringify(phase)}`);
73
+ }
9
74
  /**
10
75
  * Project one settled `image_generate` tool block onto the card model.
11
76
  *
77
+ * The stage guard is explicit, not incidental: only the Host's `result` stage
78
+ * carries `kind`, and the preparing stage's block is an ordinary object, so
79
+ * an `undefined` return is the presenter's answer for every non-result stage
80
+ * as well as for a foreign tool, an error result, or a window-truncated call
81
+ * head. The card pairs that answer with {@link toolCallPhase} to render its
82
+ * in-flight row instead of an empty node.
83
+ *
12
84
  * @param block - the frozen tool-call block (running or settled).
13
85
  * @returns the presented model, or `undefined` when the block is not a settled
14
86
  * image_generate result.
15
87
  */
16
88
  export function presentDrawResult(block) {
89
+ if (toolCallPhase(block) !== 'result')
90
+ return undefined;
17
91
  if (!('kind' in block) || block.kind !== 'tool-result' || block.isError)
18
92
  return undefined;
19
93
  if (block.call?.name !== 'image_generate')
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/client/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,wEAAwE;AACxE,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CAwC9C"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/client/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,wEAAwE;AACxE,wBAAgB,iBAAiB,IAAI,MAAM,IAAI,CA6C9C"}
@@ -28,6 +28,11 @@ export function installDrawStyles() {
28
28
  .dshdraw-figure img { max-width: 100%; border-radius: 6px; border: 1px solid var(--dsh-border, #d0d7de); }
29
29
  .dshdraw-figure figcaption { font-size: 11px; opacity: 0.75; overflow-wrap: anywhere; }
30
30
  .dshdraw-meta { font-size: 12px; opacity: 0.85; display: flex; flex-wrap: wrap; gap: 12px; }
31
+ .dshdraw-inflight { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 13px; min-height: 20px; }
32
+ .dshdraw-inflight .dshdraw-sep { opacity: 0.45; }
33
+ .dshdraw-inflight-title { font-weight: 500; }
34
+ .dshdraw-inflight-summary { opacity: 0.85; overflow-wrap: anywhere; }
35
+ .dshdraw-inflight-state { font-size: 12px; opacity: 0.7; }
31
36
  .dshdraw-actions { display: flex; gap: 8px; }
32
37
  .dshdraw-button {
33
38
  border: 1px solid var(--dsh-border, #d0d7de); border-radius: 6px; background: transparent;
@@ -7,5 +7,5 @@
7
7
  * @module dsh-draw/version
8
8
  */
9
9
  /** Plugin version; must equal the `version` field in `package.json`. */
10
- export declare const PLUGIN_VERSION = "0.2.16";
10
+ export declare const PLUGIN_VERSION = "0.2.18";
11
11
  //# sourceMappingURL=version.d.ts.map
@@ -7,4 +7,4 @@
7
7
  * @module dsh-draw/version
8
8
  */
9
9
  /** Plugin version; must equal the `version` field in `package.json`. */
10
- export const PLUGIN_VERSION = '0.2.16';
10
+ export const PLUGIN_VERSION = '0.2.18';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-draw",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Unified static-image generation router for DeepSeek Harness: one image_generate tool with standard parameters, config-driven OpenAI-compatible engine routing (OpenAI Images, Zhipu CogView, and any compatible endpoint) with health-aware fallback, durable workspace attachment results, per-session quota accounting, an in-conversation result card, and a Plugins settings panel that stores API keys as credential references.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -89,7 +89,7 @@
89
89
  "attachments:write"
90
90
  ],
91
91
  "compatibility": {
92
- "dshVersions": ["0.1.2-rc.1","0.1.5-rc.2","0.1.7-alpha.2"]
92
+ "dshVersions": ["0.1.2-rc.1","0.1.5-rc.2","0.1.7-alpha.2","0.1.7-rc.1", "0.1.7-rc.2"]
93
93
  },
94
94
  "capability": {
95
95
  "id": "image-generate-tool",
@@ -160,29 +160,30 @@
160
160
  "zod": "^4.4.3"
161
161
  },
162
162
  "devDependencies": {
163
- "@deepseek-ai/dsh-util-values": "0.1.7-alpha.2",
163
+ "@deepseek-ai/dsh-util-values": "0.1.7-rc.2",
164
164
  "@deepseek-ai/cordis": "^4.0.4",
165
165
  "@deepseek-ai/cordis-plugin-include": "^1.0.7",
166
166
  "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
167
- "@deepseek-ai/dsh-agent": "0.1.7-alpha.2",
168
- "@deepseek-ai/dsh-api-remotes": "0.1.7-alpha.2",
169
- "@deepseek-ai/dsh-attachment": "0.1.7-alpha.2",
170
- "@deepseek-ai/dsh-attachment-local": "0.1.7-alpha.2",
171
- "@deepseek-ai/dsh-brand": "0.1.7-alpha.2",
172
- "@deepseek-ai/dsh-client-connection": "0.1.7-alpha.2",
173
- "@deepseek-ai/dsh-client-locale": "0.1.7-alpha.2",
174
- "@deepseek-ai/dsh-client-ui-settings": "0.1.7-alpha.2",
175
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-alpha.2",
176
- "@deepseek-ai/dsh-client-ui-tool": "0.1.7-alpha.2",
177
- "@deepseek-ai/dsh-credentials": "0.1.7-alpha.2",
178
- "@deepseek-ai/dsh-credentials-local": "0.1.7-alpha.2",
179
- "@deepseek-ai/dsh-llm": "0.1.7-alpha.2",
180
- "@deepseek-ai/dsh-session": "0.1.7-alpha.2",
181
- "@deepseek-ai/dsh-tools": "0.1.7-alpha.2",
182
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-alpha.2",
167
+ "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
168
+ "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
169
+ "@deepseek-ai/dsh-attachment": "0.1.7-rc.2",
170
+ "@deepseek-ai/dsh-attachment-local": "0.1.7-rc.2",
171
+ "@deepseek-ai/dsh-brand": "0.1.7-rc.2",
172
+ "@deepseek-ai/dsh-client-connection": "0.1.7-rc.2",
173
+ "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
174
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2",
175
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
176
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.7-rc.2",
177
+ "@deepseek-ai/dsh-credentials": "0.1.7-rc.2",
178
+ "@deepseek-ai/dsh-credentials-local": "0.1.7-rc.2",
179
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
180
+ "@deepseek-ai/dsh-session": "0.1.7-rc.2",
181
+ "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
182
+ "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
183
183
  "@deepseek-ai/schemastery": "^3.18.3",
184
184
  "@types/node": "^22.19.0",
185
185
  "@types/react": "18.3.31",
186
+ "@types/react-dom": "18.3.7",
186
187
  "@vitest/coverage-v8": "4.1.10",
187
188
  "jsdom": "^30.0.1",
188
189
  "oxlint": "0.18.1",
@@ -201,7 +202,8 @@
201
202
  "check:readmes": "node scripts/check-readme-sync.mjs",
202
203
  "prepare": "node scripts/prepare.mjs",
203
204
  "verify:self-contained": "node scripts/verify-self-contained.mjs",
204
- "verify:artifacts": "node scripts/verify-artifacts.mjs"
205
+ "verify:artifacts": "node scripts/verify-artifacts.mjs",
206
+ "verify:host-contract": "node scripts/verify-host-contract.mjs"
205
207
  },
206
208
  "license": "Apache-2.0",
207
209
  "funding": {
@@ -1,8 +1,14 @@
1
1
  /**
2
- * The keyed `image_generate` tool result card: engine/quota facts and the
3
- * regenerate action. The images themselves are the attachment content blocks
4
- * the shell already renders from the tool result, so the card only adds the
5
- * accounting line and the action — it never duplicates image transport.
2
+ * The keyed `image_generate` tool view: engine/quota facts and the regenerate
3
+ * action once the call settles, and an in-flight row of its own while the
4
+ * arguments are still arriving. The images themselves are the attachment
5
+ * content blocks the shell already renders from the tool result, so the card
6
+ * only adds the accounting line and the action — it never duplicates image
7
+ * transport.
8
+ *
9
+ * The Host dispatches this keyed entry in EVERY stage of the call, and a keyed
10
+ * cell that is occupied never reaches the owner fallback — so the earlier
11
+ * stages cannot be answered with nothing without blanking the row.
6
12
  *
7
13
  * The `tool.call.toolview` SlotMap member is declared locally (mirroring the
8
14
  * harness's own ui-tool contract declaration, which its package index does not
@@ -13,20 +19,30 @@
13
19
 
14
20
  import { createElement as h, useState, type ReactElement } from 'react'
15
21
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
16
- import { presentDrawResult, type ToolCallBlock } from './present.ts'
22
+ import { presentDrawResult, toolCallPhase, type ToolCallBlock, type ToolCallPhase } from './present.ts'
17
23
 
18
24
  /**
19
- * Standard owner currency for one keyed tool view (mirror of the harness
20
- * ui-tool contract `ToolCallOwnerProps`, which the package index does not
21
- * re-export — keep the shape identical so the declarations merge).
25
+ * Stage-specific tool data, mirror of the harness ui-tool contract
26
+ * `ToolCallPhaseProps`. The Host dispatches the SAME keyed entry in every
27
+ * stage, so the card's own contract must discriminate on `phase` exactly as
28
+ * the Host's does — a narrower declaration here hides the preparing owner
29
+ * rather than rejecting it (the slot is occupied for every stage).
22
30
  */
23
- export interface ToolCallOwnerProps {
24
- /** Tool call identity, stable across running and settled forms. */
31
+ export type ToolCallPhaseProps =
32
+ | { readonly phase: 'preparing'; readonly block: ToolCallBlock }
33
+ | { readonly phase: 'start'; readonly block: ToolCallBlock }
34
+ | { readonly phase: 'result'; readonly block: ToolCallBlock }
35
+
36
+ /**
37
+ * Common owner currency declared beside the phase share, mirror of the
38
+ * harness ui-tool contract `ToolCallCommonProps` (which the package index
39
+ * does not re-export — keep the shape identical so the declarations merge).
40
+ */
41
+ export interface ToolCallCommonProps {
42
+ /** Tool call identity, stable across all stages. */
25
43
  callId: string
26
44
  /** Wire tool name and keyed dispatch value. */
27
45
  toolName: string
28
- /** Frozen running call or settled result node. */
29
- block: ToolCallBlock
30
46
  /** Session workspace root for relative summaries. */
31
47
  cwd?: string
32
48
  /** Open a tool argument path through the host. */
@@ -35,6 +51,12 @@ export interface ToolCallOwnerProps {
35
51
  inspect?: () => void
36
52
  }
37
53
 
54
+ /**
55
+ * Standard owner currency for one keyed tool view: the common share plus the
56
+ * stage share, mirroring the harness ui-tool contract `ToolCallOwnerProps`.
57
+ */
58
+ export type ToolCallOwnerProps = ToolCallCommonProps & ToolCallPhaseProps
59
+
38
60
  declare module '@deepseek-ai/dsh-client-ui-slots' {
39
61
  interface SlotMap {
40
62
  /** Keyed atomic tool-call view, dispatched by the wire tool name. */
@@ -55,17 +77,55 @@ export type DrawResultCardProps =
55
77
  & InjectFace<DrawResultCardInjected>
56
78
 
57
79
  /**
58
- * The keyed tool result card.
59
- * @param props - owner currency, locale, and injected bindings.
60
- * @returns the card element.
80
+ * The keyed tool view. Dispatched by the wire tool name in EVERY stage of the
81
+ * call, so the card owes a presentation to the in-flight stages too: returning
82
+ * an empty node would leave the row blank, because an occupied keyed cell
83
+ * never reaches the Host's own generic row.
84
+ *
85
+ * @param props - owner currency (common + stage shares), locale, and injected bindings.
86
+ * @returns the in-flight row, or the settled result card.
61
87
  */
62
88
  export function DrawResultCard(props: DrawResultCardProps): ReactElement {
89
+ // The owner's discriminant and the block are two statements of the same fact,
90
+ // and the block is the one the presenter reads — so the card classifies the
91
+ // stage from the block, through the same function the presenter uses. The
92
+ // owner's arm only breaks the tie in the impossible case of a `result` owner
93
+ // carrying a block that is not a settled node.
94
+ const phase = toolCallPhase(props.block)
95
+ if (phase !== 'result') {
96
+ const stage = props.phase === 'result' ? phase : props.phase
97
+ return h('div', { className: 'dshdraw-inflight', 'aria-busy': 'true', 'data-phase': stage },
98
+ h('span', { className: 'dshdraw-inflight-title' }, props.t('row.title')),
99
+ h('span', { className: 'dshdraw-sep', 'aria-hidden': 'true' }, '·'),
100
+ h('span', { className: 'dshdraw-inflight-summary' }, props.toolName),
101
+ h('span', { className: 'dshdraw-inflight-state' }, props.t(inFlightKey(stage))),
102
+ )
103
+ }
104
+ return h(DrawResultCardBody, props)
105
+ }
106
+
107
+ /** Locale key naming the stage of a call that has not settled yet. */
108
+ function inFlightKey(phase: ToolCallPhase): 'row.preparing' | 'row.running' {
109
+ return phase === 'preparing' ? 'row.preparing' : 'row.running'
110
+ }
111
+
112
+ /**
113
+ * The settled result card: engine/quota facts and the regenerate action.
114
+ *
115
+ * @param props - the owner at its `result` stage, locale, and injected bindings.
116
+ * @returns the card element.
117
+ */
118
+ function DrawResultCardBody(props: DrawResultCardProps): ReactElement {
63
119
  const { block, regenerate, t } = props
64
120
  const presented = presentDrawResult(block)
65
121
  const [busy, setBusy] = useState(false)
66
122
  const [failed, setFailed] = useState(false)
67
123
 
68
- if (presented === undefined) return h('div', null)
124
+ if (presented === undefined) {
125
+ // The Host handed this card a settled node it does not own (a foreign call
126
+ // head after window truncation, or an error result) — nothing to add.
127
+ return h('div', null)
128
+ }
69
129
 
70
130
  const runRegenerate = async (): Promise<void> => {
71
131
  if (presented.args === undefined) return