From 36895d7527a46030cbaec1e3ebf278b0739377ca Mon Sep 17 00:00:00 2001 From: Leonardo Anders Date: Mon, 27 Oct 2025 21:49:15 -0300 Subject: [PATCH 1/2] Add `Consistem: Create Item` workflow integrated with IRIS `/namespaces/{ns}/createItem` --- package.json | 11 + src/ccs/commands/createItem.ts | 252 ++++++++++++++++++ src/ccs/config/schema.md | 2 +- src/ccs/config/settings.ts | 2 +- src/ccs/core/types.ts | 11 + src/ccs/index.ts | 1 + .../sourcecontrol/clients/createItemClient.ts | 58 ++++ src/ccs/sourcecontrol/routes.ts | 1 + src/extension.ts | 5 + 9 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 src/ccs/commands/createItem.ts create mode 100644 src/ccs/sourcecontrol/clients/createItemClient.ts diff --git a/package.json b/package.json index 9af2e3d0..7a940024 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "workspaceContains:**/*.csp", "workspaceContains:**/*.csr", "onCommand:vscode-objectscript.explorer.open", + "onCommand:vscode-objectscript.ccs.createItem", "onLanguage:objectscript", "onLanguage:objectscript-int", "onLanguage:objectscript-class", @@ -651,6 +652,11 @@ } ], "file/newFile": [ + { + "command": "vscode-objectscript.ccs.createItem", + "when": "workspaceFolderCount != 0", + "group": "file" + }, { "command": "vscode-objectscript.newFile.kpi", "when": "workspaceFolderCount != 0", @@ -883,6 +889,11 @@ "command": "vscode-objectscript.ccs.followSourceAnalysisLink", "title": "Follow Source Analysis Link" }, + { + "category": "CCS", + "command": "vscode-objectscript.ccs.createItem", + "title": "Create Item" + }, { "category": "ObjectScript", "command": "vscode-objectscript.compile", diff --git a/src/ccs/commands/createItem.ts b/src/ccs/commands/createItem.ts new file mode 100644 index 00000000..c5904bb8 --- /dev/null +++ b/src/ccs/commands/createItem.ts @@ -0,0 +1,252 @@ +import * as vscode from "vscode"; +import * as path from "path"; + +import { AtelierAPI } from "../../api"; +import { getWsFolder, handleError } from "../../utils"; +import { logDebug, logError, logInfo } from "../core/logging"; +import { SourceControlApi } from "../sourcecontrol/client"; +import { CreateItemClient } from "../sourcecontrol/clients/createItemClient"; +import { getCcsSettings } from "../config/settings"; + +async function promptForItemName(): Promise { + const hasValidExt = (s: string) => /\.cls$/i.test(s) || /\.mac$/i.test(s); + const hasBadChars = (s: string) => /[\\/]/.test(s) || /\s/.test(s); + + const ib = vscode.window.createInputBox(); + ib.title = "Create InterSystems Item"; + ib.prompt = "Enter the name of the class or routine to create (.cls or .mac)"; + ib.placeholder = "MyPackage.MyClass.cls or CCTRIB001.mac"; + ib.ignoreFocusOut = true; + + return await new Promise((resolve) => { + const disposeAll = () => { + ib.dispose(); + d1.dispose(); + d2.dispose(); + d3.dispose(); + }; + + // Do not show an error while typing (silent mode) + const d1 = ib.onDidChangeValue(() => { + ib.validationMessage = undefined; + }); + + // When pressing Enter, validate EVERYTHING and highlight in red if invalid + const d2 = ib.onDidAccept(() => { + const name = ib.value.trim(); + + if (!name) { + ib.validationMessage = { message: "Item name is required", severity: vscode.InputBoxValidationSeverity.Error }; + return; + } + if (hasBadChars(name)) { + ib.validationMessage = { + message: "Invalid name: avoid spaces and path separators", + severity: vscode.InputBoxValidationSeverity.Error, + }; + return; + } + if (!hasValidExt(name)) { + ib.validationMessage = { + message: "Please include a valid extension: .cls or .mac", + severity: vscode.InputBoxValidationSeverity.Error, + }; + return; + } + + resolve(name); + disposeAll(); + }); + + const d3 = ib.onDidHide(() => { + resolve(undefined); + disposeAll(); + }); + + ib.show(); + }); +} + +function ensureWorkspaceConnection(folder: vscode.WorkspaceFolder): AtelierAPI | undefined { + const api = new AtelierAPI(folder.uri); + if (!api.active) { + void vscode.window.showErrorMessage("Workspace folder is not connected to an InterSystems server."); + return undefined; + } + + const { host, port } = api.config; + if (!host || !port || !api.ns) { + void vscode.window.showErrorMessage( + "Workspace folder does not have a fully configured InterSystems server connection." + ); + return undefined; + } + + return api; +} + +async function openCreatedFile(filePath: string): Promise { + // Ensure file exists before opening to avoid noisy errors + const uri = vscode.Uri.file(filePath); + await vscode.workspace.fs.stat(uri); + const document = await vscode.workspace.openTextDocument(uri); + await vscode.window.showTextDocument(document, { preview: false }); +} + +function extractModuleName(filePath: string, ws: vscode.WorkspaceFolder): string | undefined { + const rel = path.relative(ws.uri.fsPath, filePath); + if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return undefined; + + const parts = rel.split(path.sep).filter(Boolean); + // Drop filename + parts.pop(); + if (!parts.length) return undefined; + + // Ignore common code folders + const ignored = new Set(["src", "classes", "classescls", "mac", "int", "inc", "cls", "udl"]); + for (let i = parts.length - 1; i >= 0; i--) { + const seg = parts[i]; + if (!seg) continue; + if (seg.endsWith(":")) continue; // Windows drive guard (e.g., "C:") + if (ignored.has(seg.toLowerCase())) continue; + return seg; + } + return undefined; +} + +function isTimeoutError(err: unknown): boolean { + return typeof err === "object" && err !== null && (err as any).code === "ECONNABORTED"; +} + +async function withTimeoutRetry(fn: () => Promise, attempts = 2, delayMs = 300): Promise { + try { + return await fn(); + } catch (e) { + if (!isTimeoutError(e) || attempts <= 0) throw e; + await new Promise((r) => setTimeout(r, delayMs)); + return withTimeoutRetry(fn, attempts - 1, delayMs); + } +} + +function getErrorMessage(err: unknown): string | undefined { + // Try to extract a meaningful message without hard axios dependency + const anyErr = err as any; + if (anyErr?.response?.data) { + const d = anyErr.response.data; + if (typeof d === "string" && d.trim()) return d.trim(); + if (typeof d?.error === "string" && d.error.trim()) return d.error.trim(); + if (typeof d?.message === "string" && d.message.trim()) return d.message.trim(); + if (typeof d?.Message === "string" && d.Message.trim()) return d.Message.trim(); + } + if (typeof anyErr?.message === "string" && anyErr.message.trim()) return anyErr.message.trim(); + return undefined; +} + +export async function createItem(): Promise { + const workspaceFolder = await getWsFolder( + "Pick the workspace folder where you want to create the item", + false, + false, + false, + true + ); + + if (workspaceFolder === undefined) { + void vscode.window.showErrorMessage("No workspace folders are open."); + return; + } + if (!workspaceFolder) { + return; + } + + const api = ensureWorkspaceConnection(workspaceFolder); + if (!api) { + return; + } + + const ns = api.ns; + if (!ns) { + void vscode.window.showErrorMessage("Unable to determine active namespace for this workspace."); + return; + } + const namespace = ns.toUpperCase(); + + const itemName = await promptForItemName(); + if (!itemName) { + return; + } + + logDebug("CCS createItem invoked", { namespace, itemName }); + + let sourceControlApi: SourceControlApi; + try { + sourceControlApi = SourceControlApi.fromAtelierApi(api); + } catch (error) { + handleError(error, "Failed to connect to the InterSystems SourceControl API."); + return; + } + + const createItemClient = new CreateItemClient(sourceControlApi); + + // Use configured requestTimeout to scale retry backoff (10%, clamped 150–500ms) + const { requestTimeout } = getCcsSettings(); + const backoff = Math.min(500, Math.max(150, Math.floor(requestTimeout * 0.1))); + + try { + const { data, status } = await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Creating item...", + cancellable: false, + }, + async () => withTimeoutRetry(() => createItemClient.create(namespace, itemName), 2, backoff) + ); + + if (data.error) { + logError("CCS createItem failed", { namespace, itemName, status, error: data.error }); + void vscode.window.showErrorMessage(data.error); + return; + } + + if (status < 200 || status >= 300) { + const message = `Item creation failed with status ${status}.`; + logError("CCS createItem failed", { namespace, itemName, status }); + void vscode.window.showErrorMessage(message); + return; + } + + if (!data.file) { + const message = "Item created on server but no file path was returned."; + logError("CCS createItem missing file path", { namespace, itemName, response: data }); + void vscode.window.showErrorMessage(message); + return; + } + + try { + await openCreatedFile(data.file); + } catch (openErr) { + logError("Failed to open created file", { file: data.file, error: openErr }); + void vscode.window.showWarningMessage("Item created, but the returned file could not be opened."); + } + + const createdNamespace = data.namespace ?? namespace; + const createdItem = (data as any).itemIdCriado ?? itemName; + const moduleName = extractModuleName(data.file, workspaceFolder); + const location = moduleName ? `${createdNamespace}/${moduleName}` : createdNamespace; + const successMessage = `Item created successfully in ${location}: ${createdItem}`; + logInfo("CCS createItem succeeded", { + namespace: createdNamespace, + module: moduleName, + itemName: createdItem, + file: data.file, + }); + void vscode.window.showInformationMessage(successMessage); + } catch (error) { + const errorMessage = + (CreateItemClient as any).getErrorMessage?.(error) ?? + getErrorMessage(error) ?? + (isTimeoutError(error) ? "Item creation timed out." : "Item creation failed."); + logError("CCS createItem encountered an unexpected error", error); + void vscode.window.showErrorMessage(errorMessage); + } +} diff --git a/src/ccs/config/schema.md b/src/ccs/config/schema.md index 34d7f689..a7ef02bb 100644 --- a/src/ccs/config/schema.md +++ b/src/ccs/config/schema.md @@ -6,7 +6,7 @@ para o fork da Consistem. | Chave | Tipo | Padrão | Descrição | | ---------------- | ------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------- | | `endpoint` | `string` | `undefined` | URL base alternativa para a API. Se não definida, a URL é derivada da conexão ativa do Atelier. | -| `requestTimeout` | `number` | `500` | Tempo limite (ms) aplicado às chamadas HTTP do módulo. Valores menores ou inválidos são normalizados para zero. | +| `requestTimeout` | `number` | `5000` | Tempo limite (ms) aplicado às chamadas HTTP do módulo. Valores menores ou inválidos são normalizados para zero. | | `debugLogging` | `boolean` | `false` | Quando verdadeiro, registra mensagens detalhadas no `ObjectScript` Output Channel. | | `flags` | `Record` | `{}` | Feature flags opcionais que podem ser lidas pelas features do módulo. | diff --git a/src/ccs/config/settings.ts b/src/ccs/config/settings.ts index e641142e..4160f62e 100644 --- a/src/ccs/config/settings.ts +++ b/src/ccs/config/settings.ts @@ -8,7 +8,7 @@ export interface CcsSettings { } const CCS_CONFIGURATION_SECTION = "objectscript.ccs"; -const DEFAULT_TIMEOUT = 500; +const DEFAULT_TIMEOUT = 5000; export function getCcsSettings(): CcsSettings { const configuration = vscode.workspace.getConfiguration(CCS_CONFIGURATION_SECTION); diff --git a/src/ccs/core/types.ts b/src/ccs/core/types.ts index 707eb8ba..20e19b92 100644 --- a/src/ccs/core/types.ts +++ b/src/ccs/core/types.ts @@ -19,3 +19,14 @@ export interface GlobalDocumentationResponse { content?: string | string[] | Record | null; message?: string; } + +export interface CreateItemResponse { + item?: Record; + name?: string; + documentName?: string; + namespace?: string; + module?: string; + message?: string; + path?: string; + uri?: string; +} diff --git a/src/ccs/index.ts b/src/ccs/index.ts index b363194d..6a3a8cc6 100644 --- a/src/ccs/index.ts +++ b/src/ccs/index.ts @@ -26,3 +26,4 @@ export { followSourceAnalysisLink, followSourceAnalysisLinkCommand, } from "./providers/SourceAnalysisLinkProvider"; +export { createItem } from "./commands/createItem"; diff --git a/src/ccs/sourcecontrol/clients/createItemClient.ts b/src/ccs/sourcecontrol/clients/createItemClient.ts new file mode 100644 index 00000000..1624ea5c --- /dev/null +++ b/src/ccs/sourcecontrol/clients/createItemClient.ts @@ -0,0 +1,58 @@ +import axios from "axios"; +import { SourceControlApi } from "../client"; +import { ROUTES } from "../routes"; + +export interface CreateItemRequestBody { + itemName: string; +} + +export interface CreateItemResponse { + namespace?: string; + itemIdCriado?: string; + file?: string; + error?: string; +} + +export interface CreateItemResult { + status: number; + data: CreateItemResponse; +} + +export class CreateItemClient { + public constructor(private readonly api: SourceControlApi) {} + + public async create(namespace: string, itemName: string): Promise { + const response = await this.api.post( + ROUTES.createItem(namespace), + { itemName }, + { + validateStatus: () => true, + } + ); + + return { + status: response.status, + data: response.data ?? {}, + }; + } + + public static getErrorMessage(error: unknown): string | undefined { + if (axios.isAxiosError(error) && error.response) { + const data = error.response.data as Partial | undefined; + if (data?.error && typeof data.error === "string") { + return data.error; + } + } + + if ( + typeof error === "object" && + error !== null && + "message" in error && + typeof (error as any).message === "string" + ) { + return (error as { message: string }).message; + } + + return undefined; + } +} diff --git a/src/ccs/sourcecontrol/routes.ts b/src/ccs/sourcecontrol/routes.ts index 1561570f..d6de6c18 100644 --- a/src/ccs/sourcecontrol/routes.ts +++ b/src/ccs/sourcecontrol/routes.ts @@ -4,6 +4,7 @@ export const ROUTES = { resolveContextExpression: () => `/resolveContextExpression`, getGlobalDocumentation: () => `/getGlobalDocumentation`, resolveDefinition: (namespace: string) => `/namespaces/${encodeURIComponent(namespace)}/resolveDefinition`, + createItem: (namespace: string) => `/namespaces/${encodeURIComponent(namespace)}/createItem`, } as const; export type RouteKey = keyof typeof ROUTES; diff --git a/src/extension.ts b/src/extension.ts index 67a31aa6..04d2e29e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -172,6 +172,7 @@ import { followSourceAnalysisLink, followSourceAnalysisLinkCommand, type SourceAnalysisLinkArgs, + createItem, resolveContextExpression, showGlobalDocumentation, } from "./ccs"; @@ -1294,6 +1295,10 @@ export async function activate(context: vscode.ExtensionContext): Promise { sendCommandTelemetryEvent("resolveContextExpression"); void resolveContextExpression(); }), + vscode.commands.registerCommand("vscode-objectscript.ccs.createItem", async () => { + sendCommandTelemetryEvent("ccs.createItem"); + await createItem(); + }), vscode.commands.registerCommand("vscode-objectscript.ccs.goToDefinition", async () => { sendCommandTelemetryEvent("ccs.goToDefinition"); await goToDefinitionLocalFirst(); From ab3b43cb12d6f146060c65baff5705900cb95a3f Mon Sep 17 00:00:00 2001 From: Leonardo Anders Date: Wed, 29 Oct 2025 11:04:42 -0300 Subject: [PATCH 2/2] Show API validation errors inline in `Consistem: Create Item` input --- package.json | 3 +- src/ccs/commands/createItem.ts | 176 +++++++++++++++++++++------------ 2 files changed, 115 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index 7a940024..817fd106 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "workspaceContains:**/*.csp", "workspaceContains:**/*.csr", "onCommand:vscode-objectscript.explorer.open", - "onCommand:vscode-objectscript.ccs.createItem", "onLanguage:objectscript", "onLanguage:objectscript-int", "onLanguage:objectscript-class", @@ -890,7 +889,7 @@ "title": "Follow Source Analysis Link" }, { - "category": "CCS", + "category": "Consistem", "command": "vscode-objectscript.ccs.createItem", "title": "Create Item" }, diff --git a/src/ccs/commands/createItem.ts b/src/ccs/commands/createItem.ts index c5904bb8..cf5d4427 100644 --- a/src/ccs/commands/createItem.ts +++ b/src/ccs/commands/createItem.ts @@ -8,15 +8,29 @@ import { SourceControlApi } from "../sourcecontrol/client"; import { CreateItemClient } from "../sourcecontrol/clients/createItemClient"; import { getCcsSettings } from "../config/settings"; -async function promptForItemName(): Promise { +interface PromptForItemNameOptions { + initialValue?: string; + validationMessage?: string; +} + +async function promptForItemName(options: PromptForItemNameOptions = {}): Promise { const hasValidExt = (s: string) => /\.cls$/i.test(s) || /\.mac$/i.test(s); const hasBadChars = (s: string) => /[\\/]/.test(s) || /\s/.test(s); const ib = vscode.window.createInputBox(); - ib.title = "Create InterSystems Item"; - ib.prompt = "Enter the name of the class or routine to create (.cls or .mac)"; - ib.placeholder = "MyPackage.MyClass.cls or CCTRIB001.mac"; + ib.title = "Criar Item Consistem"; + ib.prompt = "Informe o nome da classe ou rotina a ser criada (.cls ou .mac)"; + ib.placeholder = "MeuPacote.MinhaClasse.cls ou MINHAROTINA.mac"; ib.ignoreFocusOut = true; + if (options.initialValue) { + ib.value = options.initialValue; + } + if (options.validationMessage) { + ib.validationMessage = { + message: options.validationMessage, + severity: vscode.InputBoxValidationSeverity.Error, + }; + } return await new Promise((resolve) => { const disposeAll = () => { @@ -36,19 +50,22 @@ async function promptForItemName(): Promise { const name = ib.value.trim(); if (!name) { - ib.validationMessage = { message: "Item name is required", severity: vscode.InputBoxValidationSeverity.Error }; + ib.validationMessage = { + message: "Informe o nome do item", + severity: vscode.InputBoxValidationSeverity.Error, + }; return; } if (hasBadChars(name)) { ib.validationMessage = { - message: "Invalid name: avoid spaces and path separators", + message: "Nome inválido: não use espaços nem separadores de caminho (\\ ou /)", severity: vscode.InputBoxValidationSeverity.Error, }; return; } if (!hasValidExt(name)) { ib.validationMessage = { - message: "Please include a valid extension: .cls or .mac", + message: "Inclua uma extensão válida: .cls ou .mac", severity: vscode.InputBoxValidationSeverity.Error, }; return; @@ -142,6 +159,24 @@ function getErrorMessage(err: unknown): string | undefined { return undefined; } +function getApiValidationMessage(err: unknown): string | undefined { + const anyErr = err as any; + const response = anyErr?.response; + if (!response?.data) return undefined; + + const status = typeof response.status === "number" ? response.status : undefined; + if (typeof status === "number" && status >= 500) { + return undefined; + } + + const data = response.data; + if (typeof data === "string" && data.trim()) return data.trim(); + if (typeof data?.error === "string" && data.error.trim()) return data.error.trim(); + if (typeof data?.message === "string" && data.message.trim()) return data.message.trim(); + if (typeof data?.Message === "string" && data.Message.trim()) return data.Message.trim(); + return undefined; +} + export async function createItem(): Promise { const workspaceFolder = await getWsFolder( "Pick the workspace folder where you want to create the item", @@ -171,13 +206,6 @@ export async function createItem(): Promise { } const namespace = ns.toUpperCase(); - const itemName = await promptForItemName(); - if (!itemName) { - return; - } - - logDebug("CCS createItem invoked", { namespace, itemName }); - let sourceControlApi: SourceControlApi; try { sourceControlApi = SourceControlApi.fromAtelierApi(api); @@ -192,61 +220,85 @@ export async function createItem(): Promise { const { requestTimeout } = getCcsSettings(); const backoff = Math.min(500, Math.max(150, Math.floor(requestTimeout * 0.1))); - try { - const { data, status } = await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Creating item...", - cancellable: false, - }, - async () => withTimeoutRetry(() => createItemClient.create(namespace, itemName), 2, backoff) - ); + let lastValue: string | undefined; + let lastValidationMessage: string | undefined; - if (data.error) { - logError("CCS createItem failed", { namespace, itemName, status, error: data.error }); - void vscode.window.showErrorMessage(data.error); + while (true) { + const itemName = await promptForItemName({ initialValue: lastValue, validationMessage: lastValidationMessage }); + if (!itemName) { return; } - if (status < 200 || status >= 300) { - const message = `Item creation failed with status ${status}.`; - logError("CCS createItem failed", { namespace, itemName, status }); - void vscode.window.showErrorMessage(message); - return; - } + lastValue = itemName; + lastValidationMessage = undefined; - if (!data.file) { - const message = "Item created on server but no file path was returned."; - logError("CCS createItem missing file path", { namespace, itemName, response: data }); - void vscode.window.showErrorMessage(message); - return; - } + logDebug("Consistem createItem invoked", { namespace, itemName }); try { - await openCreatedFile(data.file); - } catch (openErr) { - logError("Failed to open created file", { file: data.file, error: openErr }); - void vscode.window.showWarningMessage("Item created, but the returned file could not be opened."); - } + const { data, status } = await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Creating item...", + cancellable: false, + }, + async () => withTimeoutRetry(() => createItemClient.create(namespace, itemName), 2, backoff) + ); + + if (data.error) { + logError("Consistem createItem failed", { namespace, itemName, status, error: data.error }); + lastValidationMessage = data.error; + continue; + } - const createdNamespace = data.namespace ?? namespace; - const createdItem = (data as any).itemIdCriado ?? itemName; - const moduleName = extractModuleName(data.file, workspaceFolder); - const location = moduleName ? `${createdNamespace}/${moduleName}` : createdNamespace; - const successMessage = `Item created successfully in ${location}: ${createdItem}`; - logInfo("CCS createItem succeeded", { - namespace: createdNamespace, - module: moduleName, - itemName: createdItem, - file: data.file, - }); - void vscode.window.showInformationMessage(successMessage); - } catch (error) { - const errorMessage = - (CreateItemClient as any).getErrorMessage?.(error) ?? - getErrorMessage(error) ?? - (isTimeoutError(error) ? "Item creation timed out." : "Item creation failed."); - logError("CCS createItem encountered an unexpected error", error); - void vscode.window.showErrorMessage(errorMessage); + if (status < 200 || status >= 300) { + const message = `Item creation failed with status ${status}.`; + logError("Consistem createItem failed", { namespace, itemName, status }); + void vscode.window.showErrorMessage(message); + return; + } + + if (!data.file) { + const message = "Item created on server but no file path was returned."; + logError("Consistem createItem missing file path", { namespace, itemName, response: data }); + void vscode.window.showErrorMessage(message); + return; + } + + try { + await openCreatedFile(data.file); + } catch (openErr) { + logError("Failed to open created file", { file: data.file, error: openErr }); + void vscode.window.showWarningMessage("Item created, but the returned file could not be opened."); + } + + const createdNamespace = data.namespace ?? namespace; + const createdItem = (data as any).itemIdCriado ?? itemName; + const moduleName = extractModuleName(data.file, workspaceFolder); + const location = moduleName ? `${createdNamespace}/${moduleName}` : createdNamespace; + const successMessage = `Item created successfully in ${location}: ${createdItem}`; + logInfo("Consistem createItem succeeded", { + namespace: createdNamespace, + module: moduleName, + itemName: createdItem, + file: data.file, + }); + void vscode.window.showInformationMessage(successMessage); + return; + } catch (error) { + const apiValidationMessage = getApiValidationMessage(error); + if (apiValidationMessage) { + logError("Consistem createItem API validation failed", { namespace, itemName, error: apiValidationMessage }); + lastValidationMessage = apiValidationMessage; + continue; + } + + const errorMessage = + (CreateItemClient as any).getErrorMessage?.(error) ?? + getErrorMessage(error) ?? + (isTimeoutError(error) ? "Item creation timed out." : "Item creation failed."); + logError("Consistem createItem encountered an unexpected error", error); + void vscode.window.showErrorMessage(errorMessage); + return; + } } }