mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2025-12-27 18:24:23 +01:00
chore: support claude code v2.0.30 schema
This commit is contained in:
@@ -40,7 +40,7 @@
|
||||
"lingui:compile": "lingui compile --typescript"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "0.1.27",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.1.30",
|
||||
"@anthropic-ai/claude-code": "2.0.24",
|
||||
"@anthropic-ai/sdk": "0.67.0",
|
||||
"@effect/platform": "0.92.1",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@anthropic-ai/claude-agent-sdk':
|
||||
specifier: 0.1.27
|
||||
version: 0.1.27(zod@4.1.12)
|
||||
specifier: 0.1.30
|
||||
version: 0.1.30(zod@4.1.12)
|
||||
'@anthropic-ai/claude-code':
|
||||
specifier: 2.0.24
|
||||
version: 2.0.24
|
||||
@@ -226,8 +226,8 @@ packages:
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.27':
|
||||
resolution: {integrity: sha512-HuMPW6spj2q8FODiP/WBCqUZAYGwDPoI1EpicP9KUXvuYk+2MZQYSaD7oiN6iNPupR2T5oJ2HY/D9OzjyCD2Mw==}
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.30':
|
||||
resolution: {integrity: sha512-lo1tqxCr2vygagFp6kUMHKSN6AAWlULCskwGKtLB/JcIXy/8H8GsLSKX54anTsvc9mBbCR8wWASdFmiiL9NSKA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
@@ -4494,7 +4494,7 @@ snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.27(zod@4.1.12)':
|
||||
'@anthropic-ai/claude-agent-sdk@0.1.30(zod@4.1.12)':
|
||||
dependencies:
|
||||
zod: 4.1.12
|
||||
optionalDependencies:
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SidechainConversationModal } from "../conversationModal/SidechainConver
|
||||
import { AssistantConversationContent } from "./AssistantConversationContent";
|
||||
import { FileHistorySnapshotConversationContent } from "./FileHistorySnapshotConversationContent";
|
||||
import { MetaConversationContent } from "./MetaConversationContent";
|
||||
import { QueueOperationConversationContent } from "./QueueOperationConversationContent";
|
||||
import { SummaryConversationContent } from "./SummaryConversationContent";
|
||||
import { SystemConversationContent } from "./SystemConversationContent";
|
||||
import { UserConversationContent } from "./UserConversationContent";
|
||||
@@ -51,6 +52,10 @@ export const ConversationItem: FC<{
|
||||
);
|
||||
}
|
||||
|
||||
if (conversation.type === "queue-operation") {
|
||||
return <QueueOperationConversationContent conversation={conversation} />;
|
||||
}
|
||||
|
||||
// sidechain = サブタスクのこと
|
||||
if (conversation.isSidechain) {
|
||||
// Root 以外はモーダルで中身を表示するのでここでは描画しない
|
||||
|
||||
@@ -34,6 +34,10 @@ const getConversationKey = (conversation: Conversation) => {
|
||||
return `file-history-snapshot_${conversation.messageId}`;
|
||||
}
|
||||
|
||||
if (conversation.type === "queue-operation") {
|
||||
return `queue-operation_${conversation.operation}_${conversation.sessionId}`;
|
||||
}
|
||||
|
||||
conversation satisfies never;
|
||||
throw new Error(`Unknown conversation type: ${conversation}`);
|
||||
};
|
||||
@@ -158,6 +162,7 @@ export const ConversationList: FC<ConversationListProps> = ({
|
||||
const isSidechain =
|
||||
conversation.type !== "summary" &&
|
||||
conversation.type !== "file-history-snapshot" &&
|
||||
conversation.type !== "queue-operation" &&
|
||||
conversation.isSidechain;
|
||||
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import type { QueueOperationEntry } from "@/lib/conversation-schema/entry/QueueOperationEntrySchema";
|
||||
|
||||
export const QueueOperationConversationContent: FC<{
|
||||
conversation: QueueOperationEntry;
|
||||
}> = ({ conversation }) => {
|
||||
const title =
|
||||
conversation.operation === "enqueue"
|
||||
? "Queue Operation: Enqueue"
|
||||
: "Queue Operation: Dequeue";
|
||||
|
||||
return (
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger asChild>
|
||||
<div className="flex items-center justify-between cursor-pointer hover:bg-muted/50 rounded p-2 -mx-2">
|
||||
<h4 className="text-xs font-medium text-muted-foreground">{title}</h4>
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="bg-background rounded border p-3 mt-2">
|
||||
<div className="space-y-2 text-xs">
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">
|
||||
Operation:
|
||||
</span>{" "}
|
||||
{conversation.operation}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">
|
||||
Session ID:
|
||||
</span>{" "}
|
||||
{conversation.sessionId}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">
|
||||
Timestamp:
|
||||
</span>{" "}
|
||||
{conversation.timestamp}
|
||||
</div>
|
||||
{conversation.operation === "enqueue" && (
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">
|
||||
Content:
|
||||
</span>
|
||||
<pre className="mt-1 overflow-x-auto whitespace-pre-wrap">
|
||||
{conversation.content}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
@@ -31,7 +31,8 @@ const sidechainTitle = (conversations: Conversation[]): string => {
|
||||
|
||||
const defaultTitle = `${conversations.length} conversations (${
|
||||
firstConversation?.type !== "summary" &&
|
||||
firstConversation?.type !== "file-history-snapshot"
|
||||
firstConversation?.type !== "file-history-snapshot" &&
|
||||
firstConversation?.type !== "queue-operation"
|
||||
? firstConversation?.uuid
|
||||
: ""
|
||||
})`;
|
||||
|
||||
@@ -9,7 +9,9 @@ export const useSidechain = (conversations: Conversation[]) => {
|
||||
const sidechainConversations = conversations
|
||||
.filter(
|
||||
(conv) =>
|
||||
conv.type !== "summary" && conv.type !== "file-history-snapshot",
|
||||
conv.type !== "summary" &&
|
||||
conv.type !== "file-history-snapshot" &&
|
||||
conv.type !== "queue-operation",
|
||||
)
|
||||
.filter((conv) => conv.isSidechain === true);
|
||||
|
||||
@@ -92,7 +94,8 @@ export const useSidechain = (conversations: Conversation[]) => {
|
||||
(conversation: Conversation) => {
|
||||
if (
|
||||
conversation.type === "summary" ||
|
||||
conversation.type === "file-history-snapshot"
|
||||
conversation.type === "file-history-snapshot" ||
|
||||
conversation.type === "queue-operation"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const QueueOperationEntrySchema = z.union([
|
||||
z.object({
|
||||
type: z.literal("queue-operation"),
|
||||
operation: z.literal("enqueue"),
|
||||
content: z.string(),
|
||||
sessionId: z.string(),
|
||||
timestamp: z.iso.datetime(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("queue-operation"),
|
||||
operation: z.literal("dequeue"),
|
||||
sessionId: z.string(),
|
||||
timestamp: z.iso.datetime(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type QueueOperationEntry = z.infer<typeof QueueOperationEntrySchema>;
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
AssistantEntrySchema,
|
||||
} from "./entry/AssistantEntrySchema";
|
||||
import { FileHistorySnapshotEntrySchema } from "./entry/FileHIstorySnapshotEntrySchema";
|
||||
import { QueueOperationEntrySchema } from "./entry/QueueOperationEntrySchema";
|
||||
import { SummaryEntrySchema } from "./entry/SummaryEntrySchema";
|
||||
import { type SystemEntry, SystemEntrySchema } from "./entry/SystemEntrySchema";
|
||||
import { type UserEntry, UserEntrySchema } from "./entry/UserEntrySchema";
|
||||
@@ -14,6 +15,7 @@ export const ConversationSchema = z.union([
|
||||
SummaryEntrySchema,
|
||||
SystemEntrySchema,
|
||||
FileHistorySnapshotEntrySchema,
|
||||
QueueOperationEntrySchema,
|
||||
]);
|
||||
|
||||
export type Conversation = z.infer<typeof ConversationSchema>;
|
||||
|
||||
32
src/server/core/claude-code/functions/fallbackSdkMessage.ts
Normal file
32
src/server/core/claude-code/functions/fallbackSdkMessage.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { SDKMessage as AgentSDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { SDKMessage as ClaudeCodeSDKMessage } from "@anthropic-ai/claude-code";
|
||||
|
||||
export const fallbackSdkMessage = (
|
||||
message: AgentSDKMessage | ClaudeCodeSDKMessage,
|
||||
): AgentSDKMessage => {
|
||||
if (message.type === "system") {
|
||||
if (message.subtype === "init") {
|
||||
return {
|
||||
...message,
|
||||
plugins: [],
|
||||
};
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
if (message.type === "result") {
|
||||
if (message.subtype === "success") {
|
||||
return {
|
||||
...message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...message,
|
||||
errors: [],
|
||||
};
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
@@ -1,24 +1,19 @@
|
||||
import { query as agentSdkQuery } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { query as claudeCodeQuery } from "@anthropic-ai/claude-code";
|
||||
import {
|
||||
type CanUseTool,
|
||||
query as claudeCodeQuery,
|
||||
} from "@anthropic-ai/claude-code";
|
||||
import { Command, Path } from "@effect/platform";
|
||||
import { Data, Effect } from "effect";
|
||||
import { EnvService } from "../../platform/services/EnvService";
|
||||
import * as ClaudeCodeVersion from "./ClaudeCodeVersion";
|
||||
|
||||
type CCQuery = typeof claudeCodeQuery;
|
||||
type CCQueryPrompt = Parameters<CCQuery>[0]["prompt"];
|
||||
type CCQueryOptions = NonNullable<Parameters<CCQuery>[0]["options"]>;
|
||||
|
||||
type AgentSdkQuery = typeof agentSdkQuery;
|
||||
type AgentSdkPrompt = Parameters<AgentSdkQuery>[0]["prompt"];
|
||||
type AgentSdkQueryOptions = NonNullable<
|
||||
Parameters<AgentSdkQuery>[0]["options"]
|
||||
>;
|
||||
|
||||
type SharedOptions = Pick<
|
||||
CCQueryOptions,
|
||||
Extract<keyof AgentSdkQueryOptions, keyof CCQueryOptions>
|
||||
>;
|
||||
|
||||
class ClaudeCodePathNotFoundError extends Data.TaggedError(
|
||||
"ClaudeCodePathNotFoundError",
|
||||
)<{
|
||||
@@ -143,14 +138,17 @@ export const getAvailableFeatures = (
|
||||
: false,
|
||||
});
|
||||
|
||||
export const query = (prompt: CCQueryPrompt, options: SharedOptions) => {
|
||||
export const query = (
|
||||
prompt: AgentSdkPrompt,
|
||||
options: AgentSdkQueryOptions,
|
||||
) => {
|
||||
const { canUseTool, permissionMode, ...baseOptions } = options;
|
||||
|
||||
return Effect.gen(function* () {
|
||||
const { claudeCodeExecutablePath, claudeCodeVersion } = yield* Config;
|
||||
const availableFeatures = getAvailableFeatures(claudeCodeVersion);
|
||||
|
||||
const options: SharedOptions = {
|
||||
const options: AgentSdkQueryOptions = {
|
||||
pathToClaudeCodeExecutable: claudeCodeExecutablePath,
|
||||
...baseOptions,
|
||||
...(availableFeatures.canUseTool
|
||||
@@ -171,9 +169,30 @@ export const query = (prompt: CCQueryPrompt, options: SharedOptions) => {
|
||||
});
|
||||
}
|
||||
|
||||
const fallbackCanUseTool = (() => {
|
||||
const canUseTool = options.canUseTool;
|
||||
if (canUseTool === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fn: CanUseTool = async (toolName, input, canUseToolOptions) => {
|
||||
const response = await canUseTool(toolName, input, {
|
||||
signal: canUseToolOptions.signal,
|
||||
suggestions: canUseToolOptions.suggestions,
|
||||
toolUseID: undefined as unknown as string,
|
||||
});
|
||||
return response;
|
||||
};
|
||||
|
||||
return fn;
|
||||
})();
|
||||
|
||||
return claudeCodeQuery({
|
||||
prompt,
|
||||
options: options,
|
||||
options: {
|
||||
...options,
|
||||
canUseTool: fallbackCanUseTool,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
createMessageGenerator,
|
||||
type UserMessageInput,
|
||||
} from "../functions/createMessageGenerator";
|
||||
import { fallbackSdkMessage } from "../functions/fallbackSdkMessage";
|
||||
import * as CCSessionProcess from "../models/CCSessionProcess";
|
||||
import * as ClaudeCode from "../models/ClaudeCode";
|
||||
import { ClaudeCodePermissionService } from "./ClaudeCodePermissionService";
|
||||
@@ -146,6 +147,8 @@ const LayerImpl = Effect.gen(function* () {
|
||||
|
||||
const handleMessage = (message: SDKMessage) =>
|
||||
Effect.gen(function* () {
|
||||
console.log("[debug] handleMessage", message.type);
|
||||
|
||||
const processState = yield* sessionProcessService.getSessionProcess(
|
||||
sessionProcess.def.sessionProcessId,
|
||||
);
|
||||
@@ -283,29 +286,14 @@ const LayerImpl = Effect.gen(function* () {
|
||||
|
||||
try {
|
||||
for await (const message of messageIter) {
|
||||
if (
|
||||
message.type === "system" &&
|
||||
message.subtype === "hook_response"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const fallbackMessage = fallbackSdkMessage(message);
|
||||
|
||||
if (
|
||||
message.type === "system" &&
|
||||
message.subtype === "compact_boundary"
|
||||
) {
|
||||
if (fallbackMessage === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = await Runtime.runPromise(runtime)(
|
||||
handleMessage(
|
||||
message.type === "system"
|
||||
? {
|
||||
...message,
|
||||
plugins: [],
|
||||
}
|
||||
: message,
|
||||
),
|
||||
handleMessage(fallbackMessage),
|
||||
).catch((error) => {
|
||||
// iter 自体が落ちてなければ継続したいので握りつぶす
|
||||
Effect.runFork(
|
||||
|
||||
@@ -40,7 +40,8 @@ const LayerImpl = Effect.gen(function* () {
|
||||
conversation === undefined ||
|
||||
conversation.type === "summary" ||
|
||||
conversation.type === "x-error" ||
|
||||
conversation.type === "file-history-snapshot"
|
||||
conversation.type === "file-history-snapshot" ||
|
||||
conversation.type === "queue-operation"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user