storkit: merge 90_story_fetch_real_context_window_size_from_anthropic_models_api
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import type { AgentConfigInfo } from "../api/agents";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import type { PipelineState } from "../api/client";
|
||||
import type { AnthropicModelInfo, PipelineState } from "../api/client";
|
||||
import { api, ChatWebSocket } from "../api/client";
|
||||
import { useChatHistory } from "../hooks/useChatHistory";
|
||||
import type { Message, ProviderConfig } from "../types";
|
||||
@@ -143,8 +143,13 @@ function formatToolActivity(toolName: string): string {
|
||||
|
||||
const estimateTokens = (text: string): number => Math.ceil(text.length / 4);
|
||||
|
||||
const getContextWindowSize = (modelName: string): number => {
|
||||
if (modelName.startsWith("claude-")) return 200000;
|
||||
const getContextWindowSize = (
|
||||
modelName: string,
|
||||
claudeContextWindows?: Map<string, number>,
|
||||
): number => {
|
||||
if (modelName.startsWith("claude-")) {
|
||||
return claudeContextWindows?.get(modelName) ?? 200000;
|
||||
}
|
||||
if (modelName.includes("llama3")) return 8192;
|
||||
if (modelName.includes("qwen2.5")) return 32768;
|
||||
if (modelName.includes("deepseek")) return 16384;
|
||||
@@ -163,6 +168,9 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
const [enableTools, setEnableTools] = useState(true);
|
||||
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
||||
const [claudeModels, setClaudeModels] = useState<string[]>([]);
|
||||
const [claudeContextWindowMap, setClaudeContextWindowMap] = useState<
|
||||
Map<string, number>
|
||||
>(new Map());
|
||||
const [streamingContent, setStreamingContent] = useState("");
|
||||
const [streamingThinking, setStreamingThinking] = useState("");
|
||||
const [showApiKeyDialog, setShowApiKeyDialog] = useState(false);
|
||||
@@ -285,7 +293,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
totalTokens += estimateTokens(streamingContent);
|
||||
}
|
||||
|
||||
const contextWindow = getContextWindowSize(model);
|
||||
const contextWindow = getContextWindowSize(model, claudeContextWindowMap);
|
||||
const percentage = Math.round((totalTokens / contextWindow) * 100);
|
||||
|
||||
return {
|
||||
@@ -293,7 +301,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
total: contextWindow,
|
||||
percentage,
|
||||
};
|
||||
}, [messages, streamingContent, model]);
|
||||
}, [messages, streamingContent, model, claudeContextWindowMap]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
@@ -337,14 +345,18 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
.then((exists) => {
|
||||
setHasAnthropicKey(exists);
|
||||
if (!exists) return;
|
||||
return api.getAnthropicModels().then((models) => {
|
||||
return api.getAnthropicModels().then((models: AnthropicModelInfo[]) => {
|
||||
if (models.length > 0) {
|
||||
const sortedModels = models.sort((a, b) =>
|
||||
a.toLowerCase().localeCompare(b.toLowerCase()),
|
||||
a.id.toLowerCase().localeCompare(b.id.toLowerCase()),
|
||||
);
|
||||
setClaudeModels(sortedModels.map((m) => m.id));
|
||||
setClaudeContextWindowMap(
|
||||
new Map(sortedModels.map((m) => [m.id, m.context_window])),
|
||||
);
|
||||
setClaudeModels(sortedModels);
|
||||
} else {
|
||||
setClaudeModels([]);
|
||||
setClaudeContextWindowMap(new Map());
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::llm::chat;
|
||||
use crate::store::StoreOps;
|
||||
use poem_openapi::{Object, OpenApi, Tags, payload::Json};
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
const ANTHROPIC_MODELS_URL: &str = "https://api.anthropic.com/v1/models";
|
||||
@@ -18,6 +18,13 @@ struct AnthropicModelsResponse {
|
||||
#[derive(Deserialize)]
|
||||
struct AnthropicModelInfo {
|
||||
id: String,
|
||||
context_window: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Object)]
|
||||
struct AnthropicModelSummary {
|
||||
id: String,
|
||||
context_window: u64,
|
||||
}
|
||||
|
||||
fn get_anthropic_api_key(ctx: &AppContext) -> Result<String, String> {
|
||||
@@ -84,7 +91,7 @@ impl AnthropicApi {
|
||||
|
||||
/// List available Anthropic models.
|
||||
#[oai(path = "/anthropic/models", method = "get")]
|
||||
async fn list_anthropic_models(&self) -> OpenApiResult<Json<Vec<String>>> {
|
||||
async fn list_anthropic_models(&self) -> OpenApiResult<Json<Vec<AnthropicModelSummary>>> {
|
||||
self.list_anthropic_models_from(ANTHROPIC_MODELS_URL).await
|
||||
}
|
||||
}
|
||||
@@ -93,7 +100,7 @@ impl AnthropicApi {
|
||||
async fn list_anthropic_models_from(
|
||||
&self,
|
||||
url: &str,
|
||||
) -> OpenApiResult<Json<Vec<String>>> {
|
||||
) -> OpenApiResult<Json<Vec<AnthropicModelSummary>>> {
|
||||
let api_key = get_anthropic_api_key(self.ctx.as_ref()).map_err(bad_request)?;
|
||||
let client = reqwest::Client::new();
|
||||
let mut headers = HeaderMap::new();
|
||||
@@ -128,7 +135,14 @@ impl AnthropicApi {
|
||||
.json::<AnthropicModelsResponse>()
|
||||
.await
|
||||
.map_err(|e| bad_request(e.to_string()))?;
|
||||
let models = body.data.into_iter().map(|m| m.id).collect();
|
||||
let models = body
|
||||
.data
|
||||
.into_iter()
|
||||
.map(|m| AnthropicModelSummary {
|
||||
id: m.id,
|
||||
context_window: m.context_window,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(models))
|
||||
}
|
||||
@@ -276,4 +290,29 @@ mod tests {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let _api = make_api(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anthropic_model_info_deserializes_context_window() {
|
||||
let json = json!({
|
||||
"id": "claude-opus-4-5",
|
||||
"context_window": 200000
|
||||
});
|
||||
let info: AnthropicModelInfo = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(info.id, "claude-opus-4-5");
|
||||
assert_eq!(info.context_window, 200000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anthropic_models_response_deserializes_multiple_models() {
|
||||
let json = json!({
|
||||
"data": [
|
||||
{ "id": "claude-opus-4-5", "context_window": 200000 },
|
||||
{ "id": "claude-haiku-4-5-20251001", "context_window": 100000 }
|
||||
]
|
||||
});
|
||||
let response: AnthropicModelsResponse = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(response.data.len(), 2);
|
||||
assert_eq!(response.data[0].context_window, 200000);
|
||||
assert_eq!(response.data[1].context_window, 100000);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user