mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
d3bf6bc90a
* feat(channel): add DingTalk channel adapter - Add DingTalk channel adapter (`internal/channel/adapters/dingtalk/`) using dingtalk-stream-sdk-go, supporting inbound message receiving and outbound text/markdown reply - Register DingTalk adapter in cmd/agent and cmd/memoh - Add go.mod dependency: github.com/memohai/dingtalk-stream-sdk-go - Add Dingtalk and Wecom SVG icons and Vue components to @memohai/icon - Refactor existing icon components to remove redundant inline wrappers - Add `channelTypeDisplayName` util for consistent channel label resolution - Add DingTalk/WeCom i18n entries (en/zh) for types and typesShort - Extend channel-icon, bot-channels, channel-settings-panel to support dingtalk/wecom - Use channelTypeDisplayName in profile page to replace ad-hoc i18n lookup * fix(channel,attachment): channel quality refactor & attachment pipeline fixes Channel module: - Fix RemoveAdapter not cleaning connectionMeta (stale status leak) - Fix preparedAttachmentTypeFromMime misclassifying image/gif - Fix sleepWithContext time.After goroutine/timer leak - Export IsDataURL/IsHTTPURL/IsDataPath, dedup across packages - Cache OutboundPolicy in managerOutboundStream to avoid repeated lookups - Split OutboundAttachmentStore: extract ContainerAttachmentIngester interface - Add ManagerOption funcs (WithInboundQueueSize, WithInboundWorkers, WithRefreshInterval) - Add thread-safety docs on OutboundStream / managerOutboundStream - Add debug logs on successful send/edit paths - Expand outbound_prepare_test.go with 21 new cases - Convert no-receiver adapter helpers to package-level funcs; drop unused params DingTalk adapter: - Implement AttachmentResolver: download inbound media via /v1.0/robot/messageFiles/download - Fix pure-image inbound messages failing due to missing resolver Attachment pipeline: - Fix images invisible to LLM in pipeline (DCP) path: inject InlineImages into last user message when cfg.Query is empty - Fix public_url fallback: skip direct URL-to-LLM when ContentHash is set, always prefer inlined persisted asset - Inject path: carry ImageParts through agent.InjectMessage; inline persisted attachments in resolver inject goroutine so mid-stream images reach the model - Fix ResolveMime for images: prefer content-sniffed MIME over platform-declared MIME (fixes Feishu sending image/png header for actual JPEG content → API 400)
110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
package channel
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
)
|
|
|
|
// PreparedAttachmentKind identifies how an attachment should be delivered.
|
|
type PreparedAttachmentKind string
|
|
|
|
const (
|
|
PreparedAttachmentNativeRef PreparedAttachmentKind = "native_ref"
|
|
PreparedAttachmentPublicURL PreparedAttachmentKind = "public_url"
|
|
PreparedAttachmentUpload PreparedAttachmentKind = "upload"
|
|
)
|
|
|
|
// PreparedAttachment is the adapter-facing attachment model after preparation.
|
|
type PreparedAttachment struct {
|
|
Logical Attachment
|
|
Kind PreparedAttachmentKind
|
|
NativeRef string
|
|
PublicURL string
|
|
Name string
|
|
Mime string
|
|
Size int64
|
|
// Open must return a fresh reader each time so retries are safe.
|
|
Open func(ctx context.Context) (io.ReadCloser, error)
|
|
}
|
|
|
|
// PreparedMessage is the adapter-facing form of a Message.
|
|
type PreparedMessage struct {
|
|
Message Message
|
|
Attachments []PreparedAttachment
|
|
}
|
|
|
|
// LogicalMessage converts the prepared message back to the logical channel message.
|
|
func (m PreparedMessage) LogicalMessage() Message {
|
|
msg := m.Message
|
|
if len(m.Attachments) == 0 {
|
|
return msg
|
|
}
|
|
attachments := make([]Attachment, 0, len(m.Attachments))
|
|
for _, att := range m.Attachments {
|
|
attachments = append(attachments, att.Logical)
|
|
}
|
|
msg.Attachments = attachments
|
|
return msg
|
|
}
|
|
|
|
// PreparedOutboundMessage is the adapter-facing form of OutboundMessage.
|
|
type PreparedOutboundMessage struct {
|
|
Target string
|
|
Message PreparedMessage
|
|
}
|
|
|
|
// LogicalMessage converts the prepared outbound message back to the logical model.
|
|
func (m PreparedOutboundMessage) LogicalMessage() OutboundMessage {
|
|
return OutboundMessage{
|
|
Target: m.Target,
|
|
Message: m.Message.LogicalMessage(),
|
|
}
|
|
}
|
|
|
|
// PreparedStreamFinalizePayload is the adapter-facing stream final payload.
|
|
type PreparedStreamFinalizePayload struct {
|
|
Message PreparedMessage
|
|
}
|
|
|
|
// PreparedStreamEvent is the adapter-facing form of StreamEvent.
|
|
type PreparedStreamEvent struct {
|
|
Type StreamEventType
|
|
Status StreamStatus
|
|
Delta string
|
|
Final *PreparedStreamFinalizePayload
|
|
Error string
|
|
ToolCall *StreamToolCall
|
|
Phase StreamPhase
|
|
Attachments []PreparedAttachment
|
|
Reactions []ReactRequest
|
|
Speeches []SpeechRequest
|
|
Metadata map[string]any
|
|
}
|
|
|
|
// LogicalEvent converts the prepared stream event back to the logical model.
|
|
func (e PreparedStreamEvent) LogicalEvent() StreamEvent {
|
|
result := StreamEvent{
|
|
Type: e.Type,
|
|
Status: e.Status,
|
|
Delta: e.Delta,
|
|
Error: e.Error,
|
|
ToolCall: e.ToolCall,
|
|
Phase: e.Phase,
|
|
Reactions: e.Reactions,
|
|
Speeches: e.Speeches,
|
|
Metadata: e.Metadata,
|
|
}
|
|
if len(e.Attachments) > 0 {
|
|
result.Attachments = make([]Attachment, 0, len(e.Attachments))
|
|
for _, att := range e.Attachments {
|
|
result.Attachments = append(result.Attachments, att.Logical)
|
|
}
|
|
}
|
|
if e.Final != nil {
|
|
result.Final = &StreamFinalizePayload{
|
|
Message: e.Final.Message.LogicalMessage(),
|
|
}
|
|
}
|
|
return result
|
|
}
|