• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 21933693261

12 Feb 2026 04:34AM UTC coverage: 27.702% (-0.7%) from 28.372%
21933693261

push

github

web-flow
feat(ai): real Anthropic/OpenAI streaming + agent routing (#117)

* feat(ai): add AI client abstraction with Anthropic and OpenAI implementations

Create provider-agnostic AIClient interface with types for messages and
streaming chunks. Implement AnthropicClient using @anthropic-ai/sdk with
message streaming via the stream() API, and OpenAIClient using the openai
SDK with async iterator streaming. Add AIClientFactory for provider-based
client creation. Includes comprehensive unit tests for all components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ai): replace ChatService mock with real AI streaming

Remove simulateAgentResponse() and getAgentResponsePlaceholder() mock
methods. Integrate AIClientFactory with CredentialService to resolve the
user's AI provider and API key from secure storage. Stream real AI
responses through the existing message delta event system. Add error
handling that displays helpful messages when no API key is configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ai): add agent-specific system prompts for response routing

Create AgentPrompts module with specialized system prompts for each agent
type: Architect (system design), Implementer (code generation), Reviewer
(code review), and Tester (test writing). Wire ChatService to use
agent-specific prompts when calling the AI client, giving each agent a
distinct personality and area of expertise.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

399 of 2256 branches covered (17.69%)

Branch coverage included in aggregate %.

36 of 73 new or added lines in 5 files covered. (49.32%)

204 existing lines in 13 files now uncovered.

1075 of 3065 relevant lines covered (35.07%)

7.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/code/DiffViewer.tsx
1
/**
2
 * DiffViewer Component
3
 *
4
 * Displays code diffs with line-by-line highlighting.
5
 * Supports unified and split view modes.
6
 */
7

8
import { useState } from 'react';
9
import { Pressable, ScrollView, Text, View } from 'react-native';
10
import { ChevronDownIcon } from '@/components/icons';
11
import { organicBorderRadius } from '@/lib/organic-styles';
12

13
interface DiffLine {
14
  type: 'add' | 'remove' | 'context';
15
  content: string;
16
  oldLineNumber?: number;
17
  newLineNumber?: number;
18
}
19

20
interface DiffViewerProps {
21
  /** Old file content */
22
  oldContent?: string;
23
  /** New file content */
24
  newContent?: string;
25
  /** Pre-parsed diff lines */
26
  diff?: DiffLine[];
27
  /** File path */
28
  filename?: string;
29
  /** View mode */
30
  viewMode?: 'unified' | 'split';
31
  /** Show line numbers */
32
  showLineNumbers?: boolean;
33
}
34

35
function parseDiff(oldContent: string, newContent: string): DiffLine[] {
36
  // Simple line-by-line diff (in production, use a proper diff library)
37
  const oldLines = oldContent.split('\n');
×
38
  const newLines = newContent.split('\n');
×
39
  const result: DiffLine[] = [];
×
40

41
  let oldIndex = 0;
×
42
  let newIndex = 0;
×
43

44
  while (oldIndex < oldLines.length || newIndex < newLines.length) {
×
45
    if (oldIndex >= oldLines.length) {
×
46
      result.push({
×
47
        type: 'add',
48
        content: newLines[newIndex],
49
        newLineNumber: newIndex + 1,
50
      });
51
      newIndex++;
×
52
    } else if (newIndex >= newLines.length) {
×
53
      result.push({
×
54
        type: 'remove',
55
        content: oldLines[oldIndex],
56
        oldLineNumber: oldIndex + 1,
57
      });
58
      oldIndex++;
×
59
    } else if (oldLines[oldIndex] === newLines[newIndex]) {
×
60
      result.push({
×
61
        type: 'context',
62
        content: oldLines[oldIndex],
63
        oldLineNumber: oldIndex + 1,
64
        newLineNumber: newIndex + 1,
65
      });
66
      oldIndex++;
×
67
      newIndex++;
×
68
    } else {
69
      // Lines differ - emit both remove and add
70
      result.push({
×
71
        type: 'remove',
72
        content: oldLines[oldIndex],
73
        oldLineNumber: oldIndex + 1,
74
      });
75
      result.push({
×
76
        type: 'add',
77
        content: newLines[newIndex],
78
        newLineNumber: newIndex + 1,
79
      });
80
      oldIndex++;
×
81
      newIndex++;
×
82
    }
83
  }
84

85
  return result;
×
86
}
87

88
export function DiffViewer({
89
  oldContent,
90
  newContent,
91
  diff,
92
  filename,
93
  viewMode: _viewMode = 'unified',
×
94
  showLineNumbers = true,
×
95
}: Readonly<DiffViewerProps>) {
UNCOV
96
  const [collapsed, setCollapsed] = useState(false);
×
97

98
  const lines = diff || (oldContent && newContent ? parseDiff(oldContent, newContent) : []);
×
UNCOV
99
  const additions = lines.filter((l) => l.type === 'add').length;
×
100
  const deletions = lines.filter((l) => l.type === 'remove').length;
×
101

102
  const getLineStyle = (type: DiffLine['type']) => {
×
UNCOV
103
    switch (type) {
×
104
      case 'add':
105
        return 'bg-teal-600/20';
×
106
      case 'remove':
107
        return 'bg-coral-500/20';
×
108
      default:
109
        return 'bg-transparent';
×
110
    }
111
  };
112

UNCOV
113
  const getLinePrefix = (type: DiffLine['type']) => {
×
UNCOV
114
    switch (type) {
×
115
      case 'add':
116
        return '+';
×
117
      case 'remove':
118
        return '-';
×
119
      default:
120
        return ' ';
×
121
    }
122
  };
123

UNCOV
124
  const getPrefixColor = (type: DiffLine['type']) => {
×
UNCOV
125
    switch (type) {
×
126
      case 'add':
127
        return 'text-teal-400';
×
128
      case 'remove':
129
        return 'text-coral-400';
×
130
      default:
131
        return 'text-neutral-500';
×
132
    }
133
  };
134

UNCOV
135
  return (
×
136
    <View className="bg-charcoal overflow-hidden" style={organicBorderRadius.card}>
137
      {/* Header */}
138
      <Pressable
UNCOV
139
        onPress={() => setCollapsed(!collapsed)}
×
140
        className="flex-row items-center justify-between px-3 py-2 bg-neutral-800 border-b border-neutral-700"
141
        accessibilityRole="button"
142
        accessibilityLabel={`${filename || 'file'}, ${additions} additions, ${deletions} deletions`}
×
143
        accessibilityHint={collapsed ? 'Expand the diff' : 'Collapse the diff'}
×
144
        accessibilityState={{ expanded: !collapsed }}
145
      >
146
        <View className="flex-row items-center flex-1">
147
          <View className="mr-2" style={{ transform: [{ rotate: collapsed ? '-90deg' : '0deg' }] }}>
×
148
            <ChevronDownIcon size={14} color="warmGray" turbulence={0.12} />
149
          </View>
150
          {filename && (
×
151
            <Text className="font-mono text-sm text-neutral-200" numberOfLines={1}>
152
              {filename}
153
            </Text>
154
          )}
155
        </View>
156
        <View className="flex-row items-center gap-2">
157
          {additions > 0 && <Text className="font-mono text-xs text-teal-400">+{additions}</Text>}
×
158
          {deletions > 0 && <Text className="font-mono text-xs text-coral-400">-{deletions}</Text>}
×
159
        </View>
160
      </Pressable>
161

162
      {/* Content */}
163
      {!collapsed && (
×
164
        <ScrollView horizontal showsHorizontalScrollIndicator={false}>
165
          <View className="min-w-full">
166
            {lines.map((line, index) => (
UNCOV
167
              <View key={`${line.type}-${index}`} className={`flex-row ${getLineStyle(line.type)}`}>
×
168
                {showLineNumbers && (
×
169
                  <View className="flex-row">
170
                    <Text className="font-mono text-xs text-neutral-600 w-10 text-right px-2 py-1 bg-neutral-900/50">
171
                      {line.oldLineNumber || ''}
×
172
                    </Text>
173
                    <Text className="font-mono text-xs text-neutral-600 w-10 text-right px-2 py-1 bg-neutral-900/50">
174
                      {line.newLineNumber || ''}
×
175
                    </Text>
176
                  </View>
177
                )}
178
                <Text className={`font-mono text-sm w-4 py-1 ${getPrefixColor(line.type)}`}>
179
                  {getLinePrefix(line.type)}
180
                </Text>
181
                <Text
182
                  className="font-mono text-sm text-neutral-200 py-1 pr-4 flex-1"
183
                  numberOfLines={1}
184
                >
185
                  {line.content}
186
                </Text>
187
              </View>
188
            ))}
189
          </View>
190
        </ScrollView>
191
      )}
192
    </View>
193
  );
194
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc