Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions packages/sdk/src/qualifire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { EvaluationResult, ReportSummaryRequest, StructuredSummary } from "./types";

interface QualifireReportPayload {
job_id: string;
evaluations: EvaluationResult;
structured: StructuredSummary | null;
deep_test: boolean;
start_time: string;
judge_model: string | null;
}
Comment on lines +3 to +10
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Type mismatch: deep_test and start_time should be nullable.

The QualifireReportPayload interface defines deep_test: boolean and start_time: string as required fields. However, the source fields in ReportSummaryRequest are optional (deepTest?: boolean, startTime?: string). This creates a type mismatch where undefined values can be assigned to non-nullable fields in the payload, violating the type contract and potentially causing API errors.

Apply this diff to make the fields nullable:

 interface QualifireReportPayload {
   job_id: string;
   evaluations: EvaluationResult;
   structured: StructuredSummary | null;
-  deep_test: boolean;
-  start_time: string;
+  deep_test: boolean | null;
+  start_time: string | null;
   judge_model: string | null;
 }

Then update the mapping in convertWithStructuredSummary:

   return {
     job_id: request.job_id,
     evaluations: evaluationResults,
     structured: request.structuredSummary || null,
-    deep_test: request.deepTest,
-    start_time: request.startTime,
+    deep_test: request.deepTest ?? null,
+    start_time: request.startTime ?? null,
     judge_model: request.judgeModel || null,
   };

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In packages/sdk/src/qualifire.ts around lines 3 to 10, the
QualifireReportPayload currently declares deep_test: boolean and start_time:
string but the source ReportSummaryRequest has deepTest?: boolean and
startTime?: string, causing a type mismatch; change the interface to make
deep_test: boolean | null and start_time: string | null, and then update
convertWithStructuredSummary to map optional fields defensively (deep_test:
source.deepTest ?? null and start_time: source.startTime ?? null) so undefined
from the source becomes null in the payload.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ignorant05 I think this comment is correct

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


export interface QualifireClientOptions {
logger?: (message: string) => void;
}

export class QualifireClient {
private static convertWithStructuredSummary(
evaluationResults: EvaluationResult,
request: ReportSummaryRequest
): QualifireReportPayload {
return {
job_id: request.job_id,
evaluations: evaluationResults,
structured: request.structuredSummary || null,
deep_test: request.deepTest ?? false,
start_time: request.startTime ?? new Date().toISOString(),
judge_model: request.judgeModel || null,
};
}

/**
* Reports evaluation summary to Qualifire.
*
* @param evaluationResults - The evaluation results to report
* @param request - Configuration including Qualifire URL, API key, and metadata
* @throws {Error} If the API request fails or returns a non-2xx status
* @returns A promise that resolves when the report is successfully submitted
*/
public static async reportSummaryToQualifire(
evaluationResults: EvaluationResult,
request: ReportSummaryRequest,
options?: QualifireClientOptions
): Promise<void> {
options?.logger?.("Reporting summary to Qualifire");

const apiKey = request.qualifireApiKey;
const baseUrl = request.qualifireUrl ?? "https://api.qualifire.com";
const endpoint = `${baseUrl}/llm/summary`;

if (!apiKey) {
throw new Error("qualifireApiKey is required but was undefined");
}

if (!baseUrl || baseUrl === "undefined") {
throw new Error("Invalid qualifireUrl provided");
}
Comment on lines 46 to 56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

Design mismatch with PR objective: route via Rogue server, not direct Qualifire.

This class posts directly to Qualifire (/api/v1/evaluations). The stated goal is to mimic the Python SDK and pass the Qualifire API key to the Rogue server’s summary endpoint. Please change this to call the Rogue server endpoint and forward the key, or gate direct reporting behind an explicit opt-in and default to server routing for this PR.

Would you run the script below to locate the server summary endpoint and confirm the expected header so we can align the client?


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find Rogue summary endpoint and Qualifire service usage =="
fd -a llm.py rogue/server | xargs -I{} rg -n -C3 -e 'router\.(post|get|put)\(' -e 'summary' {}
rg -n -C3 'qualifire' rogue/server || true
rg -n -C3 'X-Qualifire' rogue/server || true

Length of output: 8186


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check SDK qualifire client implementation =="
fd -a "qualifire.ts|qualifier.ts" packages/sdk/src

echo ""
echo "== Find request models for server /summary endpoint =="
rg -n -A10 "class SummaryGenerationRequest|SummaryGenerationRequest =" rogue/server --type py

echo ""
echo "== Check what auth/headers are expected by Rogue /summary endpoint =="
rg -n -B5 -A15 "@router.post\(\s*\"/summary\"" rogue/server --type py

Length of output: 152



🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SDK qualifire.ts current implementation =="
cat /home/jailuser/git/packages/sdk/src/qualifire.ts

echo ""
echo "== Rogue server request models =="
fd -a "models.py|schemas.py|types.py" rogue/server/api rogue/server/schemas 2>/dev/null | head -5 | xargs -I{} sh -c 'echo "=== {} ===" && head -100 {}'

echo ""
echo "== Search for SummaryGenerationRequest in all Python files =="
rg -n "SummaryGenerationRequest" rogue/server --type py -B2 -A5

Length of output: 3865


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find SummaryGenerationRequest definition =="
rg -n "class SummaryGenerationRequest|SummaryGenerationRequest\s*=" rogue/server -A15 --type py

echo ""
echo "== Check import source for SummaryGenerationRequest =="
rg -n "from.*import.*SummaryGenerationRequest" rogue/server --type py

Length of output: 46


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for Summary in rogue module structure =="
fd -a ".py$" rogue/server | xargs rg -l "SummaryGenerationRequest" | head -10

echo ""
echo "== Find all python files in rogue/server/api =="
fd -a ".py$" rogue/server/api

echo ""
echo "== Check rogue/server/schemas or similar =="
fd -a "schema|model" rogue/server --type d

Length of output: 463


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== List models directory =="
fd -a ".py$" rogue/server/models

echo ""
echo "== Search for SummaryGenerationRequest in models =="
rg -n "SummaryGenerationRequest" rogue/server/models -A10

echo ""
echo "== Check rogue/server/api/__init__.py for exports =="
cat rogue/server/api/__init__.py

Length of output: 184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check models/__init__.py =="
cat rogue/server/models/__init__.py

echo ""
echo "== Check models/api_format.py =="
head -150 rogue/server/models/api_format.py

echo ""
echo "== Broader search for SummaryGenerationRequest =="
rg -n "SummaryGenerationRequest" rogue/ -A5 | head -50

Length of output: 2608


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check llm.py imports (first 30 lines) =="
head -30 rogue/server/api/llm.py

echo ""
echo "== Search for SummaryGenerationRequest definition anywhere in repo =="
rg -n "class SummaryGenerationRequest|SummaryGenerationRequest.*=" --type py

Length of output: 1043


Design mismatch confirmed: SDK posts directly to Qualifire instead of routing through Rogue server.

The SDK calls ${baseUrl}/api/v1/evaluations directly (packages/sdk/src/qualifire.ts:48). Per the PR objective, it should POST to the Rogue server's /llm/summary endpoint instead, which internally handles Qualifire reporting. This aligns with how the Python SDK works and keeps the server in control of credential forwarding.

πŸ€– Prompt for AI Agents
In packages/sdk/src/qualifire.ts around lines 46 to 56, the SDK currently
constructs and posts directly to Qualifire (`${baseUrl}/api/v1/evaluations`);
change this to post to the Rogue server's `/llm/summary` endpoint instead (use
request.rogueUrl as the base, e.g. `${request.rogueUrl}/llm/summary`, with a
sensible default or raise if missing). Update the request payload to match the
Rogue `/llm/summary` contract and forward any needed Qualifire
credentials/metadata in the body or headers so Rogue can handle reporting to
Qualifire; remove the direct Qualifire endpoint logic and keep validation for
the rogue URL instead of qualifireUrl.


const apiEvaluationResult = this.convertWithStructuredSummary(
evaluationResults,
request
);

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);

try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Qualifire-API-Key": apiKey,
},
body: JSON.stringify(apiEvaluationResult),
signal: controller.signal
});

if (!response.ok) {
const errText = await response.text();
throw new Error(
`Qualifire report failed: ${response.status} ${response.statusText} - ${errText}`
);
}
clearTimeout(timeoutId);
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('Qualifire report timed out after 30 seconds');
}
throw error;
}
}
}
Comment on lines 1 to 92
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

Verify duplicate implementation has been removed.

Past reviews flagged a duplicate QualifireClient implementation in packages/sdk/src/quatifier.ts. Ensure the duplicate file has been removed and all imports reference the canonical qualifire.ts module.

Run the following script to verify:


🏁 Script executed:

#!/bin/bash
# Check if quatifier.ts still exists and search for any references to it

# Search for quatifier.ts file
echo "=== Checking for quatifier.ts file ==="
fd -e ts quatifier

# Search for imports from quatifier module
echo "=== Checking for imports from quatifier module ==="
rg -n --type=ts "from ['\"].*quatifier['\"]"

# Search for any references to quatifier in the codebase
echo "=== Checking for any references to 'quatifier' ==="
rg -n --type=ts "quatifier"

Length of output: 249


Remove duplicated quatifier.ts and consolidate imports
The file packages/sdk/src/quatifier.ts remains, duplicating QualifireClient. Delete this file and update any imports or references (including doc comments) to use packages/sdk/src/qualifire.ts.

πŸ€– Prompt for AI Agents
In packages/sdk/src/qualifire.ts lines 1 to 80, remove the duplicate file
packages/sdk/src/quatifier.ts and update all imports/references/docs to point to
packages/sdk/src/qualifire.ts (correct filename spelling) across the repository;
search for any import paths or JSDoc references like "./quatifier" or
"quatifier" and replace them with "./qualifire" (preserve relative paths and
casing), then delete the duplicate quatifier.ts file and run tests/build to
ensure no unresolved imports remain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ignorant05 please fix this one

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

17 changes: 17 additions & 0 deletions packages/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,23 @@ export interface RogueClientConfig {
retries?: number;
}

export interface StructuredSummary {
overall_summary: string,
key_findings: string[],
recommendations: string[],
detailed_breakdown: object[]
}

export interface ReportSummaryRequest {
job_id: string,
qualifireUrl?: string;
qualifireApiKey?: string;
structuredSummary?: StructuredSummary;
deepTest?: boolean;
startTime?: string;
judgeModel?: string;
}

// Event Types for WebSocket
export type WebSocketEventType =
| "job_update"
Expand Down
4 changes: 2 additions & 2 deletions packages/tui/internal/screens/evaluations/form_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ func RenderForm(state *FormState) string {

// Prepare field values
agent := state.AgentURL
protocol := state.Protocol
transport := state.Transport
protocol := string(state.Protocol)
transport := string(state.Transport)
judge := state.JudgeModel
deep := "❌"
if state.DeepTest {
Expand Down
1 change: 1 addition & 0 deletions rogue/evaluator_agent/base_evaluator_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ def _log_evaluation(
context_id: str,
evaluation_passed: bool,
reason: str,
scenario_type: Optional[str],
**kwargs,
) -> None:
"""
Expand Down