Skip to content

Commit 7161cbe

Browse files
committed
feat(i18n): add new localization keys for result cards and telemetry labels
- Introduced new keys for omitted results and search card emitted in result card labels. - Added telemetry labels for host actions including action time, cache hit, candidate count, and more across multiple locales. - Implemented new messages for result card search details, including backend source and fallback reason. - Enhanced SSEClient to handle final chunk fallback logic for improved error handling and response validation.
1 parent a4f554d commit 7161cbe

26 files changed

Lines changed: 1995 additions & 38 deletions

server/internal/bootstrap/runtime_channel_contract_reply_context.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ func runtimeChannelReplyMetadata(metadata map[string]interface{}) map[string]int
1515
return nil
1616
}
1717
var out map[string]interface{}
18-
for _, key := range []string{"context_token", "session_id"} {
18+
for _, key := range []string{"context_token", "session_id", "target_user_id"} {
1919
value, ok := metadata[key]
2020
if !ok {
2121
continue
2222
}
2323
if out == nil {
24-
out = make(map[string]interface{}, 2)
24+
out = make(map[string]interface{}, 3)
2525
}
2626
out[key] = value
2727
}

server/internal/bootstrap/runtime_channel_contract_test.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ func TestBindRouteRuntimeChannelHandler_PrefersAutoreplyThenFallsBackToChat(t *t
153153
UserID: "user-1",
154154
Content: "hello",
155155
Metadata: map[string]interface{}{
156-
"context_token": "ctx-123",
157-
"session_id": "session-456",
156+
"context_token": "ctx-123",
157+
"session_id": "session-456",
158+
"target_user_id": "wxid-peer-123",
158159
},
159160
})
160161
if err != nil {
@@ -169,6 +170,9 @@ func TestBindRouteRuntimeChannelHandler_PrefersAutoreplyThenFallsBackToChat(t *t
169170
if got := resp.Metadata["session_id"]; got != "session-456" {
170171
t.Fatalf("session_id = %v, want %q", got, "session-456")
171172
}
173+
if got := resp.Metadata["target_user_id"]; got != "wxid-peer-123" {
174+
t.Fatalf("target_user_id = %v, want %q", got, "wxid-peer-123")
175+
}
172176
if chat.calls != 0 {
173177
t.Fatalf("chat calls = %d, want 0", chat.calls)
174178
}
@@ -186,8 +190,9 @@ func TestBindRouteRuntimeChannelHandler_PrefersAutoreplyThenFallsBackToChat(t *t
186190
UserID: "user-1",
187191
Content: "need help",
188192
Metadata: map[string]interface{}{
189-
"context_token": "ctx-789",
190-
"session_id": "session-999",
193+
"context_token": "ctx-789",
194+
"session_id": "session-999",
195+
"target_user_id": "wxid-peer-789",
191196
},
192197
})
193198
if err != nil {
@@ -202,6 +207,9 @@ func TestBindRouteRuntimeChannelHandler_PrefersAutoreplyThenFallsBackToChat(t *t
202207
if got := resp.Metadata["session_id"]; got != "session-999" {
203208
t.Fatalf("session_id = %v, want %q", got, "session-999")
204209
}
210+
if got := resp.Metadata["target_user_id"]; got != "wxid-peer-789" {
211+
t.Fatalf("target_user_id = %v, want %q", got, "wxid-peer-789")
212+
}
205213
if chat.calls != 1 {
206214
t.Fatalf("chat calls = %d, want 1", chat.calls)
207215
}

server/internal/channel/wechatilink/channel.go

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"io"
1414
"net/http"
15+
"runtime/debug"
1516
"strconv"
1617
"strings"
1718
"sync"
@@ -27,8 +28,11 @@ const (
2728
channelName = "wechat_ilink"
2829
channelType = "wechat_ilink"
2930
iLinkBotAPI = "/ilink/bot"
31+
iLinkAppID = "bot"
3032

3133
iLinkMessageTypeUser = 1
34+
iLinkMessageTypeBot = 2
35+
iLinkMessageStateFin = 2
3236
iLinkItemTypeText = 1
3337
iLinkItemTypeImage = 2
3438
iLinkItemTypeVoice = 3
@@ -201,8 +205,13 @@ func (c *Channel) Send(ctx context.Context, msg channel.OutgoingMessage) error {
201205
}
202206

203207
req := iLinkSendMessageRequest{
208+
BaseInfo: buildILinkBaseInfo(),
204209
Msg: iLinkOutgoingMessage{
205-
ToUserID: msg.ChatID,
210+
FromUserID: "",
211+
ToUserID: c.resolveILinkTargetUserID(msg),
212+
ClientID: generateILinkClientID(),
213+
MessageType: iLinkMessageTypeBot,
214+
MessageState: iLinkMessageStateFin,
206215
ContextToken: c.resolveILinkContextToken(msg),
207216
ItemList: []iLinkMessageItem{
208217
{
@@ -303,7 +312,8 @@ type iLinkResponseEnvelope struct {
303312
}
304313

305314
type iLinkGetUpdatesRequest struct {
306-
GetUpdatesBuf string `json:"get_updates_buf"`
315+
GetUpdatesBuf string `json:"get_updates_buf"`
316+
BaseInfo iLinkBaseInfo `json:"base_info"`
307317
}
308318

309319
type iLinkGetUpdatesResponse struct {
@@ -314,11 +324,16 @@ type iLinkGetUpdatesResponse struct {
314324
}
315325

316326
type iLinkSendMessageRequest struct {
317-
Msg iLinkOutgoingMessage `json:"msg"`
327+
BaseInfo iLinkBaseInfo `json:"base_info"`
328+
Msg iLinkOutgoingMessage `json:"msg"`
318329
}
319330

320331
type iLinkOutgoingMessage struct {
332+
FromUserID string `json:"from_user_id"`
321333
ToUserID string `json:"to_user_id"`
334+
ClientID string `json:"client_id"`
335+
MessageType int `json:"message_type"`
336+
MessageState int `json:"message_state"`
322337
ContextToken string `json:"context_token,omitempty"`
323338
ItemList []iLinkMessageItem `json:"item_list"`
324339
}
@@ -348,6 +363,10 @@ type iLinkTextItem struct {
348363
Text string `json:"text"`
349364
}
350365

366+
type iLinkBaseInfo struct {
367+
ChannelVersion string `json:"channel_version"`
368+
}
369+
351370
type iLinkNamedItem struct {
352371
FileName string `json:"file_name,omitempty"`
353372
Name string `json:"filename,omitempty"`
@@ -435,7 +454,10 @@ func (c *Channel) handleILinkIncomingMessages(messages []iLinkMessage) {
435454

436455
func (c *Channel) ilinkGetUpdates(ctx context.Context, cursor string) (iLinkGetUpdatesResponse, error) {
437456
var resp iLinkGetUpdatesResponse
438-
if err := c.postILinkJSON(ctx, "getupdates", iLinkGetUpdatesRequest{GetUpdatesBuf: cursor}, &resp); err != nil {
457+
if err := c.postILinkJSON(ctx, "getupdates", iLinkGetUpdatesRequest{
458+
GetUpdatesBuf: cursor,
459+
BaseInfo: buildILinkBaseInfo(),
460+
}, &resp); err != nil {
439461
return resp, err
440462
}
441463
if err := validateILinkResponse(resp.iLinkResponseEnvelope); err != nil {
@@ -463,6 +485,8 @@ func (c *Channel) postILinkJSON(ctx context.Context, endpoint string, payload an
463485
req.Header.Set("AuthorizationType", "ilink_bot_token")
464486
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(c.config.BotToken))
465487
req.Header.Set("X-WECHAT-UIN", c.ensureILinkUINHeader())
488+
req.Header.Set("iLink-App-Id", iLinkAppID)
489+
req.Header.Set("iLink-App-ClientVersion", strconv.FormatUint(buildILinkClientVersion(resolveILinkChannelVersion()), 10))
466490

467491
client := c.httpClient
468492
if client == nil {
@@ -502,6 +526,62 @@ func resolveILinkBotBaseURL(raw string) string {
502526
return baseURL + iLinkBotAPI
503527
}
504528

529+
func buildILinkBaseInfo() iLinkBaseInfo {
530+
return iLinkBaseInfo{ChannelVersion: resolveILinkChannelVersion()}
531+
}
532+
533+
func resolveILinkChannelVersion() string {
534+
if info, ok := debug.ReadBuildInfo(); ok {
535+
version := strings.TrimSpace(info.Main.Version)
536+
if version != "" && version != "(devel)" {
537+
return version
538+
}
539+
}
540+
return "unknown"
541+
}
542+
543+
func buildILinkClientVersion(version string) uint64 {
544+
version = strings.TrimSpace(strings.TrimPrefix(version, "v"))
545+
if version == "" || version == "unknown" {
546+
return 0
547+
}
548+
parts := strings.SplitN(version, ".", 3)
549+
if len(parts) < 3 {
550+
return 0
551+
}
552+
major, err := strconv.Atoi(parts[0])
553+
if err != nil {
554+
return 0
555+
}
556+
minor, err := strconv.Atoi(parts[1])
557+
if err != nil {
558+
return 0
559+
}
560+
patchPart := parts[2]
561+
for i, ch := range patchPart {
562+
if ch < '0' || ch > '9' {
563+
patchPart = patchPart[:i]
564+
break
565+
}
566+
}
567+
if patchPart == "" {
568+
return 0
569+
}
570+
patch, err := strconv.Atoi(patchPart)
571+
if err != nil {
572+
return 0
573+
}
574+
return uint64((major << 16) | (minor << 8) | patch)
575+
}
576+
577+
func generateILinkClientID() string {
578+
var raw [12]byte
579+
if _, err := rand.Read(raw[:]); err != nil {
580+
return fmt.Sprintf("zimaos-ilink-%d", time.Now().UnixNano())
581+
}
582+
return "zimaos-ilink-" + base64.RawURLEncoding.EncodeToString(raw[:])
583+
}
584+
505585
func validateILinkResponse(resp iLinkResponseEnvelope) error {
506586
if resp.Ret != 0 {
507587
if strings.TrimSpace(resp.ErrMsg) != "" {
@@ -545,6 +625,15 @@ func (c *Channel) resolveILinkContextToken(msg channel.OutgoingMessage) string {
545625
return c.ilinkContextTokens[msg.ChatID]
546626
}
547627

628+
func (c *Channel) resolveILinkTargetUserID(msg channel.OutgoingMessage) string {
629+
if msg.Metadata != nil {
630+
if raw, ok := msg.Metadata["target_user_id"].(string); ok && strings.TrimSpace(raw) != "" {
631+
return strings.TrimSpace(raw)
632+
}
633+
}
634+
return strings.TrimSpace(msg.ChatID)
635+
}
636+
548637
func (c *Channel) storeILinkContextToken(chatID, token string) {
549638
chatID = strings.TrimSpace(chatID)
550639
token = strings.TrimSpace(token)
@@ -591,9 +680,10 @@ func (c *Channel) convertILinkMessage(msg iLinkMessage) (channel.Message, bool)
591680
Attachments: attachments,
592681
Timestamp: timestamp,
593682
Metadata: map[string]interface{}{
594-
"context_token": msg.ContextToken,
595-
"session_id": msg.SessionID,
596-
"to_user_id": msg.ToUserID,
683+
"context_token": msg.ContextToken,
684+
"session_id": msg.SessionID,
685+
"target_user_id": fromUserID,
686+
"to_user_id": msg.ToUserID,
597687
},
598688
}, true
599689
}

0 commit comments

Comments
 (0)