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
233 lines
5.6 KiB
Go
233 lines
5.6 KiB
Go
package parser
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
var _ Provider = (*piProvider)(nil)
|
|
|
|
type piProviderFactory struct {
|
|
def AgentDef
|
|
}
|
|
|
|
func newPiProviderFactory(def AgentDef) ProviderFactory {
|
|
return piProviderFactory{def: cloneAgentDef(def)}
|
|
}
|
|
|
|
func (f piProviderFactory) Definition() AgentDef {
|
|
return cloneAgentDef(f.def)
|
|
}
|
|
|
|
func (f piProviderFactory) Capabilities() Capabilities {
|
|
return piProviderCapabilities()
|
|
}
|
|
|
|
func (f piProviderFactory) NewProvider(cfg ProviderConfig) Provider {
|
|
cfg = cfg.Clone()
|
|
return &piProvider{
|
|
ProviderBase: ProviderBase{
|
|
Def: cloneAgentDef(f.def),
|
|
Caps: piProviderCapabilities(),
|
|
Config: cfg,
|
|
},
|
|
sources: newPiSourceSet(f.def.Type, cfg.Roots),
|
|
}
|
|
}
|
|
|
|
type piProvider struct {
|
|
ProviderBase
|
|
sources DirectoryJSONLSourceSet
|
|
}
|
|
|
|
func (p *piProvider) Discover(ctx context.Context) ([]SourceRef, error) {
|
|
sources, err := p.sources.Discover(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.filterDiscoveredSources(sources), nil
|
|
}
|
|
|
|
func (p *piProvider) WatchPlan(ctx context.Context) (WatchPlan, error) {
|
|
return p.sources.WatchPlan(ctx)
|
|
}
|
|
|
|
func (p *piProvider) SourcesForChangedPath(
|
|
ctx context.Context,
|
|
req ChangedPathRequest,
|
|
) ([]SourceRef, error) {
|
|
sources, err := p.sources.SourcesForChangedPath(ctx, req)
|
|
if err != nil || len(sources) == 0 {
|
|
return sources, err
|
|
}
|
|
if jsonlMissingPathFallbackAllowed(req) {
|
|
return sources, nil
|
|
}
|
|
return p.filterDiscoveredSources(sources), nil
|
|
}
|
|
|
|
func (p *piProvider) FindSource(
|
|
ctx context.Context,
|
|
req FindSourceRequest,
|
|
) (SourceRef, bool, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return SourceRef{}, false, err
|
|
}
|
|
req = ProviderFindRequestWithRawSessionID(p.Def, req)
|
|
for _, path := range []string{
|
|
req.StoredFilePath,
|
|
req.FingerprintKey,
|
|
} {
|
|
if path == "" {
|
|
continue
|
|
}
|
|
if source, ok, err := p.sources.sourceForPath(ctx, path); err != nil {
|
|
return SourceRef{}, false, err
|
|
} else if ok {
|
|
return source, true, nil
|
|
}
|
|
}
|
|
if req.RawSessionID == "" || !IsValidSessionID(req.RawSessionID) {
|
|
return SourceRef{}, false, nil
|
|
}
|
|
for _, root := range p.Config.Roots {
|
|
source, ok, err := p.sourceForSessionID(ctx, root, req.RawSessionID)
|
|
if err != nil || ok {
|
|
return source, ok, err
|
|
}
|
|
}
|
|
return SourceRef{}, false, nil
|
|
}
|
|
|
|
func (p *piProvider) sourceForSessionID(
|
|
ctx context.Context,
|
|
root string,
|
|
sessionID string,
|
|
) (SourceRef, bool, error) {
|
|
entries, err := os.ReadDir(root)
|
|
if err != nil {
|
|
return SourceRef{}, false, nil
|
|
}
|
|
target := sessionID + ".jsonl"
|
|
for _, entry := range entries {
|
|
if err := ctx.Err(); err != nil {
|
|
return SourceRef{}, false, err
|
|
}
|
|
if !isDirOrSymlink(entry, root) {
|
|
continue
|
|
}
|
|
candidate := filepath.Join(root, entry.Name(), target)
|
|
source, ok, err := p.sources.sourceForPath(ctx, candidate)
|
|
if err != nil {
|
|
return SourceRef{}, false, err
|
|
}
|
|
if ok {
|
|
return source, true, nil
|
|
}
|
|
}
|
|
return SourceRef{}, false, nil
|
|
}
|
|
|
|
func (p *piProvider) Fingerprint(
|
|
ctx context.Context,
|
|
source SourceRef,
|
|
) (SourceFingerprint, error) {
|
|
return p.sources.Fingerprint(ctx, source)
|
|
}
|
|
|
|
func (p *piProvider) 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("pi source path unavailable")
|
|
}
|
|
machine := firstNonEmptyJSONLString(req.Machine, p.Config.Machine)
|
|
sess, msgs, err := p.parseSession(path, req.Source.ProjectHint, 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 *piProvider) filterDiscoveredSources(sources []SourceRef) []SourceRef {
|
|
filtered := sources[:0]
|
|
for _, source := range sources {
|
|
src, ok := source.Opaque.(JSONLSource)
|
|
if !ok || !IsPiSessionFile(src.Path) {
|
|
continue
|
|
}
|
|
filtered = append(filtered, source)
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func newPiSourceSet(agent AgentType, roots []string) DirectoryJSONLSourceSet {
|
|
return NewDirectoryJSONLSourceSet(agent, roots,
|
|
WithSymlinkFollowing(),
|
|
WithIncludePath(isPiSourcePath),
|
|
WithProjectHint(func(root, path string) string { return "" }),
|
|
WithSessionIDFromPath(piSessionIDFromPath),
|
|
// Pi/OMP 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 isPiSourcePath(root, path string) bool {
|
|
return strings.HasSuffix(filepath.Base(path), ".jsonl")
|
|
}
|
|
|
|
func piSessionIDFromPath(root, path string) string {
|
|
if !isPiSourcePath(root, path) {
|
|
return ""
|
|
}
|
|
return strings.TrimSuffix(filepath.Base(path), ".jsonl")
|
|
}
|
|
|
|
func piProviderCapabilities() Capabilities {
|
|
return Capabilities{
|
|
Source: jsonlFileProviderSourceCapabilities(),
|
|
Content: ContentCapabilities{
|
|
FirstMessage: CapabilitySupported,
|
|
SessionName: CapabilitySupported,
|
|
Cwd: CapabilitySupported,
|
|
Relationships: CapabilitySupported,
|
|
Thinking: CapabilitySupported,
|
|
ToolCalls: CapabilitySupported,
|
|
ToolResults: CapabilitySupported,
|
|
PerMessageTokenUsage: CapabilitySupported,
|
|
Model: CapabilitySupported,
|
|
},
|
|
}
|
|
}
|