• 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

2.38
/packages/core/src/github/GitHubApiService.ts
1
import { API_URLS } from '@thumbcode/config';
2
import type { Repository } from '@thumbcode/types';
3
import { CredentialService } from '../credentials';
4

5
interface GitHubRepoResponse {
6
  id: number;
7
  name: string;
8
  full_name: string;
9
  owner: { login: string };
10
  private: boolean;
11
  description: string | null;
12
  default_branch: string;
13
  clone_url: string;
14
  stargazers_count: number;
15
  forks_count: number;
16
  language: string | null;
17
  updated_at: string;
18
}
19

20
class GitHubApiServiceClass {
21
  private async getToken(): Promise<string> {
22
    const { secret } = await CredentialService.retrieve('github');
×
23
    if (!secret) {
×
24
      throw new Error('Missing GitHub credential. Connect GitHub first.');
×
25
    }
26
    return secret;
×
27
  }
28

29
  private async request<T>(path: string, init: RequestInit = {}): Promise<T> {
×
30
    const token = await this.getToken();
×
31
    const url = `${API_URLS.github}${path}`;
×
32
    const res = await fetch(url, {
×
33
      ...init,
34
      headers: {
35
        Accept: 'application/vnd.github.v3+json',
36
        Authorization: `Bearer ${token}`,
37
        ...init.headers,
38
      },
39
    });
40

41
    if (!res.ok) {
×
42
      const text = await res.text().catch(() => '');
×
43
      throw new Error(`GitHub API error: ${res.status} ${text}`.trim());
×
44
    }
45

46
    return (await res.json()) as T;
×
47
  }
48

49
  async listRepositories(options?: {
50
    perPage?: number;
51
    sort?: 'updated' | 'created' | 'pushed' | 'full_name';
52
    affiliation?: string;
53
  }): Promise<Repository[]> {
54
    const perPage = options?.perPage ?? 100;
×
55
    const sort = options?.sort ?? 'updated';
×
56
    const affiliation = options?.affiliation ?? 'owner,collaborator,organization_member';
×
57

58
    const repos = await this.request<GitHubRepoResponse[]>(
×
59
      `/user/repos?per_page=${perPage}&sort=${sort}&direction=desc&affiliation=${encodeURIComponent(affiliation)}`
60
    );
61

62
    return repos.map((r) => ({
×
63
      provider: 'github',
64
      owner: r.owner.login,
65
      name: r.name,
66
      fullName: r.full_name,
67
      defaultBranch: r.default_branch,
68
      cloneUrl: r.clone_url,
69
      isPrivate: r.private,
70
      description: r.description ?? undefined,
×
71
      language: r.language ?? undefined,
×
72
      stars: r.stargazers_count,
73
      forks: r.forks_count,
74
      updatedAt: r.updated_at,
75
    }));
76
  }
77

78
  async createRepository(options: {
79
    name: string;
80
    description?: string;
81
    isPrivate?: boolean;
82
  }): Promise<Repository> {
UNCOV
83
    const repo = await this.request<GitHubRepoResponse>('/user/repos', {
×
84
      method: 'POST',
85
      headers: { 'Content-Type': 'application/json' },
86
      body: JSON.stringify({
87
        name: options.name,
88
        description: options.description || '',
×
89
        private: options.isPrivate ?? true,
×
90
        auto_init: true,
91
      }),
92
    });
93

UNCOV
94
    return {
×
95
      provider: 'github',
96
      owner: repo.owner.login,
97
      name: repo.name,
98
      fullName: repo.full_name,
99
      defaultBranch: repo.default_branch,
100
      cloneUrl: repo.clone_url,
101
      isPrivate: repo.private,
102
      description: repo.description ?? undefined,
×
103
      language: repo.language ?? undefined,
×
104
      stars: repo.stargazers_count,
105
      forks: repo.forks_count,
106
      updatedAt: repo.updated_at,
107
    };
108
  }
109
}
110

111
export const GitHubApiService = new GitHubApiServiceClass();
1✔
112

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