diff --git a/js/app/packages/core/component/AI/component/tool/BashCodeExecution.tsx b/js/app/packages/core/component/AI/component/tool/BashCodeExecution.tsx new file mode 100644 index 00000000000..d61ee51aef2 --- /dev/null +++ b/js/app/packages/core/component/AI/component/tool/BashCodeExecution.tsx @@ -0,0 +1,128 @@ +import CaretDown from '@phosphor-icons/core/regular/caret-down.svg'; +import CaretRight from '@phosphor-icons/core/regular/caret-right.svg'; +import Terminal from '@phosphor-icons/core/regular/terminal.svg'; +import type { BashCodeExecutionResult } from '@service-cognition/generated/tools/types'; +import { createSignal, Match, Show, Switch } from 'solid-js'; +import { BaseTool } from './BaseTool'; +import { createToolRenderer } from './ToolRenderer'; + +const MAX_COMMAND_LENGTH = 80; +const MAX_OUTPUT_LINES = 3; + +function truncateCommand(command: string): string { + const firstLine = command.split('\n')[0] ?? ''; + const needsTruncation = + command.includes('\n') || firstLine.length > MAX_COMMAND_LENGTH; + + if (needsTruncation) { + return `${firstLine.slice(0, MAX_COMMAND_LENGTH)}...`; + } + return firstLine; +} + +function CodeFence(props: { content: string; maxLines: number }) { + const [expanded, setExpanded] = createSignal(false); + + const lines = () => props.content.split('\n'); + const needsTruncation = () => lines().length > props.maxLines; + const displayContent = () => { + if (expanded() || !needsTruncation()) { + return props.content; + } + return lines().slice(0, props.maxLines).join('\n'); + }; + + return ( +
+ + + +
+        {displayContent()}
+      
+
+ ); +} + +function BashResult(props: { result: BashCodeExecutionResult }) { + const output = () => { + const parts: string[] = []; + if (props.result.stdout) { + parts.push(props.result.stdout); + } + if (props.result.stderr) { + parts.push(props.result.stderr); + } + return parts.join('\n'); + }; + + const hasOutput = () => output().trim().length > 0; + + return ( +
+ + Exit code {props.result.return_code} + + + + + + No output + +
+ ); +} + +const handler = createToolRenderer({ + name: 'bash_code_execution', + renderCall: (ctx) => ( + + + {truncateCommand(ctx.tool.data.command)} + + + ), + renderResponse: (ctx) => { + const isError = + ctx.tool.data.content.type === 'bash_code_execution_tool_result_error'; + return ( + + + + Execution failed + + + + + + + ); + }, +}); + +export const bashCodeExecutionHandler = handler; diff --git a/js/app/packages/core/component/AI/component/tool/TextEditorCodeExecution.tsx b/js/app/packages/core/component/AI/component/tool/TextEditorCodeExecution.tsx new file mode 100644 index 00000000000..9a67b9cf849 --- /dev/null +++ b/js/app/packages/core/component/AI/component/tool/TextEditorCodeExecution.tsx @@ -0,0 +1,189 @@ +import CaretDown from '@phosphor-icons/core/regular/caret-down.svg'; +import CaretRight from '@phosphor-icons/core/regular/caret-right.svg'; +import File from '@phosphor-icons/core/regular/file.svg'; +import type { + TextEditorCodeExecutionContent, + TextEditorCodeExecutionResult, +} from '@service-cognition/generated/tools/types'; + +// Type aliases for backwards compatibility with discriminated union variants +type TextEditorCodeExecutionViewResult = TextEditorCodeExecutionResult & { + type: 'text_editor_code_execution_view_result'; +}; +type TextEditorCodeExecutionCreateResult = TextEditorCodeExecutionResult & { + type: 'text_editor_code_execution_create_result'; +}; +type TextEditorCodeExecutionStrReplaceResult = TextEditorCodeExecutionResult & { + type: 'text_editor_code_execution_str_replace_result'; +}; +import { createSignal, Match, Show, Switch } from 'solid-js'; +import { BaseTool } from './BaseTool'; +import { createToolRenderer } from './ToolRenderer'; + +const MAX_OUTPUT_LINES = 5; + +function getCommandLabel(command: string): string { + switch (command) { + case 'view': + return 'Viewing file'; + case 'create': + return 'Creating file'; + case 'str_replace': + return 'Editing file'; + default: + return 'Editing file'; + } +} + +function CodeFence(props: { content: string; maxLines: number }) { + const [expanded, setExpanded] = createSignal(false); + + const lines = () => props.content.split('\n'); + const needsTruncation = () => lines().length > props.maxLines; + const displayContent = () => { + if (expanded() || !needsTruncation()) { + return props.content; + } + return lines().slice(0, props.maxLines).join('\n'); + }; + + return ( +
+ + + +
+        {displayContent()}
+      
+
+ ); +} + +function ViewResult(props: { result: TextEditorCodeExecutionViewResult }) { + const hasContent = () => !!props.result.content?.trim(); + + return ( + Empty file} + > + + + ); +} + +function CreateResult(props: { result: TextEditorCodeExecutionCreateResult }) { + return ( + + {props.result.is_file_update ? 'File updated' : 'File created'} + + ); +} + +function StrReplaceResult(props: { + result: TextEditorCodeExecutionStrReplaceResult; +}) { + const diffContent = () => props.result.lines?.join('\n') ?? ''; + const hasContent = () => diffContent().trim().length > 0; + + return ( + Edit applied} + > + + + ); +} + +function TextEditorResult(props: { content: TextEditorCodeExecutionContent }) { + return ( + + + {(result) => ( + + )} + + + {(result) => ( + + )} + + + {(result) => ( + + )} + + + Failed + + + ); +} + +const handler = createToolRenderer({ + name: 'text_editor_code_execution', + renderCall: (ctx) => ( + + {ctx.tool.data.path} + + ), + renderResponse: (ctx) => { + return ( + + + + ); + }, +}); + +export const textEditorCodeExecutionHandler = handler; diff --git a/js/app/packages/core/component/AI/component/tool/handler.tsx b/js/app/packages/core/component/AI/component/tool/handler.tsx index 488f99a30c0..ee6b5555eec 100644 --- a/js/app/packages/core/component/AI/component/tool/handler.tsx +++ b/js/app/packages/core/component/AI/component/tool/handler.tsx @@ -10,12 +10,14 @@ import { } from '@service-cognition/generated/tools/tool'; import { createStore } from 'solid-js/store'; import { Dynamic, Show } from 'solid-js/web'; +import { bashCodeExecutionHandler } from './BashCodeExecution'; import { listChannelsHandler } from './ListChannels'; import { listDocumentsHandler } from './ListDocuments'; import { listEmailsHandler } from './ListEmails'; import { rewriteHandler } from './MarkdownRewrite'; import { readHandler } from './Read'; import { contentSearchHandler, nameSearchHandler } from './Search'; +import { textEditorCodeExecutionHandler } from './TextEditorCodeExecution'; import type { RenderContext } from './ToolRenderer'; import { webFetchHandler } from './WebFetch'; import { webSearchHandler } from './WebSearch'; @@ -25,15 +27,17 @@ const [renderStore, setRenderStore] = createStore< >({}); const toolHandlers: ToolHandlerMap = { + bash_code_execution: bashCodeExecutionHandler, ContentSearch: contentSearchHandler, - NameSearch: nameSearchHandler, - web_fetch: webFetchHandler, - web_search: webSearchHandler, - MarkdownRewrite: rewriteHandler, - Read: readHandler, ListChannels: listChannelsHandler, ListDocuments: listDocumentsHandler, ListEmails: listEmailsHandler, + MarkdownRewrite: rewriteHandler, + NameSearch: nameSearchHandler, + Read: readHandler, + text_editor_code_execution: textEditorCodeExecutionHandler, + web_fetch: webFetchHandler, + web_search: webSearchHandler, }; type ToolProps = { diff --git a/js/app/packages/core/component/AI/constant/fileType.ts b/js/app/packages/core/component/AI/constant/fileType.ts index 89a93f8336b..ba5fbdb06f7 100644 --- a/js/app/packages/core/component/AI/constant/fileType.ts +++ b/js/app/packages/core/component/AI/constant/fileType.ts @@ -56,6 +56,7 @@ export const SUPPORTED_CHAT_ATTACHMENT_BLOCKS: (BlockName | BlockAlias)[] = [ 'pdf', 'md', 'code', + 'csv', 'canvas', 'email', 'task', diff --git a/js/app/packages/service-clients/service-cognition/generated/tools/schemas.ts b/js/app/packages/service-clients/service-cognition/generated/tools/schemas.ts index aac0e868378..b8ad705c3b7 100644 --- a/js/app/packages/service-clients/service-cognition/generated/tools/schemas.ts +++ b/js/app/packages/service-clients/service-cognition/generated/tools/schemas.ts @@ -4,6 +4,74 @@ */ import { z } from 'zod'; +export const BashCodeExecutionToolCall = z.object({ command: z.string() }); + +export const BashCodeExecutionResponse = z.object({ + content: z.any().superRefine((x, ctx) => { + const schemas = [ + z.intersection( + z.object({ + content: z + .union([z.array(z.object({ file_id: z.string() })), z.null()]) + .optional(), + return_code: z.number().int(), + stderr: z.string(), + stdout: z.string(), + }), + z.object({ type: z.literal('bash_code_execution_result') }) + ), + z.intersection( + z.object({ + error_code: z.any().superRefine((x, ctx) => { + const schemas = [ + z.literal('unavailable'), + z.literal('execution_time_exceeded'), + z.literal('container_expired'), + z.literal('invalid_tool_input'), + z.literal('too_many_requests'), + z.literal('file_not_found'), + z.literal('string_not_found'), + ]; + const errors = schemas.reduce( + (errors, schema) => + ((result) => + result.error ? [...errors, result.error] : errors)( + schema.safeParse(x) + ), + [] + ); + if (schemas.length - errors.length !== 1) { + ctx.addIssue({ + path: ctx.path, + code: 'invalid_union', + unionErrors: errors, + message: 'Invalid input: Should pass single schema', + }); + } + }), + }), + z.object({ type: z.literal('bash_code_execution_tool_result_error') }) + ), + ]; + const errors = schemas.reduce( + (errors, schema) => + ((result) => (result.error ? [...errors, result.error] : errors))( + schema.safeParse(x) + ), + [] + ); + if (schemas.length - errors.length !== 1) { + ctx.addIssue({ + path: ctx.path, + code: 'invalid_union', + unionErrors: errors, + message: 'Invalid input: Should pass single schema', + }); + } + }), + tool_use_id: z.string(), +}); + export const ContentSearch = z .object({ entityTypes: z @@ -491,28 +559,183 @@ export const ReadResponse = z.object({ }), }); +export const TextEditorCodeExecutionToolCall = z.object({ + command: z.string(), + file_text: z.union([z.string(), z.null()]).optional(), + new_str: z.union([z.string(), z.null()]).optional(), + old_str: z.union([z.string(), z.null()]).optional(), + path: z.string(), +}); + +export const TextEditorCodeExecutionResponse = z.object({ + content: z.any().superRefine((x, ctx) => { + const schemas = [ + z.intersection( + z.object({ + content: z.union([z.string(), z.null()]).optional(), + file_type: z.union([z.string(), z.null()]).optional(), + is_file_update: z.union([z.boolean(), z.null()]).optional(), + lines: z.union([z.array(z.string()), z.null()]).optional(), + newLines: z.union([z.number().int().gte(0), z.null()]).optional(), + newStart: z.union([z.number().int().gte(0), z.null()]).optional(), + numLines: z.union([z.number().int().gte(0), z.null()]).optional(), + oldLines: z.union([z.number().int().gte(0), z.null()]).optional(), + oldStart: z.union([z.number().int().gte(0), z.null()]).optional(), + startLine: z.union([z.number().int().gte(0), z.null()]).optional(), + totalLines: z.union([z.number().int().gte(0), z.null()]).optional(), + }), + z.object({ type: z.literal('text_editor_code_execution_view_result') }) + ), + z.intersection( + z.object({ + content: z.union([z.string(), z.null()]).optional(), + file_type: z.union([z.string(), z.null()]).optional(), + is_file_update: z.union([z.boolean(), z.null()]).optional(), + lines: z.union([z.array(z.string()), z.null()]).optional(), + newLines: z.union([z.number().int().gte(0), z.null()]).optional(), + newStart: z.union([z.number().int().gte(0), z.null()]).optional(), + numLines: z.union([z.number().int().gte(0), z.null()]).optional(), + oldLines: z.union([z.number().int().gte(0), z.null()]).optional(), + oldStart: z.union([z.number().int().gte(0), z.null()]).optional(), + startLine: z.union([z.number().int().gte(0), z.null()]).optional(), + totalLines: z.union([z.number().int().gte(0), z.null()]).optional(), + }), + z.object({ + type: z.literal('text_editor_code_execution_create_result'), + }) + ), + z.intersection( + z.object({ + content: z.union([z.string(), z.null()]).optional(), + file_type: z.union([z.string(), z.null()]).optional(), + is_file_update: z.union([z.boolean(), z.null()]).optional(), + lines: z.union([z.array(z.string()), z.null()]).optional(), + newLines: z.union([z.number().int().gte(0), z.null()]).optional(), + newStart: z.union([z.number().int().gte(0), z.null()]).optional(), + numLines: z.union([z.number().int().gte(0), z.null()]).optional(), + oldLines: z.union([z.number().int().gte(0), z.null()]).optional(), + oldStart: z.union([z.number().int().gte(0), z.null()]).optional(), + startLine: z.union([z.number().int().gte(0), z.null()]).optional(), + totalLines: z.union([z.number().int().gte(0), z.null()]).optional(), + }), + z.object({ + type: z.literal('text_editor_code_execution_str_replace_result'), + }) + ), + z.intersection( + z.object({ + error_code: z.any().superRefine((x, ctx) => { + const schemas = [ + z.literal('unavailable'), + z.literal('execution_time_exceeded'), + z.literal('container_expired'), + z.literal('invalid_tool_input'), + z.literal('too_many_requests'), + z.literal('file_not_found'), + z.literal('string_not_found'), + ]; + const errors = schemas.reduce( + (errors, schema) => + ((result) => + result.error ? [...errors, result.error] : errors)( + schema.safeParse(x) + ), + [] + ); + if (schemas.length - errors.length !== 1) { + ctx.addIssue({ + path: ctx.path, + code: 'invalid_union', + unionErrors: errors, + message: 'Invalid input: Should pass single schema', + }); + } + }), + }), + z.object({ + type: z.literal('text_editor_code_execution_tool_result_error'), + }) + ), + ]; + const errors = schemas.reduce( + (errors, schema) => + ((result) => (result.error ? [...errors, result.error] : errors))( + schema.safeParse(x) + ), + [] + ); + if (schemas.length - errors.length !== 1) { + ctx.addIssue({ + path: ctx.path, + code: 'invalid_union', + unionErrors: errors, + message: 'Invalid input: Should pass single schema', + }); + } + }), + tool_use_id: z.string(), +}); + export const WebFetchToolCall = z.object({ url: z.string() }); export const WebFetchResponse = z.object({ content: z.any().superRefine((x, ctx) => { const schemas = [ - z.object({ - content: z.object({ - citations: z - .union([z.object({ enabled: z.boolean() }), z.null()]) - .optional(), - source: z.any().superRefine((x, ctx) => { + z.intersection( + z.object({ + content: z.object({ + citations: z + .union([z.object({ enabled: z.boolean() }), z.null()]) + .optional(), + source: z.any().superRefine((x, ctx) => { + const schemas = [ + z.object({ + data: z.string(), + media_type: z.string(), + type: z.literal('text'), + }), + z.object({ + data: z.string(), + media_type: z.string(), + type: z.literal('base64'), + }), + ]; + const errors = schemas.reduce( + (errors, schema) => + ((result) => + result.error ? [...errors, result.error] : errors)( + schema.safeParse(x) + ), + [] + ); + if (schemas.length - errors.length !== 1) { + ctx.addIssue({ + path: ctx.path, + code: 'invalid_union', + unionErrors: errors, + message: 'Invalid input: Should pass single schema', + }); + } + }), + title: z.union([z.string(), z.null()]).optional(), + }), + retrieved_at: z.string(), + url: z.string(), + }), + z.object({ type: z.literal('web_fetch_result') }) + ), + z.intersection( + z.object({ + error_code: z.any().superRefine((x, ctx) => { const schemas = [ - z.object({ - data: z.string(), - media_type: z.string(), - type: z.literal('text'), - }), - z.object({ - data: z.string(), - media_type: z.string(), - type: z.literal('base64'), - }), + z.literal('invalid_input'), + z.literal('url_too_long'), + z.literal('url_not_allowed'), + z.literal('url_not_accessible'), + z.literal('too_many_requests'), + z.literal('unsupported_content_type'), + z.literal('max_uses_exceeded'), + z.literal('unavailable'), ]; const errors = schemas.reduce( (errors, schema) => @@ -531,40 +754,9 @@ export const WebFetchResponse = z.object({ }); } }), - title: z.union([z.string(), z.null()]).optional(), - }), - retrieved_at: z.string(), - url: z.string(), - }), - z.object({ - error_code: z.any().superRefine((x, ctx) => { - const schemas = [ - z.literal('invalid_input'), - z.literal('url_too_long'), - z.literal('url_not_allowed'), - z.literal('url_not_accessible'), - z.literal('too_many_requests'), - z.literal('unsupported_content_type'), - z.literal('max_uses_exceeded'), - z.literal('unavailable'), - ]; - const errors = schemas.reduce( - (errors, schema) => - ((result) => (result.error ? [...errors, result.error] : errors))( - schema.safeParse(x) - ), - [] - ); - if (schemas.length - errors.length !== 1) { - ctx.addIssue({ - path: ctx.path, - code: 'invalid_union', - unionErrors: errors, - message: 'Invalid input: Should pass single schema', - }); - } }), - }), + z.object({ type: z.literal('web_fetch_tool_result_error') }) + ), ]; const errors = schemas.reduce( (errors, schema) => diff --git a/js/app/packages/service-clients/service-cognition/generated/tools/tool.ts b/js/app/packages/service-clients/service-cognition/generated/tools/tool.ts index b4687bf6d84..0029da6c47c 100644 --- a/js/app/packages/service-clients/service-cognition/generated/tools/tool.ts +++ b/js/app/packages/service-clients/service-cognition/generated/tools/tool.ts @@ -9,6 +9,10 @@ import * as schemas from './schemas'; import type * as types from './types'; type ToolParserMap = { + bash_code_execution: { + call: types.BashCodeExecutionToolCall; + response: types.BashCodeExecutionResponse; + }; ContentSearch: { call: types.ContentSearch; response: types.SearchToolResponse; @@ -31,6 +35,10 @@ type ToolParserMap = { }; NameSearch: { call: types.NameSearch; response: types.SearchToolResponse }; Read: { call: types.Read; response: types.ReadResponse }; + text_editor_code_execution: { + call: types.TextEditorCodeExecutionToolCall; + response: types.TextEditorCodeExecutionResponse; + }; web_fetch: { call: types.WebFetchToolCall; response: types.WebFetchResponse }; web_search: { call: types.WebSearchToolCall; @@ -39,6 +47,10 @@ type ToolParserMap = { }; const toolParserMap = { + bash_code_execution: { + call: schemas.BashCodeExecutionToolCall, + response: schemas.BashCodeExecutionResponse, + }, ContentSearch: { call: schemas.ContentSearch, response: schemas.SearchToolResponse, @@ -64,6 +76,10 @@ const toolParserMap = { response: schemas.SearchToolResponse, }, Read: { call: schemas.Read, response: schemas.ReadResponse }, + text_editor_code_execution: { + call: schemas.TextEditorCodeExecutionToolCall, + response: schemas.TextEditorCodeExecutionResponse, + }, web_fetch: { call: schemas.WebFetchToolCall, response: schemas.WebFetchResponse, @@ -83,6 +99,10 @@ type NamedRawTool = { }; type ToolDataMap = { + bash_code_execution: { + call: types.BashCodeExecutionToolCall; + response: types.BashCodeExecutionResponse; + }; ContentSearch: { call: types.ContentSearch; response: types.SearchToolResponse; @@ -105,6 +125,10 @@ type ToolDataMap = { }; NameSearch: { call: types.NameSearch; response: types.SearchToolResponse }; Read: { call: types.Read; response: types.ReadResponse }; + text_editor_code_execution: { + call: types.TextEditorCodeExecutionToolCall; + response: types.TextEditorCodeExecutionResponse; + }; web_fetch: { call: types.WebFetchToolCall; response: types.WebFetchResponse }; web_search: { call: types.WebSearchToolCall; diff --git a/js/app/packages/service-clients/service-cognition/generated/tools/types.ts b/js/app/packages/service-clients/service-cognition/generated/tools/types.ts index 506db952bfa..5324e091701 100644 --- a/js/app/packages/service-clients/service-cognition/generated/tools/types.ts +++ b/js/app/packages/service-clients/service-cognition/generated/tools/types.ts @@ -9,6 +9,96 @@ * and run json-schema-to-typescript to regenerate this file. */ +/** + * The expected shape of the streamed JSON following a `server_tool_use` for bash_code_execution + */ +export interface BashCodeExecutionToolCall { + /** + * The bash command to execute + */ + command: string; +} + +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * Content of a bash code execution response - either a result or an error + */ +export type BashCodeExecutionContent = + | (BashCodeExecutionResult & { + type: 'bash_code_execution_result'; + }) + | (BashCodeExecutionToolError & { + type: 'bash_code_execution_tool_result_error'; + }); +/** + * Error codes for code execution failures + */ +export type CodeExecutionErrorCode = + | 'unavailable' + | 'execution_time_exceeded' + | 'container_expired' + | 'invalid_tool_input' + | 'too_many_requests' + | 'file_not_found' + | 'string_not_found'; + +/** + * Response from bash_code_execution tool + */ +export interface BashCodeExecutionResponse { + content: BashCodeExecutionContent; + tool_use_id: string; +} +/** + * Successful bash code execution result + */ +export interface BashCodeExecutionResult { + /** + * Files generated during execution + */ + content?: CodeExecutionFile[] | null; + /** + * Exit code (0 for success, non-zero for failure) + */ + return_code: number; + /** + * Standard error from the command + */ + stderr: string; + /** + * Standard output from the command + */ + stdout: string; +} +/** + * A file generated during code execution + */ +export interface CodeExecutionFile { + /** + * File ID for retrieval via Files API + */ + file_id: string; +} +/** + * Error from bash code execution + */ +export interface BashCodeExecutionToolError { + error_code: CodeExecutionErrorCode; +} + +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + /** * Search for items by their content. For documents, this searches the document body text. For emails, this searches the email message body. For chats, this searches the message content. This tool finds items based on what's inside them, not their titles or names. */ @@ -888,6 +978,130 @@ export interface ReadResponse { }; } +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * The expected shape of the streamed JSON following a `server_tool_use` for text_editor_code_execution + */ +export interface TextEditorCodeExecutionToolCall { + /** + * The command to execute: "view", "create", or "str_replace" + */ + command: string; + /** + * File content for create operations + */ + file_text?: string | null; + /** + * Replacement string for str_replace operations + */ + new_str?: string | null; + /** + * String to find for str_replace operations + */ + old_str?: string | null; + /** + * Path to the file + */ + path: string; +} + +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * Content of a text editor code execution response - either a result or an error + */ +export type TextEditorCodeExecutionContent = + | (TextEditorCodeExecutionResult & { + type: 'text_editor_code_execution_view_result'; + }) + | (TextEditorCodeExecutionResult & { + type: 'text_editor_code_execution_create_result'; + }) + | (TextEditorCodeExecutionResult & { + type: 'text_editor_code_execution_str_replace_result'; + }) + | (TextEditorCodeExecutionToolError & { + type: 'text_editor_code_execution_tool_result_error'; + }); +/** + * Error codes for code execution failures + */ + +/** + * Response from text_editor_code_execution tool + */ +export interface TextEditorCodeExecutionResponse { + content: TextEditorCodeExecutionContent; + tool_use_id: string; +} +/** + * Result from text editor operations (view, create, str_replace) + * Fields are optional as different operations return different fields + */ +export interface TextEditorCodeExecutionResult { + /** + * File content (for view operations) + */ + content?: string | null; + /** + * Type of file (e.g., "text") + */ + file_type?: string | null; + /** + * Whether the file already existed (true = update, false = create) + */ + is_file_update?: boolean | null; + /** + * Diff lines (prefixed with +/-) + */ + lines?: string[] | null; + /** + * Number of new lines + */ + newLines?: number | null; + /** + * New content start line + */ + newStart?: number | null; + /** + * Number of lines returned + */ + numLines?: number | null; + /** + * Number of old lines changed + */ + oldLines?: number | null; + /** + * Old content start line + */ + oldStart?: number | null; + /** + * Starting line number + */ + startLine?: number | null; + /** + * Total lines in file + */ + totalLines?: number | null; +} +/** + * Error from text editor code execution + */ +export interface TextEditorCodeExecutionToolError { + error_code: CodeExecutionErrorCode; +} + /* eslint-disable */ /** * This file was automatically generated by json-schema-to-typescript. @@ -912,7 +1126,25 @@ export interface WebFetchToolCall { /** * Content of a web fetch response - either a successful result or an error */ -export type WebFetchContent = WebFetchResult | WebFetchToolError; +export type WebFetchContent = + | (WebFetchResult & { + type: 'web_fetch_result'; + }) + | (WebFetchToolError & { + type: 'web_fetch_tool_result_error'; + }); +/** + * Possible error codes for web fetch failures + */ +export type WebFetchErrorCode = + | 'invalid_input' + | 'url_too_long' + | 'url_not_allowed' + | 'url_not_accessible' + | 'too_many_requests' + | 'unsupported_content_type' + | 'max_uses_exceeded' + | 'unavailable'; /** * Web fetch tool response content returned by Claude when using the web_fetch tool @@ -925,13 +1157,54 @@ export interface WebFetchResponse { * Successful web fetch result containing the fetched content */ export interface WebFetchResult { - type: 'web_fetch_result'; + content: WebFetchDocument; + /** + * Timestamp when the content was retrieved + */ + retrieved_at: string; + /** + * The URL that was fetched + */ + url: string; +} +/** + * The fetched document content + */ +export interface WebFetchDocument { + /** + * Optional citations configuration + */ + citations?: CitationsConfig | null; + /** + * The source content (text or base64) + */ + source: + | { + data: string; + media_type: string; + type: 'text'; + } + | { + data: string; + media_type: string; + type: 'base64'; + }; + /** + * Optional title of the document + */ + title?: string | null; +} +/** + * Citations configuration for web fetch + */ +export interface CitationsConfig { + enabled: boolean; } /** * Error returned when web fetch fails */ export interface WebFetchToolError { - type: 'web_fetch_tool_result_error'; + error_code: WebFetchErrorCode; } /* eslint-disable */ diff --git a/js/app/scripts/generate-dcs-tools.ts b/js/app/scripts/generate-dcs-tools.ts index 5fc23b0a829..95dfcb84a0d 100644 --- a/js/app/scripts/generate-dcs-tools.ts +++ b/js/app/scripts/generate-dcs-tools.ts @@ -14,6 +14,56 @@ import { jsonSchemaToZod } from "json-schema-to-zod"; import { z } from "zod"; import { documentCognitionBase, serviceUrl } from "./services"; +/** + * Pre-process JSON Schema to convert $ref with sibling properties into allOf. + * json-refs doesn't properly merge $ref with sibling keywords (properties, required, etc.), + * so we need to restructure the schema to use allOf for proper composition. + */ +function preprocessSchemaForRefs(schema: unknown): unknown { + if (schema === null || typeof schema !== "object") { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map(preprocessSchemaForRefs); + } + + const obj = schema as Record; + const result: Record = {}; + + // Check if this object has $ref along with other schema keywords + if ("$ref" in obj) { + const siblingKeys = Object.keys(obj).filter( + (k) => k !== "$ref" && k !== "$schema" && k !== "description", + ); + + if (siblingKeys.length > 0) { + // Convert to allOf structure: merge $ref schema with sibling properties + const refPart = { $ref: obj.$ref }; + const siblingPart: Record = {}; + + for (const key of siblingKeys) { + siblingPart[key] = preprocessSchemaForRefs(obj[key]); + } + + // Keep description at top level if present + if ("description" in obj) { + result.description = obj.description; + } + + result.allOf = [refPart, siblingPart]; + return result; + } + } + + // Recursively process all properties + for (const [key, value] of Object.entries(obj)) { + result[key] = preprocessSchemaForRefs(value); + } + + return result; +} + const serviceDir = path.resolve( import.meta.dirname, documentCognitionBase.output, @@ -65,7 +115,9 @@ async function generateSchemasFile(tools: AiToolsResponse) { for (const tool of tools) { // jsonSchemaToZod with name option generates a proper export - const { resolved: inputSchema } = await resolveRefs(tool.inputSchema); + // Preprocess to handle $ref with sibling properties + const preprocessedInput = preprocessSchemaForRefs(tool.inputSchema); + const { resolved: inputSchema } = await resolveRefs(preprocessedInput); if (!generatedSchemas.has(inputSchema.title)) { const inputCode = jsonSchemaToZod(inputSchema, { module: "esm", @@ -77,7 +129,8 @@ async function generateSchemasFile(tools: AiToolsResponse) { generatedSchemas.add(inputSchema.title); } - const { resolved: outputSchema } = await resolveRefs(tool.outputSchema); + const preprocessedOutput = preprocessSchemaForRefs(tool.outputSchema); + const { resolved: outputSchema } = await resolveRefs(preprocessedOutput); if (!generatedSchemas.has(outputSchema.title)) { const outputCode = jsonSchemaToZod(outputSchema, { module: "esm", @@ -99,15 +152,74 @@ ${content.join("\n\n")} await Bun.write(schemasFile, contents); } +/** + * Remove duplicate type/interface declarations from generated TypeScript code. + * json-schema-to-typescript generates nested types inline, which can cause duplicates + * when multiple schemas reference the same type. + */ +function deduplicateTypes(code: string): string { + const seenTypes = new Set(); + const lines = code.split("\n"); + const result: string[] = []; + let skipUntilNextExport = false; + let braceCount = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // Check if we're starting a new export type or interface + const typeMatch = line.match( + /^export\s+(type|interface)\s+(\w+)(?:\s*=|\s*\{|\s*$)/, + ); + + if (typeMatch && !skipUntilNextExport) { + const typeName = typeMatch[2]; + + if (seenTypes.has(typeName)) { + // Skip this duplicate type definition + skipUntilNextExport = true; + braceCount = 0; + // Count opening braces on this line + braceCount += (line.match(/\{/g) || []).length; + braceCount -= (line.match(/\}/g) || []).length; + // If it's a type alias (ends with ;), skip just this line + if (line.includes(";") && braceCount === 0) { + skipUntilNextExport = false; + } + continue; + } + seenTypes.add(typeName); + } + + if (skipUntilNextExport) { + // Count braces to know when the type/interface ends + braceCount += (line.match(/\{/g) || []).length; + braceCount -= (line.match(/\}/g) || []).length; + + // For type aliases, check if line ends with semicolon at brace level 0 + if (braceCount <= 0 && (line.includes(";") || line.trim() === "}")) { + skipUntilNextExport = false; + } + continue; + } + + result.push(line); + } + + return result.join("\n"); +} + async function generateToolTypesFile(tools: AiToolsResponse) { // Generate all schemas, deduplicating by title const generatedTypes = new Set(); const content: Array = []; for (const tool of tools) { + // Preprocess to handle $ref with sibling properties + const preprocessedInput = preprocessSchemaForRefs(tool.inputSchema); if (!generatedTypes.has(tool.inputSchema.title)) { const inputCode = await compile( - tool.inputSchema, + preprocessedInput, tool.inputSchema.title, { // override top level name for export @@ -127,9 +239,10 @@ async function generateToolTypesFile(tools: AiToolsResponse) { generatedTypes.add(tool.inputSchema.title); } + const preprocessedOutput = preprocessSchemaForRefs(tool.outputSchema); if (!generatedTypes.has(tool.outputSchema.title)) { const outputCode = await compile( - tool.outputSchema, + preprocessedOutput, tool.outputSchema.title, { // override top level name for export @@ -151,19 +264,23 @@ async function generateToolTypesFile(tools: AiToolsResponse) { } } - const contents = `${warning} + // Deduplicate nested types that may have been generated multiple times + const rawContents = `${warning} ${content.join("\n\n")} `; + const deduplicatedContents = deduplicateTypes(rawContents); - await Bun.write(typesFile, contents); + await Bun.write(typesFile, deduplicatedContents); } async function generateToolsFile(tools: AiToolsResponse) { // Resolve schemas to get titles const toolsWithTitles = await Promise.all( tools.map(async (tool) => { - const { resolved: inputSchema } = await resolveRefs(tool.inputSchema); - const { resolved: outputSchema } = await resolveRefs(tool.outputSchema); + const preprocessedInput = preprocessSchemaForRefs(tool.inputSchema); + const preprocessedOutput = preprocessSchemaForRefs(tool.outputSchema); + const { resolved: inputSchema } = await resolveRefs(preprocessedInput); + const { resolved: outputSchema } = await resolveRefs(preprocessedOutput); return { name: tool.name, inputTitle: inputSchema.title, diff --git a/rust/cloud-storage/ai/src/tool/tool_loop/ai_client.rs b/rust/cloud-storage/ai/src/tool/tool_loop/ai_client.rs index db324819122..5ae9e432cd6 100644 --- a/rust/cloud-storage/ai/src/tool/tool_loop/ai_client.rs +++ b/rust/cloud-storage/ai/src/tool/tool_loop/ai_client.rs @@ -24,6 +24,7 @@ where let extensions = AnthropicRequestExtensions(vec![ AnthropicRequestExtension::WebSearchTool, AnthropicRequestExtension::FetchTool, + AnthropicRequestExtension::CodeExecutionTool, ]); let client = AnthropicClient::new(extensions); let toolset = Arc::new(toolset); diff --git a/rust/cloud-storage/ai/src/types/client/anthropic.rs b/rust/cloud-storage/ai/src/types/client/anthropic.rs index 668387ab452..970ec257904 100644 --- a/rust/cloud-storage/ai/src/types/client/anthropic.rs +++ b/rust/cloud-storage/ai/src/types/client/anthropic.rs @@ -85,6 +85,26 @@ impl ExtendedClient for AnthropicClient { name: "web_fetch".into(), })) } + AnthropicResponseExtension::BashCodeExecutionToolResponse(response) => { + let id = response.tool_use_id.clone(); + let json = serde_json::to_value(&response).ok()?; + + Some(StreamPart::ToolResponse(ToolResponse::Json { + id, + json, + name: "bash_code_execution".into(), + })) + } + AnthropicResponseExtension::TextEditorCodeExecutionToolResponse(response) => { + let id = response.tool_use_id.clone(); + let json = serde_json::to_value(&response).ok()?; + + Some(StreamPart::ToolResponse(ToolResponse::Json { + id, + json, + name: "text_editor_code_execution".into(), + })) + } } } } diff --git a/rust/cloud-storage/ai_tools/src/code_execution.rs b/rust/cloud-storage/ai_tools/src/code_execution.rs new file mode 100644 index 00000000000..880631a847d --- /dev/null +++ b/rust/cloud-storage/ai_tools/src/code_execution.rs @@ -0,0 +1,13 @@ +use ai::tool::schema::PhantomTool; +use anthropic::types::response::code_execution::{ + BashCodeExecutionResponse, BashCodeExecutionToolCall, TextEditorCodeExecutionResponse, + TextEditorCodeExecutionToolCall, +}; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref anthropic_bash_code_execution_tool: PhantomTool = + PhantomTool::new("bash_code_execution"); + pub static ref anthropic_text_editor_code_execution_tool: PhantomTool = + PhantomTool::new("text_editor_code_execution"); +} diff --git a/rust/cloud-storage/ai_tools/src/lib.rs b/rust/cloud-storage/ai_tools/src/lib.rs index 951ce41adf6..9c49174552f 100644 --- a/rust/cloud-storage/ai_tools/src/lib.rs +++ b/rust/cloud-storage/ai_tools/src/lib.rs @@ -1,5 +1,6 @@ use ai_toolset::AsyncToolSet; use ai_toolset::schema::{ToolSchemaGenerator, ToolSchemas}; +pub mod code_execution; pub mod list; pub mod prompts; pub mod read; @@ -7,6 +8,9 @@ pub mod rewrite; pub mod search; mod tool_context; pub mod web_fetch; +use code_execution::{ + anthropic_bash_code_execution_tool, anthropic_text_editor_code_execution_tool, +}; use search::web::anthropic_web_search::anthropic_web_search_tool; use std::sync::Arc; use web_fetch::anthropic_web_fetch_tool; @@ -50,6 +54,8 @@ pub fn all_tool_schemas() -> ToolSchemas { all_tools() .merge(&*anthropic_web_search_tool) .merge(&*anthropic_web_fetch_tool) + .merge(&*anthropic_bash_code_execution_tool) + .merge(&*anthropic_text_editor_code_execution_tool) .generate_schemas() } diff --git a/rust/cloud-storage/anthropic/src/client/mod.rs b/rust/cloud-storage/anthropic/src/client/mod.rs index 319a0aa0f0d..4378aa77c88 100644 --- a/rust/cloud-storage/anthropic/src/client/mod.rs +++ b/rust/cloud-storage/anthropic/src/client/mod.rs @@ -2,7 +2,7 @@ pub mod chat; use crate::error::AnthropicError; use crate::openai::request::AnthropicRequestExtension; use crate::prelude::ApiError; -use crate::types::request::WEB_FETCH_TOOL_HEADER; +use crate::types::request::{CODE_EXECUTION_TOOL_HEADER, WEB_FETCH_TOOL_HEADER}; use crate::{config::Config, openai::request::AnthropicRequestExtensions}; use futures::stream::{Stream, StreamExt}; use reqwest::Client as RequestClient; @@ -19,17 +19,24 @@ pub struct Client { impl Client { pub fn dangerously_try_from_env(extensions: Option) -> Self { let mut config = Config::dangrously_try_from_env(); - if let Some(extensions) = extensions - && extensions + if let Some(ref extensions) = extensions { + if extensions.0.contains(&AnthropicRequestExtension::FetchTool) { + tracing::debug!("Adding web_fetch beta header"); + config.headers.append( + WEB_FETCH_TOOL_HEADER.0.clone(), + WEB_FETCH_TOOL_HEADER.1.clone(), + ); + } + if extensions .0 - .into_iter() - .any(|f| f == AnthropicRequestExtension::FetchTool) - { - tracing::debug!("Adding web_fetch beta header"); - config.headers.append( - WEB_FETCH_TOOL_HEADER.0.clone(), - WEB_FETCH_TOOL_HEADER.1.clone(), - ); + .contains(&AnthropicRequestExtension::CodeExecutionTool) + { + tracing::debug!("Adding code_execution beta header"); + config.headers.append( + CODE_EXECUTION_TOOL_HEADER.0.clone(), + CODE_EXECUTION_TOOL_HEADER.1.clone(), + ); + } } tracing::debug!("Anthropic client headers: {:?}", config.headers); Self::with_config(config) diff --git a/rust/cloud-storage/anthropic/src/openai/request/request_extension.rs b/rust/cloud-storage/anthropic/src/openai/request/request_extension.rs index 60bd8aaf0f4..84aa2c7c7b1 100644 --- a/rust/cloud-storage/anthropic/src/openai/request/request_extension.rs +++ b/rust/cloud-storage/anthropic/src/openai/request/request_extension.rs @@ -1,6 +1,6 @@ use crate::{ prelude::{ServerTool, WEB_FETCH_TOOL}, - types::request::WEB_SEARCH_TOOL, + types::request::{CODE_EXECUTION_TOOL, WEB_SEARCH_TOOL}, }; /// Request extensions are functionality that is supported by the anthropic API but not supported by OpenAI/completions/v1 @@ -12,8 +12,10 @@ use crate::{ pub enum AnthropicRequestExtension { /// WebSearchTool, - // https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool + /// FetchTool, + /// + CodeExecutionTool, } #[derive(Clone, Debug)] @@ -50,6 +52,7 @@ impl AnthropicRequestExtension { match self { Self::WebSearchTool => add_tool(WEB_SEARCH_TOOL.clone(), request), Self::FetchTool => add_tool(WEB_FETCH_TOOL.clone(), request), + Self::CodeExecutionTool => add_tool(CODE_EXECUTION_TOOL.clone(), request), } } } diff --git a/rust/cloud-storage/anthropic/src/openai/stream_extension.rs b/rust/cloud-storage/anthropic/src/openai/stream_extension.rs index ef4eaf41263..5b86e23c378 100644 --- a/rust/cloud-storage/anthropic/src/openai/stream_extension.rs +++ b/rust/cloud-storage/anthropic/src/openai/stream_extension.rs @@ -1,5 +1,8 @@ use crate::prelude::ServerToolUse; use crate::types::response::Citation; +use crate::types::response::code_execution::{ + BashCodeExecutionResponse, TextEditorCodeExecutionResponse, +}; use crate::types::response::web_fetch::WebFetchResponse; use crate::types::response::web_search::WebSearchResponse; use async_openai::error::OpenAIError; @@ -13,6 +16,8 @@ pub enum AnthropicResponseExtension { Citation(Citation), WebSearchToolResponse(WebSearchResponse), WebFetchToolResponse(WebFetchResponse), + BashCodeExecutionToolResponse(BashCodeExecutionResponse), + TextEditorCodeExecutionToolResponse(TextEditorCodeExecutionResponse), ServerToolUse(ServerToolUse), } diff --git a/rust/cloud-storage/anthropic/src/openai/stream_response.rs b/rust/cloud-storage/anthropic/src/openai/stream_response.rs index a5d6e7feab1..5740fc827e8 100644 --- a/rust/cloud-storage/anthropic/src/openai/stream_response.rs +++ b/rust/cloud-storage/anthropic/src/openai/stream_response.rs @@ -210,6 +210,12 @@ fn map_stream_extended(mut stream: MessageCompletionResponseStream) -> ExtendedS ContentDeltaEvent::WebFetchToolResult(web_fetch_response) => { yield Ok(AnthropicResponseExtension::WebFetchToolResponse(web_fetch_response).into()); } + ContentDeltaEvent::BashCodeExecutionToolResult(bash_response) => { + yield Ok(AnthropicResponseExtension::BashCodeExecutionToolResponse(bash_response).into()); + } + ContentDeltaEvent::TextEditorCodeExecutionToolResult(text_editor_response) => { + yield Ok(AnthropicResponseExtension::TextEditorCodeExecutionToolResponse(text_editor_response).into()); + } _ => {} } // Skip content block start events @@ -228,6 +234,12 @@ fn map_stream_extended(mut stream: MessageCompletionResponseStream) -> ExtendedS ContentDeltaEvent::WebFetchToolResult(web_fetch_response) => { yield Ok(AnthropicResponseExtension::WebFetchToolResponse(web_fetch_response).into()); } + ContentDeltaEvent::BashCodeExecutionToolResult(bash_response) => { + yield Ok(AnthropicResponseExtension::BashCodeExecutionToolResponse(bash_response).into()); + } + ContentDeltaEvent::TextEditorCodeExecutionToolResult(text_editor_response) => { + yield Ok(AnthropicResponseExtension::TextEditorCodeExecutionToolResponse(text_editor_response).into()); + } ContentDeltaEvent::TextDelta { text } | ContentDeltaEvent::StartTextDelta { text } => { yield Ok(create_response( &message_id.clone().unwrap_or_default(), diff --git a/rust/cloud-storage/anthropic/src/types/request/server_tools.rs b/rust/cloud-storage/anthropic/src/types/request/server_tools.rs index ce9b6725c3d..d1787bdcc02 100644 --- a/rust/cloud-storage/anthropic/src/types/request/server_tools.rs +++ b/rust/cloud-storage/anthropic/src/types/request/server_tools.rs @@ -15,6 +15,11 @@ lazy_static! { r#type: "web_fetch_20250910".into(), args: Value::Null, }; + pub static ref CODE_EXECUTION_TOOL: ServerTool = ServerTool { + name: "code_execution".into(), + r#type: "code_execution_20250825".into(), + args: Value::Null, + }; } pub static WEB_FETCH_TOOL_HEADER: (reqwest::header::HeaderName, reqwest::header::HeaderValue) = ( @@ -22,6 +27,11 @@ pub static WEB_FETCH_TOOL_HEADER: (reqwest::header::HeaderName, reqwest::header: reqwest::header::HeaderValue::from_static("web-fetch-2025-09-10"), ); +pub static CODE_EXECUTION_TOOL_HEADER: (reqwest::header::HeaderName, reqwest::header::HeaderValue) = ( + reqwest::header::HeaderName::from_static("anthropic-beta"), + reqwest::header::HeaderValue::from_static("code-execution-2025-08-25"), +); + lazy_static! { static ref re: Regex = Regex::new("(.+?)").expect("link-regex"); } diff --git a/rust/cloud-storage/anthropic/src/types/response/code_execution.rs b/rust/cloud-storage/anthropic/src/types/response/code_execution.rs new file mode 100644 index 00000000000..1998d675a2c --- /dev/null +++ b/rust/cloud-storage/anthropic/src/types/response/code_execution.rs @@ -0,0 +1,175 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +// ============================================================================ +// Bash Code Execution Types +// ============================================================================ + +/// Response from bash_code_execution tool +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct BashCodeExecutionResponse { + pub tool_use_id: String, + pub content: BashCodeExecutionContent, +} + +/// Content of a bash code execution response - either a result or an error +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum BashCodeExecutionContent { + BashCodeExecutionResult(BashCodeExecutionResult), + #[serde(rename = "bash_code_execution_tool_result_error")] + BashCodeExecutionToolResultError(BashCodeExecutionToolError), +} + +/// Successful bash code execution result +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +pub struct BashCodeExecutionResult { + /// Standard output from the command + pub stdout: String, + /// Standard error from the command + pub stderr: String, + /// Exit code (0 for success, non-zero for failure) + pub return_code: i32, + /// Files generated during execution + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option>, +} + +/// A file generated during code execution +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +pub struct CodeExecutionFile { + /// File ID for retrieval via Files API + pub file_id: String, +} + +/// Error from bash code execution +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +pub struct BashCodeExecutionToolError { + pub error_code: CodeExecutionErrorCode, +} + +/// The expected shape of the streamed JSON following a `server_tool_use` for bash_code_execution +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +pub struct BashCodeExecutionToolCall { + /// The bash command to execute + pub command: String, +} + +// ============================================================================ +// Text Editor Code Execution Types +// ============================================================================ + +/// Response from text_editor_code_execution tool +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct TextEditorCodeExecutionResponse { + pub tool_use_id: String, + pub content: TextEditorCodeExecutionContent, +} + +/// Content of a text editor code execution response - either a result or an error +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum TextEditorCodeExecutionContent { + #[serde(rename = "text_editor_code_execution_view_result")] + TextEditorCodeExecutionViewResult(TextEditorCodeExecutionResult), + #[serde(rename = "text_editor_code_execution_create_result")] + TextEditorCodeExecutionCreateResult(TextEditorCodeExecutionResult), + #[serde(rename = "text_editor_code_execution_str_replace_result")] + TextEditorCodeExecutionStrReplaceResult(TextEditorCodeExecutionResult), + #[serde(rename = "text_editor_code_execution_tool_result_error")] + TextEditorCodeExecutionToolResultError(TextEditorCodeExecutionToolError), +} + +/// Result from text editor operations (view, create, str_replace) +/// Fields are optional as different operations return different fields +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +pub struct TextEditorCodeExecutionResult { + // View result fields + /// Type of file (e.g., "text") + #[serde(skip_serializing_if = "Option::is_none")] + pub file_type: Option, + /// File content (for view operations) + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + /// Number of lines returned + #[serde(rename = "numLines", skip_serializing_if = "Option::is_none")] + pub num_lines: Option, + /// Starting line number + #[serde(rename = "startLine", skip_serializing_if = "Option::is_none")] + pub start_line: Option, + /// Total lines in file + #[serde(rename = "totalLines", skip_serializing_if = "Option::is_none")] + pub total_lines: Option, + + // Create result fields + /// Whether the file already existed (true = update, false = create) + #[serde(skip_serializing_if = "Option::is_none")] + pub is_file_update: Option, + + // Edit (str_replace) result fields + /// Old content start line + #[serde(rename = "oldStart", skip_serializing_if = "Option::is_none")] + pub old_start: Option, + /// Number of old lines changed + #[serde(rename = "oldLines", skip_serializing_if = "Option::is_none")] + pub old_lines: Option, + /// New content start line + #[serde(rename = "newStart", skip_serializing_if = "Option::is_none")] + pub new_start: Option, + /// Number of new lines + #[serde(rename = "newLines", skip_serializing_if = "Option::is_none")] + pub new_lines: Option, + /// Diff lines (prefixed with +/-) + #[serde(skip_serializing_if = "Option::is_none")] + pub lines: Option>, +} + +/// Error from text editor code execution +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +pub struct TextEditorCodeExecutionToolError { + pub error_code: CodeExecutionErrorCode, +} + +/// The expected shape of the streamed JSON following a `server_tool_use` for text_editor_code_execution +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +pub struct TextEditorCodeExecutionToolCall { + /// The command to execute: "view", "create", or "str_replace" + pub command: String, + /// Path to the file + pub path: String, + /// File content for create operations + #[serde(skip_serializing_if = "Option::is_none")] + pub file_text: Option, + /// String to find for str_replace operations + #[serde(skip_serializing_if = "Option::is_none")] + pub old_str: Option, + /// Replacement string for str_replace operations + #[serde(skip_serializing_if = "Option::is_none")] + pub new_str: Option, +} + +// ============================================================================ +// Shared Types +// ============================================================================ + +/// Error codes for code execution failures +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CodeExecutionErrorCode { + /// The tool is temporarily unavailable + Unavailable, + /// Execution exceeded maximum time limit + ExecutionTimeExceeded, + /// Container expired and is no longer available + ContainerExpired, + /// Invalid parameters provided to the tool + InvalidToolInput, + /// Rate limit exceeded for tool usage + TooManyRequests, + /// File doesn't exist (for view/edit operations) - text_editor only + FileNotFound, + /// The old_str not found in file (for str_replace) - text_editor only + StringNotFound, +} diff --git a/rust/cloud-storage/anthropic/src/types/response/mod.rs b/rust/cloud-storage/anthropic/src/types/response/mod.rs index 1f4a966850d..d3d97dd5bd1 100644 --- a/rust/cloud-storage/anthropic/src/types/response/mod.rs +++ b/rust/cloud-storage/anthropic/src/types/response/mod.rs @@ -1,3 +1,4 @@ +pub mod code_execution; mod stream_types; #[cfg(test)] mod test; diff --git a/rust/cloud-storage/anthropic/src/types/response/stream_types.rs b/rust/cloud-storage/anthropic/src/types/response/stream_types.rs index 3af0d296990..40be4c64232 100644 --- a/rust/cloud-storage/anthropic/src/types/response/stream_types.rs +++ b/rust/cloud-storage/anthropic/src/types/response/stream_types.rs @@ -1,5 +1,6 @@ use crate::prelude::ServerToolUse; +use super::code_execution::{BashCodeExecutionResponse, TextEditorCodeExecutionResponse}; use super::web_fetch::WebFetchResponse; use super::web_search::WebSearchResponse; @@ -71,6 +72,8 @@ pub enum ContentDeltaEvent { }, WebSearchToolResult(WebSearchResponse), WebFetchToolResult(WebFetchResponse), + BashCodeExecutionToolResult(BashCodeExecutionResponse), + TextEditorCodeExecutionToolResult(TextEditorCodeExecutionResponse), } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] diff --git a/rust/cloud-storage/anthropic/src/types/response/types.rs b/rust/cloud-storage/anthropic/src/types/response/types.rs index e1583b9ccdd..7855b783175 100644 --- a/rust/cloud-storage/anthropic/src/types/response/types.rs +++ b/rust/cloud-storage/anthropic/src/types/response/types.rs @@ -90,6 +90,16 @@ pub struct Usage { pub service_tier: Option, } +/// Container information for code execution sessions +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Container { + /// Unique identifier for the container + pub id: String, + /// When the container expires (ISO 8601 timestamp) + #[serde(skip_serializing_if = "Option::is_none")] + pub expires_at: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct MessageResponse { #[serde(skip_serializing_if = "Option::is_none")] @@ -108,6 +118,7 @@ pub struct MessageResponse { pub usage: Option, #[serde(skip_serializing_if = "Option::is_none")] pub context_management: Option, + /// Container information for code execution sessions #[serde(skip_serializing_if = "Option::is_none")] - pub container: Option, + pub container: Option, } diff --git a/rust/cloud-storage/document_cognition_service/schemas/tools.json b/rust/cloud-storage/document_cognition_service/schemas/tools.json index 7194ae476bb..d8bd49ae482 100644 --- a/rust/cloud-storage/document_cognition_service/schemas/tools.json +++ b/rust/cloud-storage/document_cognition_service/schemas/tools.json @@ -1,512 +1,537 @@ { "schemas": [ { - "name": "ListChannels", + "name": "MarkdownRewrite", "inputSchema": { "additionalProperties": false, - "description": "List all chat channels the user has access to. Returns channel names, IDs, and types. Use this tool to discover available channels for reading messages or participating in conversations.", + "description": "Instruct an agent to edit a markdown file identified by an id.\n This tool should be used when the user include a markdown file in context and requests a revision or edit to that file", "properties": { - "unused": { + "instructions": { "additionalProperties": false, - "default": null, - "type": "null" + "description": "Instructions for the revision agent to follow to edit the markdown. These instructions will be provided by the user.", + "type": "string" + }, + "markdown_file_id": { + "additionalProperties": false, + "description": "The markdown file id to target for editing. This file id will be in your context and labeled as a markdown (md) document", + "type": "string" } }, "required": [ - "unused" + "instructions", + "markdown_file_id" ], - "title": "ListChannels", + "title": "MarkdownRewrite", "type": "object" }, "outputSchema": { - "description": "Response from listing channels", + "additionalProperties": false, "properties": { - "channels": { - "description": "The list of channels the user has access to", + "diffs": { "items": { - "description": "A channel item returned by the list channels tool", + "additionalProperties": false, "properties": { - "channelType": { - "description": "The type of channel (public, private, organization, or direct_message)", - "enum": [ - "public", - "organization", - "private", - "direct_message" - ], + "markdown_text": { "type": "string" }, - "id": { - "description": "The channel's unique identifier", - "format": "uuid", + "node_key": { "type": "string" }, - "name": { - "description": "The channel's display name (may be None for direct messages)", - "type": [ - "string", - "null" - ] + "operation": { + "type": "string" } }, "required": [ - "id", - "channelType" + "operation", + "node_key", + "markdown_text" ], "type": "object" }, "type": "array" - }, - "total": { - "description": "Total number of channels returned", - "format": "uint", - "minimum": 0, - "type": "integer" } }, "required": [ - "channels", - "total" + "diffs" ], - "title": "ListChannelsResponse", + "title": "AIDiffResponse", "type": "object" } }, { - "name": "ContentSearch", + "name": "ListEmails", "inputSchema": { "additionalProperties": false, - "description": "Search for items by their content. For documents, this searches the document body text. For emails, this searches the email message body. For chats, this searches the message content. This tool finds items based on what's inside them, not their titles or names.", + "description": "List the emails the user has access to. Use this tool to find all emails from / to a specific person. Prefer using the search tool to search on a specific matching string within the content or the name of the entity.", "properties": { - "entityTypes": { + "cursor": { "additionalProperties": false, - "default": [], - "description": "Which types of items to search. Leave empty to search all types. Examples: ['documents'], ['emails', 'documents'], ['channels']", - "items": { - "additionalProperties": false, - "enum": [ - "documents", - "chats", - "emails", - "channels", - "projects" - ], - "type": "string" - }, - "type": "array" + "default": null, + "description": "A preview response will tell you what the next cursor id is.\n If expected emails are not in the current response and it gives you a cursor id, use this field to list the next page of emails. If no cursor id is provided you've reached the end", + "type": [ + "string", + "null" + ] }, - "query": { + "limit": { "additionalProperties": false, - "description": "The text content to search for. This searches within the body of documents, emails, and messages.", + "default": 20, + "description": "limit the max emails returned. This defaults to 20 and has a maximum of 500", + "format": "int64", + "type": "integer" + }, + "sortMethod": { + "additionalProperties": false, + "default": "viewed_at", + "description": "Sort response by one of: viewed_at | updated_at | created_at | viewed_updated. Prefer viewed_at unless the otherwise specified", + "enum": [ + "viewed_at", + "updated_at", + "created_at", + "viewed_updated" + ], "type": "string" + }, + "view": { + "additionalProperties": false, + "default": { + "standardLabel": null, + "userLabel": null + }, + "description": "Choose the view to list emails from. One of either standard label or user label must be specified, otherwise defaults to important.", + "properties": { + "standardLabel": { + "additionalProperties": false, + "description": "Prefer important or all. Leave empty if using a user label", + "enum": [ + "Inbox", + "Sent", + "Drafts", + "Starred", + "All", + "Important", + "Other", + null + ], + "type": [ + "string", + "null" + ] + }, + "userLabel": { + "additionalProperties": false, + "description": "Choose a custom user label to filter by (optional). Standard label will take precedence over user label if both are specified.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "standardLabel", + "userLabel" + ], + "type": "object" } }, "required": [ - "entityTypes", - "query" + "cursor", + "limit", + "sortMethod", + "view" ], - "title": "ContentSearch", + "title": "ListEmails", "type": "object" }, "outputSchema": { "properties": { - "results": { + "items": { "items": { - "oneOf": [ - { - "description": "DocumentSearchResponseItem object with document metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update document page record\nevery time the document updates (specifically for updated_at and viewed_at)", - "properties": { - "document_id": { - "description": "The id of the document", - "format": "uuid", - "type": "string" + "properties": { + "attachments": { + "items": { + "properties": { + "contentId": { + "type": [ + "string", + "null" + ] + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "filename": { + "type": [ + "string", + "null" + ] + }, + "id": { + "format": "uuid", + "type": "string" + }, + "messageId": { + "format": "uuid", + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "providerAttachmentId": { + "type": [ + "string", + "null" + ] + }, + "sizeBytes": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } }, - "document_name": { - "description": "The name of the document", - "type": "string" + "required": [ + "id", + "messageId", + "createdAt" + ], + "type": "object" + }, + "type": "array" + }, + "contacts": { + "items": { + "properties": { + "emailAddress": { + "type": [ + "string", + "null" + ] + }, + "id": { + "format": "uuid", + "type": "string" + }, + "linkId": { + "format": "uuid", + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "sfsPhotoUrl": { + "type": [ + "string", + "null" + ] + } }, - "document_search_results": { - "description": "The search results for the document\nThis may be empty if the search result match was on the document name only", - "items": { - "description": "A document match for a given node", - "properties": { - "highlight": { - "description": "The highlights for the document", - "properties": { - "bcc": { - "description": "The highlight match on the bcc (email only)", - "items": { - "type": "string" - }, - "type": "array" - }, - "cc": { - "description": "The highlight match on the cc (email only)", - "items": { - "type": "string" - }, - "type": "array" - }, - "content": { - "description": "The highlight match on the content field", - "items": { - "type": "string" - }, - "type": "array" - }, - "name": { - "description": "The highlight match on the name field", - "type": [ - "string", - "null" - ] - }, - "recipients": { - "description": "The highlight match on the recipients (email only)", - "items": { - "type": "string" - }, - "type": "array" - }, - "sender": { - "description": "The highlight match on the sender (email only)", - "type": [ - "string", - "null" - ] - }, - "user_id": { - "description": "The highlight match on the user (owner) of the entity", - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "node_id": { - "description": "The node id for the document.\nThis is only useful for markdown at the moment\nThis will only be provided if the match was on content", - "type": [ - "string", - "null" - ] - }, - "raw_content": { - "description": "The raw content of the document.\nThis is only included for markdown files and will be the raw json node of the match", - "type": [ - "string", - "null" - ] - }, - "score": { - "description": "The score of the result", - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "required": [ - "highlight" - ], - "type": "object" - }, - "type": "array" - }, - "file_type": { - "description": "The file type of the document", - "type": [ - "string", - "null" - ] - }, - "id": { - "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", - "format": "uuid", - "type": "string" - }, - "metadata": { - "description": "Metadata from the database. None if the document doesn't exist in the database.", - "properties": { - "created_at": { - "format": "int64", - "type": "integer" - }, - "deleted_at": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "project_id": { - "type": [ - "string", - "null" - ] - }, - "updated_at": { - "format": "int64", - "type": "integer" - }, - "viewed_at": { - "format": "int64", - "type": [ - "integer", - "null" - ] - } - }, - "required": [ - "created_at", - "updated_at" - ], - "type": [ - "object", - "null" - ] - }, - "name": { - "type": "string" - }, - "owner_id": { - "type": "string" - }, - "sub_type": { - "anyOf": [ - { - "description": "The document sub type enum represents all values of document sub types.\nThese values should match the `document_sub_type_value` table in macrodb.", - "oneOf": [ - { - "const": "task", - "description": "A task document", - "type": "string" - } - ] - }, - { - "type": "null" - } - ], - "description": "The sub type of the document if present." - }, - "type": { - "const": "document", - "type": "string" - } + "required": [ + "id", + "linkId" + ], + "type": "object" }, - "required": [ - "type", - "id", - "name", - "owner_id", - "document_id", - "document_name", - "document_search_results" - ], - "type": "object" + "type": "array" }, - { - "description": "ChatSearchResponse object with channel metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update each chat message record for the chat\nevery time the chat updates (specifically for updated_at and viewed_at)", - "properties": { - "chat_id": { - "description": "The id of the chat", - "format": "uuid", - "type": "string" - }, - "chat_search_results": { - "description": "The search results for the chat\nThis may be empty if the search result match was on the chat title only", - "items": { - "description": "A chat match for a given message id", - "properties": { - "chat_message_id": { - "description": "The chat message id for the chat\nThis is only present if the search match was on the chat message content", - "format": "uuid", - "type": [ - "string", - "null" - ] - }, - "highlight": { - "description": "The highlights for the chat message", - "properties": { - "bcc": { - "description": "The highlight match on the bcc (email only)", - "items": { - "type": "string" - }, - "type": "array" - }, - "cc": { - "description": "The highlight match on the cc (email only)", - "items": { - "type": "string" - }, - "type": "array" - }, - "content": { - "description": "The highlight match on the content field", - "items": { - "type": "string" - }, - "type": "array" - }, - "name": { - "description": "The highlight match on the name field", - "type": [ - "string", - "null" - ] - }, - "recipients": { - "description": "The highlight match on the recipients (email only)", - "items": { - "type": "string" - }, - "type": "array" - }, - "sender": { - "description": "The highlight match on the sender (email only)", - "type": [ - "string", - "null" - ] - }, - "user_id": { - "description": "The highlight match on the user (owner) of the entity", - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "role": { - "description": "The role of the chat message\nThis is only present if the search match was on the chat message content", - "type": [ - "string", - "null" - ] - }, - "score": { - "description": "The score of the result", - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "required": [ - "highlight" + "createdAt": { + "format": "int64", + "type": "integer" + }, + "frecencyScore": { + "format": "double", + "type": [ + "number", + "null" + ] + }, + "id": { + "format": "uuid", + "type": "string" + }, + "inboxVisible": { + "type": "boolean" + }, + "isDraft": { + "type": "boolean" + }, + "isImportant": { + "type": "boolean" + }, + "isRead": { + "type": "boolean" + }, + "labels": { + "items": { + "properties": { + "createdAt": { + "format": "date-time", + "type": "string" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "labelListVisibility": { + "enum": [ + "LabelShow", + "LabelShowIfUnread", + "LabelHide" ], - "type": "object" + "type": "string" }, - "type": "array" - }, - "id": { - "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", - "format": "uuid", - "type": "string" + "linkId": { + "format": "uuid", + "type": "string" + }, + "messageListVisibility": { + "enum": [ + "Show", + "Hide" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "providerLabelId": { + "type": "string" + }, + "type": { + "enum": [ + "System", + "User" + ], + "type": "string" + } }, - "metadata": { - "description": "Metadata from the database. None if the chat doesn't exist in the database.", - "properties": { - "created_at": { - "format": "int64", - "type": "integer" - }, - "deleted_at": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "project_id": { - "type": [ - "string", - "null" - ] - }, - "updated_at": { - "format": "int64", - "type": "integer" - }, - "viewed_at": { - "format": "int64", - "type": [ - "integer", - "null" - ] - } + "required": [ + "id", + "linkId", + "providerLabelId", + "name", + "createdAt", + "messageListVisibility", + "labelListVisibility", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "macroAttachments": { + "items": { + "properties": { + "dbId": { + "format": "uuid", + "type": "string" }, - "required": [ - "created_at", - "updated_at" - ], - "type": [ - "object", - "null" - ] + "itemId": { + "format": "uuid", + "type": "string" + }, + "itemType": { + "type": "string" + }, + "messageId": { + "format": "uuid", + "type": "string" + } }, - "name": { - "type": "string" + "required": [ + "dbId", + "messageId", + "itemId", + "itemType" + ], + "type": "object" + }, + "type": "array" + }, + "metadata": { + "properties": { + "calendar_invite": { + "description": "if any email contains a calendar invite", + "type": "boolean" }, - "owner_id": { - "type": "string" + "generic_sender": { + "description": "if any sender in the thread is a generic email", + "type": "boolean" }, - "type": { - "const": "chat", + "known_sender": { + "description": "if user has previously emailed any sender in the thread", + "type": "boolean" + }, + "tabular": { + "description": "if any email contains a html tag", + "type": "boolean" + } + }, + "required": [ + "known_sender", + "tabular", + "calendar_invite", + "generic_sender" + ], + "type": "object" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "ownerId": { + "type": "string" + }, + "providerId": { + "type": [ + "string", + "null" + ] + }, + "senderEmail": { + "type": [ + "string", + "null" + ] + }, + "senderName": { + "type": [ + "string", + "null" + ] + }, + "senderPhotoUrl": { + "type": [ + "string", + "null" + ] + }, + "snippet": { + "type": [ + "string", + "null" + ] + }, + "sortTs": { + "format": "int64", + "type": "integer" + }, + "updatedAt": { + "format": "int64", + "type": "integer" + }, + "viewedAt": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "id", + "ownerId", + "inboxVisible", + "isRead", + "isDraft", + "isImportant", + "sortTs", + "createdAt", + "updatedAt", + "attachments", + "macroAttachments", + "contacts", + "labels", + "metadata" + ], + "type": "object" + }, + "type": "array" + }, + "next_cursor": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "items" + ], + "title": "ApiPaginatedThreadCursor", + "type": "object" + } + }, + { + "name": "NameSearch", + "inputSchema": { + "additionalProperties": false, + "description": "Search for items by their name or title. For documents, this searches the document name. For emails, this searches the subject line. For chats, this searches the chat title. For projects (folders), this searches the project name. This tool finds items based on what they're called, not their content.", + "properties": { + "entityTypes": { + "additionalProperties": false, + "default": [], + "description": "Which types of items to search. Leave empty to search all types. Examples: ['documents'], ['emails', 'documents'], ['channels']", + "items": { + "additionalProperties": false, + "enum": [ + "documents", + "chats", + "emails", + "channels", + "projects" + ], + "type": "string" + }, + "type": "array" + }, + "name": { + "additionalProperties": false, + "description": "The name or title to search for. For emails, this is the subject line. For channels, this can be the channel name or participant names.", + "type": "string" + } + }, + "required": [ + "entityTypes", + "name" + ], + "title": "NameSearch", + "type": "object" + }, + "outputSchema": { + "properties": { + "results": { + "items": { + "oneOf": [ + { + "description": "DocumentSearchResponseItem object with document metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update document page record\nevery time the document updates (specifically for updated_at and viewed_at)", + "properties": { + "document_id": { + "description": "The id of the document", + "format": "uuid", "type": "string" }, - "user_id": { - "description": "The id of the creator of the chat", + "document_name": { + "description": "The name of the document", "type": "string" - } - }, - "required": [ - "type", - "id", - "name", - "owner_id", - "chat_id", - "user_id", - "chat_search_results" - ], - "type": "object" - }, - { - "description": "EmailSearchResponseItem object with email metadata we fetch from email service. we don't store these\ntimestamps in opensearch as they would require us to update each email message record for the thread\nevery time the thread updates (specifically for updated_at and viewed_at)", - "properties": { - "created_at": { - "format": "int64", - "type": "integer" }, - "email_message_search_results": { - "description": "The search results for the document\nThis may be empty if the search result match was on the email subject only", + "document_search_results": { + "description": "The search results for the document\nThis may be empty if the search result match was on the document name only", "items": { - "description": "A email message match for a given thread id", + "description": "A document match for a given node", "properties": { - "bcc": { - "description": "This is only present if the search result is on the message content", - "items": { - "type": "string" - }, - "type": "array" - }, - "cc": { - "description": "This is only present if the search result is on the message content", - "items": { - "type": "string" - }, - "type": "array" - }, "highlight": { - "description": "The highlights for the email message", + "description": "The highlights for the document", "properties": { "bcc": { "description": "The highlight match on the bcc (email only)", @@ -560,31 +585,19 @@ }, "type": "object" }, - "labels": { - "description": "This is only present if the search result is on the message content", - "items": { - "type": "string" - }, - "type": "array" - }, - "message_id": { - "description": "The email message id.\nThis is only present if the search result is on the message content", - "format": "uuid", + "node_id": { + "description": "The node id for the document.\nThis is only useful for markdown at the moment\nThis will only be provided if the match was on content", "type": [ "string", "null" ] }, - "pretty_sender": { - "description": "The pretty sender.\nIf the match is on the subject, the pretty sender is the latest sender on the thread.\nThis could be the sender's email if there is no contact name for the sender.", - "type": "string" - }, - "recipients": { - "description": "This is only present if the search result is on the message content", - "items": { - "type": "string" - }, - "type": "array" + "raw_content": { + "description": "The raw content of the document.\nThis is only included for markdown files and will be the raw json node of the match", + "type": [ + "string", + "null" + ] }, "score": { "description": "The score of the result", @@ -593,121 +606,131 @@ "number", "null" ] - }, - "sender": { - "description": "The sender.\nIf the match is on the subject, the sender is the latest sender on the thread.", - "type": "string" - }, - "sent_at": { - "description": "When the email message was sent\nThis is only present if the search result is on the message content", - "format": "int64", - "type": [ - "integer", - "null" - ] } }, "required": [ - "sender", - "pretty_sender", - "recipients", - "cc", - "bcc", - "labels", "highlight" ], "type": "object" }, "type": "array" }, - "id": { - "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", - "format": "uuid", - "type": "string" - }, - "name": { - "description": "Subject of the email thread", + "file_type": { + "description": "The file type of the document", "type": [ "string", "null" ] }, - "owner_id": { + "id": { + "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", + "format": "uuid", "type": "string" }, - "snippet": { - "type": [ - "string", - "null" - ] - }, - "subject": { - "description": "The subject of the email\nThis is only present if the search result is on the message content", + "metadata": { + "description": "Metadata from the database. None if the document doesn't exist in the database.", + "properties": { + "created_at": { + "format": "int64", + "type": "integer" + }, + "deleted_at": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "project_id": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "format": "int64", + "type": "integer" + }, + "viewed_at": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "created_at", + "updated_at" + ], "type": [ - "string", + "object", "null" ] }, - "thread_id": { - "description": "The id of the email thread", - "format": "uuid", + "name": { "type": "string" }, - "type": { - "const": "email", + "owner_id": { "type": "string" }, - "updated_at": { - "format": "int64", - "type": "integer" + "sub_type": { + "anyOf": [ + { + "description": "The document sub type enum represents all values of document sub types.\nThese values should match the `document_sub_type_value` table in macrodb.", + "oneOf": [ + { + "const": "task", + "description": "A task document", + "type": "string" + } + ] + }, + { + "type": "null" + } + ], + "description": "The sub type of the document if present." }, - "user_id": { - "description": "The id of the owner of the email thread", + "type": { + "const": "document", "type": "string" - }, - "viewed_at": { - "format": "int64", - "type": [ - "integer", - "null" - ] } }, "required": [ "type", - "created_at", - "updated_at", "id", + "name", "owner_id", - "thread_id", - "user_id", - "email_message_search_results" + "document_id", + "document_name", + "document_search_results" ], "type": "object" }, { - "description": "ChannelSearchResponseItem object with channel metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update each chat message record for the chat\nevery time the chat updates (specifically for updated_at and viewed_at and interacted_at)", + "description": "ChatSearchResponse object with channel metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update each chat message record for the chat\nevery time the chat updates (specifically for updated_at and viewed_at)", "properties": { - "channel_id": { - "description": "The id of the channel", + "chat_id": { + "description": "The id of the chat", "format": "uuid", "type": "string" }, - "channel_message_search_results": { - "description": "The search results for the channel\nThis may be empty if the search result match was not on content", + "chat_search_results": { + "description": "The search results for the chat\nThis may be empty if the search result match was on the chat title only", "items": { - "description": "A channel message match for a given channel id", + "description": "A chat match for a given message id", "properties": { - "created_at": { - "description": "When the channel message was created\nThis is only prsent if the search result is on the message content", - "format": "int64", + "chat_message_id": { + "description": "The chat message id for the chat\nThis is only present if the search match was on the chat message content", + "format": "uuid", "type": [ - "integer", + "string", "null" ] }, "highlight": { - "description": "The highlights for the channel message", + "description": "The highlights for the chat message", "properties": { "bcc": { "description": "The highlight match on the bcc (email only)", @@ -761,9 +784,8 @@ }, "type": "object" }, - "message_id": { - "description": "The channel message id\nThis is only prsent if the search result is on the message content", - "format": "uuid", + "role": { + "description": "The role of the chat message\nThis is only present if the search match was on the chat message content", "type": [ "string", "null" @@ -776,29 +798,6 @@ "number", "null" ] - }, - "sender_id": { - "description": "The sender id\nThis is only prsent if the search result is on the message content", - "type": [ - "string", - "null" - ] - }, - "thread_id": { - "description": "The channel message thread id", - "format": "uuid", - "type": [ - "string", - "null" - ] - }, - "updated_at": { - "description": "When the channel message was last updated\nThis is only prsent if the search result is on the message content", - "format": "int64", - "type": [ - "integer", - "null" - ] } }, "required": [ @@ -808,29 +807,31 @@ }, "type": "array" }, - "channel_type": { - "description": "The type of channel", - "type": "string" - }, "id": { "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", "format": "uuid", "type": "string" }, "metadata": { - "description": "Metadata from the database. None if the channel doesn't exist in the database.", + "description": "Metadata from the database. None if the chat doesn't exist in the database.", "properties": { "created_at": { "format": "int64", "type": "integer" }, - "interacted_at": { + "deleted_at": { "format": "int64", "type": [ "integer", "null" ] }, + "project_id": { + "type": [ + "string", + "null" + ] + }, "updated_at": { "format": "int64", "type": "integer" @@ -852,90 +853,261 @@ "null" ] }, + "name": { + "type": "string" + }, "owner_id": { - "description": "we don't store this for channels atm but keeping it here for consistency", - "type": [ - "string", - "null" - ] + "type": "string" }, "type": { - "const": "channel", + "const": "chat", + "type": "string" + }, + "user_id": { + "description": "The id of the creator of the chat", "type": "string" } }, "required": [ "type", "id", - "channel_type", - "channel_id", - "channel_message_search_results" + "name", + "owner_id", + "chat_id", + "user_id", + "chat_search_results" ], "type": "object" }, { - "description": "ProjectSearchResponseItem object with project metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update the project record\nevery time the project updates (specifically for updated_at and viewed_at)", + "description": "EmailSearchResponseItem object with email metadata we fetch from email service. we don't store these\ntimestamps in opensearch as they would require us to update each email message record for the thread\nevery time the thread updates (specifically for updated_at and viewed_at)", "properties": { "created_at": { "format": "int64", "type": "integer" }, + "email_message_search_results": { + "description": "The search results for the document\nThis may be empty if the search result match was on the email subject only", + "items": { + "description": "A email message match for a given thread id", + "properties": { + "bcc": { + "description": "This is only present if the search result is on the message content", + "items": { + "type": "string" + }, + "type": "array" + }, + "cc": { + "description": "This is only present if the search result is on the message content", + "items": { + "type": "string" + }, + "type": "array" + }, + "highlight": { + "description": "The highlights for the email message", + "properties": { + "bcc": { + "description": "The highlight match on the bcc (email only)", + "items": { + "type": "string" + }, + "type": "array" + }, + "cc": { + "description": "The highlight match on the cc (email only)", + "items": { + "type": "string" + }, + "type": "array" + }, + "content": { + "description": "The highlight match on the content field", + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "description": "The highlight match on the name field", + "type": [ + "string", + "null" + ] + }, + "recipients": { + "description": "The highlight match on the recipients (email only)", + "items": { + "type": "string" + }, + "type": "array" + }, + "sender": { + "description": "The highlight match on the sender (email only)", + "type": [ + "string", + "null" + ] + }, + "user_id": { + "description": "The highlight match on the user (owner) of the entity", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "labels": { + "description": "This is only present if the search result is on the message content", + "items": { + "type": "string" + }, + "type": "array" + }, + "message_id": { + "description": "The email message id.\nThis is only present if the search result is on the message content", + "format": "uuid", + "type": [ + "string", + "null" + ] + }, + "pretty_sender": { + "description": "The pretty sender.\nIf the match is on the subject, the pretty sender is the latest sender on the thread.\nThis could be the sender's email if there is no contact name for the sender.", + "type": "string" + }, + "recipients": { + "description": "This is only present if the search result is on the message content", + "items": { + "type": "string" + }, + "type": "array" + }, + "score": { + "description": "The score of the result", + "format": "double", + "type": [ + "number", + "null" + ] + }, + "sender": { + "description": "The sender.\nIf the match is on the subject, the sender is the latest sender on the thread.", + "type": "string" + }, + "sent_at": { + "description": "When the email message was sent\nThis is only present if the search result is on the message content", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "sender", + "pretty_sender", + "recipients", + "cc", + "bcc", + "labels", + "highlight" + ], + "type": "object" + }, + "type": "array" + }, "id": { "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", "format": "uuid", "type": "string" }, - "metadata": { - "description": "Metadata from the database. None if the project doesn't exist in the database.", - "properties": { - "created_at": { - "format": "int64", - "type": "integer" - }, - "deleted_at": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "parent_project_id": { - "type": [ - "string", - "null" - ] - }, - "updated_at": { - "format": "int64", - "type": "integer" - }, - "viewed_at": { - "format": "int64", - "type": [ - "integer", - "null" - ] - } - }, - "required": [ - "created_at", - "updated_at" - ], + "name": { + "description": "Subject of the email thread", + "type": [ + "string", + "null" + ] + }, + "owner_id": { + "type": "string" + }, + "snippet": { + "type": [ + "string", + "null" + ] + }, + "subject": { + "description": "The subject of the email\nThis is only present if the search result is on the message content", "type": [ - "object", + "string", "null" ] }, - "name": { + "thread_id": { + "description": "The id of the email thread", + "format": "uuid", "type": "string" }, - "owner_id": { + "type": { + "const": "email", "type": "string" }, - "project_search_results": { + "updated_at": { + "format": "int64", + "type": "integer" + }, + "user_id": { + "description": "The id of the owner of the email thread", + "type": "string" + }, + "viewed_at": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "type", + "created_at", + "updated_at", + "id", + "owner_id", + "thread_id", + "user_id", + "email_message_search_results" + ], + "type": "object" + }, + { + "description": "ChannelSearchResponseItem object with channel metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update each chat message record for the chat\nevery time the chat updates (specifically for updated_at and viewed_at and interacted_at)", + "properties": { + "channel_id": { + "description": "The id of the channel", + "format": "uuid", + "type": "string" + }, + "channel_message_search_results": { + "description": "The search results for the channel\nThis may be empty if the search result match was not on content", "items": { + "description": "A channel message match for a given channel id", "properties": { + "created_at": { + "description": "When the channel message was created\nThis is only prsent if the search result is on the message content", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, "highlight": { + "description": "The highlights for the channel message", "properties": { "bcc": { "description": "The highlight match on the bcc (email only)", @@ -989,6 +1161,14 @@ }, "type": "object" }, + "message_id": { + "description": "The channel message id\nThis is only prsent if the search result is on the message content", + "format": "uuid", + "type": [ + "string", + "null" + ] + }, "score": { "description": "The score of the result", "format": "double", @@ -996,458 +1176,264 @@ "number", "null" ] - } - }, - "required": [ - "highlight" - ], - "type": "object" - }, - "type": "array" - }, - "type": { - "const": "project", - "type": "string" - }, - "updated_at": { - "format": "int64", - "type": "integer" - } - }, - "required": [ - "type", - "id", - "name", - "owner_id", - "updated_at", - "created_at", - "project_search_results" - ], - "type": "object" - } - ] - }, - "type": "array" - } - }, - "required": [ - "results" - ], - "title": "SearchToolResponse", - "type": "object" - } - }, - { - "name": "ListEmails", - "inputSchema": { - "additionalProperties": false, - "description": "List the emails the user has access to. Use this tool to find all emails from / to a specific person. Prefer using the search tool to search on a specific matching string within the content or the name of the entity.", - "properties": { - "cursor": { - "additionalProperties": false, - "default": null, - "description": "A preview response will tell you what the next cursor id is.\n If expected emails are not in the current response and it gives you a cursor id, use this field to list the next page of emails. If no cursor id is provided you've reached the end", - "type": [ - "string", - "null" - ] - }, - "limit": { - "additionalProperties": false, - "default": 20, - "description": "limit the max emails returned. This defaults to 20 and has a maximum of 500", - "format": "int64", - "type": "integer" - }, - "sortMethod": { - "additionalProperties": false, - "default": "viewed_at", - "description": "Sort response by one of: viewed_at | updated_at | created_at | viewed_updated. Prefer viewed_at unless the otherwise specified", - "enum": [ - "viewed_at", - "updated_at", - "created_at", - "viewed_updated" - ], - "type": "string" - }, - "view": { - "additionalProperties": false, - "default": { - "standardLabel": null, - "userLabel": null - }, - "description": "Choose the view to list emails from. One of either standard label or user label must be specified, otherwise defaults to important.", - "properties": { - "standardLabel": { - "additionalProperties": false, - "description": "Prefer important or all. Leave empty if using a user label", - "enum": [ - "Inbox", - "Sent", - "Drafts", - "Starred", - "All", - "Important", - "Other", - null - ], - "type": [ - "string", - "null" - ] - }, - "userLabel": { - "additionalProperties": false, - "description": "Choose a custom user label to filter by (optional). Standard label will take precedence over user label if both are specified.", - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "standardLabel", - "userLabel" - ], - "type": "object" - } - }, - "required": [ - "cursor", - "limit", - "sortMethod", - "view" - ], - "title": "ListEmails", - "type": "object" - }, - "outputSchema": { - "properties": { - "items": { - "items": { - "properties": { - "attachments": { - "items": { - "properties": { - "contentId": { - "type": [ - "string", - "null" - ] - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "filename": { - "type": [ - "string", - "null" - ] - }, - "id": { - "format": "uuid", - "type": "string" - }, - "messageId": { - "format": "uuid", - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "providerAttachmentId": { - "type": [ - "string", - "null" - ] + }, + "sender_id": { + "description": "The sender id\nThis is only prsent if the search result is on the message content", + "type": [ + "string", + "null" + ] + }, + "thread_id": { + "description": "The channel message thread id", + "format": "uuid", + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "description": "When the channel message was last updated\nThis is only prsent if the search result is on the message content", + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "highlight" + ], + "type": "object" }, - "sizeBytes": { - "format": "int64", - "type": [ - "integer", - "null" - ] - } + "type": "array" }, - "required": [ - "id", - "messageId", - "createdAt" - ], - "type": "object" - }, - "type": "array" - }, - "contacts": { - "items": { - "properties": { - "emailAddress": { - "type": [ - "string", - "null" - ] - }, - "id": { - "format": "uuid", - "type": "string" - }, - "linkId": { - "format": "uuid", - "type": "string" - }, - "name": { - "type": [ - "string", - "null" - ] - }, - "sfsPhotoUrl": { - "type": [ - "string", - "null" - ] - } + "channel_type": { + "description": "The type of channel", + "type": "string" }, - "required": [ - "id", - "linkId" - ], - "type": "object" - }, - "type": "array" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "frecencyScore": { - "format": "double", - "type": [ - "number", - "null" - ] - }, - "id": { - "format": "uuid", - "type": "string" - }, - "inboxVisible": { - "type": "boolean" - }, - "isDraft": { - "type": "boolean" - }, - "isImportant": { - "type": "boolean" - }, - "isRead": { - "type": "boolean" - }, - "labels": { - "items": { - "properties": { - "createdAt": { - "format": "date-time", - "type": "string" - }, - "id": { - "format": "uuid", - "type": "string" - }, - "labelListVisibility": { - "enum": [ - "LabelShow", - "LabelShowIfUnread", - "LabelHide" - ], - "type": "string" - }, - "linkId": { - "format": "uuid", - "type": "string" - }, - "messageListVisibility": { - "enum": [ - "Show", - "Hide" - ], - "type": "string" - }, - "name": { - "type": "string" - }, - "providerLabelId": { - "type": "string" + "id": { + "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", + "format": "uuid", + "type": "string" + }, + "metadata": { + "description": "Metadata from the database. None if the channel doesn't exist in the database.", + "properties": { + "created_at": { + "format": "int64", + "type": "integer" + }, + "interacted_at": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "updated_at": { + "format": "int64", + "type": "integer" + }, + "viewed_at": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } }, - "type": { - "enum": [ - "System", - "User" - ], - "type": "string" - } + "required": [ + "created_at", + "updated_at" + ], + "type": [ + "object", + "null" + ] }, - "required": [ - "id", - "linkId", - "providerLabelId", - "name", - "createdAt", - "messageListVisibility", - "labelListVisibility", - "type" - ], - "type": "object" + "owner_id": { + "description": "we don't store this for channels atm but keeping it here for consistency", + "type": [ + "string", + "null" + ] + }, + "type": { + "const": "channel", + "type": "string" + } }, - "type": "array" + "required": [ + "type", + "id", + "channel_type", + "channel_id", + "channel_message_search_results" + ], + "type": "object" }, - "macroAttachments": { - "items": { - "properties": { - "dbId": { - "format": "uuid", - "type": "string" - }, - "itemId": { - "format": "uuid", - "type": "string" - }, - "itemType": { - "type": "string" + { + "description": "ProjectSearchResponseItem object with project metadata we fetch from macrodb. we don't store these\ntimestamps in opensearch as they would require us to update the project record\nevery time the project updates (specifically for updated_at and viewed_at)", + "properties": { + "created_at": { + "format": "int64", + "type": "integer" + }, + "id": { + "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model.", + "format": "uuid", + "type": "string" + }, + "metadata": { + "description": "Metadata from the database. None if the project doesn't exist in the database.", + "properties": { + "created_at": { + "format": "int64", + "type": "integer" + }, + "deleted_at": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "parent_project_id": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "format": "int64", + "type": "integer" + }, + "viewed_at": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } }, - "messageId": { - "format": "uuid", - "type": "string" - } + "required": [ + "created_at", + "updated_at" + ], + "type": [ + "object", + "null" + ] }, - "required": [ - "dbId", - "messageId", - "itemId", - "itemType" - ], - "type": "object" - }, - "type": "array" - }, - "metadata": { - "properties": { - "calendar_invite": { - "description": "if any email contains a calendar invite", - "type": "boolean" + "name": { + "type": "string" }, - "generic_sender": { - "description": "if any sender in the thread is a generic email", - "type": "boolean" + "owner_id": { + "type": "string" }, - "known_sender": { - "description": "if user has previously emailed any sender in the thread", - "type": "boolean" + "project_search_results": { + "items": { + "properties": { + "highlight": { + "properties": { + "bcc": { + "description": "The highlight match on the bcc (email only)", + "items": { + "type": "string" + }, + "type": "array" + }, + "cc": { + "description": "The highlight match on the cc (email only)", + "items": { + "type": "string" + }, + "type": "array" + }, + "content": { + "description": "The highlight match on the content field", + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "description": "The highlight match on the name field", + "type": [ + "string", + "null" + ] + }, + "recipients": { + "description": "The highlight match on the recipients (email only)", + "items": { + "type": "string" + }, + "type": "array" + }, + "sender": { + "description": "The highlight match on the sender (email only)", + "type": [ + "string", + "null" + ] + }, + "user_id": { + "description": "The highlight match on the user (owner) of the entity", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "score": { + "description": "The score of the result", + "format": "double", + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "highlight" + ], + "type": "object" + }, + "type": "array" }, - "tabular": { - "description": "if any email contains a
html tag", - "type": "boolean" + "type": { + "const": "project", + "type": "string" + }, + "updated_at": { + "format": "int64", + "type": "integer" } }, "required": [ - "known_sender", - "tabular", - "calendar_invite", - "generic_sender" + "type", + "id", + "name", + "owner_id", + "updated_at", + "created_at", + "project_search_results" ], "type": "object" - }, - "name": { - "type": [ - "string", - "null" - ] - }, - "ownerId": { - "type": "string" - }, - "providerId": { - "type": [ - "string", - "null" - ] - }, - "senderEmail": { - "type": [ - "string", - "null" - ] - }, - "senderName": { - "type": [ - "string", - "null" - ] - }, - "senderPhotoUrl": { - "type": [ - "string", - "null" - ] - }, - "snippet": { - "type": [ - "string", - "null" - ] - }, - "sortTs": { - "format": "int64", - "type": "integer" - }, - "updatedAt": { - "format": "int64", - "type": "integer" - }, - "viewedAt": { - "format": "int64", - "type": [ - "integer", - "null" - ] } - }, - "required": [ - "id", - "ownerId", - "inboxVisible", - "isRead", - "isDraft", - "isImportant", - "sortTs", - "createdAt", - "updatedAt", - "attachments", - "macroAttachments", - "contacts", - "labels", - "metadata" - ], - "type": "object" + ] }, "type": "array" - }, - "next_cursor": { - "type": [ - "string", - "null" - ] } }, "required": [ - "items" + "results" ], - "title": "ApiPaginatedThreadCursor", + "title": "SearchToolResponse", "type": "object" } }, @@ -1714,10 +1700,10 @@ } }, { - "name": "NameSearch", + "name": "ContentSearch", "inputSchema": { "additionalProperties": false, - "description": "Search for items by their name or title. For documents, this searches the document name. For emails, this searches the subject line. For chats, this searches the chat title. For projects (folders), this searches the project name. This tool finds items based on what they're called, not their content.", + "description": "Search for items by their content. For documents, this searches the document body text. For emails, this searches the email message body. For chats, this searches the message content. This tool finds items based on what's inside them, not their titles or names.", "properties": { "entityTypes": { "additionalProperties": false, @@ -1736,17 +1722,17 @@ }, "type": "array" }, - "name": { + "query": { "additionalProperties": false, - "description": "The name or title to search for. For emails, this is the subject line. For channels, this can be the channel name or participant names.", + "description": "The text content to search for. This searches within the body of documents, emails, and messages.", "type": "string" } }, "required": [ "entityTypes", - "name" + "query" ], - "title": "NameSearch", + "title": "ContentSearch", "type": "object" }, "outputSchema": { @@ -2838,60 +2824,74 @@ } }, { - "name": "MarkdownRewrite", + "name": "ListChannels", "inputSchema": { "additionalProperties": false, - "description": "Instruct an agent to edit a markdown file identified by an id.\n This tool should be used when the user include a markdown file in context and requests a revision or edit to that file", + "description": "List all chat channels the user has access to. Returns channel names, IDs, and types. Use this tool to discover available channels for reading messages or participating in conversations.", "properties": { - "instructions": { - "additionalProperties": false, - "description": "Instructions for the revision agent to follow to edit the markdown. These instructions will be provided by the user.", - "type": "string" - }, - "markdown_file_id": { + "unused": { "additionalProperties": false, - "description": "The markdown file id to target for editing. This file id will be in your context and labeled as a markdown (md) document", - "type": "string" + "default": null, + "type": "null" } }, "required": [ - "instructions", - "markdown_file_id" + "unused" ], - "title": "MarkdownRewrite", + "title": "ListChannels", "type": "object" }, "outputSchema": { - "additionalProperties": false, + "description": "Response from listing channels", "properties": { - "diffs": { + "channels": { + "description": "The list of channels the user has access to", "items": { - "additionalProperties": false, + "description": "A channel item returned by the list channels tool", "properties": { - "markdown_text": { + "channelType": { + "description": "The type of channel (public, private, organization, or direct_message)", + "enum": [ + "public", + "organization", + "private", + "direct_message" + ], "type": "string" }, - "node_key": { + "id": { + "description": "The channel's unique identifier", + "format": "uuid", "type": "string" }, - "operation": { - "type": "string" + "name": { + "description": "The channel's display name (may be None for direct messages)", + "type": [ + "string", + "null" + ] } }, "required": [ - "operation", - "node_key", - "markdown_text" + "id", + "channelType" ], "type": "object" }, "type": "array" + }, + "total": { + "description": "Total number of channels returned", + "format": "uint", + "minimum": 0, + "type": "integer" } }, "required": [ - "diffs" + "channels", + "total" ], - "title": "AIDiffResponse", + "title": "ListChannelsResponse", "type": "object" } }, @@ -2995,10 +2995,237 @@ "description": "Content of a web fetch response - either a successful result or an error", "oneOf": [ { - "$ref": "#/$defs/WebFetchResult", + "$ref": "#/$defs/WebFetchResult", + "properties": { + "type": { + "const": "web_fetch_result", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + { + "$ref": "#/$defs/WebFetchToolError", + "properties": { + "type": { + "const": "web_fetch_tool_result_error", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + } + ] + }, + "WebFetchDocument": { + "description": "Document content from a web fetch", + "properties": { + "citations": { + "anyOf": [ + { + "$ref": "#/$defs/CitationsConfig" + }, + { + "type": "null" + } + ], + "description": "Optional citations configuration" + }, + "source": { + "$ref": "#/$defs/WebFetchSource", + "description": "The source content (text or base64)" + }, + "title": { + "description": "Optional title of the document", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "source" + ], + "type": "object" + }, + "WebFetchErrorCode": { + "description": "Possible error codes for web fetch failures", + "oneOf": [ + { + "const": "invalid_input", + "description": "Invalid URL format", + "type": "string" + }, + { + "const": "url_too_long", + "description": "URL exceeds maximum length (250 characters)", + "type": "string" + }, + { + "const": "url_not_allowed", + "description": "URL blocked by domain filtering rules and model restrictions", + "type": "string" + }, + { + "const": "url_not_accessible", + "description": "Failed to fetch content (HTTP error)", + "type": "string" + }, + { + "const": "too_many_requests", + "description": "Rate limit exceeded", + "type": "string" + }, + { + "const": "unsupported_content_type", + "description": "Content type not supported (only text and PDF)", + "type": "string" + }, + { + "const": "max_uses_exceeded", + "description": "Maximum web fetch tool uses exceeded", + "type": "string" + }, + { + "const": "unavailable", + "description": "An internal error occurred", + "type": "string" + } + ] + }, + "WebFetchResult": { + "description": "Successful web fetch result containing the fetched content", + "properties": { + "content": { + "$ref": "#/$defs/WebFetchDocument", + "description": "The fetched document content" + }, + "retrieved_at": { + "description": "Timestamp when the content was retrieved", + "type": "string" + }, + "url": { + "description": "The URL that was fetched", + "type": "string" + } + }, + "required": [ + "url", + "content", + "retrieved_at" + ], + "type": "object" + }, + "WebFetchSource": { + "description": "Source content of a fetched document", + "oneOf": [ + { + "description": "Plain text content", + "properties": { + "data": { + "type": "string" + }, + "media_type": { + "type": "string" + }, + "type": { + "const": "text", + "type": "string" + } + }, + "required": [ + "type", + "media_type", + "data" + ], + "type": "object" + }, + { + "description": "Base64-encoded content (e.g., PDF)", + "properties": { + "data": { + "type": "string" + }, + "media_type": { + "type": "string" + }, + "type": { + "const": "base64", + "type": "string" + } + }, + "required": [ + "type", + "media_type", + "data" + ], + "type": "object" + } + ] + }, + "WebFetchToolError": { + "description": "Error returned when web fetch fails", + "properties": { + "error_code": { + "$ref": "#/$defs/WebFetchErrorCode" + } + }, + "required": [ + "error_code" + ], + "type": "object" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Web fetch tool response content returned by Claude when using the web_fetch tool", + "properties": { + "content": { + "$ref": "#/$defs/WebFetchContent" + }, + "tool_use_id": { + "type": "string" + } + }, + "required": [ + "tool_use_id", + "content" + ], + "title": "WebFetchResponse", + "type": "object" + } + }, + { + "name": "bash_code_execution", + "inputSchema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "The expected shape of the streamed JSON following a `server_tool_use` for bash_code_execution", + "properties": { + "command": { + "description": "The bash command to execute", + "type": "string" + } + }, + "required": [ + "command" + ], + "title": "BashCodeExecutionToolCall", + "type": "object" + }, + "outputSchema": { + "$defs": { + "BashCodeExecutionContent": { + "description": "Content of a bash code execution response - either a result or an error", + "oneOf": [ + { + "$ref": "#/$defs/BashCodeExecutionResult", "properties": { "type": { - "const": "web_fetch_result", + "const": "bash_code_execution_result", "type": "string" } }, @@ -3008,10 +3235,10 @@ "type": "object" }, { - "$ref": "#/$defs/WebFetchToolError", + "$ref": "#/$defs/BashCodeExecutionToolError", "properties": { "type": { - "const": "web_fetch_tool_result_error", + "const": "bash_code_execution_tool_result_error", "type": "string" } }, @@ -3022,157 +3249,371 @@ } ] }, - "WebFetchDocument": { - "description": "Document content from a web fetch", + "BashCodeExecutionResult": { + "description": "Successful bash code execution result", "properties": { - "citations": { - "anyOf": [ - { - "$ref": "#/$defs/CitationsConfig" - }, - { - "type": "null" - } - ], - "description": "Optional citations configuration" - }, - "source": { - "$ref": "#/$defs/WebFetchSource", - "description": "The source content (text or base64)" - }, - "title": { - "description": "Optional title of the document", + "content": { + "description": "Files generated during execution", + "items": { + "$ref": "#/$defs/CodeExecutionFile" + }, "type": [ - "string", + "array", "null" ] + }, + "return_code": { + "description": "Exit code (0 for success, non-zero for failure)", + "format": "int32", + "type": "integer" + }, + "stderr": { + "description": "Standard error from the command", + "type": "string" + }, + "stdout": { + "description": "Standard output from the command", + "type": "string" } }, "required": [ - "source" + "stdout", + "stderr", + "return_code" ], "type": "object" }, - "WebFetchErrorCode": { - "description": "Possible error codes for web fetch failures", + "BashCodeExecutionToolError": { + "description": "Error from bash code execution", + "properties": { + "error_code": { + "$ref": "#/$defs/CodeExecutionErrorCode" + } + }, + "required": [ + "error_code" + ], + "type": "object" + }, + "CodeExecutionErrorCode": { + "description": "Error codes for code execution failures", "oneOf": [ { - "const": "invalid_input", - "description": "Invalid URL format", + "const": "unavailable", + "description": "The tool is temporarily unavailable", "type": "string" }, { - "const": "url_too_long", - "description": "URL exceeds maximum length (250 characters)", + "const": "execution_time_exceeded", + "description": "Execution exceeded maximum time limit", "type": "string" }, { - "const": "url_not_allowed", - "description": "URL blocked by domain filtering rules and model restrictions", + "const": "container_expired", + "description": "Container expired and is no longer available", "type": "string" }, { - "const": "url_not_accessible", - "description": "Failed to fetch content (HTTP error)", + "const": "invalid_tool_input", + "description": "Invalid parameters provided to the tool", "type": "string" }, { "const": "too_many_requests", - "description": "Rate limit exceeded", - "type": "string" - }, - { - "const": "unsupported_content_type", - "description": "Content type not supported (only text and PDF)", + "description": "Rate limit exceeded for tool usage", "type": "string" }, { - "const": "max_uses_exceeded", - "description": "Maximum web fetch tool uses exceeded", + "const": "file_not_found", + "description": "File doesn't exist (for view/edit operations) - text_editor only", "type": "string" }, { - "const": "unavailable", - "description": "An internal error occurred", + "const": "string_not_found", + "description": "The old_str not found in file (for str_replace) - text_editor only", "type": "string" } ] }, - "WebFetchResult": { - "description": "Successful web fetch result containing the fetched content", + "CodeExecutionFile": { + "description": "A file generated during code execution", "properties": { - "content": { - "$ref": "#/$defs/WebFetchDocument", - "description": "The fetched document content" - }, - "retrieved_at": { - "description": "Timestamp when the content was retrieved", - "type": "string" - }, - "url": { - "description": "The URL that was fetched", + "file_id": { + "description": "File ID for retrieval via Files API", "type": "string" } }, "required": [ - "url", - "content", - "retrieved_at" + "file_id" ], "type": "object" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Response from bash_code_execution tool", + "properties": { + "content": { + "$ref": "#/$defs/BashCodeExecutionContent" }, - "WebFetchSource": { - "description": "Source content of a fetched document", + "tool_use_id": { + "type": "string" + } + }, + "required": [ + "tool_use_id", + "content" + ], + "title": "BashCodeExecutionResponse", + "type": "object" + } + }, + { + "name": "text_editor_code_execution", + "inputSchema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "The expected shape of the streamed JSON following a `server_tool_use` for text_editor_code_execution", + "properties": { + "command": { + "description": "The command to execute: \"view\", \"create\", or \"str_replace\"", + "type": "string" + }, + "file_text": { + "description": "File content for create operations", + "type": [ + "string", + "null" + ] + }, + "new_str": { + "description": "Replacement string for str_replace operations", + "type": [ + "string", + "null" + ] + }, + "old_str": { + "description": "String to find for str_replace operations", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Path to the file", + "type": "string" + } + }, + "required": [ + "command", + "path" + ], + "title": "TextEditorCodeExecutionToolCall", + "type": "object" + }, + "outputSchema": { + "$defs": { + "CodeExecutionErrorCode": { + "description": "Error codes for code execution failures", "oneOf": [ { - "description": "Plain text content", + "const": "unavailable", + "description": "The tool is temporarily unavailable", + "type": "string" + }, + { + "const": "execution_time_exceeded", + "description": "Execution exceeded maximum time limit", + "type": "string" + }, + { + "const": "container_expired", + "description": "Container expired and is no longer available", + "type": "string" + }, + { + "const": "invalid_tool_input", + "description": "Invalid parameters provided to the tool", + "type": "string" + }, + { + "const": "too_many_requests", + "description": "Rate limit exceeded for tool usage", + "type": "string" + }, + { + "const": "file_not_found", + "description": "File doesn't exist (for view/edit operations) - text_editor only", + "type": "string" + }, + { + "const": "string_not_found", + "description": "The old_str not found in file (for str_replace) - text_editor only", + "type": "string" + } + ] + }, + "TextEditorCodeExecutionContent": { + "description": "Content of a text editor code execution response - either a result or an error", + "oneOf": [ + { + "$ref": "#/$defs/TextEditorCodeExecutionResult", "properties": { - "data": { - "type": "string" - }, - "media_type": { - "type": "string" - }, "type": { - "const": "text", + "const": "text_editor_code_execution_view_result", "type": "string" } }, "required": [ - "type", - "media_type", - "data" + "type" ], "type": "object" }, { - "description": "Base64-encoded content (e.g., PDF)", + "$ref": "#/$defs/TextEditorCodeExecutionResult", "properties": { - "data": { + "type": { + "const": "text_editor_code_execution_create_result", "type": "string" - }, - "media_type": { + } + }, + "required": [ + "type" + ], + "type": "object" + }, + { + "$ref": "#/$defs/TextEditorCodeExecutionResult", + "properties": { + "type": { + "const": "text_editor_code_execution_str_replace_result", "type": "string" - }, + } + }, + "required": [ + "type" + ], + "type": "object" + }, + { + "$ref": "#/$defs/TextEditorCodeExecutionToolError", + "properties": { "type": { - "const": "base64", + "const": "text_editor_code_execution_tool_result_error", "type": "string" } }, "required": [ - "type", - "media_type", - "data" + "type" ], "type": "object" } ] }, - "WebFetchToolError": { - "description": "Error returned when web fetch fails", + "TextEditorCodeExecutionResult": { + "description": "Result from text editor operations (view, create, str_replace)\nFields are optional as different operations return different fields", + "properties": { + "content": { + "description": "File content (for view operations)", + "type": [ + "string", + "null" + ] + }, + "file_type": { + "description": "Type of file (e.g., \"text\")", + "type": [ + "string", + "null" + ] + }, + "is_file_update": { + "description": "Whether the file already existed (true = update, false = create)", + "type": [ + "boolean", + "null" + ] + }, + "lines": { + "description": "Diff lines (prefixed with +/-)", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "newLines": { + "description": "Number of new lines", + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "newStart": { + "description": "New content start line", + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "numLines": { + "description": "Number of lines returned", + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "oldLines": { + "description": "Number of old lines changed", + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "oldStart": { + "description": "Old content start line", + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "startLine": { + "description": "Starting line number", + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "totalLines": { + "description": "Total lines in file", + "format": "uint32", + "minimum": 0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "TextEditorCodeExecutionToolError": { + "description": "Error from text editor code execution", "properties": { "error_code": { - "$ref": "#/$defs/WebFetchErrorCode" + "$ref": "#/$defs/CodeExecutionErrorCode" } }, "required": [ @@ -3182,10 +3623,10 @@ } }, "$schema": "https://json-schema.org/draft/2020-12/schema", - "description": "Web fetch tool response content returned by Claude when using the web_fetch tool", + "description": "Response from text_editor_code_execution tool", "properties": { "content": { - "$ref": "#/$defs/WebFetchContent" + "$ref": "#/$defs/TextEditorCodeExecutionContent" }, "tool_use_id": { "type": "string" @@ -3195,7 +3636,7 @@ "tool_use_id", "content" ], - "title": "WebFetchResponse", + "title": "TextEditorCodeExecutionResponse", "type": "object" } }