Files
wehub-resource-sync f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

146 lines
4.2 KiB
Go

package parser
import (
"context"
"path/filepath"
"strings"
)
// Kimi stores each session as a wire.jsonl transcript under a per-workspace
// directory, with subagent transcripts nested under an "agents" subdirectory.
// It is a directory-of-files provider: discovery, watching, change
// classification, and fingerprinting come from JSONLSourceSet. The ParseFile
// option makes that source set a full SourceSet so it rides the generic
// factory; RawSessionIDSourceFiles reconstructs the wire.jsonl path from a
// colon-joined raw ID, which the standard filename-stem lookup cannot match.
func newKimiProviderFactory(def AgentDef) ProviderFactory {
return NewSourceSetFactory(
def,
kimiProviderCapabilities(),
func(cfg ProviderConfig) SourceSet { return newKimiSourceSet(cfg.Roots) },
)
}
func newKimiSourceSet(roots []string) JSONLSourceSet {
return NewJSONLSourceSet(AgentKimi, roots,
WithRecursive(),
WithSymlinkFollowing(),
WithIncludePath(isKimiSourcePath),
WithProjectHint(kimiProjectHintFromPath),
WithSessionIDFromPath(func(root, path string) string {
if !isKimiSourcePath(root, path) {
return ""
}
return kimiSessionIDFromPath(path)
}),
WithRawSessionIDSourceFiles(kimiRawSessionIDSourceFiles),
WithParseFile(kimiParseFile),
// Kimi persisted a full-file content hash (file_hash) in the legacy
// per-agent parse. Without this the provider fingerprint hash is empty
// and a resync clears the stored file_hash to NULL.
WithContentHashing(),
)
}
func kimiParseFile(
_ context.Context, path string, req ParseRequest,
) ([]ParseResult, []string, error) {
sess, msgs, err := parseKimiSession(path, req.Source.ProjectHint, req.Machine)
if err != nil {
return nil, nil, err
}
if sess == nil {
return nil, nil, nil
}
if req.Fingerprint.Hash != "" {
sess.File.Hash = req.Fingerprint.Hash
}
// Kimi sessions with only session-level token aggregates emit a
// session-level usage event (parseKimiSession sets sess.UsageEvents);
// carry it through so the cost engine can price the session.
return []ParseResult{{
Session: *sess,
Messages: msgs,
UsageEvents: sess.UsageEvents,
}}, nil, nil
}
// kimiRawSessionIDSourceFiles reconstructs wire.jsonl candidate paths from a
// colon-joined raw ID. A two-part ID maps to <root>/<workspace>/<session>/
// wire.jsonl; a three-part ID adds the agents/ subagent layout
// <root>/<workspace>/<session>/agents/<agent>/wire.jsonl.
func kimiRawSessionIDSourceFiles(roots []string, rawID string) []string {
parts := strings.Split(rawID, ":")
if !kimiIDComponentsValid(parts...) {
return nil
}
var candidates []string
for _, root := range roots {
if root == "" {
continue
}
switch len(parts) {
case 2:
candidates = append(
candidates,
filepath.Join(root, parts[0], parts[1], "wire.jsonl"),
)
case 3:
candidates = append(candidates, filepath.Join(
root, parts[0], parts[2], "agents", parts[1], "wire.jsonl",
))
}
}
return candidates
}
func isKimiSourcePath(root, path string) bool {
parts, ok := kimiSourceRelParts(root, path)
if !ok || len(parts) == 0 || parts[len(parts)-1] != "wire.jsonl" {
return false
}
switch len(parts) {
case 3:
return kimiIDComponentsValid(parts[0], parts[1])
case 5:
return parts[2] == "agents" &&
kimiIDComponentsValid(parts[0], parts[1], parts[3])
default:
return false
}
}
func kimiProjectHintFromPath(root, path string) string {
parts, ok := kimiSourceRelParts(root, path)
if !ok || len(parts) == 0 {
return ""
}
return DecodeKimiProjectDir(parts[0])
}
func kimiSourceRelParts(root, path string) ([]string, bool) {
rel, err := filepath.Rel(filepath.Clean(root), filepath.Clean(path))
if err != nil {
return nil, false
}
parts := strings.Split(rel, string(filepath.Separator))
for _, part := range parts {
if part == "" || part == "." || part == ".." {
return nil, false
}
}
return parts, true
}
func kimiProviderCapabilities() Capabilities {
return Capabilities{
Source: jsonlFileProviderSourceCapabilities(),
Content: ContentCapabilities{
FirstMessage: CapabilitySupported,
Thinking: CapabilitySupported,
ToolCalls: CapabilitySupported,
ToolResults: CapabilitySupported,
},
}
}