Files
wehub-resource-sync f99010fae1
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
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

324 lines
7.5 KiB
Go

package parser
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
)
type gptmeProviderFactory struct {
def AgentDef
}
func newGptmeProviderFactory(def AgentDef) ProviderFactory {
return gptmeProviderFactory{def: cloneAgentDef(def)}
}
func (f gptmeProviderFactory) Definition() AgentDef {
return cloneAgentDef(f.def)
}
func (f gptmeProviderFactory) Capabilities() Capabilities {
return gptmeProviderCapabilities()
}
func (f gptmeProviderFactory) NewProvider(cfg ProviderConfig) Provider {
cfg = cfg.Clone()
return &gptmeProvider{
ProviderBase: ProviderBase{
Def: cloneAgentDef(f.def),
Caps: gptmeProviderCapabilities(),
Config: cfg,
},
sources: newGptmeSourceSet(cfg.Roots),
}
}
type gptmeProvider struct {
ProviderBase
sources JSONLSourceSet
}
func (p *gptmeProvider) Discover(ctx context.Context) ([]SourceRef, error) {
sources, err := p.sources.Discover(ctx)
if err != nil {
return nil, err
}
return p.filterSources(sources), nil
}
func (p *gptmeProvider) WatchPlan(ctx context.Context) (WatchPlan, error) {
return p.sources.WatchPlan(ctx)
}
func (p *gptmeProvider) SourcesForChangedPath(
ctx context.Context,
req ChangedPathRequest,
) ([]SourceRef, error) {
sources, err := p.sources.SourcesForChangedPath(ctx, req)
if err != nil {
return nil, err
}
filtered := p.filterSources(sources)
if len(filtered) > 0 {
return filtered, nil
}
source, ok := p.sourceForEventPath(req)
if !ok {
return nil, nil
}
return []SourceRef{source}, nil
}
func (p *gptmeProvider) FindSource(
ctx context.Context,
req FindSourceRequest,
) (SourceRef, bool, error) {
if err := ctx.Err(); err != nil {
return SourceRef{}, false, err
}
for _, path := range []string{
req.StoredFilePath,
req.FingerprintKey,
} {
if path == "" {
continue
}
if source, ok, err := p.sourceForExistingPath(ctx, path); err != nil {
return SourceRef{}, false, err
} else if ok {
return source, true, nil
}
}
for _, id := range []string{
req.RawSessionID,
p.rawSessionIDFromFull(req.FullSessionID),
} {
if id == "" {
continue
}
if source, ok, err := p.sourceForSessionID(ctx, id); err != nil {
return SourceRef{}, false, err
} else if ok {
return source, true, nil
}
}
return SourceRef{}, false, nil
}
func (p *gptmeProvider) sourceForExistingPath(
ctx context.Context,
path string,
) (SourceRef, bool, error) {
source, ok, err := p.sources.sourceForPath(ctx, path)
if err != nil {
return SourceRef{}, false, err
}
if ok && p.isSource(source) {
return source, true, nil
}
return SourceRef{}, false, nil
}
func (p *gptmeProvider) sourceForSessionID(
ctx context.Context,
id string,
) (SourceRef, bool, error) {
for _, root := range p.Config.Roots {
path := filepath.Join(root, id, "conversation.jsonl")
if source, ok, err := p.sourceForExistingPath(ctx, path); err != nil {
return SourceRef{}, false, err
} else if ok {
return source, true, nil
}
}
return SourceRef{}, false, nil
}
func (p *gptmeProvider) rawSessionIDFromFull(id string) string {
if id == "" {
return ""
}
_, rawID := StripHostPrefix(id)
if !strings.HasPrefix(rawID, p.Def.IDPrefix) {
return ""
}
return strings.TrimPrefix(rawID, p.Def.IDPrefix)
}
func (p *gptmeProvider) sourceForEventPath(req ChangedPathRequest) (SourceRef, bool) {
if req.Path == "" {
return SourceRef{}, false
}
if req.WatchRoot != "" {
root := filepath.Clean(req.WatchRoot)
if !p.hasRoot(root) {
return SourceRef{}, false
}
return gptmeSourceRef(root, filepath.Clean(req.Path))
}
for _, root := range p.Config.Roots {
if source, ok := gptmeSourceRef(root, filepath.Clean(req.Path)); ok {
return source, true
}
}
return SourceRef{}, false
}
func (p *gptmeProvider) hasRoot(root string) bool {
for _, configured := range p.Config.Roots {
if samePath(configured, root) {
return true
}
}
return false
}
func (p *gptmeProvider) Fingerprint(
ctx context.Context,
source SourceRef,
) (SourceFingerprint, error) {
return p.sources.Fingerprint(ctx, source)
}
func (p *gptmeProvider) Parse(
ctx context.Context,
req ParseRequest,
) (ParseOutcome, error) {
if err := ctx.Err(); err != nil {
return ParseOutcome{}, err
}
path, ok, err := p.sources.pathFromSource(ctx, req.Source)
if err != nil {
return ParseOutcome{}, err
}
if !ok {
return ParseOutcome{}, fmt.Errorf("gptme source path unavailable")
}
machine := firstNonEmptyJSONLString(req.Machine, p.Config.Machine)
sess, msgs, err := p.parseSession(path, machine)
if err != nil {
return ParseOutcome{}, err
}
if sess == nil {
return ParseOutcome{
ResultSetComplete: true,
SkipReason: SkipNoSession,
}, nil
}
if req.Fingerprint.Hash != "" {
sess.File.Hash = req.Fingerprint.Hash
}
return ParseOutcome{
Results: []ParseResultOutcome{{
Result: ParseResult{
Session: *sess,
Messages: msgs,
},
DataVersion: DataVersionCurrent,
}},
ResultSetComplete: true,
}, nil
}
func (p *gptmeProvider) filterSources(sources []SourceRef) []SourceRef {
if len(sources) == 0 {
return nil
}
filtered := sources[:0]
for _, source := range sources {
if p.isSource(source) {
filtered = append(filtered, source)
}
}
return filtered
}
func (p *gptmeProvider) isSource(source SourceRef) bool {
src, ok := source.Opaque.(JSONLSource)
if !ok {
return false
}
return isGptmeConversationPath(src.Root, src.Path)
}
func newGptmeSourceSet(roots []string) JSONLSourceSet {
return NewJSONLSourceSet(AgentGptme, roots,
WithRecursive(),
WithContentHashing(),
WithSymlinkFollowing(),
WithInclude(func(path string, info os.FileInfo) bool {
return !info.IsDir() && filepath.Base(path) == "conversation.jsonl"
}),
WithProjectHint(func(root, path string) string {
sessionID := gptmeSessionIDFromPath(root, path)
if sessionID == "" {
return ""
}
return gptmeProjectFromSessionName(sessionID)
}),
WithSessionIDFromPath(gptmeSessionIDFromPath),
)
}
func gptmeProviderCapabilities() Capabilities {
return Capabilities{
Source: SourceCapabilities{
DiscoverSources: CapabilitySupported,
WatchSources: CapabilitySupported,
ClassifyChangedPath: CapabilitySupported,
FindSource: CapabilitySupported,
CompositeFingerprint: CapabilitySupported,
MultiSessionSource: CapabilityNotApplicable,
PerSessionErrors: CapabilityNotApplicable,
ExcludedSessions: CapabilityNotApplicable,
ForceReplaceOnParse: CapabilityNotApplicable,
},
Content: ContentCapabilities{
FirstMessage: CapabilitySupported,
Model: CapabilitySupported,
PerMessageTokenUsage: CapabilitySupported,
},
}
}
func isGptmeConversationPath(root, path string) bool {
rel, err := filepath.Rel(root, path)
if err != nil {
return false
}
parts := strings.Split(rel, string(filepath.Separator))
return len(parts) == 2 && parts[1] == "conversation.jsonl" &&
parts[0] != "." && parts[0] != ".." && parts[0] != ""
}
func gptmeSessionIDFromPath(root, path string) string {
if !isGptmeConversationPath(root, path) {
return ""
}
return filepath.Base(filepath.Dir(path))
}
func gptmeSourceRef(root, path string) (SourceRef, bool) {
root = filepath.Clean(root)
path = filepath.Clean(path)
if !isGptmeConversationPath(root, path) {
return SourceRef{}, false
}
sessionID := gptmeSessionIDFromPath(root, path)
return SourceRef{
Provider: AgentGptme,
Key: path,
DisplayPath: path,
FingerprintKey: path,
ProjectHint: gptmeProjectFromSessionName(sessionID),
Opaque: JSONLSource{
Root: root,
Path: path,
RelPath: filepath.Join(sessionID, "conversation.jsonl"),
},
}, true
}