From 76aaf1013cbfde45b12a1b1acd4967d66aabacda Mon Sep 17 00:00:00 2001 From: d-kimsuon Date: Sun, 2 Nov 2025 01:41:44 +0900 Subject: [PATCH] chore: support claude code v2.0.30 schema --- package.json | 2 +- pnpm-lock.yaml | 10 +-- .../conversationList/ConversationItem.tsx | 5 ++ .../conversationList/ConversationList.tsx | 5 ++ .../QueueOperationConversationContent.tsx | 62 +++++++++++++++++++ .../SidechainConversationModal.tsx | 3 +- .../[sessionId]/hooks/useSidechain.ts | 7 ++- .../entry/QueueOperationEntrySchema.ts | 19 ++++++ src/lib/conversation-schema/index.ts | 2 + .../functions/fallbackSdkMessage.ts | 32 ++++++++++ .../core/claude-code/models/ClaudeCode.ts | 45 ++++++++++---- .../services/ClaudeCodeLifeCycleService.ts | 24 ++----- .../project/services/ProjectMetaService.ts | 3 +- 13 files changed, 178 insertions(+), 41 deletions(-) create mode 100644 src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/QueueOperationConversationContent.tsx create mode 100644 src/lib/conversation-schema/entry/QueueOperationEntrySchema.ts create mode 100644 src/server/core/claude-code/functions/fallbackSdkMessage.ts diff --git a/package.json b/package.json index 3bca6e8..b69c5a5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b08fc9f..4a9f63a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx index dcc8306..b284098 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx @@ -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 ; + } + // sidechain = サブタスクのこと if (conversation.isSidechain) { // Root 以外はモーダルで中身を表示するのでここでは描画しない diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx index 88da868..d7eee55 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx @@ -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 = ({ const isSidechain = conversation.type !== "summary" && conversation.type !== "file-history-snapshot" && + conversation.type !== "queue-operation" && conversation.isSidechain; return [ diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/QueueOperationConversationContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/QueueOperationConversationContent.tsx new file mode 100644 index 0000000..b371f32 --- /dev/null +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/QueueOperationConversationContent.tsx @@ -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 ( + + +
+

{title}

+ +
+
+ +
+
+
+ + Operation: + {" "} + {conversation.operation} +
+
+ + Session ID: + {" "} + {conversation.sessionId} +
+
+ + Timestamp: + {" "} + {conversation.timestamp} +
+ {conversation.operation === "enqueue" && ( +
+ + Content: + +
+                  {conversation.content}
+                
+
+ )} +
+
+
+
+ ); +}; diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationModal/SidechainConversationModal.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationModal/SidechainConversationModal.tsx index 8b8bda3..9b7de1f 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationModal/SidechainConversationModal.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationModal/SidechainConversationModal.tsx @@ -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 : "" })`; diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSidechain.ts b/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSidechain.ts index 47c2909..71cb714 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSidechain.ts +++ b/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSidechain.ts @@ -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; } diff --git a/src/lib/conversation-schema/entry/QueueOperationEntrySchema.ts b/src/lib/conversation-schema/entry/QueueOperationEntrySchema.ts new file mode 100644 index 0000000..6afc849 --- /dev/null +++ b/src/lib/conversation-schema/entry/QueueOperationEntrySchema.ts @@ -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; diff --git a/src/lib/conversation-schema/index.ts b/src/lib/conversation-schema/index.ts index 13929d4..6e3ae5e 100644 --- a/src/lib/conversation-schema/index.ts +++ b/src/lib/conversation-schema/index.ts @@ -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; diff --git a/src/server/core/claude-code/functions/fallbackSdkMessage.ts b/src/server/core/claude-code/functions/fallbackSdkMessage.ts new file mode 100644 index 0000000..7aeac1a --- /dev/null +++ b/src/server/core/claude-code/functions/fallbackSdkMessage.ts @@ -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; +}; diff --git a/src/server/core/claude-code/models/ClaudeCode.ts b/src/server/core/claude-code/models/ClaudeCode.ts index f67e9cf..4b7be44 100644 --- a/src/server/core/claude-code/models/ClaudeCode.ts +++ b/src/server/core/claude-code/models/ClaudeCode.ts @@ -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[0]["prompt"]; -type CCQueryOptions = NonNullable[0]["options"]>; - type AgentSdkQuery = typeof agentSdkQuery; +type AgentSdkPrompt = Parameters[0]["prompt"]; type AgentSdkQueryOptions = NonNullable< Parameters[0]["options"] >; -type SharedOptions = Pick< - CCQueryOptions, - Extract ->; - 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, + }, }); }); }; diff --git a/src/server/core/claude-code/services/ClaudeCodeLifeCycleService.ts b/src/server/core/claude-code/services/ClaudeCodeLifeCycleService.ts index c87707e..4097d3f 100644 --- a/src/server/core/claude-code/services/ClaudeCodeLifeCycleService.ts +++ b/src/server/core/claude-code/services/ClaudeCodeLifeCycleService.ts @@ -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( diff --git a/src/server/core/project/services/ProjectMetaService.ts b/src/server/core/project/services/ProjectMetaService.ts index e643323..9e4d20b 100644 --- a/src/server/core/project/services/ProjectMetaService.ts +++ b/src/server/core/project/services/ProjectMetaService.ts @@ -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; }