chore: support claude code v2.0.30 schema

This commit is contained in:
d-kimsuon
2025-11-02 01:41:44 +09:00
parent e17b58b481
commit 76aaf1013c
13 changed files with 178 additions and 41 deletions

View File

@@ -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
View File

@@ -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:

View File

@@ -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 以外はモーダルで中身を表示するのでここでは描画しない

View File

@@ -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 [

View File

@@ -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>
);
};

View File

@@ -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
: ""
})`;

View File

@@ -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;
}

View File

@@ -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>;

View File

@@ -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>;

View 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;
};

View File

@@ -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,
},
});
});
};

View File

@@ -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(

View File

@@ -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;
}