mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
修复编译问题
This commit is contained in:
@@ -289,19 +289,19 @@ impl ProviderCredential {
|
||||
/// 解析当前凭证应采用的 Prompt Cache 模式。
|
||||
pub fn effective_prompt_cache_mode(&self) -> Option<ProviderPromptCacheMode> {
|
||||
self.prompt_cache_mode_override.or_else(|| {
|
||||
if self.provider_type.supports_anthropic_prompt_cache() {
|
||||
Some(ProviderPromptCacheMode::Automatic)
|
||||
} else if matches!(self.provider_type, PoolProviderType::AnthropicCompatible)
|
||||
&& is_known_automatic_anthropic_compatible_host(
|
||||
if matches!(self.provider_type, PoolProviderType::AnthropicCompatible) {
|
||||
return if is_known_automatic_anthropic_compatible_host(
|
||||
get_base_url(&self.credential).as_deref(),
|
||||
)
|
||||
{
|
||||
Some(ProviderPromptCacheMode::Automatic)
|
||||
} else if matches!(self.provider_type, PoolProviderType::AnthropicCompatible) {
|
||||
Some(ProviderPromptCacheMode::ExplicitOnly)
|
||||
} else {
|
||||
None
|
||||
) {
|
||||
Some(ProviderPromptCacheMode::Automatic)
|
||||
} else {
|
||||
Some(ProviderPromptCacheMode::ExplicitOnly)
|
||||
};
|
||||
}
|
||||
|
||||
self.provider_type
|
||||
.supports_anthropic_prompt_cache()
|
||||
.then_some(ProviderPromptCacheMode::Automatic)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -144,10 +144,6 @@ impl WorkspaceSandboxedBashTool {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_output(stdout: &str, stderr: &str, exit_code: i32) -> String {
|
||||
Self::format_output_with_message(stdout, stderr, exit_code, None)
|
||||
}
|
||||
|
||||
fn format_output_with_message(
|
||||
stdout: &str,
|
||||
stderr: &str,
|
||||
|
||||
@@ -598,7 +598,7 @@ function createAlignedRuntimeToolInventory(): AgentRuntimeToolInventory {
|
||||
caller_allowed: true,
|
||||
visible_in_context: true,
|
||||
},
|
||||
...base.runtime_tools.filter((entry) => entry.name !== "Agent"),
|
||||
...(base.runtime_tools ?? []).filter((entry) => entry.name !== "Agent"),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -417,6 +417,36 @@ describe("MarkdownRenderer", () => {
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it("标题后的正文应保持聊天正文排版,不应缩小变灰", () => {
|
||||
const container = document.createElement("div");
|
||||
container.style.setProperty("--foreground", "17 24 39");
|
||||
container.style.setProperty("--muted-foreground", "100 116 139");
|
||||
container.style.fontSize = "15px";
|
||||
container.style.lineHeight = "1.7";
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<MarkdownRenderer content={"## 小结\n\n这段正文应该和聊天正文保持同一字号与主色。"} />,
|
||||
);
|
||||
});
|
||||
|
||||
mountedRoots.push({ container, root });
|
||||
|
||||
const heading = container.querySelector(
|
||||
'h2[data-markdown-heading-level="2"]',
|
||||
);
|
||||
const paragraph = container.querySelector("p");
|
||||
|
||||
expect(heading).not.toBeNull();
|
||||
expect(paragraph).not.toBeNull();
|
||||
expect(getComputedStyle(paragraph as Element).fontSize).toBe("1em");
|
||||
expect(document.head.textContent).not.toContain("h1 + p");
|
||||
expect(document.head.textContent).not.toContain("h2 + p");
|
||||
expect(document.head.textContent).not.toContain("h3 + p");
|
||||
});
|
||||
|
||||
it("非流式时应保留 raw html 渲染能力", () => {
|
||||
const content = [
|
||||
"前置文本",
|
||||
|
||||
@@ -39,8 +39,8 @@ const CODE_BLOCK_BUTTON_HOVER_SURFACE = "rgba(248, 250, 252, 0.98)";
|
||||
|
||||
// 收紧正文与代码块表面,让消息正文更接近单列执行流的阅读节奏。
|
||||
const MarkdownContainer = styled.div`
|
||||
font-size: 14px;
|
||||
line-height: 1.76;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
color: hsl(var(--foreground));
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
@@ -57,14 +57,8 @@ const MarkdownContainer = styled.div`
|
||||
p {
|
||||
margin: 0 0 0.95em;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
h1 + p,
|
||||
h2 + p,
|
||||
h3 + p {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 1.02em;
|
||||
line-height: 1.8;
|
||||
font-size: 1em;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
h1,
|
||||
@@ -74,9 +68,9 @@ const MarkdownContainer = styled.div`
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 700;
|
||||
margin: 1.34em 0 0.58em;
|
||||
line-height: 1.32;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 1.08em 0 0.5em;
|
||||
line-height: 1.42;
|
||||
letter-spacing: 0;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
@@ -87,21 +81,20 @@ const MarkdownContainer = styled.div`
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.54em;
|
||||
font-size: 1.16em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.28em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.12em;
|
||||
font-size: 1.04em;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.03em;
|
||||
font-size: 1em;
|
||||
}
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 0.96em;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.98em;
|
||||
}
|
||||
|
||||
ul,
|
||||
|
||||
@@ -1653,7 +1653,7 @@ describe("MessageList", () => {
|
||||
content: "正在分析依赖关系。",
|
||||
timestamp: now,
|
||||
runtimeStatus: {
|
||||
phase: "reasoning",
|
||||
phase: "routing",
|
||||
title: "处理中",
|
||||
detail: "正在读取多个 crate 的依赖。",
|
||||
},
|
||||
|
||||
@@ -288,11 +288,19 @@ describe("agentStreamTurnEventBinding", () => {
|
||||
setIsSending: noopDispatch<boolean>(),
|
||||
});
|
||||
|
||||
streamHandler?.({
|
||||
if (!streamHandler) {
|
||||
throw new Error("expected stream handler to be registered");
|
||||
}
|
||||
|
||||
const activeStreamHandler = streamHandler as (event: {
|
||||
payload: unknown;
|
||||
}) => void;
|
||||
|
||||
activeStreamHandler({
|
||||
payload: {
|
||||
type: "runtime_status",
|
||||
status: {
|
||||
phase: "reasoning",
|
||||
phase: "routing",
|
||||
title: "分析中",
|
||||
detail: "正在整理仓库结构",
|
||||
},
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import type { AgentRuntimeToolInventory } from "@/lib/api/agentRuntime";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
deriveRuntimeToolAvailability,
|
||||
RUNTIME_TOOL_AVAILABILITY_OVERRIDE_STORAGE_KEY,
|
||||
} from "./runtimeToolAvailability";
|
||||
|
||||
function asToolInventory(value: unknown): AgentRuntimeToolInventory {
|
||||
return value as AgentRuntimeToolInventory;
|
||||
}
|
||||
|
||||
describe("runtime tool surface 派生", () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear();
|
||||
@@ -14,24 +19,26 @@ describe("runtime tool surface 派生", () => {
|
||||
});
|
||||
|
||||
it("runtime tool surface 应优先从 runtime_tools 派生 current capability", () => {
|
||||
const availability = deriveRuntimeToolAvailability({
|
||||
agent_initialized: true,
|
||||
runtime_tools: [
|
||||
{ name: "WebSearch" },
|
||||
{ name: "Agent" },
|
||||
{ name: "SendMessage" },
|
||||
{ name: "TeamCreate" },
|
||||
{ name: "TeamDelete" },
|
||||
{ name: "ListPeers" },
|
||||
{ name: "TaskCreate" },
|
||||
{ name: "TaskGet" },
|
||||
{ name: "TaskList" },
|
||||
{ name: "TaskUpdate" },
|
||||
{ name: "TaskOutput" },
|
||||
{ name: "TaskStop" },
|
||||
],
|
||||
registry_tools: [{ name: "registry-only" }],
|
||||
} as Parameters<typeof deriveRuntimeToolAvailability>[0]);
|
||||
const availability = deriveRuntimeToolAvailability(
|
||||
asToolInventory({
|
||||
agent_initialized: true,
|
||||
runtime_tools: [
|
||||
{ name: "WebSearch" },
|
||||
{ name: "Agent" },
|
||||
{ name: "SendMessage" },
|
||||
{ name: "TeamCreate" },
|
||||
{ name: "TeamDelete" },
|
||||
{ name: "ListPeers" },
|
||||
{ name: "TaskCreate" },
|
||||
{ name: "TaskGet" },
|
||||
{ name: "TaskList" },
|
||||
{ name: "TaskUpdate" },
|
||||
{ name: "TaskOutput" },
|
||||
{ name: "TaskStop" },
|
||||
],
|
||||
registry_tools: [],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(availability).toMatchObject({
|
||||
source: "runtime_tools",
|
||||
@@ -62,11 +69,13 @@ describe("runtime tool surface 派生", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const availability = deriveRuntimeToolAvailability({
|
||||
agent_initialized: true,
|
||||
runtime_tools: [{ name: "WebSearch" }],
|
||||
registry_tools: [],
|
||||
} as Parameters<typeof deriveRuntimeToolAvailability>[0]);
|
||||
const availability = deriveRuntimeToolAvailability(
|
||||
asToolInventory({
|
||||
agent_initialized: true,
|
||||
runtime_tools: [{ name: "WebSearch" }],
|
||||
registry_tools: [],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(availability).toMatchObject({
|
||||
source: "runtime_tools",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
normalizeToolNameKey,
|
||||
parseToolCallArguments,
|
||||
resolveToolFilePath,
|
||||
type ToolCallArgumentValue,
|
||||
} from "./toolDisplayInfo";
|
||||
|
||||
export type ToolBatchKind = "exploration" | "browser";
|
||||
@@ -39,11 +40,16 @@ interface ToolBatchAccumulator {
|
||||
|
||||
interface ToolLikeDescriptor {
|
||||
toolName: string;
|
||||
argumentsValue?: string | Record<string, unknown>;
|
||||
argumentsValue?: string | Record<string, ToolCallArgumentValue>;
|
||||
command?: string | null;
|
||||
query?: string | null;
|
||||
}
|
||||
|
||||
type ThreadProcessBatchItem = Extract<
|
||||
AgentThreadItem,
|
||||
{ type: "tool_call" | "command_execution" | "web_search" }
|
||||
>;
|
||||
|
||||
function shorten(value: string | null | undefined, maxLength = 72): string | null {
|
||||
const normalized = value?.trim();
|
||||
if (!normalized) {
|
||||
@@ -55,7 +61,7 @@ function shorten(value: string | null | undefined, maxLength = 72): string | nul
|
||||
return `${normalized.slice(0, maxLength - 1).trimEnd()}…`;
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
function asRecord(value: unknown): Record<string, ToolCallArgumentValue> {
|
||||
if (!value) {
|
||||
return {};
|
||||
}
|
||||
@@ -63,18 +69,28 @@ function asRecord(value: unknown): Record<string, unknown> {
|
||||
if (typeof value === "string") {
|
||||
const parsed = parseToolCallArguments(value);
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>;
|
||||
return parsed;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
return value as Record<string, unknown>;
|
||||
return value as Record<string, ToolCallArgumentValue>;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function isThreadProcessBatchItem(
|
||||
item: AgentThreadItem,
|
||||
): item is ThreadProcessBatchItem {
|
||||
return (
|
||||
item.type === "tool_call" ||
|
||||
item.type === "command_execution" ||
|
||||
item.type === "web_search"
|
||||
);
|
||||
}
|
||||
|
||||
function readString(
|
||||
record: Record<string, unknown>,
|
||||
keys: string[],
|
||||
@@ -378,32 +394,32 @@ export function summarizeStreamingToolBatch(
|
||||
export function summarizeThreadProcessBatch(
|
||||
items: AgentThreadItem[],
|
||||
): ToolBatchSummaryDescriptor | null {
|
||||
if (
|
||||
items.length < 2 ||
|
||||
items.some(
|
||||
(item) =>
|
||||
item.type !== "tool_call" &&
|
||||
item.type !== "command_execution" &&
|
||||
item.type !== "web_search",
|
||||
)
|
||||
) {
|
||||
const processItems = items.filter(isThreadProcessBatchItem);
|
||||
if (processItems.length < 2 || processItems.length !== items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const descriptors: ToolLikeDescriptor[] = items.map((item) => {
|
||||
const descriptors: ToolLikeDescriptor[] = processItems.map((item) => {
|
||||
if (item.type === "command_execution") {
|
||||
const argumentsValue: Record<string, ToolCallArgumentValue> = {
|
||||
command: item.command,
|
||||
cwd: item.cwd,
|
||||
};
|
||||
return {
|
||||
toolName: "exec_command",
|
||||
command: item.command,
|
||||
argumentsValue: { command: item.command, cwd: item.cwd },
|
||||
argumentsValue,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "web_search") {
|
||||
const argumentsValue: Record<string, ToolCallArgumentValue> = {
|
||||
query: item.query || item.action || "",
|
||||
};
|
||||
return {
|
||||
toolName: item.action || "web_search",
|
||||
query: item.query || item.action || null,
|
||||
argumentsValue: { query: item.query || item.action || "" },
|
||||
argumentsValue,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -411,7 +427,7 @@ export function summarizeThreadProcessBatch(
|
||||
toolName: item.tool_name,
|
||||
argumentsValue:
|
||||
item.arguments && typeof item.arguments === "object"
|
||||
? (item.arguments as Record<string, unknown>)
|
||||
? (item.arguments as Record<string, ToolCallArgumentValue>)
|
||||
: item.arguments === undefined
|
||||
? undefined
|
||||
: String(item.arguments),
|
||||
|
||||
@@ -36,10 +36,7 @@ import { WorkspaceConversationScene } from "./WorkspaceConversationScene";
|
||||
|
||||
type InputbarScene = Pick<
|
||||
ReturnType<typeof useWorkspaceInputbarSceneRuntime>,
|
||||
| "inputbarNode"
|
||||
| "generalWorkbenchDialog"
|
||||
| "teamWorkbenchSurfaceProps"
|
||||
| "runtimeToolAvailability"
|
||||
"inputbarNode" | "generalWorkbenchDialog" | "teamWorkbenchSurfaceProps"
|
||||
>;
|
||||
type CanvasScene = Pick<
|
||||
ReturnType<typeof useWorkspaceCanvasSceneRuntime>,
|
||||
@@ -723,7 +720,6 @@ export function useWorkspaceConversationSceneRuntime({
|
||||
setAccessMode,
|
||||
onManageProviders: navigationActions.handleManageProviders,
|
||||
toolPreferences: chatToolPreferences,
|
||||
runtimeToolAvailability: inputbarScene.runtimeToolAvailability,
|
||||
onToolPreferenceChange: (key, enabled) =>
|
||||
setChatToolPreferences((previous) => ({
|
||||
...previous,
|
||||
|
||||
@@ -336,6 +336,17 @@ const MEMORY_SCOPE_CARD_META: Array<{
|
||||
},
|
||||
];
|
||||
|
||||
type MemoryAvailabilityStatus = "loaded" | "exists" | "missing";
|
||||
|
||||
interface MemoryScopeCardView {
|
||||
key: "user" | "project" | "local" | "auto" | "durable" | "team";
|
||||
label: string;
|
||||
description: string;
|
||||
status: MemoryAvailabilityStatus;
|
||||
detail: string;
|
||||
helper: string;
|
||||
}
|
||||
|
||||
const MEMORY_DO_NOT_SAVE = [
|
||||
"代码模式、约定、架构、文件路径或项目结构,这些应直接从当前仓库读取。",
|
||||
"Git 历史、最近改动、谁改了什么,`git log` 和 `git blame` 才是事实源。",
|
||||
@@ -637,7 +648,7 @@ function buildCreationPrompt(
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function getMemoryAvailabilityBadge(status: "loaded" | "exists" | "missing"): {
|
||||
function getMemoryAvailabilityBadge(status: MemoryAvailabilityStatus): {
|
||||
label: string;
|
||||
className: string;
|
||||
} {
|
||||
@@ -932,10 +943,11 @@ export function MemoryPage({ onNavigate, pageParams }: MemoryPageProps) {
|
||||
[sourceBuckets],
|
||||
);
|
||||
|
||||
const memoryScopeCards = useMemo(() => {
|
||||
const memoryScopeCards = useMemo<MemoryScopeCardView[]>(() => {
|
||||
return MEMORY_SCOPE_CARD_META.map((card) => {
|
||||
if (card.key === "team") {
|
||||
const status = teamSnapshots.length > 0 ? "loaded" : "missing";
|
||||
const status: MemoryAvailabilityStatus =
|
||||
teamSnapshots.length > 0 ? "loaded" : "missing";
|
||||
return {
|
||||
...card,
|
||||
status,
|
||||
@@ -961,7 +973,7 @@ export function MemoryPage({ onNavigate, pageParams }: MemoryPageProps) {
|
||||
? "durable"
|
||||
: "user";
|
||||
const bucket = sourceBucketMap.get(bucketKey);
|
||||
const status = bucket?.status || "missing";
|
||||
const status: MemoryAvailabilityStatus = bucket?.status || "missing";
|
||||
|
||||
if (card.key === "auto") {
|
||||
return {
|
||||
|
||||
@@ -14,7 +14,6 @@ import type {
|
||||
BaseSetupPackage,
|
||||
BaseSetupPolicyProfile,
|
||||
BaseSetupProjectionIndex,
|
||||
BaseSetupScorecardProfile,
|
||||
BaseSetupSlotProfile,
|
||||
} from "../types";
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ describe("http-client", () => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("事件流在已建立连接后断开时应主动关闭并停止重复告警", async () => {
|
||||
it("事件流在已建立连接后断开时应保留连接并停止重复告警", async () => {
|
||||
class MockEventSource {
|
||||
static instances: MockEventSource[] = [];
|
||||
|
||||
@@ -226,10 +226,15 @@ describe("http-client", () => {
|
||||
source.emitError();
|
||||
source.emitError();
|
||||
|
||||
expect(source.close).toHaveBeenCalledTimes(1);
|
||||
const secondUnlisten = await listenViaHttpEvent("config-changed", vi.fn());
|
||||
|
||||
expect(MockEventSource.instances).toHaveLength(1);
|
||||
expect(source.close).not.toHaveBeenCalled();
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
unlisten();
|
||||
secondUnlisten();
|
||||
expect(source.close).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("事件流在建立后结束不应把整个桥接误标记为 unavailable", async () => {
|
||||
|
||||
@@ -336,6 +336,7 @@ export async function listenViaHttpEvent<T = unknown>(
|
||||
let hasOpened = false;
|
||||
let settleOpen: ((value: void | PromiseLike<void>) => void) | null = null;
|
||||
let settleOpenError: ((reason?: unknown) => void) | null = null;
|
||||
let reconnectWarningShown = false;
|
||||
const openPromise = new Promise<void>((resolve, reject) => {
|
||||
settleOpen = resolve;
|
||||
settleOpenError = reject;
|
||||
@@ -354,6 +355,10 @@ export async function listenViaHttpEvent<T = unknown>(
|
||||
}, DEV_BRIDGE_EVENT_CONNECT_TIMEOUT_MS);
|
||||
|
||||
source.onmessage = (messageEvent) => {
|
||||
if (reconnectWarningShown) {
|
||||
reconnectWarningShown = false;
|
||||
markBridgeHealthy();
|
||||
}
|
||||
const parsed = parseBridgeEventPayload<unknown>(messageEvent.data);
|
||||
if (!parsed) {
|
||||
return;
|
||||
@@ -375,6 +380,7 @@ export async function listenViaHttpEvent<T = unknown>(
|
||||
return;
|
||||
}
|
||||
hasOpened = true;
|
||||
reconnectWarningShown = false;
|
||||
markBridgeHealthy();
|
||||
window.clearTimeout(connectTimeout);
|
||||
settleOpen?.();
|
||||
@@ -386,13 +392,17 @@ export async function listenViaHttpEvent<T = unknown>(
|
||||
if (!hubActive) {
|
||||
return;
|
||||
}
|
||||
if (hasOpened) {
|
||||
if (!reconnectWarningShown) {
|
||||
reconnectWarningShown = true;
|
||||
console.warn(`[DevBridge] 事件流异常: ${normalizedEvent}`, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.warn(`[DevBridge] 事件流异常: ${normalizedEvent}`, error);
|
||||
hubActive = false;
|
||||
bridgeEventHubs.delete(normalizedEvent);
|
||||
source.close();
|
||||
if (hasOpened) {
|
||||
return;
|
||||
}
|
||||
markBridgeUnavailable();
|
||||
window.clearTimeout(connectTimeout);
|
||||
settleOpenError?.(
|
||||
|
||||
Reference in New Issue
Block a user