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
2 changes: 1 addition & 1 deletion apps/tui/src/components/ServerScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NETWORK } from "@ccflare/core";
import { Config } from "@ccflare/config";
import { NETWORK } from "@ccflare/core";
import { Box, Text, useInput } from "ink";

interface ServerScreenProps {
Expand Down
1 change: 1 addition & 0 deletions apps/tui/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ Examples:
"opus-4": CLAUDE_MODEL_IDS.OPUS_4,
"sonnet-4": CLAUDE_MODEL_IDS.SONNET_4,
"opus-4.1": CLAUDE_MODEL_IDS.OPUS_4_1,
"sonnet-4.5": CLAUDE_MODEL_IDS.SONNET_4_5,
};

const fullModel = modelMap[parsed.setModel];
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const CLAUDE_MODEL_IDS = {

// Claude 4 models
SONNET_4: "claude-sonnet-4-20250514",
SONNET_4_5: "claude-sonnet-4-5-20250929",
OPUS_4: "claude-opus-4-20250514",
OPUS_4_1: "claude-opus-4-1-20250805",

Expand All @@ -24,6 +25,7 @@ export const MODEL_DISPLAY_NAMES: Record<string, string> = {
[CLAUDE_MODEL_IDS.HAIKU_3_5]: "Claude Haiku 3.5",
[CLAUDE_MODEL_IDS.SONNET_3_5]: "Claude Sonnet 3.5 v2",
[CLAUDE_MODEL_IDS.SONNET_4]: "Claude Sonnet 4",
[CLAUDE_MODEL_IDS.SONNET_4_5]: "Claude Sonnet 4.5",
[CLAUDE_MODEL_IDS.OPUS_4]: "Claude Opus 4",
[CLAUDE_MODEL_IDS.OPUS_4_1]: "Claude Opus 4.1",
[CLAUDE_MODEL_IDS.OPUS_3]: "Claude Opus 3",
Expand All @@ -35,6 +37,7 @@ export const MODEL_SHORT_NAMES: Record<string, string> = {
[CLAUDE_MODEL_IDS.HAIKU_3_5]: "claude-3.5-haiku",
[CLAUDE_MODEL_IDS.SONNET_3_5]: "claude-3.5-sonnet",
[CLAUDE_MODEL_IDS.SONNET_4]: "claude-sonnet-4",
[CLAUDE_MODEL_IDS.SONNET_4_5]: "claude-sonnet-4.5",
[CLAUDE_MODEL_IDS.OPUS_4]: "claude-opus-4",
[CLAUDE_MODEL_IDS.OPUS_4_1]: "claude-opus-4.1",
[CLAUDE_MODEL_IDS.OPUS_3]: "claude-3-opus",
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ const BUNDLED_PRICING: ApiResponse = {
cache_write: 3.75,
},
},
[CLAUDE_MODEL_IDS.SONNET_4_5]: {
id: CLAUDE_MODEL_IDS.SONNET_4_5,
name: MODEL_DISPLAY_NAMES[CLAUDE_MODEL_IDS.SONNET_4_5],
cost: {
input: 3,
output: 15,
cache_read: 0.3,
cache_write: 3.75,
},
},
[CLAUDE_MODEL_IDS.OPUS_4]: {
id: CLAUDE_MODEL_IDS.OPUS_4,
name: MODEL_DISPLAY_NAMES[CLAUDE_MODEL_IDS.OPUS_4],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const MODEL_COLORS: Record<string, string> = {
"claude-3.5-haiku": COLORS.success,
"claude-3-opus": COLORS.blue,
"claude-opus-4": COLORS.pink,
"claude-opus-4.1": COLORS.indigo,
"claude-sonnet-4": COLORS.cyan,
"claude-sonnet-4.5": COLORS.purple,
};

function getModelColor(model: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ const MODEL_COLORS: Record<string, string> = {
"claude-3.5-haiku": COLORS.success,
"claude-3-opus": COLORS.blue,
"claude-opus-4": COLORS.pink,
// Add more models as needed
"claude-opus-4.1": COLORS.indigo,
"claude-sonnet-4": COLORS.cyan,
"claude-sonnet-4.5": COLORS.purple,
};

function getModelColor(model: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ const MODEL_COLORS: Record<string, string> = {
"claude-3.5-haiku": COLORS.success,
"claude-3-opus": COLORS.blue,
"claude-opus-4": COLORS.pink,
"claude-opus-4.1": COLORS.indigo,
"claude-sonnet-4": COLORS.cyan,
"claude-sonnet-4.5": COLORS.purple,
};

function getModelColor(model: string, index: number): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ function MessageComponent({
);
const thinkingText =
typeof thinkingBlock?.thinking === "string" ? thinkingBlock.thinking : "";
const hasThinking = thinkingText && cleanLineNumbers(thinkingText).trim().length > 0;
const hasThinking =
thinkingText && cleanLineNumbers(thinkingText).trim().length > 0;
const cleanedContent =
typeof content === "string" ? cleanLineNumbers(content).trim() : "";
const hasTools = tools?.length || 0;
Expand Down
3 changes: 3 additions & 0 deletions packages/providers/src/providers/anthropic/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ export class AnthropicProvider extends BaseProvider {
newHeaders.set("x-api-key", apiKey);
}

// Add anthropic-beta header for extended context support
newHeaders.set("anthropic-beta", "context-1m-2025-08-07");

// Remove host header
newHeaders.delete("host");

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const ALLOWED_MODELS = [
CLAUDE_MODEL_IDS.OPUS_4,
CLAUDE_MODEL_IDS.OPUS_4_1,
CLAUDE_MODEL_IDS.SONNET_4,
CLAUDE_MODEL_IDS.SONNET_4_5,
] as const;

export type AllowedModel = (typeof ALLOWED_MODELS)[number];
116 changes: 65 additions & 51 deletions packages/ui-common/src/parsers/parse-conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function parseRequestMessages(body: string | null): MessageData[] {

for (const item of msg.content) {
if (item.type === "text") {
// Filter out system reminders
// Filter out system reminders
let text = normalizeText(item.text || "");
if (text.includes("<system-reminder>")) {
text = text
Expand All @@ -72,11 +72,18 @@ export function parseRequestMessages(body: string | null): MessageData[] {
name: item.name,
input: item.input,
});
} else if (item.type === "tool_result") {
} else if (item.type === "tool_result") {
const resultContent = Array.isArray((item as any).content)
? ((item as any).content as Array<{ type: string; text?: string }>)
.map((c) => normalizeText(typeof c.text === "string" ? c.text : ""))
.join("")
? (
(item as any).content as Array<{
type: string;
text?: string;
}>
)
.map((c) =>
normalizeText(typeof c.text === "string" ? c.text : ""),
)
.join("")
: typeof (item as any).content === "string"
? normalizeText((item as any).content as string)
: "";
Expand All @@ -89,7 +96,7 @@ export function parseRequestMessages(body: string | null): MessageData[] {
tool_use_id: (item as any).tool_use_id,
content: resultContent,
});
} else if (item.type === "thinking") {
} else if (item.type === "thinking") {
const thinking = normalizeText((item as any).thinking || "");
if (thinking) {
message.contentBlocks?.push({
Expand Down Expand Up @@ -221,54 +228,61 @@ export function parseAssistantMessage(body: string | null): MessageData | null {
if (!isStreaming) {
try {
const parsed = JSON.parse(body);
if (parsed.content) {
if (typeof parsed.content === "string") {
currentContent = normalizeText(parsed.content);
} else if (Array.isArray(parsed.content)) {
for (const item of parsed.content) {
if (item.type === "text" && item.text) {
const norm = normalizeText(item.text);
currentContent += norm;
message.contentBlocks?.push({
type: ContentBlockType.Text,
text: norm,
});
} else if (item.type === "tool_use") {
message.tools?.push({
id: item.id,
name: item.name || "unknown",
input: item.input,
});
message.contentBlocks?.push({
type: ContentBlockType.ToolUse,
...item,
});
} else if (item.type === "thinking") {
const thinking = normalizeText((item as any).thinking || "");
if (thinking) {
message.contentBlocks?.push({
type: ContentBlockType.Thinking,
thinking,
});
}
} else if (item.type === "tool_result") {
const resultContent = Array.isArray((item as any).content)
? ((item as any).content as Array<{ type: string; text?: string }>)
.map((c) => normalizeText(typeof c.text === "string" ? c.text : ""))
.join("")
: typeof (item as any).content === "string"
? normalizeText((item as any).content as string)
: "";
message.toolResults?.push({
tool_use_id: (item as any).tool_use_id || "",
content: resultContent,
});
if (parsed.content) {
if (typeof parsed.content === "string") {
currentContent = normalizeText(parsed.content);
} else if (Array.isArray(parsed.content)) {
for (const item of parsed.content) {
if (item.type === "text" && item.text) {
const norm = normalizeText(item.text);
currentContent += norm;
message.contentBlocks?.push({
type: ContentBlockType.Text,
text: norm,
});
} else if (item.type === "tool_use") {
message.tools?.push({
id: item.id,
name: item.name || "unknown",
input: item.input,
});
message.contentBlocks?.push({
type: ContentBlockType.ToolUse,
...item,
});
} else if (item.type === "thinking") {
const thinking = normalizeText((item as any).thinking || "");
if (thinking) {
message.contentBlocks?.push({
type: ContentBlockType.ToolResult,
tool_use_id: (item as any).tool_use_id,
content: resultContent,
type: ContentBlockType.Thinking,
thinking,
});
}
} else if (item.type === "tool_result") {
const resultContent = Array.isArray((item as any).content)
? (
(item as any).content as Array<{
type: string;
text?: string;
}>
)
.map((c) =>
normalizeText(typeof c.text === "string" ? c.text : ""),
)
.join("")
: typeof (item as any).content === "string"
? normalizeText((item as any).content as string)
: "";
message.toolResults?.push({
tool_use_id: (item as any).tool_use_id || "",
content: resultContent,
});
message.contentBlocks?.push({
type: ContentBlockType.ToolResult,
tool_use_id: (item as any).tool_use_id,
content: resultContent,
});
}
}
}
}
Expand Down
82 changes: 42 additions & 40 deletions packages/ui-common/src/utils/normalize-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,49 @@
* and optionally strip a single pair of wrapping quotes.
*/
export function normalizeText(input: unknown): string {
let s = typeof input === "string" ? input : "";
if (!s) return "";
let s = typeof input === "string" ? input : "";
if (!s) return "";

// 1) If it's a JSON-encoded string (e.g., "...\n..."), try to parse directly
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
try {
s = JSON.parse(s);
} catch {
// If JSON.parse fails, fall back to manual unquoting
s = s.slice(1, -1);
}
} else if (/\\[nrt"\\]/.test(s)) {
// 2) If it contains escaped sequences, decode them via JSON.parse wrapper
try {
const literal =
'"' +
s
.replace(/\\/g, "\\\\")
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/\t/g, "\\t")
.replace(/"/g, "\\\"") +
'"';
s = JSON.parse(literal);
} catch {
// Ignore if decoding fails
}
}
// 1) If it's a JSON-encoded string (e.g., "...\n..."), try to parse directly
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
try {
s = JSON.parse(s);
} catch {
// If JSON.parse fails, fall back to manual unquoting
s = s.slice(1, -1);
}
} else if (/\\[nrt"\\]/.test(s)) {
// 2) If it contains escaped sequences, decode them via JSON.parse wrapper
try {
const literal =
'"' +
s
.replace(/\\/g, "\\\\")
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/\t/g, "\\t")
.replace(/"/g, '\\"') +
'"';
s = JSON.parse(literal);
} catch {
// Ignore if decoding fails
}
}

// 3) Heuristic: repair mojibake (UTF-8 mis-decoded as Latin-1)
if (/[ÃÂâ]/.test(s)) {
try {
const bytes = new Uint8Array(Array.from(s, (ch) => ch.charCodeAt(0) & 0xff));
const recoded = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
if (recoded && recoded !== s) {
s = recoded;
}
} catch {
// Ignore decoding errors
}
}
// 3) Heuristic: repair mojibake (UTF-8 mis-decoded as Latin-1)
if (/[ÃÂâ]/.test(s)) {
try {
const bytes = new Uint8Array(
Array.from(s, (ch) => ch.charCodeAt(0) & 0xff),
);
const recoded = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
if (recoded && recoded !== s) {
s = recoded;
}
} catch {
// Ignore decoding errors
}
}

return s;
return s;
}
2 changes: 2 additions & 0 deletions packages/ui-constants/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const COLORS = {
blue: "#3b82f6",
purple: "#8b5cf6",
pink: "#ec4899",
indigo: "#6366f1",
cyan: "#06b6d4",
} as const;

// Chart color sequence for multi-series charts
Expand Down