feat: migrate to OpenCode SDK v2

Update all import statements to use @opencode-ai/sdk/v2
Modify Vite configurations to alias new SDK path
Update API client method signatures for SDK v2 compatibility
This commit is contained in:
Bohdan Triapitsyn
2026-01-03 13:39:59 +02:00
parent 5029acfb9d
commit f2507d58cf
46 changed files with 167 additions and 184 deletions
+7 -7
View File
@@ -12,12 +12,12 @@ export default defineConfig({
root: path.resolve(__dirname, '.'), root: path.resolve(__dirname, '.'),
plugins: [react(), themeStoragePlugin()], plugins: [react(), themeStoragePlugin()],
resolve: { resolve: {
alias: { alias: [
'@desktop': path.resolve(__dirname, './src'), { find: '@opencode-ai/sdk/v2', replacement: path.resolve(__dirname, '../../node_modules/@opencode-ai/sdk/dist/v2/client.js') },
'@openchamber/ui': path.resolve(__dirname, '../ui/src'), { find: '@openchamber/ui', replacement: path.resolve(__dirname, '../ui/src') },
'@': path.resolve(__dirname, '../ui/src'), { find: '@desktop', replacement: path.resolve(__dirname, './src') },
'@opencode-ai/sdk': path.resolve(__dirname, '../../node_modules/@opencode-ai/sdk/dist/client.js'), { find: '@', replacement: path.resolve(__dirname, '../ui/src') },
}, ],
}, },
worker: { worker: {
format: 'es', format: 'es',
@@ -31,7 +31,7 @@ export default defineConfig({
__APP_VERSION__: JSON.stringify(packageJson.version), __APP_VERSION__: JSON.stringify(packageJson.version),
}, },
optimizeDeps: { optimizeDeps: {
include: ['@opencode-ai/sdk'], include: ['@opencode-ai/sdk/v2'],
exclude: [ exclude: [
'@tauri-apps/plugin-dialog', '@tauri-apps/plugin-dialog',
'@tauri-apps/api/core', '@tauri-apps/api/core',
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Message, Part } from '@opencode-ai/sdk'; import type { Message, Part } from '@opencode-ai/sdk/v2';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { defaultCodeDark, defaultCodeLight } from '@/lib/codeTheme'; import { defaultCodeDark, defaultCodeLight } from '@/lib/codeTheme';
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Streamdown } from 'streamdown'; import { Streamdown } from 'streamdown';
import { FadeInOnReveal } from './message/FadeInOnReveal'; import { FadeInOnReveal } from './message/FadeInOnReveal';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { RiFileCopyLine, RiCheckLine, RiDownloadLine } from '@remixicon/react'; import { RiFileCopyLine, RiCheckLine, RiDownloadLine } from '@remixicon/react';
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Message, Part } from '@opencode-ai/sdk'; import type { Message, Part } from '@opencode-ai/sdk/v2';
import ChatMessage from './ChatMessage'; import ChatMessage from './ChatMessage';
import { PermissionCard } from './PermissionCard'; import { PermissionCard } from './PermissionCard';
@@ -12,7 +12,7 @@ import { useSessionStore } from '@/stores/useSessionStore';
import { useMessageStore } from '@/stores/messageStore'; import { useMessageStore } from '@/stores/messageStore';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { RiLoader4Line, RiSearchLine, RiTimeLine, RiGitBranchLine } from '@remixicon/react'; import { RiLoader4Line, RiSearchLine, RiTimeLine, RiGitBranchLine } from '@remixicon/react';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
interface TimelineDialogProps { interface TimelineDialogProps {
open: boolean; open: boolean;
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Message, Part } from '@opencode-ai/sdk'; import type { Message, Part } from '@opencode-ai/sdk/v2';
import { useCurrentSessionActivity } from '@/hooks/useSessionActivity'; import { useCurrentSessionActivity } from '@/hooks/useSessionActivity';
import { useUIStore } from '@/stores/useUIStore'; import { useUIStore } from '@/stores/useUIStore';
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
import AssistantTextPart from './parts/AssistantTextPart'; import AssistantTextPart from './parts/AssistantTextPart';
import UserTextPart from './parts/UserTextPart'; import UserTextPart from './parts/UserTextPart';
@@ -8,7 +8,7 @@ import ToolPart from './parts/ToolPart';
import ProgressiveGroup from './parts/ProgressiveGroup'; import ProgressiveGroup from './parts/ProgressiveGroup';
import MigratingPart from './parts/MigratingPart'; import MigratingPart from './parts/MigratingPart';
import { MessageFilesDisplay } from '../FileAttachment'; import { MessageFilesDisplay } from '../FileAttachment';
import type { ToolPart as ToolPartType } from '@opencode-ai/sdk'; import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2';
import type { StreamPhase, ToolPopupContent, AgentMentionInfo } from './types'; import type { StreamPhase, ToolPopupContent, AgentMentionInfo } from './types';
import type { TurnGroupingContext } from '../hooks/useTurnGrouping'; import type { TurnGroupingContext } from '../hooks/useTurnGrouping';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@@ -1,4 +1,4 @@
import type { Message } from '@opencode-ai/sdk'; import type { Message } from '@opencode-ai/sdk/v2';
export interface MessageRoleInfo { export interface MessageRoleInfo {
role: string; role: string;
@@ -1,4 +1,4 @@
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
type PartWithText = Part & { text?: string; content?: string; value?: string }; type PartWithText = Part & { text?: string; content?: string; value?: string };
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
import { MarkdownRenderer } from '../../MarkdownRenderer'; import { MarkdownRenderer } from '../../MarkdownRenderer';
import type { StreamPhase } from '../types'; import type { StreamPhase } from '../types';
import type { ContentChangeReason } from '@/hooks/useChatScrollManager'; import type { ContentChangeReason } from '@/hooks/useChatScrollManager';
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
import type { ContentChangeReason } from '@/hooks/useChatScrollManager'; import type { ContentChangeReason } from '@/hooks/useChatScrollManager';
import { ReasoningTimelineBlock } from './ReasoningPart'; import { ReasoningTimelineBlock } from './ReasoningPart';
@@ -2,7 +2,7 @@ import React from 'react';
import { RiArrowDownSLine, RiArrowRightSLine, RiStackLine } from '@remixicon/react'; import { RiArrowDownSLine, RiArrowRightSLine, RiStackLine } from '@remixicon/react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { TurnActivityPart } from '../../hooks/useTurnGrouping'; import type { TurnActivityPart } from '../../hooks/useTurnGrouping';
import type { ToolPart as ToolPartType } from '@opencode-ai/sdk'; import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2';
import type { ContentChangeReason } from '@/hooks/useChatScrollManager'; import type { ContentChangeReason } from '@/hooks/useChatScrollManager';
import type { ToolPopupContent } from '../types'; import type { ToolPopupContent } from '../types';
import ToolPart from './ToolPart'; import ToolPart from './ToolPart';
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import type { ComponentType } from 'react'; import type { ComponentType } from 'react';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
import { RiArrowDownSLine, RiArrowRightSLine, RiBrainAi3Line, RiChatAi3Line } from '@remixicon/react'; import { RiArrowDownSLine, RiArrowRightSLine, RiBrainAi3Line, RiChatAi3Line } from '@remixicon/react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { ContentChangeReason } from '@/hooks/useChatScrollManager'; import type { ContentChangeReason } from '@/hooks/useChatScrollManager';
@@ -5,7 +5,7 @@ import { RiArrowDownSLine, RiArrowRightSLine, RiBookLine, RiFileEditLine, RiFile
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { SimpleMarkdownRenderer } from '../../MarkdownRenderer'; import { SimpleMarkdownRenderer } from '../../MarkdownRenderer';
import { getToolMetadata, getLanguageFromExtension, isImageFile, getImageMimeType } from '@/lib/toolHelpers'; import { getToolMetadata, getLanguageFromExtension, isImageFile, getImageMimeType } from '@/lib/toolHelpers';
import type { ToolPart as ToolPartType, ToolState as ToolStateUnion } from '@opencode-ai/sdk'; import type { ToolPart as ToolPartType, ToolState as ToolStateUnion } from '@opencode-ai/sdk/v2';
import { toolDisplayStyles } from '@/lib/typography'; import { toolDisplayStyles } from '@/lib/typography';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore';
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
import type { AgentMentionInfo } from '../types'; import type { AgentMentionInfo } from '../types';
type PartWithText = Part & { text?: string; content?: string; value?: string }; type PartWithText = Part & { text?: string; content?: string; value?: string };
@@ -23,7 +23,7 @@ import { useUIStore } from '@/stores/useUIStore';
import { useDeviceInfo } from '@/lib/device'; import { useDeviceInfo } from '@/lib/device';
import { isVSCodeRuntime } from '@/lib/desktop'; import { isVSCodeRuntime } from '@/lib/desktop';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { Agent } from '@opencode-ai/sdk'; import type { Agent } from '@opencode-ai/sdk/v2';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
interface AgentsSidebarProps { interface AgentsSidebarProps {
@@ -400,10 +400,8 @@ export const DirectoryTree: React.FC<DirectoryTreeProps> = ({
const tempClient = opencodeClient.getApiClient(); const tempClient = opencodeClient.getApiClient();
const response = await tempClient.file.list({ const response = await tempClient.file.list({
query: {
path: '.', path: '.',
directory: path directory: path
}
}); });
if (!response.data) { if (!response.data) {
@@ -25,7 +25,7 @@ import { RiCheckboxBlankLine, RiCheckboxLine, RiDeleteBinLine } from '@remixicon
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { DirectoryExplorerDialog } from './DirectoryExplorerDialog'; import { DirectoryExplorerDialog } from './DirectoryExplorerDialog';
import { cn, formatPathForDisplay } from '@/lib/utils'; import { cn, formatPathForDisplay } from '@/lib/utils';
import type { Session } from '@opencode-ai/sdk'; import type { Session } from '@opencode-ai/sdk/v2';
import type { WorktreeMetadata } from '@/types/worktree'; import type { WorktreeMetadata } from '@/types/worktree';
import { import {
archiveWorktree, archiveWorktree,
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Session } from '@opencode-ai/sdk'; import type { Session } from '@opencode-ai/sdk/v2';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { import {
DropdownMenu, DropdownMenu,
+1 -1
View File
@@ -15,7 +15,7 @@ import {
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { RiGitBranchLine, RiLoader4Line } from '@remixicon/react'; import { RiGitBranchLine, RiLoader4Line } from '@remixicon/react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { Session } from '@opencode-ai/sdk'; import type { Session } from '@opencode-ai/sdk/v2';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useUIStore } from '@/stores/useUIStore'; import { useUIStore } from '@/stores/useUIStore';
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { AssistantMessage, Message, Part, ReasoningPart, TextPart, ToolPart } from '@opencode-ai/sdk'; import type { AssistantMessage, Message, Part, ReasoningPart, TextPart, ToolPart } from '@opencode-ai/sdk/v2';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import type { MessageStreamPhase } from '@/stores/types/sessionTypes'; import type { MessageStreamPhase } from '@/stores/types/sessionTypes';
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
import { MessageFreshnessDetector } from '@/lib/messageFreshness'; import { MessageFreshnessDetector } from '@/lib/messageFreshness';
+1 -1
View File
@@ -5,7 +5,7 @@ import { useSessionStore } from '@/stores/useSessionStore';
import { useConfigStore } from '@/stores/useConfigStore'; import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore, type EventStreamStatus } from '@/stores/useUIStore'; import { useUIStore, type EventStreamStatus } from '@/stores/useUIStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore';
import type { Part, Session, Message, Permission } from '@opencode-ai/sdk'; import type { Part, Session, Message, Permission } from '@opencode-ai/sdk/v2';
import { streamDebugEnabled } from '@/stores/utils/streamDebug'; import { streamDebugEnabled } from '@/stores/utils/streamDebug';
import { handleTodoUpdatedEvent } from '@/stores/useTodoStore'; import { handleTodoUpdatedEvent } from '@/stores/useTodoStore';
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { AssistantMessage, Message, Part } from '@opencode-ai/sdk'; import type { AssistantMessage, Message, Part } from '@opencode-ai/sdk/v2';
import { useSessionStore, MEMORY_LIMITS } from '@/stores/useSessionStore'; import { useSessionStore, MEMORY_LIMITS } from '@/stores/useSessionStore';
import { opencodeClient } from '@/lib/opencode/client'; import { opencodeClient } from '@/lib/opencode/client';
import { readSessionCursor } from '@/lib/messageCursorPersistence'; import { readSessionCursor } from '@/lib/messageCursorPersistence';
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useConfigStore } from '@/stores/useConfigStore'; import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore } from '@/stores/useUIStore'; import { useUIStore } from '@/stores/useUIStore';
import type { Provider } from '@opencode-ai/sdk'; import type { Provider } from '@opencode-ai/sdk/v2';
type ProviderModel = Provider["models"][string]; type ProviderModel = Provider["models"][string];
type ProviderWithModelList = Omit<Provider, "models"> & { models: ProviderModel[] }; type ProviderWithModelList = Omit<Provider, "models"> & { models: ProviderModel[] };
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { Session } from '@opencode-ai/sdk'; import type { Session } from '@opencode-ai/sdk/v2';
import { useSessionStore } from '@/stores/useSessionStore'; import { useSessionStore } from '@/stores/useSessionStore';
import { useUIStore } from '@/stores/useUIStore'; import { useUIStore } from '@/stores/useUIStore';
+1 -1
View File
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { Part } from "@opencode-ai/sdk"; import type { Part } from "@opencode-ai/sdk/v2";
import { isFullySyntheticMessage } from "@/lib/messages/synthetic"; import { isFullySyntheticMessage } from "@/lib/messages/synthetic";
export interface MessageInfo { export interface MessageInfo {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Message } from '@opencode-ai/sdk'; import type { Message } from '@opencode-ai/sdk/v2';
export class MessageFreshnessDetector { export class MessageFreshnessDetector {
private static instance: MessageFreshnessDetector; private static instance: MessageFreshnessDetector;
@@ -1,4 +1,4 @@
import type { Agent } from "@opencode-ai/sdk"; import type { Agent } from "@opencode-ai/sdk/v2";
export interface AgentMentionSource { export interface AgentMentionSource {
value: string; value: string;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Part } from '@opencode-ai/sdk'; import type { Part } from '@opencode-ai/sdk/v2';
type TextLikePart = Part & { text?: string; content?: string }; type TextLikePart = Part & { text?: string; content?: string };
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Part } from "@opencode-ai/sdk"; import type { Part } from "@opencode-ai/sdk/v2";
export const isSyntheticPart = (part: Part | undefined): boolean => { export const isSyntheticPart = (part: Part | undefined): boolean => {
if (!part || typeof part !== "object") { if (!part || typeof part !== "object") {
+65 -76
View File
@@ -1,4 +1,4 @@
import { createOpencodeClient, OpencodeClient } from "@opencode-ai/sdk"; import { createOpencodeClient, OpencodeClient } from "@opencode-ai/sdk/v2";
import type { FilesAPI, RuntimeAPIs } from "../api/types"; import type { FilesAPI, RuntimeAPIs } from "../api/types";
import { getDesktopHomeDirectory } from "../desktop"; import { getDesktopHomeDirectory } from "../desktop";
import type { import type {
@@ -12,7 +12,7 @@ import type {
TextPartInput, TextPartInput,
FilePartInput, FilePartInput,
Event, Event,
} from "@opencode-ai/sdk"; } from "@opencode-ai/sdk/v2";
type StreamEvent<TData> = { type StreamEvent<TData> = {
data: TData; data: TData;
event?: string; event?: string;
@@ -238,9 +238,9 @@ class OpencodeService {
}; };
try { try {
const response = await this.client.path.get({ const response = await this.client.path.get(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
const info = response.data; const info = response.data;
if (info) { if (info) {
addCandidate(info.directory); addCandidate(info.directory);
@@ -253,9 +253,9 @@ class OpencodeService {
if (!candidates.size) { if (!candidates.size) {
try { try {
const project = await this.client.project.current({ const project = await this.client.project.current(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
addCandidate(project.data?.worktree); addCandidate(project.data?.worktree);
} catch (error) { } catch (error) {
console.debug('Failed to load project info:', error); console.debug('Failed to load project info:', error);
@@ -296,19 +296,17 @@ class OpencodeService {
// Session Management // Session Management
async listSessions(): Promise<Session[]> { async listSessions(): Promise<Session[]> {
const response = await this.client.session.list({ const response = await this.client.session.list(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
return Array.isArray(response.data) ? response.data : []; return Array.isArray(response.data) ? response.data : [];
} }
async createSession(params?: { parentID?: string; title?: string }): Promise<Session> { async createSession(params?: { parentID?: string; title?: string }): Promise<Session> {
const response = await this.client.session.create({ const response = await this.client.session.create({
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined, ...(this.currentDirectory ? { directory: this.currentDirectory } : {}),
body: {
parentID: params?.parentID, parentID: params?.parentID,
title: params?.title title: params?.title
}
}); });
if (!response.data) throw new Error('Failed to create session'); if (!response.data) throw new Error('Failed to create session');
return response.data; return response.data;
@@ -316,8 +314,8 @@ class OpencodeService {
async getSession(id: string): Promise<Session> { async getSession(id: string): Promise<Session> {
const response = await this.client.session.get({ const response = await this.client.session.get({
path: { id }, sessionID: id,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined ...(this.currentDirectory ? { directory: this.currentDirectory } : {})
}); });
if (!response.data) throw new Error('Session not found'); if (!response.data) throw new Error('Session not found');
return response.data; return response.data;
@@ -325,17 +323,17 @@ class OpencodeService {
async deleteSession(id: string): Promise<boolean> { async deleteSession(id: string): Promise<boolean> {
const response = await this.client.session.delete({ const response = await this.client.session.delete({
path: { id }, sessionID: id,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined ...(this.currentDirectory ? { directory: this.currentDirectory } : {})
}); });
return response.data || false; return response.data || false;
} }
async updateSession(id: string, title?: string): Promise<Session> { async updateSession(id: string, title?: string): Promise<Session> {
const response = await this.client.session.update({ const response = await this.client.session.update({
path: { id }, sessionID: id,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined, ...(this.currentDirectory ? { directory: this.currentDirectory } : {}),
body: { title } title
}); });
if (!response.data) throw new Error('Failed to update session'); if (!response.data) throw new Error('Failed to update session');
return response.data; return response.data;
@@ -343,8 +341,8 @@ class OpencodeService {
async getSessionMessages(id: string): Promise<{ info: Message; parts: Part[] }[]> { async getSessionMessages(id: string): Promise<{ info: Message; parts: Part[] }[]> {
const response = await this.client.session.messages({ const response = await this.client.session.messages({
path: { id }, sessionID: id,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined ...(this.currentDirectory ? { directory: this.currentDirectory } : {})
}); });
return response.data || []; return response.data || [];
} }
@@ -618,9 +616,8 @@ class OpencodeService {
// Use SDK session.prompt() method // Use SDK session.prompt() method
// DON'T send messageID - let server generate it (fixes Claude empty response issue) // DON'T send messageID - let server generate it (fixes Claude empty response issue)
await this.client.session.prompt({ await this.client.session.prompt({
path: { id: params.id }, sessionID: params.id,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined, ...(this.currentDirectory ? { directory: this.currentDirectory } : {}),
body: {
// messageID intentionally omitted - server will generate // messageID intentionally omitted - server will generate
model: { model: {
providerID: params.providerID, providerID: params.providerID,
@@ -628,7 +625,6 @@ class OpencodeService {
}, },
agent: params.agent, agent: params.agent,
parts parts
}
}); });
// Return temporary ID for optimistic UI // Return temporary ID for optimistic UI
@@ -637,19 +633,22 @@ class OpencodeService {
} }
async abortSession(id: string): Promise<boolean> { async abortSession(id: string): Promise<boolean> {
const response = await this.client.session.abort({ const response = await this.client.session.abort(
path: { id }, {
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined, sessionID: id,
throwOnError: true, ...(this.currentDirectory ? { directory: this.currentDirectory } : {})
}); },
{ throwOnError: true }
);
return Boolean(response.data); return Boolean(response.data);
} }
async revertSession(sessionId: string, messageId: string, partId?: string): Promise<Session> { async revertSession(sessionId: string, messageId: string, partId?: string): Promise<Session> {
const response = await this.client.session.revert({ const response = await this.client.session.revert({
path: { id: sessionId }, sessionID: sessionId,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined, ...(this.currentDirectory ? { directory: this.currentDirectory } : {}),
body: { messageID: messageId, partID: partId } messageID: messageId,
partID: partId
}); });
if (!response.data) throw new Error('Failed to revert session'); if (!response.data) throw new Error('Failed to revert session');
return response.data; return response.data;
@@ -657,33 +656,25 @@ class OpencodeService {
async unrevertSession(sessionId: string): Promise<Session> { async unrevertSession(sessionId: string): Promise<Session> {
const response = await this.client.session.unrevert({ const response = await this.client.session.unrevert({
path: { id: sessionId }, sessionID: sessionId,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined ...(this.currentDirectory ? { directory: this.currentDirectory } : {})
}); });
if (!response.data) throw new Error('Failed to unrevert session'); if (!response.data) throw new Error('Failed to unrevert session');
return response.data; return response.data;
} }
async forkSession(sessionId: string, messageId?: string): Promise<Session> { async forkSession(sessionId: string, messageId?: string): Promise<Session> {
const baseUrl = this.baseUrl.replace(/\/$/, ''); const response = await this.client.session.fork({
const url = new URL(`/api/session/${sessionId}/fork`, baseUrl); sessionID: sessionId,
...(this.currentDirectory ? { directory: this.currentDirectory } : {}),
if (this.currentDirectory) { messageID: messageId
url.searchParams.set('directory', this.currentDirectory);
}
const response = await fetch(url.toString(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(messageId ? { messageID: messageId } : {}),
}); });
if (!response.ok) { if (!response.data) {
const text = await response.text(); throw new Error('Failed to fork session');
throw new Error(`Failed to fork session: ${text || response.statusText}`);
} }
return response.json(); return response.data;
} }
async getSessionStatus(): Promise< async getSessionStatus(): Promise<
@@ -741,10 +732,11 @@ class OpencodeService {
permissionId: string, permissionId: string,
response: 'once' | 'always' | 'reject' response: 'once' | 'always' | 'reject'
): Promise<boolean> { ): Promise<boolean> {
const result = await this.client.postSessionIdPermissionsPermissionId({ const result = await this.client.permission.respond({
path: { id: sessionId, permissionID: permissionId }, sessionID: sessionId,
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined, permissionID: permissionId,
body: { response } ...(this.currentDirectory ? { directory: this.currentDirectory } : {}),
response
}); });
return result.data || false; return result.data || false;
} }
@@ -800,9 +792,9 @@ class OpencodeService {
providers: Provider[]; providers: Provider[];
default: { [key: string]: string }; default: { [key: string]: string };
}> { }> {
const response = await this.client.config.providers({ const response = await this.client.config.providers(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
if (!response.data) throw new Error('Failed to get providers'); if (!response.data) throw new Error('Failed to get providers');
return response.data; return response.data;
} }
@@ -829,9 +821,9 @@ class OpencodeService {
// Agent Management // Agent Management
async listAgents(): Promise<Agent[]> { async listAgents(): Promise<Agent[]> {
try { try {
const response = await this.client.app.agents({ const response = await this.client.app.agents(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
return response.data || []; return response.data || [];
} catch { } catch {
return []; return [];
@@ -868,17 +860,15 @@ class OpencodeService {
const connect = async (attempt: number): Promise<void> => { const connect = async (attempt: number): Promise<void> => {
try { try {
const subscribeParameters = resolvedDirectory ? { directory: resolvedDirectory } : undefined;
const subscribeOptions: { const subscribeOptions: {
query?: { directory?: string };
signal: AbortSignal; signal: AbortSignal;
sseDefaultRetryDelay: number; sseDefaultRetryDelay: number;
sseMaxRetryDelay: number; sseMaxRetryDelay: number;
onSseError?: (error: unknown) => void; onSseError?: (error: unknown) => void;
onSseEvent: (event: StreamEvent<unknown>) => void; onSseEvent: (event: StreamEvent<unknown>) => void;
headers?: Record<string, string>; headers?: Record<string, string>;
lastEventId?: string;
} = { } = {
query: resolvedDirectory ? { directory: resolvedDirectory } : undefined,
signal: abortController.signal, signal: abortController.signal,
sseDefaultRetryDelay: 3000, sseDefaultRetryDelay: 3000,
sseMaxRetryDelay: 30000, sseMaxRetryDelay: 30000,
@@ -904,11 +894,10 @@ class OpencodeService {
}; };
if (lastEventId) { if (lastEventId) {
subscribeOptions.lastEventId = lastEventId;
subscribeOptions.headers = { ...(subscribeOptions.headers || {}), 'Last-Event-ID': lastEventId }; subscribeOptions.headers = { ...(subscribeOptions.headers || {}), 'Last-Event-ID': lastEventId };
} }
const result = await this.client.event.subscribe(subscribeOptions); const result = await this.client.event.subscribe(subscribeParameters, subscribeOptions);
if (onOpen && !abortController.signal.aborted) { if (onOpen && !abortController.signal.aborted) {
console.log('[OpencodeClient] SSE connection opened'); console.log('[OpencodeClient] SSE connection opened');
@@ -1019,9 +1008,9 @@ class OpencodeService {
// Command Management // Command Management
async listCommands(): Promise<Array<{ name: string; description?: string; agent?: string; model?: string }>> { async listCommands(): Promise<Array<{ name: string; description?: string; agent?: string; model?: string }>> {
try { try {
const response = await this.client.command.list({ const response = await this.client.command.list(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
// Return only lightweight info for autocomplete // Return only lightweight info for autocomplete
return (response.data || []).map((cmd: Record<string, unknown>) => ({ return (response.data || []).map((cmd: Record<string, unknown>) => ({
name: cmd.name as string, name: cmd.name as string,
@@ -1037,9 +1026,9 @@ class OpencodeService {
async listCommandsWithDetails(): Promise<Array<{ name: string; description?: string; agent?: string; model?: string; template?: string; subtask?: boolean }>> { async listCommandsWithDetails(): Promise<Array<{ name: string; description?: string; agent?: string; model?: string; template?: string; subtask?: boolean }>> {
try { try {
const response = await this.client.command.list({ const response = await this.client.command.list(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
// Return full command details including template // Return full command details including template
return (response.data || []).map((cmd: Record<string, unknown>) => ({ return (response.data || []).map((cmd: Record<string, unknown>) => ({
name: cmd.name as string, name: cmd.name as string,
@@ -1056,9 +1045,9 @@ class OpencodeService {
async getCommandDetails(name: string): Promise<{ name: string; template: string; description?: string; agent?: string; model?: string } | null> { async getCommandDetails(name: string): Promise<{ name: string; template: string; description?: string; agent?: string; model?: string } | null> {
try { try {
const response = await this.client.command.list({ const response = await this.client.command.list(
query: this.currentDirectory ? { directory: this.currentDirectory } : undefined this.currentDirectory ? { directory: this.currentDirectory } : undefined
}); );
if (response.data) { if (response.data) {
const command = response.data.find((cmd: Record<string, unknown>) => cmd.name === name); const command = response.data.find((cmd: Record<string, unknown>) => cmd.name === name);
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Session } from '@opencode-ai/sdk'; import type { Session } from '@opencode-ai/sdk/v2';
import type { WorktreeMetadata } from '@/types/worktree'; import type { WorktreeMetadata } from '@/types/worktree';
export type SessionDeleteRequest = { export type SessionDeleteRequest = {
-2
View File
@@ -216,10 +216,8 @@ export const useFileStore = create<FileStore>()(
const filename = lastSlashIndex > 0 ? path.substring(lastSlashIndex + 1) : path; const filename = lastSlashIndex > 0 ? path.substring(lastSlashIndex + 1) : path;
const response = await tempClient.file.read({ const response = await tempClient.file.read({
query: {
path: filename, path: filename,
directory: directory, directory: directory,
},
}); });
if (response.data && "content" in response.data) { if (response.data && "content" in response.data) {
+5 -9
View File
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { create } from "zustand"; import { create } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware"; import { devtools, persist, createJSONStorage } from "zustand/middleware";
import type { Message, Part } from "@opencode-ai/sdk"; import type { Message, Part } from "@opencode-ai/sdk/v2";
import { opencodeClient } from "@/lib/opencode/client"; import { opencodeClient } from "@/lib/opencode/client";
import { isExecutionForkMetaText } from "@/lib/messages/executionMeta"; import { isExecutionForkMetaText } from "@/lib/messages/executionMeta";
import type { SessionMemoryState, MessageStreamLifecycle, AttachedFile } from "./types/sessionTypes"; import type { SessionMemoryState, MessageStreamLifecycle, AttachedFile } from "./types/sessionTypes";
@@ -586,13 +586,11 @@ export const useMessageStore = create<MessageStore>()(
const messageId = `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; const messageId = `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
await apiClient.session.init({ await apiClient.session.init({
path: { id: sessionId }, sessionID: sessionId,
body: { ...(directory ? { directory } : {}),
messageID: messageId, messageID: messageId,
providerID, providerID,
modelID, modelID,
},
query: directory ? { directory } : undefined,
}); });
return; return;
@@ -600,12 +598,10 @@ export const useMessageStore = create<MessageStore>()(
if (command === "summarize") { if (command === "summarize") {
await apiClient.session.summarize({ await apiClient.session.summarize({
path: { id: sessionId }, sessionID: sessionId,
body: { ...(directory ? { directory } : {}),
providerID, providerID,
modelID, modelID,
},
query: directory ? { directory } : undefined,
}); });
return; return;
+11 -11
View File
@@ -1,6 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware"; import { devtools, persist, createJSONStorage } from "zustand/middleware";
import type { Session } from "@opencode-ai/sdk"; import type { Session } from "@opencode-ai/sdk/v2";
import { opencodeClient } from "@/lib/opencode/client"; import { opencodeClient } from "@/lib/opencode/client";
import { getSafeStorage } from "./utils/safeStorage"; import { getSafeStorage } from "./utils/safeStorage";
import type { WorktreeMetadata } from "@/types/worktree"; import type { WorktreeMetadata } from "@/types/worktree";
@@ -254,9 +254,9 @@ export const useSessionStore = create<SessionStore>()(
const apiClient = opencodeClient.getApiClient(); const apiClient = opencodeClient.getApiClient();
const fetchSessions = async (directoryParam?: string | null): Promise<Session[]> => { const fetchSessions = async (directoryParam?: string | null): Promise<Session[]> => {
const response = await apiClient.session.list({ const response = await apiClient.session.list(
query: directoryParam ? { directory: directoryParam } : undefined, directoryParam ? { directory: directoryParam } : undefined
}); );
return Array.isArray(response.data) ? response.data : []; return Array.isArray(response.data) ? response.data : [];
}; };
@@ -536,9 +536,9 @@ export const useSessionStore = create<SessionStore>()(
const attempts = 20; const attempts = 20;
for (let attempt = 0; attempt < attempts; attempt += 1) { for (let attempt = 0; attempt < attempts; attempt += 1) {
try { try {
const response = await apiClient.session.list({ const response = await apiClient.session.list(
query: targetDirectory ? { directory: targetDirectory } : undefined, targetDirectory ? { directory: targetDirectory } : undefined
}); );
const list = Array.isArray(response.data) ? response.data : []; const list = Array.isArray(response.data) ? response.data : [];
const candidate = list.find((entry) => { const candidate = list.find((entry) => {
if (existingIds.has(entry.id)) return false; if (existingIds.has(entry.id)) return false;
@@ -817,8 +817,8 @@ export const useSessionStore = create<SessionStore>()(
const shareRequest = async () => { const shareRequest = async () => {
const directory = sessionDirectory ?? opencodeClient.getDirectory(); const directory = sessionDirectory ?? opencodeClient.getDirectory();
return apiClient.session.share({ return apiClient.session.share({
path: { id }, sessionID: id,
query: directory ? { directory } : undefined, ...(directory ? { directory } : {})
}); });
}; };
const response = overrideDirectory const response = overrideDirectory
@@ -849,8 +849,8 @@ export const useSessionStore = create<SessionStore>()(
const unshareRequest = async () => { const unshareRequest = async () => {
const directory = sessionDirectory ?? opencodeClient.getDirectory(); const directory = sessionDirectory ?? opencodeClient.getDirectory();
return apiClient.session.unshare({ return apiClient.session.unshare({
path: { id }, sessionID: id,
query: directory ? { directory } : undefined, ...(directory ? { directory } : {})
}); });
}; };
const response = overrideDirectory const response = overrideDirectory
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Session, Message, Part } from "@opencode-ai/sdk"; import type { Session, Message, Part } from "@opencode-ai/sdk/v2";
import type { Permission, PermissionResponse } from "@/types/permission"; import type { Permission, PermissionResponse } from "@/types/permission";
export interface AttachedFile { export interface AttachedFile {
+5 -3
View File
@@ -1,7 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import type { StoreApi, UseBoundStore } from "zustand"; import type { StoreApi, UseBoundStore } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware"; import { devtools, persist, createJSONStorage } from "zustand/middleware";
import type { Agent } from "@opencode-ai/sdk"; import type { Agent } from "@opencode-ai/sdk/v2";
import { opencodeClient } from "@/lib/opencode/client"; import { opencodeClient } from "@/lib/opencode/client";
import { emitConfigChange, scopeMatches, subscribeToConfigChanges } from "@/lib/configSync"; import { emitConfigChange, scopeMatches, subscribeToConfigChanges } from "@/lib/configSync";
import { import {
@@ -62,8 +62,10 @@ export type AgentWithExtras = Agent & {
}; };
// Helper to check if agent is built-in (handles both SDK 'builtIn' and API 'native') // Helper to check if agent is built-in (handles both SDK 'builtIn' and API 'native')
export const isAgentBuiltIn = (agent: Agent): boolean => export const isAgentBuiltIn = (agent: Agent): boolean => {
agent.builtIn || (agent as AgentWithExtras).native === true; const extended = agent as AgentWithExtras & { builtIn?: boolean };
return extended.native === true || extended.builtIn === true;
};
// Helper to check if agent is hidden (internal agents like title, compaction, summary) // Helper to check if agent is hidden (internal agents like title, compaction, summary)
// Checks both top-level hidden and options.hidden (OpenCode API inconsistency workaround) // Checks both top-level hidden and options.hidden (OpenCode API inconsistency workaround)
+1 -1
View File
@@ -1,7 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import type { StoreApi, UseBoundStore } from "zustand"; import type { StoreApi, UseBoundStore } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware"; import { devtools, persist, createJSONStorage } from "zustand/middleware";
import type { Provider, Agent } from "@opencode-ai/sdk"; import type { Provider, Agent } from "@opencode-ai/sdk/v2";
import { opencodeClient } from "@/lib/opencode/client"; import { opencodeClient } from "@/lib/opencode/client";
import { scopeMatches, subscribeToConfigChanges } from "@/lib/configSync"; import { scopeMatches, subscribeToConfigChanges } from "@/lib/configSync";
import type { ModelMetadata } from "@/types"; import type { ModelMetadata } from "@/types";
+1 -1
View File
@@ -1,7 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import type { StoreApi, UseBoundStore } from "zustand"; import type { StoreApi, UseBoundStore } from "zustand";
import { devtools } from "zustand/middleware"; import { devtools } from "zustand/middleware";
import type { Session, Message, Part } from "@opencode-ai/sdk"; import type { Session, Message, Part } from "@opencode-ai/sdk/v2";
import type { Permission, PermissionResponse } from "@/types/permission"; import type { Permission, PermissionResponse } from "@/types/permission";
import type { SessionStore, AttachedFile, EditPermissionMode } from "./types/sessionTypes"; import type { SessionStore, AttachedFile, EditPermissionMode } from "./types/sessionTypes";
import { ACTIVE_SESSION_WINDOW, MEMORY_LIMITS } from "./types/sessionTypes"; import { ACTIVE_SESSION_WINDOW, MEMORY_LIMITS } from "./types/sessionTypes";
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Part } from "@opencode-ai/sdk"; import type { Part } from "@opencode-ai/sdk/v2";
const extractTextFromDelta = (delta: unknown): string => { const extractTextFromDelta = (delta: unknown): string => {
if (!delta) return ''; if (!delta) return '';
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Message, Part } from "@opencode-ai/sdk"; import type { Message, Part } from "@opencode-ai/sdk/v2";
type TokenBreakdown = { type TokenBreakdown = {
input?: number; input?: number;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Session, Message, Part, Provider } from "@opencode-ai/sdk"; import type { Session, Message, Part, Provider } from "@opencode-ai/sdk/v2";
export type { Session, Message, Part, Provider }; export type { Session, Message, Part, Provider };
+7 -7
View File
@@ -10,12 +10,12 @@ export default defineConfig({
base: './', // Use relative paths for VS Code webview base: './', // Use relative paths for VS Code webview
plugins: [react()], plugins: [react()],
resolve: { resolve: {
alias: { alias: [
'@': path.resolve(__dirname, '../ui/src'), { find: '@opencode-ai/sdk/v2', replacement: path.resolve(__dirname, '../../node_modules/@opencode-ai/sdk/dist/v2/client.js') },
'@vscode': path.resolve(__dirname, './webview'), { find: '@openchamber/ui', replacement: path.resolve(__dirname, '../ui/src') },
'@openchamber/ui': path.resolve(__dirname, '../ui/src'), { find: '@vscode', replacement: path.resolve(__dirname, './webview') },
'@opencode-ai/sdk': path.resolve(__dirname, '../../node_modules/@opencode-ai/sdk/dist/client.js'), { find: '@', replacement: path.resolve(__dirname, '../ui/src') },
}, ],
}, },
worker: { worker: {
format: 'es', format: 'es',
@@ -26,7 +26,7 @@ export default defineConfig({
}, },
envPrefix: ['VITE_'], envPrefix: ['VITE_'],
optimizeDeps: { optimizeDeps: {
include: ['@opencode-ai/sdk'], include: ['@opencode-ai/sdk/v2'],
}, },
build: { build: {
outDir: path.resolve(__dirname, 'dist/webview'), outDir: path.resolve(__dirname, 'dist/webview'),
+7 -7
View File
@@ -15,12 +15,12 @@ export default defineConfig({
themeStoragePlugin(), themeStoragePlugin(),
], ],
resolve: { resolve: {
alias: { alias: [
'@': path.resolve(__dirname, '../ui/src'), { find: '@opencode-ai/sdk/v2', replacement: path.resolve(__dirname, '../../node_modules/@opencode-ai/sdk/dist/v2/client.js') },
'@web': path.resolve(__dirname, './src'), { find: '@openchamber/ui', replacement: path.resolve(__dirname, '../ui/src') },
'@openchamber/ui': path.resolve(__dirname, '../ui/src'), { find: '@web', replacement: path.resolve(__dirname, './src') },
'@opencode-ai/sdk': path.resolve(__dirname, '../../node_modules/@opencode-ai/sdk/dist/client.js'), { find: '@', replacement: path.resolve(__dirname, '../ui/src') },
}, ],
}, },
worker: { worker: {
format: 'es', format: 'es',
@@ -31,7 +31,7 @@ export default defineConfig({
__APP_VERSION__: JSON.stringify(packageJson.version), __APP_VERSION__: JSON.stringify(packageJson.version),
}, },
optimizeDeps: { optimizeDeps: {
include: ['@opencode-ai/sdk'], include: ['@opencode-ai/sdk/v2'],
}, },
server: { server: {
port: 5173, port: 5173,
+5 -5
View File
@@ -12,10 +12,10 @@ export default defineConfig({
themeStoragePlugin(), themeStoragePlugin(),
], ],
resolve: { resolve: {
alias: { alias: [
'@': path.resolve(__dirname, './src'), { find: '@opencode-ai/sdk/v2', replacement: path.resolve(__dirname, './node_modules/@opencode-ai/sdk/dist/v2/client.js') },
'@opencode-ai/sdk': path.resolve(__dirname, './node_modules/@opencode-ai/sdk/dist/client.js'), { find: '@', replacement: path.resolve(__dirname, './src') },
}, ],
}, },
worker: { worker: {
format: 'es', format: 'es',
@@ -25,7 +25,7 @@ export default defineConfig({
global: 'globalThis', global: 'globalThis',
}, },
optimizeDeps: { optimizeDeps: {
include: ['@opencode-ai/sdk'], include: ['@opencode-ai/sdk/v2'],
}, },
build: { build: {
chunkSizeWarningLimit: 1200, chunkSizeWarningLimit: 1200,