dsh-connect-minimaxcode 0.6.0 → 0.6.1

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/README.en.md CHANGED
@@ -357,7 +357,7 @@ A picture *can* reach a conversation: the host publishes
357
357
  store → durable reference" on its own. But the attachment entry point is a
358
358
  browser `File` upload and there is **no attachment library** to pick from,
359
359
  so it still has to travel as part of the next prompt. That last hop is
360
- planned for the next release.
360
+ done in 0.6.1 (see below).
361
361
 
362
362
  ### About token refresh
363
363
 
@@ -384,6 +384,12 @@ The OAuth token is valid for about an hour and is refreshed automatically by the
384
384
 
385
385
  ## Changelog
386
386
 
387
+ ### 0.6.1 (2026-11-11)
388
+
389
+ - **A generated picture can now go straight into the conversation**: the composer tool row gained a **Generate an image** entry. Write a prompt, press generate, and the picture is sent as part of this turn — through the host's own road (`ctx.sessions.binding(id).session.prompt(content, mode, signal, requestId)`, the very call the send button makes), so the host validates it, commits it as a durable attachment and treats it as an ordinary message.
390
+ - A host that exposes no session face gets a plain "could not send it to the conversation" instead of a silent failure, and the plugin page's panel keeps working regardless.
391
+ - Added `tests/generate-dock.spec.ts` (4 cases) pinning: the entry is registered in the tool row, it declares the `sessions` service, the picture travels as `{type:"image", mediaType, data}` (raw base64, no data-url prefix) together with the prompt, and the message shown when there is no session to send to. All 252 cases pass.
392
+
387
393
  ### 0.6.0 (2026-11-11)
388
394
 
389
395
  - **Added H3 image and video generation**: the plugin page gained a **Generate** panel that drives MiniMax's `MiniMax-H3-Image` (text to image) and `MiniMax-H3` (video) directly. It goes through the endpoints the open platform's documentation gives (`POST /v1/images/generations`, `POST /v2/video_generation` plus polling `/v2/query/video_generation/{id}`), and **no key has to be configured**: the generation endpoints want the account's own open-platform key, and the plugin gets it by asking `token_plan_credit` with the OAuth bearer — the endpoint it already reads for the balance.
package/README.md CHANGED
@@ -368,7 +368,7 @@ GET https://api.minimax.cn/v2/query/video_generation/{id} # 轮询任务
368
368
  1. **不能像普通模型那样出现在模型选择器里。** DSH 宿主的 `ctx.llm` 只有对话适配器这一个注册点(`registerAdapter`),没有图像 provider 的挂载处;模型描述符 `LlmModelInfo` 也没有「输出模态」字段,选择器无从区分图像模型。pi-ai 自己虽有图像生成 API(`ImagesProvider` / `generateImages`),但 DSH 从不构造 `ImagesModels`,插件自己 `new` 一个也没有人会读。这三点都是宿主层面的事,插件改不动。
369
369
  2. **视频在 DSH 侧目前没有承载。** `pi-ai`、`dsh-llm`、`dsh-attachment`、`dsh-llm-pi-ai` 四个包里关于 video 的声明各为零处 —— 没有视频内容块、没有附件媒体类型、没有渲染。视频因此只能生成后在面板里播放与下载,不能像图片那样进对话。
370
370
 
371
- 图片进对话是可行的:宿主公开了 `ctx.attachments.admitPromptContent()`,插件可以独立完成「生成 → 落库 → 拿到持久引用」;但附件入口是浏览器 `File` 上传,**没有附件库**可供挑选,因此要进对话仍需作为下一轮 prompt 的一部分提交。这一步的宿主接线待下一版完成。
371
+ 图片进对话是可行的:宿主公开了 `ctx.attachments.admitPromptContent()`,插件可以独立完成「生成 → 落库 → 拿到持久引用」;但附件入口是浏览器 `File` 上传,**没有附件库**可供挑选,因此要进对话仍需作为下一轮 prompt 的一部分提交。这一步已由 0.6.1 完成(见下)。
372
372
 
373
373
  ### 关于续期
374
374
 
@@ -395,6 +395,12 @@ OAuth 令牌有效期约 1 小时,由客户端自动续期。插件**只在自
395
395
 
396
396
  ## 更新记录
397
397
 
398
+ ### 0.6.1(2026-11-11)
399
+
400
+ - **生成的图片现在能直接进对话**:输入框工具行新增一个「生成图片」入口,写下提示词点生成,图片会作为**这一轮消息的一部分**发给模型 —— 走的是宿主自己那条路(`ctx.sessions.binding(id).session.prompt(content, mode, signal, requestId)`,与输入框按发送键完全相同),因此图片会被宿主校验、落成持久附件,并按普通消息参与对话。
401
+ - 宿主若不暴露会话句柄,该入口会明确提示「无法发送到对话」,而不是静默失败;插件页的生成面板仍可照常使用。
402
+ - 新增 `tests/generate-dock.spec.ts`(4 个用例)钉住:入口注册在工具行、声明了 `sessions` 服务、生成的图以 `{type:"image", mediaType, data}` 的形态(原始 base64,不带 data-url 前缀)随提示词一起发出、以及没有会话句柄时的提示。全量 252 个用例通过。
403
+
398
404
  ### 0.6.0(2026-11-11)
399
405
 
400
406
  - **新增 H3 图像与视频生成**:插件页多了一个「生成」面板,可直接调用 MiniMax 的 `MiniMax-H3-Image`(文生图)与 `MiniMax-H3`(视频生成)。走的是开放平台官方文档给出的接口(`POST /v1/images/generations`、`POST /v2/video_generation` + 轮询 `/v2/query/video_generation/{id}`),**用户无需配置任何密钥** —— 生成接口要的是账户自己的开放平台密钥,插件用 OAuth 令牌问本来就在读的 `token_plan_credit` 就能拿到。
package/lib/client.js CHANGED
@@ -94,6 +94,10 @@
94
94
  generateSaved: '已存入 DSH 附件库',
95
95
  generateFailed: '生成失败',
96
96
  generateHint: '生成走 MiniMax 的开放平台,用的是你账号自己的额度。图片会并存进 DSH 的附件库。',
97
+ generateToChat: '生成图片',
98
+ generateToChatHint: '生成的图片会作为这一轮消息的一部分发给模型。',
99
+ generateSent: '图片已发送到对话',
100
+ generateSendFailed: '无法发送到对话,请到插件页生成后下载。',
97
101
  };
98
102
  const en = {
99
103
  title: 'MiniMax Code credits',
@@ -161,6 +165,10 @@
161
165
  generateSaved: 'Saved to DSH attachments',
162
166
  generateFailed: 'Generation failed',
163
167
  generateHint: 'Generation runs on MiniMax\'s open platform with your own account\'s allowance. Images are also stored in DSH\'s attachment store.',
168
+ generateToChat: 'Generate an image',
169
+ generateToChatHint: 'The generated picture is sent as part of this turn.',
170
+ generateSent: 'The picture is on its way to the conversation',
171
+ generateSendFailed: 'Could not send it to the conversation; generate on the plugin page and download it instead.',
164
172
  };
165
173
  const loader = globalThis.__ModuleLoader__;
166
174
  if (loader === undefined)
@@ -170,6 +178,13 @@
170
178
  factory(require) {
171
179
  const React = require('react');
172
180
  const h = React.createElement;
181
+ /**
182
+ * The host's client context, once the plugin is applied.
183
+ *
184
+ * The components are closures over the module, not over the context, so
185
+ * this is how they reach the host's services.
186
+ */
187
+ let clientContext;
173
188
  /** Format a credit amount without pretending to more precision. */
174
189
  function credits(value, digits) {
175
190
  if (value === undefined || !Number.isFinite(value))
@@ -419,6 +434,110 @@
419
434
  return null;
420
435
  return h(CreditPill, props);
421
436
  }
437
+ /**
438
+ * Generate a picture and send it to the conversation.
439
+ *
440
+ * The composer already knows how to send a message with an image in it,
441
+ * and the picture the upstream produces is an ordinary image file, so
442
+ * the dock does the same thing the composer does: it asks the session
443
+ * to accept a turn whose content is the picture plus the prompt.
444
+ *
445
+ * Nothing here is required of the host beyond what the composer itself
446
+ * uses. A host that does not expose the session face simply gets the
447
+ * message that says so, and the plugin page still offers the picture.
448
+ */
449
+ function GenerateDock(props) {
450
+ const t = props.t ?? ((key, params) => {
451
+ const template = zh[key] ?? key;
452
+ return template.replace(/\\{(\\w+)\\}/g, (_match, name) => String(params?.[name] ?? ''));
453
+ });
454
+ const [open, setOpen] = React.useState(false);
455
+ const [prompt, setPrompt] = React.useState('');
456
+ const [busy, setBusy] = React.useState(false);
457
+ const [note, setNote] = React.useState('');
458
+ /**
459
+ * The session's own send path.
460
+ *
461
+ * `binding(id).session.prompt(content, mode, signal, requestId)` is
462
+ * what the composer calls when the person presses send, so a picture
463
+ * sent this way is a normal turn.
464
+ */
465
+ const sessionFace = () => {
466
+ const sessionId = props.sessionId;
467
+ if (sessionId === undefined)
468
+ return undefined;
469
+ const binding = clientContext?.sessions?.binding?.(sessionId);
470
+ const send = binding?.session?.prompt;
471
+ return typeof send === 'function' ? { prompt: send } : undefined;
472
+ };
473
+ /** Turn a data URL into the parts the session's admission accepts. */
474
+ const imagePart = (dataUrl, mediaType) => {
475
+ const comma = dataUrl.indexOf(',');
476
+ if (comma < 0)
477
+ return undefined;
478
+ return { type: 'image', mediaType, data: dataUrl.slice(comma + 1) };
479
+ };
480
+ async function send() {
481
+ const text = prompt.trim();
482
+ if (text === '')
483
+ return;
484
+ setBusy(true);
485
+ setNote('');
486
+ try {
487
+ const response = await fetch(GENERATE_PATH, {
488
+ method: 'POST',
489
+ headers: { 'content-type': 'application/json' },
490
+ body: JSON.stringify({ kind: 'image', prompt: text, area: '1K' }),
491
+ });
492
+ const body = await response.json();
493
+ if (body.ok !== true || body.dataUrl === undefined) {
494
+ setNote(body.error ?? t('generateFailed'));
495
+ return;
496
+ }
497
+ const part = imagePart(body.dataUrl, body.mediaType ?? 'image/jpeg');
498
+ const face = sessionFace();
499
+ if (part === undefined || face === undefined) {
500
+ setNote(t('generateSendFailed'));
501
+ return;
502
+ }
503
+ await face.prompt([part, { type: 'text', text }], 'queue', undefined, `minimax-h3-${Date.now()}`);
504
+ setPrompt('');
505
+ setOpen(false);
506
+ setNote(t('generateSent'));
507
+ }
508
+ catch {
509
+ setNote(t('generateSendFailed'));
510
+ }
511
+ finally {
512
+ setBusy(false);
513
+ }
514
+ }
515
+ if (!open) {
516
+ return h('button', {
517
+ type: 'button',
518
+ style: buttonStyle,
519
+ title: t('generateToChatHint'),
520
+ onClick: () => { setOpen(true); setNote(''); },
521
+ }, t('generateToChat'));
522
+ }
523
+ return h('div', { style: dockStyle, 'data-minimax-generate-dock': true }, h('input', {
524
+ value: prompt,
525
+ placeholder: t('generatePrompt'),
526
+ style: dockInputStyle,
527
+ disabled: busy,
528
+ onChange: (event) => setPrompt(event.target.value),
529
+ }), h('button', {
530
+ type: 'button',
531
+ style: buttonStyle,
532
+ disabled: busy || prompt.trim() === '',
533
+ onClick: () => { void send(); },
534
+ }, busy ? t('generateRunning') : t('generateRun')), h('button', {
535
+ type: 'button',
536
+ style: buttonStyle,
537
+ disabled: busy,
538
+ onClick: () => { setOpen(false); setPrompt(''); setNote(''); },
539
+ }, '✕'), note === '' ? null : h('span', { style: mutedStyle }, note));
540
+ }
422
541
  /** Ask the host to do something, and let the card read the result. */
423
542
  async function act(action, body) {
424
543
  try {
@@ -694,6 +813,21 @@
694
813
  opacity: 0.8,
695
814
  padding: '1px 8px',
696
815
  };
816
+ const dockStyle = {
817
+ display: 'flex',
818
+ alignItems: 'center',
819
+ gap: '4px',
820
+ };
821
+ const dockInputStyle = {
822
+ width: '160px',
823
+ padding: '2px 6px',
824
+ borderRadius: '999px',
825
+ border: '1px solid var(--dsh-composer-dock-inset, currentColor)',
826
+ background: 'transparent',
827
+ color: 'inherit',
828
+ font: 'inherit',
829
+ fontSize: '12px',
830
+ };
697
831
  const detailStyle = {
698
832
  position: 'absolute',
699
833
  bottom: 'calc(100% + 6px)',
@@ -828,9 +962,15 @@
828
962
  borderRadius: '8px',
829
963
  };
830
964
  return {
831
- inject: ['slots', 'locale'],
965
+ // The sessions service is what lets a generated picture be delivered
966
+ // into the conversation the composer belongs to.
967
+ inject: ['slots', 'locale', 'sessions'],
832
968
  apply(ctx) {
833
969
  const context = ctx;
970
+ // The components need the host's sessions service to deliver a
971
+ // generated picture into the conversation, so it is kept where the
972
+ // components can reach it.
973
+ clientContext = context;
834
974
  // Visible text goes through the host's locale service.
835
975
  context.effect(() => context.locale.register(NAMESPACE, { zh, en }), `${BUNDLE_NAME}: locale`);
836
976
  // The composer and plugin-page slots carry no translator in their
@@ -850,6 +990,15 @@
850
990
  locale: NAMESPACE,
851
991
  inject: () => ({ t }),
852
992
  }, CreditPillGate));
993
+ // The image dock sits beside the balance: it is the same provider's
994
+ // capability, and it needs the session the composer belongs to.
995
+ context.slots.inject('conversation.input.left', () => context.slots.register({
996
+ name: 'conversation.input.left',
997
+ id: 'minimax-generate',
998
+ order: 5,
999
+ locale: NAMESPACE,
1000
+ inject: () => ({ t }),
1001
+ }, GenerateDock));
853
1002
  // The plugin's own page, keyed by the package name, where the
854
1003
  // balance and the expiry schedule are spelled out.
855
1004
  context.slots.inject('plugins.bundle.config', () => context.slots.register({
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-minimaxcode",
3
3
  "displayName": "DSH Connect MiniMax Code",
4
4
  "description": "把本机已登录的 MiniMax Code 桌面 App 模型接入 DeepSeek Harness:零配置、Anthropic Messages 兼容、动态模型目录,并显示积分余额与到期 — bring MiniMax Code desktop app models into DSH with a dynamic model catalog and a credit balance with expiry details.",
5
- "version": "0.6.0",
5
+ "version": "0.6.1",
6
6
  "author": "JeffTsuiCUG",
7
7
  "contributors": [
8
8
  {