Skip to content

Conversation

@clansty
Copy link
Contributor

@clansty clansty commented Jan 10, 2026

拆分的 #678
我们等 SaltifyDev/milky#44 决定

Summary by Sourcery

为转发消息提供可自定义的元数据支持,并在 Milky API 中对外暴露。

New Features:

  • 允许调用方在发送转发的私聊或群聊消息时提供标题、预览、摘要和提示文案等元数据。
  • 在转换流水线中传递转发消息的元数据,以控制卡片来源、摘要、预览和提示文案。

Enhancements:

  • 在生成的转发消息卡片中,使用转发消息的提示文案作为展示描述和提示内容,而不再使用固定的「[聊天记录]」标签。
Original summary in English

Summary by Sourcery

Support customizable metadata for forwarded messages and surface it in Milky APIs.

New Features:

  • Allow callers to provide title, preview, summary, and prompt metadata when sending forwarded private or group messages.
  • Propagate forwarded message metadata through the transform pipeline to control card source, summary, preview, and prompt text.

Enhancements:

  • Use the forwarded message prompt as the displayed description and prompt in generated forward message cards instead of a fixed '[聊天记录]' label.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 10, 2026

审阅者指南

为转发消息增加可自定义元数据(title、preview、summary、prompt)的支持,并通过 API 层一路传递到 forward message encoder,使生成的转发消息载荷和 UI 字段能够反映这些自定义值,而不是使用静态默认值。

通过 API 传递的可自定义转发消息元数据时序图

sequenceDiagram
  actor User
  participant Client as MilkyClient
  participant Api as SendPrivateMessageAPI
  participant Transform as transformOutgoingForwardMessages
  participant Encoder as ForwardMessageEncoder
  participant PMHQ as pmhqUpload
  participant App as AppSendMessage

  User->>Client: Choose forward messages
  User->>Client: Set title, preview, summary, prompt
  Client->>Api: SendPrivateMessage(forwardData)
  Api->>Transform: transformOutgoingForwardMessages(ctx, forwardData.messages, peer, options)
  Transform->>Encoder: new ForwardMessageEncoder(ctx, peer)
  Transform->>Encoder: generate(messages, options)
  Encoder-->>Transform: raw{multiMsgItems, tsum, source, summary, news, prompt}
  Transform-->>Api: raw
  Api->>PMHQ: uploadForward(peer, raw.multiMsgItems)
  PMHQ-->>Api: resid
  Api->>App: sendMessage(ctx, peer, payload with desc=raw.prompt, prompt=raw.prompt)
  App-->>Client: Message sent response
  Client-->>User: UI shows customized forward card metadata
Loading

更新后的转发消息编码元数据流程类图

classDiagram
  class Context
  class Peer
  class OutgoingForwardedMessage

  class ForwardMessageEncoder {
    - Context ctx
    - Peer peer
    - number tsum
    - boolean isGroup
    - any news
    + ForwardMessageEncoder(ctx, peer)
    + render(content)
    + generate(content, options)
  }

  class TransformOutgoingForwardMessages {
    + transformOutgoingForwardMessages(ctx, messages, peer, options)
  }

  class ForwardMetadataOptions {
    <<interface>>
    + string title
    + PreviewItem[] preview
    + string summary
    + string prompt
  }

  class PreviewItem {
    + string text
  }

  class ForwardGenerateResult {
    + any[] multiMsgItems
    + number tsum
    + string source
    + string summary
    + any[] news
    + string prompt
  }

  Context <.. TransformOutgoingForwardMessages : uses
  Peer <.. TransformOutgoingForwardMessages : uses
  OutgoingForwardedMessage <.. TransformOutgoingForwardMessages : uses
  ForwardMetadataOptions <.. TransformOutgoingForwardMessages : uses

  TransformOutgoingForwardMessages ..> ForwardMessageEncoder : creates
  ForwardMessageEncoder ..> ForwardGenerateResult : returns
  ForwardMessageEncoder ..> ForwardMetadataOptions : reads

  ForwardMetadataOptions <.. ForwardMessageEncoder : options
  PreviewItem <.. ForwardMetadataOptions : contains
Loading

文件级变更

Change Details Files
将可选的转发元数据从 API payload 贯通到转发消息转换流程中,并在发送私聊和群聊转发消息时使用这些元数据。
  • 在 SendPrivateMessage 和 SendGroupMessage 中,从 payload.message[0].data 中提取类型化的 forwardData 对象,包括 messages、title、preview、summary 和 prompt 字段。
  • 将提取出的元数据作为 options 对象,与 messages 和 peer 一起传入 transformOutgoingForwardMessages。
  • 使用 transformOutgoingForwardMessages 返回的 prompt 值来填充转发消息元素中的 desc 和 prompt 属性,而不再使用之前硬编码的 '[聊天记录]'。
src/milky/api/message.ts
扩展转发消息转换管道以接收可选元数据,并在编码结果中覆盖默认的 title/summary/preview/prompt 值。
  • 更新 transformOutgoingForwardMessages,使其接收一个可选的 options 对象,包含 title、preview、summary 和 prompt,并将其转发给 ForwardMessageEncoder.generate。
  • 扩展 ForwardMessageEncoder.generate 接收相同的 options,并在提供时使用其中的值覆盖默认的 source、summary、news 和 prompt。
  • 在未提供 options 时保留现有行为作为默认值,从而保持向后兼容性。
src/milky/transform/message/outgoing.ts

提示与命令

与 Sourcery 交互

  • 触发新的代码审阅: 在 pull request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub issue: 在某条审阅评论下回复,要求 Sourcery 根据该评论创建 issue。你也可以在审阅评论中回复 @sourcery-ai issue 来基于该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文任意位置写上 @sourcery-ai summary,即可在指定位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来在任意时间(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 忽略所有 Sourcery 审阅: 在 pull request 中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审阅。特别适用于你希望从零开始新一轮审阅的情况——别忘了随后再评论 @sourcery-ai review 以触发新的审阅!

自定义你的使用体验

访问你的 dashboard 以:

  • 启用或禁用审阅功能,例如 Sourcery 生成的 pull request 摘要、审阅者指南等。
  • 更改审阅语言。
  • 添加、删除或编辑自定义审阅指令。
  • 调整其他审阅设置。

获取帮助

Original review guide in English

Reviewer's Guide

Adds support for customizable metadata (title, preview, summary, prompt) on forwarded messages and wires it through the API layer into the forward message encoder so that these values are reflected in the generated forward message payloads and UI fields instead of static defaults.

Sequence diagram for customizable forward message metadata through API

sequenceDiagram
  actor User
  participant Client as MilkyClient
  participant Api as SendPrivateMessageAPI
  participant Transform as transformOutgoingForwardMessages
  participant Encoder as ForwardMessageEncoder
  participant PMHQ as pmhqUpload
  participant App as AppSendMessage

  User->>Client: Choose forward messages
  User->>Client: Set title, preview, summary, prompt
  Client->>Api: SendPrivateMessage(forwardData)
  Api->>Transform: transformOutgoingForwardMessages(ctx, forwardData.messages, peer, options)
  Transform->>Encoder: new ForwardMessageEncoder(ctx, peer)
  Transform->>Encoder: generate(messages, options)
  Encoder-->>Transform: raw{multiMsgItems, tsum, source, summary, news, prompt}
  Transform-->>Api: raw
  Api->>PMHQ: uploadForward(peer, raw.multiMsgItems)
  PMHQ-->>Api: resid
  Api->>App: sendMessage(ctx, peer, payload with desc=raw.prompt, prompt=raw.prompt)
  App-->>Client: Message sent response
  Client-->>User: UI shows customized forward card metadata
Loading

Class diagram for updated forward message encoding metadata flow

classDiagram
  class Context
  class Peer
  class OutgoingForwardedMessage

  class ForwardMessageEncoder {
    - Context ctx
    - Peer peer
    - number tsum
    - boolean isGroup
    - any news
    + ForwardMessageEncoder(ctx, peer)
    + render(content)
    + generate(content, options)
  }

  class TransformOutgoingForwardMessages {
    + transformOutgoingForwardMessages(ctx, messages, peer, options)
  }

  class ForwardMetadataOptions {
    <<interface>>
    + string title
    + PreviewItem[] preview
    + string summary
    + string prompt
  }

  class PreviewItem {
    + string text
  }

  class ForwardGenerateResult {
    + any[] multiMsgItems
    + number tsum
    + string source
    + string summary
    + any[] news
    + string prompt
  }

  Context <.. TransformOutgoingForwardMessages : uses
  Peer <.. TransformOutgoingForwardMessages : uses
  OutgoingForwardedMessage <.. TransformOutgoingForwardMessages : uses
  ForwardMetadataOptions <.. TransformOutgoingForwardMessages : uses

  TransformOutgoingForwardMessages ..> ForwardMessageEncoder : creates
  ForwardMessageEncoder ..> ForwardGenerateResult : returns
  ForwardMessageEncoder ..> ForwardMetadataOptions : reads

  ForwardMetadataOptions <.. ForwardMessageEncoder : options
  PreviewItem <.. ForwardMetadataOptions : contains
Loading

File-Level Changes

Change Details Files
Plumbs optional forward metadata from API payload into the forward message transformation and uses it when sending private and group forward messages.
  • In SendPrivateMessage and SendGroupMessage, extracts a typed forwardData object from payload.message[0].data including messages, title, preview, summary, and prompt fields.
  • Passes the extracted metadata into transformOutgoingForwardMessages as an options object alongside the messages and peer.
  • Uses the prompt value returned from transformOutgoingForwardMessages to populate the desc and prompt properties in the forwarded message element instead of the previous hard-coded '[聊天记录]'.
src/milky/api/message.ts
Extends the forward message transformation pipeline to accept optional metadata and override default title/summary/preview/prompt values in the encoded result.
  • Updates transformOutgoingForwardMessages to accept an optional options object containing title, preview, summary, and prompt, and forwards it to ForwardMessageEncoder.generate.
  • Extends ForwardMessageEncoder.generate to accept the same options and override default values for source, summary, news, and prompt using the provided options when present.
  • Keeps existing behavior as defaults when options are not supplied, preserving backward compatibility.
src/milky/transform/message/outgoing.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你好——我发现了两个问题,并留下了一些总体反馈:

  • 内联的 forwardData 类型在 SendPrivateMessageSendGroupMessage 中都被重复定义;建议提取一个共享的类型/接口(例如 ForwardMessagePayload),以保持结构一致并便于更新。
  • transformOutgoingForwardMessagesoptions 对象结构在函数签名和 ForwardMessageEncoder.generate 中都重复了一遍;建议定义一个单独导出的 options 类型,以减少重复和潜在的偏差。
  • 现在 transformOutgoingForwardMessages 会返回 prompt,你可以考虑返回一个强类型对象(例如包含 prompt 的接口),并在消费 raw 的地方复用该类型,从而避免对未类型化属性的访问。
给 AI 代理的提示
请根据这次代码审查中的评论进行修改:

## 总体评论
- 内联的 `forwardData` 类型在 `SendPrivateMessage``SendGroupMessage` 中都被重复定义;建议提取一个共享的类型/接口(例如 `ForwardMessagePayload`),以保持结构一致并便于更新。
- `transformOutgoingForwardMessages``options` 对象结构在函数签名和 `ForwardMessageEncoder.generate` 中都重复了一遍;建议定义一个单独导出的 options 类型,以减少重复和潜在的偏差。
- 现在 `transformOutgoingForwardMessages` 会返回 `prompt`,你可以考虑返回一个强类型对象(例如包含 `prompt` 的接口),并在消费 `raw` 的地方复用该类型,从而避免对未类型化属性的访问。

## 单独评论

### 评论 1
<location> `src/milky/api/message.ts:47-53` </location>
<code_context>

     let result: RawMessage
     if (payload.message[0].type === 'forward') {
+      const forwardData = payload.message[0].data as {
+        messages: OutgoingForwardedMessage[]
+        title?: string
+        preview?: { text: string }[]
+        summary?: string
+        prompt?: string
+      }
       const raw = await transformOutgoingForwardMessages(
         ctx,
</code_context>

<issue_to_address>
**suggestion:** 转发消息 payload 的结构在私聊和群聊处理器中都被重复并手动类型断言;建议提取一个共享的类型/辅助方法。

`payload.message[0].data` 的这个结构性类型断言现在在 `SendPrivateMessage``SendGroupMessage` 中都出现了一次,因此只要转发 payload 的结构发生变更(例如新增字段),就必须在多个地方同步修改。定义一个共享的 `ForwardPayload` 类型和辅助方法(例如 `getForwardPayload(payload.message[0])`)可以把这部分结构集中管理,降低两个处理器实现产生不一致的风险。

推荐实现方式:

```typescript
    type ForwardPayload = {
      messages: OutgoingForwardedMessage[]
      title?: string
      preview?: { text: string }[]
      summary?: string
      prompt?: string
    }

    const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
      return message.data as ForwardPayload
    }

    let result: RawMessage

```

```typescript
    if (payload.message[0].type === 'forward') {
      const forwardData = getForwardPayload(payload.message[0])
      const raw = await transformOutgoingForwardMessages(
        ctx,
        forwardData.messages,
        peer,
        {
          title: forwardData.title,
          preview: forwardData.preview,
          summary: forwardData.summary,
          prompt: forwardData.prompt
        }

```

你提到这个结构性类型断言在 `SendPrivateMessage``SendGroupMessage` 中都被重复使用。请在群聊处理器中做同样的替换:

1. 找到群聊消息处理器中与之对应的代码块,即检查 `payload.message[0].type === 'forward'` 并对 `payload.message[0].data` 进行相同结构的内联类型断言的地方。
2. 用 `const forwardData = getForwardPayload(payload.message[0])` 来替换这段内联断言,其余使用方式保持不变(例如 `forwardData.messages``forwardData.title` 等)。

由于 `ForwardPayload``getForwardPayload` 只在第一次使用附近定义一次,它们会被这两个处理器共享,而无需在其他地方再做额外修改。
</issue_to_address>

### 评论 2
<location> `src/milky/transform/message/outgoing.ts:90-94` </location>
<code_context>
 export async function transformOutgoingForwardMessages(
   ctx: Context,
   messages: OutgoingForwardedMessage[],
-  peer: Peer
+  peer: Peer,
+  options?: {
+    title?: string
+    preview?: { text: string }[]
</code_context>

<issue_to_address>
**suggestion:** `options` 的结构在 `transformOutgoingForwardMessages``ForwardMessageEncoder.generate` 中都被重复定义;建议提取一个共享的类型别名。

`{ title?, preview?, summary?, prompt? }` 这个 options 对象分别在函数和类方法中单独定义,一旦属性发生变化,它们可能会在没有任何提示的情况下产生偏差。请提取一个共享类型(例如 `ForwardRenderOptions`),并在两个签名中使用它,以保持一致并提高可读性。

推荐实现方式:

```typescript
type ForwardRenderOptions = {
  title?: string
  preview?: { text: string }[]
  summary?: string
  prompt?: string
}

export async function transformOutgoingForwardMessages(
  ctx: Context,
  messages: OutgoingForwardedMessage[],
  peer: Peer,
  options?: ForwardRenderOptions
) {
  const encoder = new ForwardMessageEncoder(ctx, peer)
  return await encoder.generate(messages, options)
}

class ForwardMessageEncoder {

```

请在本文件中相应地更新 `ForwardMessageEncoder.generate` 方法签名以使用共享类型:

- 将它的参数从内联的 options 结构替换为 `options?: ForwardRenderOptions`(或者如果目前还没有该参数,则添加它)。
- 删除任何与 `{ title?, preview?, summary?, prompt? }` 相关的重复 options 类型定义,统一改为引用 `ForwardRenderOptions`,以确保该函数和类方法始终保持同步。
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的代码审查有帮助,请考虑分享给他人 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续审查。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The inline forwardData type is duplicated in both SendPrivateMessage and SendGroupMessage; consider extracting a shared type/interface (e.g. ForwardMessagePayload) to keep the shape consistent and easier to update.
  • The options object shape for transformOutgoingForwardMessages is repeated in both the function signature and ForwardMessageEncoder.generate; defining a single exported options type would reduce duplication and potential drift.
  • Now that transformOutgoingForwardMessages returns prompt, you might consider returning a strongly typed object (e.g. an interface including prompt) and reusing that type where raw is consumed to avoid untyped property access.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The inline `forwardData` type is duplicated in both `SendPrivateMessage` and `SendGroupMessage`; consider extracting a shared type/interface (e.g. `ForwardMessagePayload`) to keep the shape consistent and easier to update.
- The `options` object shape for `transformOutgoingForwardMessages` is repeated in both the function signature and `ForwardMessageEncoder.generate`; defining a single exported options type would reduce duplication and potential drift.
- Now that `transformOutgoingForwardMessages` returns `prompt`, you might consider returning a strongly typed object (e.g. an interface including `prompt`) and reusing that type where `raw` is consumed to avoid untyped property access.

## Individual Comments

### Comment 1
<location> `src/milky/api/message.ts:47-53` </location>
<code_context>

     let result: RawMessage
     if (payload.message[0].type === 'forward') {
+      const forwardData = payload.message[0].data as {
+        messages: OutgoingForwardedMessage[]
+        title?: string
+        preview?: { text: string }[]
+        summary?: string
+        prompt?: string
+      }
       const raw = await transformOutgoingForwardMessages(
         ctx,
</code_context>

<issue_to_address>
**suggestion:** The forward message payload shape is duplicated and manually cast in both private and group handlers; consider extracting a shared type/helper.

This structural cast of `payload.message[0].data` is now duplicated in both `SendPrivateMessage` and `SendGroupMessage`, so any change to the forward payload (e.g., new fields) must be updated in multiple places. A shared `ForwardPayload` type and helper (e.g., `getForwardPayload(payload.message[0])`) would centralize this shape and reduce the risk of the handlers drifting out of sync.

Suggested implementation:

```typescript
    type ForwardPayload = {
      messages: OutgoingForwardedMessage[]
      title?: string
      preview?: { text: string }[]
      summary?: string
      prompt?: string
    }

    const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
      return message.data as ForwardPayload
    }

    let result: RawMessage

```

```typescript
    if (payload.message[0].type === 'forward') {
      const forwardData = getForwardPayload(payload.message[0])
      const raw = await transformOutgoingForwardMessages(
        ctx,
        forwardData.messages,
        peer,
        {
          title: forwardData.title,
          preview: forwardData.preview,
          summary: forwardData.summary,
          prompt: forwardData.prompt
        }

```

You mentioned this structural cast is duplicated in both `SendPrivateMessage` and `SendGroupMessage`. Apply the same replacement in the group handler:

1. Locate the analogous block in the group message handler where `payload.message[0].type === 'forward'` is checked and `payload.message[0].data` is cast inline to the same shape.
2. Replace that inline cast with `const forwardData = getForwardPayload(payload.message[0])` and keep the rest of the usage (`forwardData.messages`, `forwardData.title`, etc.) the same.

Because `ForwardPayload` and `getForwardPayload` are defined once near the first usage, they will be shared by both handlers without further changes.
</issue_to_address>

### Comment 2
<location> `src/milky/transform/message/outgoing.ts:90-94` </location>
<code_context>
 export async function transformOutgoingForwardMessages(
   ctx: Context,
   messages: OutgoingForwardedMessage[],
-  peer: Peer
+  peer: Peer,
+  options?: {
+    title?: string
+    preview?: { text: string }[]
</code_context>

<issue_to_address>
**suggestion:** The `options` shape is duplicated in both `transformOutgoingForwardMessages` and `ForwardMessageEncoder.generate`; consider a shared type alias.

The `{ title?, preview?, summary?, prompt? }` options object is defined separately in the function and the class method, so they can silently diverge if properties change. Please extract a shared type (e.g., `ForwardRenderOptions`) and use it in both signatures to keep them aligned and easier to read.

Suggested implementation:

```typescript
type ForwardRenderOptions = {
  title?: string
  preview?: { text: string }[]
  summary?: string
  prompt?: string
}

export async function transformOutgoingForwardMessages(
  ctx: Context,
  messages: OutgoingForwardedMessage[],
  peer: Peer,
  options?: ForwardRenderOptions
) {
  const encoder = new ForwardMessageEncoder(ctx, peer)
  return await encoder.generate(messages, options)
}

class ForwardMessageEncoder {

```

Update the `ForwardMessageEncoder.generate` method signature elsewhere in this file to also use the shared type:

- Change its parameter from an inline options shape to `options?: ForwardRenderOptions` (or add the parameter if it doesn’t exist yet).
- Remove any duplicated options type definitions related to `{ title?, preview?, summary?, prompt? }` and reference `ForwardRenderOptions` instead, so both the function and the class method stay in sync.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +47 to +53
const forwardData = payload.message[0].data as {
messages: OutgoingForwardedMessage[]
title?: string
preview?: { text: string }[]
summary?: string
prompt?: string
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: 转发消息 payload 的结构在私聊和群聊处理器中都被重复并手动类型断言;建议提取一个共享的类型/辅助方法。

payload.message[0].data 的这个结构性类型断言现在在 SendPrivateMessageSendGroupMessage 中都出现了一次,因此只要转发 payload 的结构发生变更(例如新增字段),就必须在多个地方同步修改。定义一个共享的 ForwardPayload 类型和辅助方法(例如 getForwardPayload(payload.message[0]))可以把这部分结构集中管理,降低两个处理器实现产生不一致的风险。

推荐实现方式:

    type ForwardPayload = {
      messages: OutgoingForwardedMessage[]
      title?: string
      preview?: { text: string }[]
      summary?: string
      prompt?: string
    }

    const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
      return message.data as ForwardPayload
    }

    let result: RawMessage
    if (payload.message[0].type === 'forward') {
      const forwardData = getForwardPayload(payload.message[0])
      const raw = await transformOutgoingForwardMessages(
        ctx,
        forwardData.messages,
        peer,
        {
          title: forwardData.title,
          preview: forwardData.preview,
          summary: forwardData.summary,
          prompt: forwardData.prompt
        }

你提到这个结构性类型断言在 SendPrivateMessageSendGroupMessage 中都被重复使用。请在群聊处理器中做同样的替换:

  1. 找到群聊消息处理器中与之对应的代码块,即检查 payload.message[0].type === 'forward' 并对 payload.message[0].data 进行相同结构的内联类型断言的地方。
  2. const forwardData = getForwardPayload(payload.message[0]) 来替换这段内联断言,其余使用方式保持不变(例如 forwardData.messagesforwardData.title 等)。

由于 ForwardPayloadgetForwardPayload 只在第一次使用附近定义一次,它们会被这两个处理器共享,而无需在其他地方再做额外修改。

Original comment in English

suggestion: The forward message payload shape is duplicated and manually cast in both private and group handlers; consider extracting a shared type/helper.

This structural cast of payload.message[0].data is now duplicated in both SendPrivateMessage and SendGroupMessage, so any change to the forward payload (e.g., new fields) must be updated in multiple places. A shared ForwardPayload type and helper (e.g., getForwardPayload(payload.message[0])) would centralize this shape and reduce the risk of the handlers drifting out of sync.

Suggested implementation:

    type ForwardPayload = {
      messages: OutgoingForwardedMessage[]
      title?: string
      preview?: { text: string }[]
      summary?: string
      prompt?: string
    }

    const getForwardPayload = (message: { data: unknown }): ForwardPayload => {
      return message.data as ForwardPayload
    }

    let result: RawMessage
    if (payload.message[0].type === 'forward') {
      const forwardData = getForwardPayload(payload.message[0])
      const raw = await transformOutgoingForwardMessages(
        ctx,
        forwardData.messages,
        peer,
        {
          title: forwardData.title,
          preview: forwardData.preview,
          summary: forwardData.summary,
          prompt: forwardData.prompt
        }

You mentioned this structural cast is duplicated in both SendPrivateMessage and SendGroupMessage. Apply the same replacement in the group handler:

  1. Locate the analogous block in the group message handler where payload.message[0].type === 'forward' is checked and payload.message[0].data is cast inline to the same shape.
  2. Replace that inline cast with const forwardData = getForwardPayload(payload.message[0]) and keep the rest of the usage (forwardData.messages, forwardData.title, etc.) the same.

Because ForwardPayload and getForwardPayload are defined once near the first usage, they will be shared by both handlers without further changes.

Comment on lines 90 to +94
export async function transformOutgoingForwardMessages(
ctx: Context,
messages: OutgoingForwardedMessage[],
peer: Peer
peer: Peer,
options?: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: options 的结构在 transformOutgoingForwardMessagesForwardMessageEncoder.generate 中都被重复定义;建议提取一个共享的类型别名。

{ title?, preview?, summary?, prompt? } 这个 options 对象分别在函数和类方法中单独定义,一旦属性发生变化,它们可能会在没有任何提示的情况下产生偏差。请提取一个共享类型(例如 ForwardRenderOptions),并在两个签名中使用它,以保持一致并提高可读性。

推荐实现方式:

type ForwardRenderOptions = {
  title?: string
  preview?: { text: string }[]
  summary?: string
  prompt?: string
}

export async function transformOutgoingForwardMessages(
  ctx: Context,
  messages: OutgoingForwardedMessage[],
  peer: Peer,
  options?: ForwardRenderOptions
) {
  const encoder = new ForwardMessageEncoder(ctx, peer)
  return await encoder.generate(messages, options)
}

class ForwardMessageEncoder {

请在本文件中相应地更新 ForwardMessageEncoder.generate 方法签名以使用共享类型:

  • 将它的参数从内联的 options 结构替换为 options?: ForwardRenderOptions(或者如果目前还没有该参数,则添加它)。
  • 删除任何与 { title?, preview?, summary?, prompt? } 相关的重复 options 类型定义,统一改为引用 ForwardRenderOptions,以确保该函数和类方法始终保持同步。
Original comment in English

suggestion: The options shape is duplicated in both transformOutgoingForwardMessages and ForwardMessageEncoder.generate; consider a shared type alias.

The { title?, preview?, summary?, prompt? } options object is defined separately in the function and the class method, so they can silently diverge if properties change. Please extract a shared type (e.g., ForwardRenderOptions) and use it in both signatures to keep them aligned and easier to read.

Suggested implementation:

type ForwardRenderOptions = {
  title?: string
  preview?: { text: string }[]
  summary?: string
  prompt?: string
}

export async function transformOutgoingForwardMessages(
  ctx: Context,
  messages: OutgoingForwardedMessage[],
  peer: Peer,
  options?: ForwardRenderOptions
) {
  const encoder = new ForwardMessageEncoder(ctx, peer)
  return await encoder.generate(messages, options)
}

class ForwardMessageEncoder {

Update the ForwardMessageEncoder.generate method signature elsewhere in this file to also use the shared type:

  • Change its parameter from an inline options shape to options?: ForwardRenderOptions (or add the parameter if it doesn’t exist yet).
  • Remove any duplicated options type definitions related to { title?, preview?, summary?, prompt? } and reference ForwardRenderOptions instead, so both the function and the class method stay in sync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant