mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
CI / Docker build smoke (push) Waiting to run
CI / Triage ledger up to date (push) Waiting to run
CI / ui bun.lock in sync (push) Waiting to run
* feat(models): re-add Fable 5 with admin disable toggle (port of upstream #1677) * refactor(models): convert re-added Fable 5 to Bedrock model IDs * fix(open-swe): correct Fable copy to describe provider data sharing, not ZDR The ported admin toggle description and code comments described Fable 5 as incompatible with Zero Data Retention. That is backwards: Fable 5 requires the account to opt into Bedrock provider_data_share — prompts/completions are retained and shared with Anthropic (up to 30 days, incl. human review). The old UI copy would lead an admin to believe the opposite of what enabling the toggle does. Reword the toggle description and the gate_fable_model / team_settings comments accordingly. Still off by default. Refs #171.
843 lines
23 KiB
TypeScript
843 lines
23 KiB
TypeScript
/**
|
|
* Typed client for the open-swe dashboard backend.
|
|
*
|
|
* All requests are sent with credentials so the httpOnly `osw_session`
|
|
* cookie set by the OAuth callback rides along on cross-origin calls.
|
|
*/
|
|
|
|
const API_BASE = (import.meta.env.VITE_DASHBOARD_API_BASE_URL ?? "").replace(
|
|
/\/$/,
|
|
""
|
|
)
|
|
|
|
if (!API_BASE && typeof window !== "undefined") {
|
|
console.warn("VITE_DASHBOARD_API_BASE_URL is not set")
|
|
}
|
|
|
|
const GITHUB_IMAGE_HOST_RE =
|
|
/^(?:www\.)?github\.com$|\.githubusercontent\.com$/i
|
|
|
|
/**
|
|
* Build an authenticated proxy URL for GitHub-hosted PR images. Private-repo
|
|
* attachments can't be loaded directly by the browser, so they're routed
|
|
* through the dashboard backend which holds the App token. Non-GitHub image
|
|
* URLs are returned unchanged.
|
|
*/
|
|
export function reviewImageProxyUrl(
|
|
owner: string,
|
|
repo: string,
|
|
number: number,
|
|
src: string
|
|
): string {
|
|
let parsed: URL
|
|
try {
|
|
parsed = new URL(src)
|
|
} catch {
|
|
return src
|
|
}
|
|
if (
|
|
parsed.protocol !== "https:" ||
|
|
!GITHUB_IMAGE_HOST_RE.test(parsed.hostname)
|
|
) {
|
|
return src
|
|
}
|
|
if (
|
|
/^(?:www\.)?github\.com$/i.test(parsed.hostname) &&
|
|
!parsed.pathname.startsWith("/user-attachments/")
|
|
) {
|
|
return src
|
|
}
|
|
const path = `/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/image`
|
|
return `${API_BASE}/dashboard/api${path}?url=${encodeURIComponent(src)}`
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
public readonly status: number,
|
|
message: string
|
|
) {
|
|
super(message)
|
|
this.name = "ApiError"
|
|
}
|
|
}
|
|
|
|
export function isGithubReauthError(error: unknown): boolean {
|
|
if (!(error instanceof ApiError)) return false
|
|
if (error.status === 401) return true
|
|
return /github token|re-login required/i.test(error.message)
|
|
}
|
|
|
|
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const res = await fetch(`${API_BASE}/dashboard/api${path}`, {
|
|
...init,
|
|
credentials: "include",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(init.headers ?? {}),
|
|
},
|
|
})
|
|
if (!res.ok) {
|
|
let message = res.statusText
|
|
try {
|
|
const body = await res.json()
|
|
if (body?.detail)
|
|
message =
|
|
typeof body.detail === "string"
|
|
? body.detail
|
|
: JSON.stringify(body.detail)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
throw new ApiError(res.status, message)
|
|
}
|
|
if (res.status === 204) return undefined as T
|
|
return (await res.json()) as T
|
|
}
|
|
|
|
export interface PRTraceResolutionResult {
|
|
resolved: boolean
|
|
detail: string
|
|
project: string | null
|
|
thread_id: string | null
|
|
confidence: number | null
|
|
evidence: Array<string>
|
|
trace_url: string | null
|
|
run_count: number
|
|
first_turn: string | null
|
|
last_turn: string | null
|
|
}
|
|
|
|
export interface SessionUser {
|
|
login: string
|
|
email: string | null
|
|
avatar_url: string | null
|
|
is_admin: boolean
|
|
slack_oauth_enabled?: boolean
|
|
}
|
|
|
|
export interface ModelOption {
|
|
id: string
|
|
label: string
|
|
efforts: Array<string>
|
|
default_effort: string
|
|
supports_images: boolean
|
|
}
|
|
|
|
export interface OptionsPayload {
|
|
models: Array<ModelOption>
|
|
default_agent_model: string
|
|
default_agent_reasoning_effort: string
|
|
default_agent_subagent_model: string
|
|
default_agent_subagent_reasoning_effort: string
|
|
}
|
|
|
|
export interface Profile {
|
|
login?: string
|
|
email?: string
|
|
default_model?: string
|
|
reasoning_effort?: string
|
|
default_subagent_model?: string | null
|
|
subagent_reasoning_effort?: string | null
|
|
default_repo?: string | null
|
|
base_branch?: string | null
|
|
branch_prefix?: string | null
|
|
auto_fix_ci?: boolean
|
|
create_prs?: boolean
|
|
review_draft_prs?: boolean | null
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface ProfileUpdate {
|
|
default_model: string
|
|
reasoning_effort: string
|
|
default_subagent_model?: string | null
|
|
subagent_reasoning_effort?: string | null
|
|
default_repo?: string | null
|
|
base_branch?: string | null
|
|
branch_prefix?: string | null
|
|
auto_fix_ci?: boolean
|
|
create_prs?: boolean
|
|
review_draft_prs?: boolean | null
|
|
}
|
|
|
|
export interface TeamSettings {
|
|
review_draft_prs: boolean
|
|
pr_summaries: boolean
|
|
review_trace_links: boolean
|
|
/** Tri-state LLM Gateway toggle; null inherits the LANGSMITH_GATEWAY_ENABLED default. */
|
|
gateway_enabled?: boolean | null
|
|
fable_enabled?: boolean
|
|
review_tracing_project?: string | null
|
|
org_guidelines?: string | null
|
|
default_agent_model?: string | null
|
|
default_agent_reasoning_effort?: string | null
|
|
default_agent_subagent_model?: string | null
|
|
default_agent_subagent_reasoning_effort?: string | null
|
|
default_repo?: string | null
|
|
default_reviewer_model?: string | null
|
|
default_reviewer_reasoning_effort?: string | null
|
|
default_reviewer_subagent_model?: string | null
|
|
default_reviewer_subagent_reasoning_effort?: string | null
|
|
default_grouping_model?: string | null
|
|
default_grouping_reasoning_effort?: string | null
|
|
default_chat_model?: string | null
|
|
default_chat_reasoning_effort?: string | null
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface ProviderCredentialStatus {
|
|
connected: boolean
|
|
site?: string
|
|
endpoint?: string
|
|
api_key_last4?: string
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface TeamCredentialsStatus {
|
|
datadog: ProviderCredentialStatus
|
|
langsmith: ProviderCredentialStatus
|
|
}
|
|
|
|
export interface DatadogConnectBody {
|
|
site: string
|
|
api_key: string
|
|
app_key: string
|
|
}
|
|
|
|
export interface LangSmithConnectBody {
|
|
api_key: string
|
|
endpoint?: string | null
|
|
}
|
|
|
|
export interface CurrentsCredentialStatus {
|
|
connected: boolean
|
|
api_key_last4?: string
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface CurrentsConnectBody {
|
|
api_key: string
|
|
}
|
|
|
|
export interface NotionCredentialStatus {
|
|
connected: boolean
|
|
token_expires_at?: string | null
|
|
updated_at?: string | null
|
|
}
|
|
|
|
export interface UserMapping {
|
|
github_login: string
|
|
work_email: string
|
|
slack_user_id?: string | null
|
|
source?: string
|
|
status?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface UserMappingsPage {
|
|
items: Array<UserMapping>
|
|
total: number
|
|
page: number
|
|
page_size: number
|
|
}
|
|
|
|
export type UsageLeaderboardPeriod = "7d" | "30d" | "all"
|
|
|
|
export interface UsageLeaderboardRow {
|
|
rank: number
|
|
user: {
|
|
name: string
|
|
github_login: string | null
|
|
email: string | null
|
|
}
|
|
favorite_model: string
|
|
agent_runs: number
|
|
prs_opened: number
|
|
merged_prs: number
|
|
agent_loc: number
|
|
additions: number
|
|
deletions: number
|
|
}
|
|
|
|
export interface ReviewerStatsCounterRow {
|
|
name: string
|
|
count: number
|
|
}
|
|
|
|
export interface ReviewerStatsPayload {
|
|
period: UsageLeaderboardPeriod
|
|
reviewed_prs: number
|
|
prs_with_findings: number
|
|
findings_recorded: number
|
|
surfaced_findings: number
|
|
addressed_findings: number
|
|
resolved_after_update: number
|
|
dismissed_findings: number
|
|
unresolved_surfaced_findings: number
|
|
resolution_rate: number
|
|
human_replies: number
|
|
severity_counts: Record<string, number>
|
|
top_categories: Array<ReviewerStatsCounterRow>
|
|
generated_at_ms: number | null
|
|
}
|
|
|
|
export interface UsageLeaderboardPayload {
|
|
period: UsageLeaderboardPeriod
|
|
rows: Array<UsageLeaderboardRow>
|
|
total_members: number
|
|
current_user_rank: number | null
|
|
generated_at_ms: number | null
|
|
reviewer_stats: ReviewerStatsPayload
|
|
}
|
|
|
|
export interface Repository {
|
|
full_name: string
|
|
private: boolean
|
|
}
|
|
|
|
export interface Installation {
|
|
id: number
|
|
account: string | null
|
|
account_type: string | null
|
|
}
|
|
|
|
export interface ReposPayload {
|
|
installations: Array<Installation>
|
|
repositories: Array<Repository>
|
|
}
|
|
|
|
export type ReviewStyleStatus = "idle" | "running" | "completed" | "failed"
|
|
|
|
export interface ReviewStyle {
|
|
full_name: string
|
|
owner?: string
|
|
name?: string
|
|
status: ReviewStyleStatus
|
|
custom_prompt: string | null
|
|
analysis_summary: string | null
|
|
top_reviewers: Array<string>
|
|
prs_sampled: number
|
|
reviews_sampled: number
|
|
analysis_thread_id: string | null
|
|
analysis_run_id: string | null
|
|
error: string | null
|
|
created_by?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface AgentInstructions {
|
|
full_name: string
|
|
owner?: string
|
|
name?: string
|
|
instructions: string
|
|
created_by?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export type RepoSnapshotStatus = "none" | "building" | "ready" | "failed"
|
|
|
|
export interface RepoSnapshot {
|
|
full_name: string
|
|
owner?: string
|
|
name?: string
|
|
dockerfile: string
|
|
snapshot_id: string | null
|
|
snapshot_name: string | null
|
|
status: RepoSnapshotStatus
|
|
status_message: string | null
|
|
build_log: string | null
|
|
fs_capacity_bytes?: number
|
|
vcpus?: number
|
|
mem_bytes?: number
|
|
target?: string | null
|
|
build_args?: Record<string, string> | null
|
|
build_started_at?: string | null
|
|
last_built_at?: string | null
|
|
created_by?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
export interface RepoSnapshotUpdateBody {
|
|
dockerfile: string
|
|
fs_capacity_bytes?: number | null
|
|
vcpus?: number | null
|
|
mem_bytes?: number | null
|
|
target?: string | null
|
|
build_args?: Record<string, string> | null
|
|
}
|
|
|
|
export type FindingSeverity = "low" | "medium" | "high" | "critical"
|
|
export type FindingConfidence = "low" | "medium" | "high"
|
|
export type FindingStatus = "open" | "resolved" | "dismissed"
|
|
export type FindingGroup = "bug" | "investigate" | "informational"
|
|
|
|
export interface FindingInteraction {
|
|
kind: "human_reply" | "bot_reply"
|
|
author?: string
|
|
body?: string
|
|
created_at?: string
|
|
}
|
|
|
|
export interface ReviewFinding {
|
|
id: string
|
|
severity: FindingSeverity
|
|
confidence: FindingConfidence
|
|
category: string
|
|
title: string
|
|
description: string
|
|
suggestion: string | null
|
|
file: string
|
|
start_line: number | null
|
|
end_line: number | null
|
|
side: "LEFT" | "RIGHT"
|
|
in_diff: boolean
|
|
status: FindingStatus
|
|
outdated: boolean
|
|
resolution_note: string | null
|
|
diff_hunk: string | null
|
|
github_thread_resolved: boolean
|
|
github_review_comment_id: number | null
|
|
interactions: Array<FindingInteraction>
|
|
group: FindingGroup
|
|
}
|
|
|
|
export interface ReviewCommentCreate {
|
|
path: string
|
|
line: number
|
|
side: "LEFT" | "RIGHT"
|
|
body: string
|
|
start_line?: number | null
|
|
start_side?: "LEFT" | "RIGHT" | null
|
|
}
|
|
|
|
export interface ReviewCommentResult {
|
|
id: number
|
|
html_url: string
|
|
}
|
|
|
|
export interface PrReviewComment {
|
|
id: number
|
|
author: string
|
|
author_avatar_url: string
|
|
path: string
|
|
line: number | null
|
|
side: "LEFT" | "RIGHT"
|
|
body: string
|
|
html_url: string
|
|
created_at: string
|
|
is_open_swe: boolean
|
|
// Outdated: the line no longer appears in the current diff, so it can't render inline.
|
|
is_outdated: boolean
|
|
}
|
|
|
|
export interface ReviewCommentsPayload {
|
|
comments: Array<PrReviewComment>
|
|
}
|
|
|
|
export interface ReviewCounts {
|
|
open: number
|
|
resolved: number
|
|
dismissed: number
|
|
bugs: number
|
|
flags: number
|
|
}
|
|
|
|
export interface ReviewSummary {
|
|
thread_id: string
|
|
owner: string
|
|
repo: string
|
|
number: number
|
|
title: string
|
|
url: string
|
|
head_ref: string
|
|
base_ref: string
|
|
author: string
|
|
head_sha: string
|
|
watch: boolean
|
|
status: "running" | "error" | "idle"
|
|
counts: ReviewCounts
|
|
updated_at: string | null
|
|
full_name?: string
|
|
}
|
|
|
|
export interface ReviewListPayload {
|
|
reviews: Array<ReviewSummary>
|
|
page: number
|
|
has_more: boolean
|
|
}
|
|
|
|
export interface ReviewUserRef {
|
|
login: string
|
|
avatar_url?: string | null
|
|
}
|
|
|
|
export interface ReviewCheckRun {
|
|
name: string
|
|
status: string
|
|
conclusion: string | null
|
|
url: string | null
|
|
}
|
|
|
|
export interface ReviewPrDetails {
|
|
state: string
|
|
title: string
|
|
body: string
|
|
additions: number
|
|
deletions: number
|
|
changed_files: number
|
|
commits: number
|
|
head_sha: string
|
|
head_ref: string
|
|
base_ref: string
|
|
author: ReviewUserRef | null
|
|
assignees: Array<ReviewUserRef>
|
|
requested_reviewers: Array<ReviewUserRef>
|
|
labels: Array<{ name: string; color: string | null }>
|
|
}
|
|
|
|
export interface ReviewDiffGroup {
|
|
index: number
|
|
title: string
|
|
summary: string
|
|
files: Array<string>
|
|
}
|
|
|
|
export interface ReviewDetail extends ReviewSummary {
|
|
pr: ReviewPrDetails
|
|
checks: Array<ReviewCheckRun>
|
|
findings: Array<ReviewFinding>
|
|
diff_groups: Array<ReviewDiffGroup>
|
|
diff_groups_stale: boolean
|
|
}
|
|
|
|
export interface ReviewDiffFile {
|
|
path: string
|
|
previousPath: string | null
|
|
status: "added" | "removed" | "modified" | "renamed"
|
|
additions: number
|
|
deletions: number
|
|
originalContent: string
|
|
modifiedContent: string
|
|
unrenderable?: boolean
|
|
}
|
|
|
|
export interface ReviewDiffPayload {
|
|
files: Array<ReviewDiffFile>
|
|
total_additions: number
|
|
total_deletions: number
|
|
truncated: boolean
|
|
}
|
|
|
|
export interface ReviewChatMeta {
|
|
available: boolean
|
|
assistant_id: string
|
|
}
|
|
|
|
export interface ReviewChatThread {
|
|
thread_id: string
|
|
title: string
|
|
updated_at?: string | null
|
|
}
|
|
|
|
/**
|
|
* Absolute base URL for the PR chat's LangGraph StreamProvider. The SDK builds
|
|
* request URLs as `new URL(apiUrl + path)`, so this must be absolute — a
|
|
* same-origin base is promoted using the current origin.
|
|
*/
|
|
export function reviewChatApiBase(
|
|
owner: string,
|
|
repo: string,
|
|
number: number
|
|
): string {
|
|
const path = `${API_BASE}/dashboard/api/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat`
|
|
if (/^https?:\/\//.test(path)) return path
|
|
if (typeof window !== "undefined") {
|
|
return `${window.location.origin}${path.startsWith("/") ? "" : "/"}${path}`
|
|
}
|
|
return path
|
|
}
|
|
|
|
export type ReviewerEvalScoreMode = "all_findings" | "surfaced_findings"
|
|
export type ReviewerEvalSeverity = "low" | "medium" | "high" | "critical"
|
|
|
|
export interface ReviewerEvalConfig {
|
|
dataset_name: string
|
|
experiment_prefix: string
|
|
max_concurrency: number
|
|
langsmith_project: string
|
|
langgraph_url: string
|
|
assistant_id: string
|
|
model_id: string
|
|
reasoning_effort: string
|
|
score_mode: ReviewerEvalScoreMode
|
|
severity_threshold: ReviewerEvalSeverity
|
|
cap: number
|
|
}
|
|
|
|
export interface ReviewerEvalProgress {
|
|
completed: number
|
|
total: number | null
|
|
}
|
|
|
|
export interface ReviewerEvalStatus {
|
|
name: string
|
|
status: "idle" | "running" | "completed" | "failed"
|
|
run_name?: string
|
|
langsmith_project: string
|
|
limit: number | null
|
|
config_snapshot?: ReviewerEvalConfig
|
|
started_at: string | null
|
|
finished_at: string | null
|
|
created_by: string | null
|
|
pid: number | null
|
|
exit_code: number | null
|
|
experiment_url: string | null
|
|
error: string | null
|
|
log_tail: string | null
|
|
progress?: ReviewerEvalProgress | null
|
|
github_run_url?: string | null
|
|
trigger?: string | null
|
|
updated_at: string
|
|
}
|
|
|
|
export const api = {
|
|
me: () => request<SessionUser>("/me"),
|
|
options: () => request<OptionsPayload>("/options"),
|
|
profile: () => request<Profile>("/profile"),
|
|
saveProfile: (body: ProfileUpdate) =>
|
|
request<Profile>("/profile", { method: "PUT", body: JSON.stringify(body) }),
|
|
repos: () => request<ReposPayload>("/repos"),
|
|
listReviewStyles: () => request<Array<ReviewStyle>>("/review-styles"),
|
|
createReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>("/review-styles", {
|
|
method: "POST",
|
|
body: JSON.stringify({ full_name }),
|
|
}),
|
|
getReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>(`/review-styles/${encodeURIComponent(full_name)}`),
|
|
saveReviewStylePrompt: (full_name: string, custom_prompt: string) =>
|
|
request<ReviewStyle>(`/review-styles/${encodeURIComponent(full_name)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ custom_prompt }),
|
|
}),
|
|
analyzeReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>(
|
|
`/review-styles/${encodeURIComponent(full_name)}/analyze`,
|
|
{
|
|
method: "POST",
|
|
}
|
|
),
|
|
cancelReviewStyle: (full_name: string) =>
|
|
request<ReviewStyle>(
|
|
`/review-styles/${encodeURIComponent(full_name)}/cancel`,
|
|
{
|
|
method: "POST",
|
|
}
|
|
),
|
|
deleteReviewStyle: (full_name: string) =>
|
|
request<void>(`/review-styles/${encodeURIComponent(full_name)}`, {
|
|
method: "DELETE",
|
|
}),
|
|
listAgentInstructions: () =>
|
|
request<Array<AgentInstructions>>("/agent-instructions"),
|
|
createAgentInstructions: (full_name: string) =>
|
|
request<AgentInstructions>("/agent-instructions", {
|
|
method: "POST",
|
|
body: JSON.stringify({ full_name }),
|
|
}),
|
|
getAgentInstructions: (full_name: string) =>
|
|
request<AgentInstructions>(
|
|
`/agent-instructions/${encodeURIComponent(full_name)}`
|
|
),
|
|
saveAgentInstructions: (full_name: string, instructions: string) =>
|
|
request<AgentInstructions>(
|
|
`/agent-instructions/${encodeURIComponent(full_name)}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify({ instructions }),
|
|
}
|
|
),
|
|
deleteAgentInstructions: (full_name: string) =>
|
|
request<void>(`/agent-instructions/${encodeURIComponent(full_name)}`, {
|
|
method: "DELETE",
|
|
}),
|
|
listRepoSnapshots: () => request<Array<RepoSnapshot>>("/repo-snapshots"),
|
|
createRepoSnapshot: (full_name: string) =>
|
|
request<RepoSnapshot>("/repo-snapshots", {
|
|
method: "POST",
|
|
body: JSON.stringify({ full_name }),
|
|
}),
|
|
getRepoSnapshot: (full_name: string) =>
|
|
request<RepoSnapshot>(`/repo-snapshots/${encodeURIComponent(full_name)}`),
|
|
getRepoSnapshotTemplate: (full_name: string) =>
|
|
request<{ dockerfile: string }>(
|
|
`/repo-snapshots/template?full_name=${encodeURIComponent(full_name)}`
|
|
),
|
|
saveRepoSnapshot: (full_name: string, body: RepoSnapshotUpdateBody) =>
|
|
request<RepoSnapshot>(`/repo-snapshots/${encodeURIComponent(full_name)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
buildRepoSnapshot: (full_name: string) =>
|
|
request<RepoSnapshot>(
|
|
`/repo-snapshots/${encodeURIComponent(full_name)}/build`,
|
|
{ method: "POST" }
|
|
),
|
|
deleteRepoSnapshot: (full_name: string) =>
|
|
request<void>(`/repo-snapshots/${encodeURIComponent(full_name)}`, {
|
|
method: "DELETE",
|
|
}),
|
|
getTeamSettings: () => request<TeamSettings>("/team-settings"),
|
|
saveTeamSettings: (body: TeamSettings) =>
|
|
request<TeamSettings>("/team-settings", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
getTeamCredentials: () => request<TeamCredentialsStatus>("/team-credentials"),
|
|
connectDatadog: (body: DatadogConnectBody) =>
|
|
request<TeamCredentialsStatus>("/team-credentials/datadog", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
disconnectDatadog: () =>
|
|
request<TeamCredentialsStatus>("/team-credentials/datadog", {
|
|
method: "DELETE",
|
|
}),
|
|
connectLangSmith: (body: LangSmithConnectBody) =>
|
|
request<TeamCredentialsStatus>("/team-credentials/langsmith", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
disconnectLangSmith: () =>
|
|
request<TeamCredentialsStatus>("/team-credentials/langsmith", {
|
|
method: "DELETE",
|
|
}),
|
|
getMyCurrentsStatus: () =>
|
|
request<CurrentsCredentialStatus>("/my-credentials/currents"),
|
|
connectCurrents: (body: CurrentsConnectBody) =>
|
|
request<CurrentsCredentialStatus>("/my-credentials/currents", {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
disconnectCurrents: () =>
|
|
request<CurrentsCredentialStatus>("/my-credentials/currents", {
|
|
method: "DELETE",
|
|
}),
|
|
getMyNotionStatus: () =>
|
|
request<NotionCredentialStatus>("/my-credentials/notion"),
|
|
disconnectNotion: () =>
|
|
request<NotionCredentialStatus>("/my-credentials/notion", {
|
|
method: "DELETE",
|
|
}),
|
|
listEnabledReviewRepos: () =>
|
|
request<{ repos: Array<string> }>("/enabled-review-repos"),
|
|
setEnabledReviewRepo: (full_name: string, enabled: boolean) =>
|
|
request<{ repos: Array<string> }>("/enabled-review-repos", {
|
|
method: "PUT",
|
|
body: JSON.stringify({ full_name, enabled }),
|
|
}),
|
|
usageLeaderboard: (period: UsageLeaderboardPeriod = "30d", limit = 10) =>
|
|
request<UsageLeaderboardPayload>(
|
|
`/agent-usage-leaderboard?period=${encodeURIComponent(period)}&limit=${limit}`
|
|
),
|
|
myMapping: () => request<Partial<UserMapping>>("/my-mapping"),
|
|
adminListUserMappings: (page = 1, pageSize = 20) =>
|
|
request<UserMappingsPage>(
|
|
`/admin/user-mappings?page=${page}&page_size=${pageSize}`
|
|
),
|
|
adminUpsertUserMapping: (input: {
|
|
github_login: string
|
|
work_email: string
|
|
slack_user_id?: string | null
|
|
}) =>
|
|
request<UserMapping>("/admin/user-mappings", {
|
|
method: "POST",
|
|
body: JSON.stringify(input),
|
|
}),
|
|
adminDeleteUserMapping: (github_login: string) =>
|
|
request<{ deleted: boolean }>(
|
|
`/admin/user-mappings/${encodeURIComponent(github_login)}`,
|
|
{ method: "DELETE" }
|
|
),
|
|
listReviews: (page: number, mine: boolean) =>
|
|
request<ReviewListPayload>(`/reviews?page=${page}&mine=${mine}`),
|
|
getReview: (owner: string, repo: string, number: number) =>
|
|
request<ReviewDetail>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}`
|
|
),
|
|
getReviewDiff: (owner: string, repo: string, number: number) =>
|
|
request<ReviewDiffPayload>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/diff`
|
|
),
|
|
getReviewChat: (owner: string, repo: string, number: number) =>
|
|
request<ReviewChatMeta>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat`
|
|
),
|
|
listReviewChatThreads: (owner: string, repo: string, number: number) =>
|
|
request<{ threads: Array<ReviewChatThread> }>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat/threads`
|
|
),
|
|
deleteReviewChatThread: (
|
|
owner: string,
|
|
repo: string,
|
|
number: number,
|
|
threadId: string
|
|
) =>
|
|
request<void>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/chat/threads/${encodeURIComponent(threadId)}`,
|
|
{ method: "DELETE" }
|
|
),
|
|
reReview: (owner: string, repo: string, number: number) =>
|
|
request<{
|
|
success: boolean
|
|
queued: boolean
|
|
thread_id: string
|
|
pr_url: string
|
|
}>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/re-review`,
|
|
{ method: "POST" }
|
|
),
|
|
resolveTrace: (owner: string, repo: string, number: number) =>
|
|
request<PRTraceResolutionResult>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/resolve-trace`,
|
|
{ method: "POST" }
|
|
),
|
|
createReviewComment: (
|
|
owner: string,
|
|
repo: string,
|
|
number: number,
|
|
body: ReviewCommentCreate
|
|
) =>
|
|
request<ReviewCommentResult>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/comments`,
|
|
{ method: "POST", body: JSON.stringify(body) }
|
|
),
|
|
listReviewComments: (owner: string, repo: string, number: number) =>
|
|
request<ReviewCommentsPayload>(
|
|
`/reviews/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${number}/comments`
|
|
),
|
|
getReviewerEval: () => request<ReviewerEvalStatus>("/admin/evals/reviewer"),
|
|
logout: () => request<void>("/auth/logout", { method: "POST" }),
|
|
}
|
|
|
|
export function loginUrl(redirectTo?: string): string {
|
|
const target =
|
|
redirectTo ??
|
|
(typeof window !== "undefined"
|
|
? `${window.location.pathname}${window.location.search}${window.location.hash}`
|
|
: "")
|
|
const qs = target ? `?redirect_to=${encodeURIComponent(target)}` : ""
|
|
return `${API_BASE}/dashboard/api/auth/login${qs}`
|
|
}
|
|
|
|
export function slackConnectUrl(): string {
|
|
return `${API_BASE}/dashboard/api/slack/login`
|
|
}
|
|
|
|
export function notionConnectUrl(): string {
|
|
return `${API_BASE}/dashboard/api/notion/login`
|
|
}
|