Dax Raad 6 месяцев назад
Родитель
Сommit
a2191ce6fb

+ 1 - 1
packages/opencode/script/publish.ts

@@ -38,7 +38,7 @@ for (const [os, arch] of targets) {
   await $`CGO_ENABLED=0 GOOS=${os} GOARCH=${GOARCH[arch]} go build -ldflags="-s -w -X main.Version=${version}" -o ../opencode/dist/${name}/bin/tui ../tui/cmd/opencode/main.go`.cwd(
     "../tui",
   )
-  await $`bun build --define OPENCODE_VERSION="'${version}'" --compile --minify --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts ./dist/${name}/bin/tui`
+  await $`bun build --define OPENCODE_TUI_PATH="'../../../dist/${name}/bin/tui'" --define OPENCODE_VERSION="'${version}'" --compile --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts`
   await $`rm -rf ./dist/${name}/bin/tui`
   await Bun.file(`dist/${name}/package.json`).write(
     JSON.stringify(

+ 14 - 4
packages/opencode/src/cli/cmd/tui.ts

@@ -14,6 +14,16 @@ import { FileWatcher } from "../../file/watch"
 import { Mode } from "../../session/mode"
 import { Ide } from "../../ide"
 
+declare global {
+  const OPENCODE_TUI_PATH: string
+}
+
+if (typeof OPENCODE_TUI_PATH !== "undefined") {
+  await import(OPENCODE_TUI_PATH as string, {
+    with: { type: "file" },
+  })
+}
+
 export const TuiCommand = cmd({
   command: "$0 [project]",
   describe: "start opencode tui",
@@ -71,16 +81,16 @@ export const TuiCommand = cmd({
 
         let cmd = ["go", "run", "./main.go"]
         let cwd = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
-        if (Bun.embeddedFiles.length > 0) {
-          const blob = Bun.embeddedFiles[0] as File
-          let binaryName = blob.name
+        const tui = Bun.embeddedFiles.find((item) => (item as File).name.includes("tui")) as File
+        if (tui) {
+          let binaryName = tui.name
           if (process.platform === "win32" && !binaryName.endsWith(".exe")) {
             binaryName += ".exe"
           }
           const binary = path.join(Global.Path.cache, "tui", binaryName)
           const file = Bun.file(binary)
           if (!(await file.exists())) {
-            await Bun.write(file, blob, { mode: 0o755 })
+            await Bun.write(file, tui, { mode: 0o755 })
             await fs.chmod(binary, 0o755)
           }
           cwd = process.cwd()

+ 1 - 1
packages/opencode/src/session/index.ts

@@ -721,7 +721,7 @@ export namespace Session {
             sessionID: input.sessionID,
             abort: abort.signal,
             messageID: assistantMsg.id,
-            toolCallID: options.toolCallId,
+            callID: options.toolCallId,
             metadata: async (val) => {
               const match = processor.partFromToolCall(options.toolCallId)
               if (match && match.state.status === "running") {

+ 9 - 27
packages/opencode/src/tool/bash.ts

@@ -3,18 +3,18 @@ import { Tool } from "./tool"
 import DESCRIPTION from "./bash.txt"
 import { App } from "../app/app"
 import { Permission } from "../permission"
+import Parser from "tree-sitter"
+import Bash from "tree-sitter-bash"
 import { Config } from "../config/config"
-
-// import Parser from "tree-sitter"
-// import Bash from "tree-sitter-bash"
-// import { Config } from "../config/config"
+import { Filesystem } from "../util/filesystem"
+import path from "path"
 
 const MAX_OUTPUT_LENGTH = 30000
 const DEFAULT_TIMEOUT = 1 * 60 * 1000
 const MAX_TIMEOUT = 10 * 60 * 1000
 
-// const parser = new Parser()
-// parser.setLanguage(Bash.language as any)
+const parser = new Parser()
+parser.setLanguage(Bash.language as any)
 
 export const BashTool = Tool.define("bash", {
   description: DESCRIPTION,
@@ -30,8 +30,7 @@ export const BashTool = Tool.define("bash", {
   async execute(params, ctx) {
     const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
     const app = App.info()
-    /*
-    const _cfg = await Config.get()
+    const cfg = await Config.get()
     const tree = parser.parse(params.command)
     const permissions = (() => {
       const value = cfg.permission?.bash
@@ -93,33 +92,16 @@ export const BashTool = Tool.define("bash", {
 
     if (needsAsk) {
       await Permission.ask({
-        id: "bash",
+        type: "bash",
         sessionID: ctx.sessionID,
         messageID: ctx.messageID,
-        toolCallID: ctx.toolCallID,
+        callID: ctx.callID,
         title: params.command,
         metadata: {
           command: params.command,
         },
       })
     }
-    */
-
-    const cfg = await Config.get()
-    if (cfg.permission?.bash === "ask")
-      await Permission.ask({
-        type: "bash",
-        pattern: params.command.split(" ").slice(0, 2).join(" ").trim(),
-        sessionID: ctx.sessionID,
-        messageID: ctx.messageID,
-        callID: ctx.toolCallID,
-        title: "Run this command: " + params.command,
-        metadata: {
-          command: params.command,
-          description: params.description,
-          timeout: params.timeout,
-        },
-      })
 
     const process = Bun.spawn({
       cmd: ["bash", "-c", params.command],

+ 2 - 2
packages/opencode/src/tool/edit.ts

@@ -53,7 +53,7 @@ export const EditTool = Tool.define("edit", {
             type: "edit",
             sessionID: ctx.sessionID,
             messageID: ctx.messageID,
-            callID: ctx.toolCallID,
+            callID: ctx.callID,
             title: "Edit this file: " + filePath,
             metadata: {
               filePath,
@@ -82,7 +82,7 @@ export const EditTool = Tool.define("edit", {
           type: "edit",
           sessionID: ctx.sessionID,
           messageID: ctx.messageID,
-          callID: ctx.toolCallID,
+          callID: ctx.callID,
           title: "Edit this file: " + filePath,
           metadata: {
             filePath,

+ 53 - 0
packages/opencode/src/tool/test.ts

@@ -0,0 +1,53 @@
+import Parser from "tree-sitter";
+import Bash from "tree-sitter-bash";
+
+const parser = new Parser();
+parser.setLanguage(Bash.language as any);
+
+const sourceCode = `cd --foo foo/bar && echo "hello" && cd ../baz`;
+
+const tree = parser.parse(sourceCode);
+
+// Function to extract commands and arguments
+function extractCommands(
+  node: any,
+): Array<{ command: string; args: string[] }> {
+  const commands: Array<{ command: string; args: string[] }> = [];
+
+  function traverse(node: any) {
+    if (node.type === "command") {
+      const commandNode = node.child(0);
+      if (commandNode) {
+        const command = commandNode.text;
+        const args: string[] = [];
+
+        // Extract arguments
+        for (let i = 1; i < node.childCount; i++) {
+          const child = node.child(i);
+          if (child && child.type === "word") {
+            args.push(child.text);
+          }
+        }
+
+        commands.push({ command, args });
+      }
+    }
+
+    // Traverse children
+    for (let i = 0; i < node.childCount; i++) {
+      traverse(node.child(i));
+    }
+  }
+
+  traverse(node);
+  return commands;
+}
+
+// Extract and display commands
+console.log("Source code: " + sourceCode);
+const commands = extractCommands(tree.rootNode);
+console.log("Extracted commands:");
+commands.forEach((cmd, index) => {
+  console.log(`${index + 1}. Command: ${cmd.command}`);
+  console.log(`   Args: [${cmd.args.join(", ")}]`);
+});

+ 1 - 1
packages/opencode/src/tool/tool.ts

@@ -7,7 +7,7 @@ export namespace Tool {
   export type Context<M extends Metadata = Metadata> = {
     sessionID: string
     messageID: string
-    toolCallID?: string
+    callID?: string
     abort: AbortSignal
     metadata(input: { title?: string; metadata?: M }): void
   }

+ 1 - 1
packages/opencode/src/tool/write.ts

@@ -34,7 +34,7 @@ export const WriteTool = Tool.define("write", {
         type: "write",
         sessionID: ctx.sessionID,
         messageID: ctx.messageID,
-        callID: ctx.toolCallID,
+        callID: ctx.callID,
         title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
         metadata: {
           filePath: filepath,

+ 1 - 1
packages/sdk/js/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/sdk",
-  "version": "0.0.0",
+  "version": "0.0.0-202507312003",
   "type": "module",
   "exports": {
     ".": "./dist/index.js"

+ 88 - 1
packages/sdk/js/src/gen/sdk.gen.ts

@@ -1,7 +1,7 @@
 // This file is auto-generated by @hey-api/openapi-ts
 
 import type { Options as ClientOptions, TDataShape, Client } from './client';
-import type { EventSubscribeData, EventSubscribeResponses, AppGetData, AppGetResponses, AppInitData, AppInitResponses, ConfigGetData, ConfigGetResponses, SessionListData, SessionListResponses, SessionCreateData, SessionCreateResponses, SessionCreateErrors, SessionDeleteData, SessionDeleteResponses, SessionInitData, SessionInitResponses, SessionAbortData, SessionAbortResponses, SessionUnshareData, SessionUnshareResponses, SessionShareData, SessionShareResponses, SessionSummarizeData, SessionSummarizeResponses, SessionMessagesData, SessionMessagesResponses, SessionChatData, SessionChatResponses, SessionRevertData, SessionRevertResponses, SessionUnrevertData, SessionUnrevertResponses, ConfigProvidersData, ConfigProvidersResponses, FindTextData, FindTextResponses, FindFilesData, FindFilesResponses, FindSymbolsData, FindSymbolsResponses, FileReadData, FileReadResponses, FileStatusData, FileStatusResponses, AppLogData, AppLogResponses, AppModesData, AppModesResponses, TuiAppendPromptData, TuiAppendPromptResponses, TuiOpenHelpData, TuiOpenHelpResponses } from './types.gen';
+import type { EventSubscribeData, EventSubscribeResponses, AppGetData, AppGetResponses, AppInitData, AppInitResponses, ConfigGetData, ConfigGetResponses, SessionListData, SessionListResponses, SessionCreateData, SessionCreateResponses, SessionCreateErrors, SessionDeleteData, SessionDeleteResponses, SessionInitData, SessionInitResponses, SessionAbortData, SessionAbortResponses, SessionUnshareData, SessionUnshareResponses, SessionShareData, SessionShareResponses, SessionSummarizeData, SessionSummarizeResponses, SessionMessagesData, SessionMessagesResponses, SessionChatData, SessionChatResponses, SessionMessageData, SessionMessageResponses, SessionRevertData, SessionRevertResponses, SessionUnrevertData, SessionUnrevertResponses, PostSessionByIdPermissionsByPermissionIdData, PostSessionByIdPermissionsByPermissionIdResponses, ConfigProvidersData, ConfigProvidersResponses, FindTextData, FindTextResponses, FindFilesData, FindFilesResponses, FindSymbolsData, FindSymbolsResponses, FileReadData, FileReadResponses, FileStatusData, FileStatusResponses, AppLogData, AppLogResponses, AppModesData, AppModesResponses, TuiAppendPromptData, TuiAppendPromptResponses, TuiOpenHelpData, TuiOpenHelpResponses, TuiOpenSessionsData, TuiOpenSessionsResponses, TuiOpenThemesData, TuiOpenThemesResponses, TuiOpenModelsData, TuiOpenModelsResponses, TuiSubmitPromptData, TuiSubmitPromptResponses, TuiClearPromptData, TuiClearPromptResponses, TuiExecuteCommandData, TuiExecuteCommandResponses } from './types.gen';
 import { client as _heyApiClient } from './client.gen';
 
 export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
@@ -223,6 +223,16 @@ class Session extends _HeyApiClient {
         });
     }
     
+    /**
+     * Get a message from a session
+     */
+    public message<ThrowOnError extends boolean = false>(options: Options<SessionMessageData, ThrowOnError>) {
+        return (options.client ?? this._client).get<SessionMessageResponses, unknown, ThrowOnError>({
+            url: '/session/{id}/message/{messageID}',
+            ...options
+        });
+    }
+    
     /**
      * Revert a message
      */
@@ -326,9 +336,86 @@ class Tui extends _HeyApiClient {
             ...options
         });
     }
+    
+    /**
+     * Open the session dialog
+     */
+    public openSessions<ThrowOnError extends boolean = false>(options?: Options<TuiOpenSessionsData, ThrowOnError>) {
+        return (options?.client ?? this._client).post<TuiOpenSessionsResponses, unknown, ThrowOnError>({
+            url: '/tui/open-sessions',
+            ...options
+        });
+    }
+    
+    /**
+     * Open the theme dialog
+     */
+    public openThemes<ThrowOnError extends boolean = false>(options?: Options<TuiOpenThemesData, ThrowOnError>) {
+        return (options?.client ?? this._client).post<TuiOpenThemesResponses, unknown, ThrowOnError>({
+            url: '/tui/open-themes',
+            ...options
+        });
+    }
+    
+    /**
+     * Open the model dialog
+     */
+    public openModels<ThrowOnError extends boolean = false>(options?: Options<TuiOpenModelsData, ThrowOnError>) {
+        return (options?.client ?? this._client).post<TuiOpenModelsResponses, unknown, ThrowOnError>({
+            url: '/tui/open-models',
+            ...options
+        });
+    }
+    
+    /**
+     * Submit the prompt
+     */
+    public submitPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiSubmitPromptData, ThrowOnError>) {
+        return (options?.client ?? this._client).post<TuiSubmitPromptResponses, unknown, ThrowOnError>({
+            url: '/tui/submit-prompt',
+            ...options
+        });
+    }
+    
+    /**
+     * Clear the prompt
+     */
+    public clearPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiClearPromptData, ThrowOnError>) {
+        return (options?.client ?? this._client).post<TuiClearPromptResponses, unknown, ThrowOnError>({
+            url: '/tui/clear-prompt',
+            ...options
+        });
+    }
+    
+    /**
+     * Execute a TUI command (e.g. switch_mode)
+     */
+    public executeCommand<ThrowOnError extends boolean = false>(options?: Options<TuiExecuteCommandData, ThrowOnError>) {
+        return (options?.client ?? this._client).post<TuiExecuteCommandResponses, unknown, ThrowOnError>({
+            url: '/tui/execute-command',
+            ...options,
+            headers: {
+                'Content-Type': 'application/json',
+                ...options?.headers
+            }
+        });
+    }
 }
 
 export class OpencodeClient extends _HeyApiClient {
+    /**
+     * Respond to a permission request
+     */
+    public postSessionByIdPermissionsByPermissionId<ThrowOnError extends boolean = false>(options: Options<PostSessionByIdPermissionsByPermissionIdData, ThrowOnError>) {
+        return (options.client ?? this._client).post<PostSessionByIdPermissionsByPermissionIdResponses, unknown, ThrowOnError>({
+            url: '/session/{id}/permissions/{permissionID}',
+            ...options,
+            headers: {
+                'Content-Type': 'application/json',
+                ...options.headers
+            }
+        });
+    }
     event = new Event({ client: this._client });
     app = new App({ client: this._client });
     config = new Config({ client: this._client });

+ 153 - 2
packages/sdk/js/src/gen/types.gen.ts

@@ -355,12 +355,16 @@ export type EventStorageWrite = {
 
 export type EventPermissionUpdated = {
     type: string;
-    properties: PermissionInfo;
+    properties: Permission;
 };
 
-export type PermissionInfo = {
+export type Permission = {
     id: string;
+    type: string;
+    pattern?: string;
     sessionID: string;
+    messageID: string;
+    callID?: string;
     title: string;
     metadata: {
         [key: string]: unknown;
@@ -1193,6 +1197,34 @@ export type SessionChatResponses = {
 
 export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses];
 
+export type SessionMessageData = {
+    body?: never;
+    path: {
+        /**
+         * Session ID
+         */
+        id: string;
+        /**
+         * Message ID
+         */
+        messageID: string;
+    };
+    query?: never;
+    url: '/session/{id}/message/{messageID}';
+};
+
+export type SessionMessageResponses = {
+    /**
+     * Message
+     */
+    200: {
+        info: Message;
+        parts: Array<Part>;
+    };
+};
+
+export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses];
+
 export type SessionRevertData = {
     body?: {
         messageID: string;
@@ -1232,6 +1264,27 @@ export type SessionUnrevertResponses = {
 
 export type SessionUnrevertResponse = SessionUnrevertResponses[keyof SessionUnrevertResponses];
 
+export type PostSessionByIdPermissionsByPermissionIdData = {
+    body?: {
+        response: 'once' | 'always' | 'reject';
+    };
+    path: {
+        id: string;
+        permissionID: string;
+    };
+    query?: never;
+    url: '/session/{id}/permissions/{permissionID}';
+};
+
+export type PostSessionByIdPermissionsByPermissionIdResponses = {
+    /**
+     * Permission processed successfully
+     */
+    200: boolean;
+};
+
+export type PostSessionByIdPermissionsByPermissionIdResponse = PostSessionByIdPermissionsByPermissionIdResponses[keyof PostSessionByIdPermissionsByPermissionIdResponses];
+
 export type ConfigProvidersData = {
     body?: never;
     path?: never;
@@ -1445,6 +1498,104 @@ export type TuiOpenHelpResponses = {
 
 export type TuiOpenHelpResponse = TuiOpenHelpResponses[keyof TuiOpenHelpResponses];
 
+export type TuiOpenSessionsData = {
+    body?: never;
+    path?: never;
+    query?: never;
+    url: '/tui/open-sessions';
+};
+
+export type TuiOpenSessionsResponses = {
+    /**
+     * Session dialog opened successfully
+     */
+    200: boolean;
+};
+
+export type TuiOpenSessionsResponse = TuiOpenSessionsResponses[keyof TuiOpenSessionsResponses];
+
+export type TuiOpenThemesData = {
+    body?: never;
+    path?: never;
+    query?: never;
+    url: '/tui/open-themes';
+};
+
+export type TuiOpenThemesResponses = {
+    /**
+     * Theme dialog opened successfully
+     */
+    200: boolean;
+};
+
+export type TuiOpenThemesResponse = TuiOpenThemesResponses[keyof TuiOpenThemesResponses];
+
+export type TuiOpenModelsData = {
+    body?: never;
+    path?: never;
+    query?: never;
+    url: '/tui/open-models';
+};
+
+export type TuiOpenModelsResponses = {
+    /**
+     * Model dialog opened successfully
+     */
+    200: boolean;
+};
+
+export type TuiOpenModelsResponse = TuiOpenModelsResponses[keyof TuiOpenModelsResponses];
+
+export type TuiSubmitPromptData = {
+    body?: never;
+    path?: never;
+    query?: never;
+    url: '/tui/submit-prompt';
+};
+
+export type TuiSubmitPromptResponses = {
+    /**
+     * Prompt submitted successfully
+     */
+    200: boolean;
+};
+
+export type TuiSubmitPromptResponse = TuiSubmitPromptResponses[keyof TuiSubmitPromptResponses];
+
+export type TuiClearPromptData = {
+    body?: never;
+    path?: never;
+    query?: never;
+    url: '/tui/clear-prompt';
+};
+
+export type TuiClearPromptResponses = {
+    /**
+     * Prompt cleared successfully
+     */
+    200: boolean;
+};
+
+export type TuiClearPromptResponse = TuiClearPromptResponses[keyof TuiClearPromptResponses];
+
+export type TuiExecuteCommandData = {
+    body?: {
+        command: string;
+    };
+    path?: never;
+    query?: never;
+    url: '/tui/execute-command';
+};
+
+export type TuiExecuteCommandResponses = {
+    /**
+     * Command executed successfully
+     */
+    200: boolean;
+};
+
+export type TuiExecuteCommandResponse = TuiExecuteCommandResponses[keyof TuiExecuteCommandResponses];
+
 export type ClientOptions = {
     baseUrl: `${string}://${string}` | (string & {});
 };