File size: 4,670 Bytes
7c5ed63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { describe, it, expect } from 'vitest';
import { resolveBlobRefUrl, isSafeBlobHash, rehydrateWireEntries } from '../../src/lib/blob-resolver';

describe('blob-resolver', () => {
  describe('resolveBlobRefUrl', () => {
    it('converts a well-formed blobref into a relative route by default', () => {
      const url = resolveBlobRefUrl(
        'blobref:image/png;abc123def456',
        'sess-1',
        'main',
      );
      expect(url).toBe(
        '/api/sessions/sess-1/blobs/abc123def456?agent=main&mime=image%2Fpng',
      );
    });

    it('returns an absolute URL when baseUrl is provided', () => {
      const url = resolveBlobRefUrl(
        'blobref:image/png;abc123def456',
        'sess-1',
        'main',
        'http://localhost:3001',
      );
      expect(url).toBe(
        'http://localhost:3001/api/sessions/sess-1/blobs/abc123def456?agent=main&mime=image%2Fpng',
      );
    });

    it('returns non-blobref URLs unchanged', () => {
      expect(resolveBlobRefUrl('https://example.com/x.png', 's', 'a')).toBe(
        'https://example.com/x.png',
      );
      expect(resolveBlobRefUrl('data:image/png;base64,abc', 's', 'a')).toBe(
        'data:image/png;base64,abc',
      );
    });

    it('returns malformed blobrefs unchanged', () => {
      expect(resolveBlobRefUrl('blobref:nosemicolon', 's', 'a')).toBe(
        'blobref:nosemicolon',
      );
      expect(resolveBlobRefUrl('blobref:image/png;', 's', 'a')).toBe(
        'blobref:image/png;',
      );
    });
  });

  describe('isSafeBlobHash', () => {
    it('accepts a 64-char hex string', () => {
      expect(isSafeBlobHash('a'.repeat(64))).toBe(true);
      expect(isSafeBlobHash('0'.repeat(64))).toBe(true);
      expect(isSafeBlobHash('f'.repeat(64))).toBe(true);
    });

    it('rejects non-hex, wrong length, and path-traversal strings', () => {
      expect(isSafeBlobHash('')).toBe(false);
      expect(isSafeBlobHash('a'.repeat(63))).toBe(false);
      expect(isSafeBlobHash('a'.repeat(65))).toBe(false);
      expect(isSafeBlobHash('x'.repeat(64))).toBe(false);
      expect(isSafeBlobHash('../etc/passwd')).toBe(false);
      expect(isSafeBlobHash('a'.repeat(64) + '\n')).toBe(false);
    });
  });

  describe('rehydrateWireEntries', () => {
    it('mutates entry.data but leaves entry.raw untouched', () => {
      const data: Record<string, unknown> = {
        type: 'turn.prompt',
        input: [
          {
            type: 'image_url',
            imageUrl: { url: 'blobref:image/png;hashA' },
          },
        ],
      };
      const raw = JSON.parse(JSON.stringify(data));
      const entries = [{ lineNo: 1, data: data as any, raw }];

      rehydrateWireEntries(entries, 'sess-1', 'main');

      expect((entries[0]!.data as any).input[0].imageUrl.url).toBe(
        '/api/sessions/sess-1/blobs/hashA?agent=main&mime=image%2Fpng',
      );
      expect((entries[0]!.raw as any).input[0].imageUrl.url).toBe(
        'blobref:image/png;hashA',
      );
    });

    it('handles audio_url, video_url, and nested tool result parts', () => {
      const data: Record<string, unknown> = {
        type: 'context.append_loop_event',
        event: {
          type: 'tool.result',
          result: {
            output: [
              {
                type: 'audio_url',
                audioUrl: { url: 'blobref:audio/wav;hashB' },
              },
            ],
          },
        },
      };
      const entries = [{ lineNo: 1, data: data as any, raw: {} }];

      rehydrateWireEntries(entries, 'sess-2', 'sub-1');

      const parts = (entries[0]!.data as any).event.result.output;
      expect(parts[0].audioUrl.url).toBe(
        '/api/sessions/sess-2/blobs/hashB?agent=sub-1&mime=audio%2Fwav',
      );
    });

    it('resolves blobrefs to absolute URLs when baseUrl is provided', () => {
      const data: Record<string, unknown> = {
        type: 'turn.prompt',
        input: [
          {
            type: 'image_url',
            imageUrl: { url: 'blobref:image/png;hashC' },
          },
        ],
      };
      const entries = [{ lineNo: 1, data: data as any, raw: {} }];

      rehydrateWireEntries(entries, 'sess-3', 'main', 'http://localhost:3001');

      expect((entries[0]!.data as any).input[0].imageUrl.url).toBe(
        'http://localhost:3001/api/sessions/sess-3/blobs/hashC?agent=main&mime=image%2Fpng',
      );
    });

    it('ignores records without media URLs', () => {
      const data = { type: 'config.update', cwd: '/tmp' };
      const entries = [{ lineNo: 1, data: data as any, raw: {} }];
      rehydrateWireEntries(entries, 's', 'a');
      expect(entries[0]!.data).toEqual(data);
    });
  });
});