diff --git a/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs b/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs index 30cd795c4..b8c7366dc 100644 --- a/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs +++ b/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs @@ -136,23 +136,23 @@ pub(crate) fn parse_generator( } }; - match parse_required_key(&args, "version", ast_generator.span()) { - Ok((version_str, version_span)) => match Version::parse(version_str) { + match parse_optional_key(&args, "version") { + Ok(Some(version_str)) => match Version::parse(version_str) { Ok(version) => { builder.version(version.to_string()); } Err(_) => { errors.push(DatamodelError::new_validation_error( &format!("Invalid semver version string: '{}'", version_str), - version_span.clone(), + args.get("version").unwrap().span().clone(), )); } }, - Err(_) => { - errors.push(DatamodelError::new_validation_error( - "Missing 'version' property in your generator. It must match the baml package version you have installed in your project and the VSCode extension, like 'version \"0.50.0\"'", - ast_generator.span().clone(), - )); + Ok(None) => { + builder.version("0.0.0".to_string()); + } + Err(err) => { + errors.push(err); } } diff --git a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs index 3562fafde..7978b21cd 100644 --- a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs +++ b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs @@ -10,6 +10,8 @@ use baml_runtime::{ }; use baml_types::{BamlMap, BamlValue}; +use internal_baml_codegen::version_check::GeneratorType; +use internal_baml_codegen::version_check::{check_version, VersionCheckMode}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; @@ -907,6 +909,45 @@ impl WasmRuntime { .collect() } + #[wasm_bindgen] + pub fn check_version( + generator_version: &str, + current_version: &str, + generator_type: &str, + version_check_mode: &str, + generator_language: &str, + ) -> Option { + // Convert string parameters to enums + let generator_type = match generator_type { + "VSCodeCLI" => GeneratorType::VSCodeCLI, + "VSCode" => GeneratorType::VSCode, + "CLI" => GeneratorType::CLI, + _ => return Some("Invalid generator type".to_string()), + }; + + let version_check_mode = match version_check_mode { + "Strict" => VersionCheckMode::Strict, + "None" => VersionCheckMode::None, + _ => return Some("Invalid version check mode".to_string()), + }; + + let generator_language = match generator_language { + "python/pydantic" => GeneratorOutputType::PythonPydantic, + "typescript" => GeneratorOutputType::Typescript, + "ruby/sorbet" => GeneratorOutputType::RubySorbet, + _ => return Some("Invalid generator language".to_string()), + }; + + check_version( + generator_version, + current_version, + generator_type, + version_check_mode, + generator_language, + ) + .map(|error| error.msg) + } + #[wasm_bindgen] pub fn required_env_vars(&self) -> Vec { self.runtime diff --git a/engine/language-client-codegen/src/lib.rs b/engine/language-client-codegen/src/lib.rs index 1c0cf6acc..0d5cd6334 100644 --- a/engine/language-client-codegen/src/lib.rs +++ b/engine/language-client-codegen/src/lib.rs @@ -4,11 +4,13 @@ use indexmap::IndexMap; use internal_baml_core::{configuration::GeneratorOutputType, ir::repr::IntermediateRepr}; use semver::Version; use std::{collections::BTreeMap, path::PathBuf}; +use version_check::{check_version, GeneratorType, VersionCheckMode}; mod dir_writer; mod python; mod ruby; mod typescript; +pub mod version_check; pub struct GeneratorArgs { /// Output directory for the generated client, relative to baml_src @@ -109,13 +111,46 @@ pub trait GenerateClient { -> Result; } -fn versions_equal_ignoring_patch(v1: &str, v2: &str) -> bool { +pub fn versions_equal_ignoring_patch(v1: &str, v2: &str) -> bool { let version1 = Version::parse(v1).unwrap(); let version2 = Version::parse(v2).unwrap(); version1.major == version2.major && version1.minor == version2.minor } +// Assume VSCode is the only one that uses WASM, and it does call this method but at a different time. +#[cfg(target_arch = "wasm32")] +fn version_check_with_error( + runtime_version: &str, + gen_version: &str, + generator_type: GeneratorType, + mode: VersionCheckMode, + client_type: GeneratorOutputType, +) -> Result<()> { + Ok(()) +} + +#[cfg(not(target_arch = "wasm32"))] +fn version_check_with_error( + runtime_version: &str, + gen_version: &str, + generator_type: GeneratorType, + mode: VersionCheckMode, + client_type: GeneratorOutputType, +) -> Result<()> { + let res = check_version( + runtime_version, + gen_version, + generator_type, + mode, + client_type, + ); + match res { + Some(e) => Err(anyhow::anyhow!("Version mismatch: {}", e.msg)), + None => Ok(()), + } +} + impl GenerateClient for GeneratorOutputType { fn generate_client( &self, @@ -123,50 +158,15 @@ impl GenerateClient for GeneratorOutputType { gen: &GeneratorArgs, ) -> Result { let runtime_version = env!("CARGO_PKG_VERSION"); - let gen_version = semver::Version::parse(&gen.version)?; - let runtime_semver = semver::Version::parse(runtime_version)?; - // with version check enabled, error if they dont match - if !gen.no_version_check { - if !versions_equal_ignoring_patch(&runtime_version, gen_version.to_string().as_str()) { - let error_message = format!( - "The generator in the BAML files (version: {}) does not match the installed baml version ({}). Major and minor version must match. Update the installed BAML package or the version in the BAML generator config. See https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#best-practices", - gen.version, runtime_version - ); - return Err(anyhow::anyhow!(error_message)); - } - } - // Uncomment if we want to error if your generated code is newer than the runtime. - // Error if the version of the generator args is greater than this version which has been the main or only situation that has caused us issues since the generated code of a new version - // may contain types or constructs not actually available from the runtime. - // if gen_version > runtime_semver { - // let error_message = format!( - // "The generator in the BAML files (version: {}) is older than the installed baml version ({}). Try updating your installed BAML package to {}. See https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#best-practices", - // gen.version, runtime_version, gen.version - // ); - // return Err(anyhow::anyhow!(error_message)); - // } - - // log a warning everytime anyway regardless of what happens. - if gen_version != runtime_semver { - // Log a warning if BAML generated files are stale / user forgot to update the version in the config. - if gen_version < runtime_semver { - let warning_message = format!( - "The generator in the BAML files (version: {}) is older than the installed baml version ({}). Please update the generator configuration to {}. See https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#best-practices", - gen.version, runtime_version, runtime_version - ); - println!("{}", "BAML Warning:".yellow().bold()); - println!("{}", warning_message.yellow()); - log::warn!("{}", warning_message); - } else { - let warning_message = format!( - "The generator in the BAML files (version: {}) is newer than the installed baml version ({}). Please update the installed BAML package to {}. See https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#best-practices", - gen.version, runtime_version, gen.version - ); - println!("{}", "BAML Warning:".yellow().bold()); - println!("{}", warning_message.yellow()); - log::warn!("{}", warning_message); - } + if !gen.no_version_check { + version_check_with_error( + runtime_version, + &gen.version, + GeneratorType::CLI, + VersionCheckMode::Strict, + self.clone(), + )?; } let files = match self { diff --git a/engine/language-client-codegen/src/version_check.rs b/engine/language-client-codegen/src/version_check.rs new file mode 100644 index 000000000..e5a4d8cf8 --- /dev/null +++ b/engine/language-client-codegen/src/version_check.rs @@ -0,0 +1,195 @@ +use internal_baml_core::configuration::GeneratorOutputType; +use semver::Version; + +#[derive(Debug, PartialEq)] +pub struct VersionCheckError { + pub msg: String, +} + +#[derive(Debug, PartialEq)] +pub enum GeneratorType { + VSCodeCLI, + VSCode, + CLI, +} + +#[derive(Debug, PartialEq)] +pub enum VersionCheckMode { + Strict, + None, +} + +pub fn check_version( + generator_version: &str, + current_version: &str, + generator_type: GeneratorType, + version_check_mode: VersionCheckMode, + generator_language: GeneratorOutputType, +) -> Option { + if version_check_mode == VersionCheckMode::None { + return None; + } + + let gen_version = match Version::parse(generator_version) { + Ok(v) => v, + Err(_) => return Some(VersionCheckError { + msg: format!("Invalid generator version in BAML config: {}", generator_version), + }), + }; + + let runtime_version = match Version::parse(current_version) { + Ok(v) => v, + Err(_) => return Some(VersionCheckError { + msg: format!("Invalid current version: {}", current_version), + }), + }; + + if generator_version == "0.0.0" { + return Some(VersionCheckError { + msg: format!("You must now add a 'version' to the generator config. Please add 'version \"{}\"' to continue generating baml_client\n. Make sure your installed baml package dependency and VSCode are the same version. \n\nSee https://github.com/BoundaryML/baml/pull/791", current_version).to_string(), + }); + } + + if gen_version.major != runtime_version.major || gen_version.minor != runtime_version.minor { + let base_message = format!( + "Version mismatch: Generator version ({}) does not match the {} version ({}). Major and minor versions must match.", + gen_version, + match generator_type { + GeneratorType::VSCodeCLI | GeneratorType::VSCode => "VSCode extension", + GeneratorType::CLI => "installed BAML CLI", + }, + runtime_version + ); + + let (update_message, docs_link) = if runtime_version > gen_version { + ( + match generator_type { + GeneratorType::VSCodeCLI | GeneratorType::VSCode => + format!("Update the 'version' in your BAML generator config to '{}' to match the VSCode extension version.", runtime_version), + GeneratorType::CLI => + format!("Update the 'version' in your BAML generator config to '{}' to match the installed BAML CLI version.", runtime_version), + }, + "https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#troubleshooting-version-conflicts" + ) + } else { + let update_instruction = match generator_language { + GeneratorOutputType::PythonPydantic => format!("pip install --upgrade baml-py=={}", gen_version), + GeneratorOutputType::Typescript => format!("npm install --save-dev @boundaryml/baml@{}", gen_version), + GeneratorOutputType::RubySorbet => format!("gem install baml -v {}", gen_version), + }; + ( + match generator_type { + GeneratorType::VSCodeCLI | GeneratorType::VSCode => + format!("Update your VSCode extension to version '{}' to match the version in the BAML generator config. Also update your BAML package: {}", gen_version, update_instruction), + GeneratorType::CLI => + format!("Update your installed BAML CLI package to version '{}' to match the version in the BAML generator config: {}", gen_version, update_instruction), + }, + "https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#troubleshooting-version-conflicts" + ) + }; + + let formatted_link = match generator_type { + GeneratorType::VSCodeCLI | GeneratorType::VSCode => format!("[documentation]({})", docs_link), + GeneratorType::CLI => docs_link.to_string(), + }; + + let error_message = format!( + "{}\n\nAction required: {}\n\nFor more information, see: {}", + base_message, update_message, formatted_link + ); + + return Some(VersionCheckError { msg: error_message }); + } + + None +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vscode_link_formatting() { + let result = check_version("1.3.0", "1.2.0", GeneratorType::VSCode, VersionCheckMode::Strict, GeneratorOutputType::Typescript); + assert!(result.is_some()); + let error_msg = result.unwrap().msg; + assert!(error_msg.contains("[documentation](https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#updating-baml-package)")); + } + + #[test] + fn test_cli_link_formatting() { + let result = check_version("1.3.0", "1.2.0", GeneratorType::CLI, VersionCheckMode::Strict, GeneratorOutputType::PythonPydantic); + assert!(result.is_some()); + let error_msg = result.unwrap().msg; + assert!(error_msg.contains("https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#updating-baml-package")); + assert!(!error_msg.contains("[documentation]")); + } + #[test] + fn test_version_check_none() { + assert_eq!( + check_version("1.0.0", "2.0.0", GeneratorType::CLI, VersionCheckMode::None, GeneratorOutputType::PythonPydantic), + None + ); + } + + #[test] + fn test_invalid_generator_version() { + let result = check_version("invalid", "1.0.0", GeneratorType::CLI, VersionCheckMode::Strict, GeneratorOutputType::PythonPydantic); + assert!(result.is_some()); + assert!(result.unwrap().msg.contains("Invalid generator version")); + } + + #[test] + fn test_invalid_current_version() { + let result = check_version("1.0.0", "invalid", GeneratorType::CLI, VersionCheckMode::Strict, GeneratorOutputType::PythonPydantic); + assert!(result.is_some()); + assert!(result.unwrap().msg.contains("Invalid current version")); + } + + #[test] + fn test_matching_versions() { + assert_eq!( + check_version("1.2.3", "1.2.4", GeneratorType::CLI, VersionCheckMode::Strict, GeneratorOutputType::PythonPydantic), + None + ); + } + + #[test] + fn test_mismatched_major_version_cli_python() { + let result = check_version("2.0.0", "1.0.0", GeneratorType::CLI, VersionCheckMode::Strict, GeneratorOutputType::PythonPydantic); + assert!(result.is_some()); + let error_msg = result.unwrap().msg; + assert!(error_msg.contains("Version mismatch")); + assert!(error_msg.contains("installed BAML CLI")); + assert!(error_msg.contains("pip install --upgrade baml==2.0.0")); + } + + #[test] + fn test_mismatched_minor_version_vscode_typescript() { + let result = check_version("1.3.0", "1.2.0", GeneratorType::VSCode, VersionCheckMode::Strict, GeneratorOutputType::Typescript); + assert!(result.is_some()); + let error_msg = result.unwrap().msg; + assert!(error_msg.contains("Version mismatch")); + assert!(error_msg.contains("VSCode extension")); + assert!(error_msg.contains("npm install --save-dev @baml/baml@1.3.0")); + } + + #[test] + fn test_older_vscode_version_ruby() { + let result = check_version("1.3.0", "1.2.0", GeneratorType::VSCodeCLI, VersionCheckMode::Strict, GeneratorOutputType::RubySorbet); + assert!(result.is_some()); + let error_msg = result.unwrap().msg; + assert!(error_msg.contains("Version mismatch")); + assert!(error_msg.contains("VSCode extension")); + assert!(error_msg.contains("gem install baml -v 1.3.0")); + } + + #[test] + fn test_patch_version_difference() { + assert_eq!( + check_version("1.2.3", "1.2.4", GeneratorType::CLI, VersionCheckMode::Strict, GeneratorOutputType::PythonPydantic), + None + ); + } +} diff --git a/integ-tests/python/baml_src/clients.baml b/integ-tests/python/baml_src/clients.baml new file mode 100644 index 000000000..17fd09e97 --- /dev/null +++ b/integ-tests/python/baml_src/clients.baml @@ -0,0 +1,49 @@ +client GPT4 { + provider openai + options { + model "gpt-4" + api_key env.OPENAI_API_KEY + } +} + +client Claude { + provider anthropic + options { + model "claude-3-opus-20240229" + api_key env.ANTHROPIC_API_KEY + } +} + + +client FastAnthropic { + provider anthropic + options { + model "claude-3-haiku-20240307" + api_key env.ANTHROPIC_API_KEY + } +} + +client FastOpenAI { + provider openai + options { + model "gpt-3.5-turbo" + api_key env.OPENAI_API_KEY + } +} + + +client Fast { + provider round-robin + options { + // This will alternate between the two clients + strategy [FastAnthropic, FastOpenAI] + } +} + +client Openai { + provider fallback + options { + // This will try the clients in order until one succeeds + strategy [GPT4, FastOpenAI] + } +} \ No newline at end of file diff --git a/integ-tests/python/baml_src/generators.baml b/integ-tests/python/baml_src/generators.baml new file mode 100644 index 000000000..875b0580e --- /dev/null +++ b/integ-tests/python/baml_src/generators.baml @@ -0,0 +1,14 @@ + +// This helps use auto generate libraries you can use in the language of +// your choice. You can have multiple generators if you use multiple languages. +// Just ensure that the output_dir is different for each generator. +generator target { + // Valid values: "python/pydantic", "typescript", "ruby/sorbet" + output_type "python/pydantic" + // Where the generated code will be saved (relative to baml_src/) + output_dir "../" + // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml). + // The VSCode extension version should also match this version. + version "0.50.0" +} + \ No newline at end of file diff --git a/integ-tests/python/baml_src/resume.baml b/integ-tests/python/baml_src/resume.baml new file mode 100644 index 000000000..8ee437fb0 --- /dev/null +++ b/integ-tests/python/baml_src/resume.baml @@ -0,0 +1,38 @@ +// Defining a data model. +class Resume { + name string + email string + experience string[] + skills string[] +} + +// Creating a function to extract the resume from a string. +function ExtractResume(resume: string) -> Resume { + client GPT4 + prompt #" + Extract from this content: + {{ resume }} + + {{ ctx.output_format }} + "# +} + +// Testing the function with a sample resume. +test vaibhav_resume { + functions [ExtractResume] + args { + resume #" + Vaibhav Gupta + vbv@boundaryml.com + + Experience: + - Founder at BoundaryML + - CV Engineer at Google + - CV Engineer at Microsoft + + Skills: + - Rust + - C++ + "# + } +} diff --git a/typescript/vscode-ext/packages/language-server/src/bamlConfig.ts b/typescript/vscode-ext/packages/language-server/src/bamlConfig.ts index d0b6fa202..05bf050c2 100644 --- a/typescript/vscode-ext/packages/language-server/src/bamlConfig.ts +++ b/typescript/vscode-ext/packages/language-server/src/bamlConfig.ts @@ -1,9 +1,8 @@ import { z } from 'zod' export const bamlConfigSchema = z .object({ - versionCheckMode: z.optional(z.enum(['strict', 'allow-newer'])).default('allow-newer'), cliPath: z.optional(z.string().nullable()).default(null), - generateCodeOnSave: z.enum(['never', 'always', 'same-version-only', 'use-cli-path']).default('always'), + generateCodeOnSave: z.enum(['never', 'always']).default('always'), envCommand: z.string().default('env'), fileWatcher: z.boolean().default(false), trace: z.object({ @@ -15,4 +14,7 @@ export const bamlConfigSchema = z type BamlConfig = z.infer let config: BamlConfig | null = null -export const bamlConfig: { config: BamlConfig | null } = { config: null } +export const bamlConfig: { config: BamlConfig | null; cliVersion: string | null } = { + config: null, + cliVersion: null, +} diff --git a/typescript/vscode-ext/packages/language-server/src/lib/baml_project_manager.ts b/typescript/vscode-ext/packages/language-server/src/lib/baml_project_manager.ts index 8fa57219d..f33233242 100644 --- a/typescript/vscode-ext/packages/language-server/src/lib/baml_project_manager.ts +++ b/typescript/vscode-ext/packages/language-server/src/lib/baml_project_manager.ts @@ -1,9 +1,10 @@ -import BamlWasm, { type WasmDiagnosticError } from '@gloo-ai/baml-schema-wasm-node' +import BamlWasm, { WasmRuntime, type WasmDiagnosticError } from '@gloo-ai/baml-schema-wasm-node' import { access, mkdir, open, readdir, readFile, rename, rm, writeFile } from 'fs/promises' import path from 'path' import { type Diagnostic, DiagnosticSeverity, Position, LocationLink, Hover } from 'vscode-languageserver' import { TextDocument } from 'vscode-languageserver-textdocument' import { CompletionList, CompletionItem } from 'vscode-languageserver' +import { exec } from 'child_process' import { existsSync, readFileSync } from 'fs' @@ -40,8 +41,14 @@ export enum GeneratorDisabledReason { } export enum GeneratorType { - Cli, - VSCode, + CLI = 'CLI', + VSCode = 'VSCode', + VSCodeCLI = 'VSCodeCLI', +} + +export enum VersionCheckMode { + Strict = 'Strict', + None = 'None', } export type GeneratorStatus = { @@ -61,58 +68,69 @@ class Project { private onSuccess: (e: WasmDiagnosticError, files: Record) => void, ) {} - getGeneratorStatus(): GeneratorStatus { - const generatorType = bamlConfig.config?.cliPath ? GeneratorType.Cli : GeneratorType.VSCode - // for now, if the cli is set, ignore any other checks and let the CLI run. - if (generatorType === GeneratorType.Cli) { - return { isReady: true, generatorType: generatorType, diagnostics: new Map() } - } + private checkVersion(generator: BamlWasm.WasmGeneratorConfig) { + const current_version = BamlWasm.version() // TODO set to CLI or other thing + + const message = WasmRuntime.check_version( + generator.version, + current_version, + bamlConfig?.config?.cliPath ? GeneratorType.VSCodeCLI : GeneratorType.VSCode, + VersionCheckMode.Strict, + generator.output_type, + ) + return message + } - if (this.current_runtime === undefined) { - return { - isReady: false, - generatorType: generatorType, - disabledReason: GeneratorDisabledReason.EmptyGenerators, - diagnostics: new Map(), + checkVersionOnSave() { + let firstErrorMessage: undefined | string = undefined + this.list_generators().forEach((g) => { + const message = this.checkVersion(g) + if (message) { + if (!firstErrorMessage) { + firstErrorMessage = message + } + console.error(message) } + }) + return firstErrorMessage + } + + getGeneratorDiagnostics() { + if (!this.current_runtime) { + return } - const generators = this.list_generators() - if (generators.length === 0) { - return { - isReady: false, - generatorType: generatorType, - disabledReason: GeneratorDisabledReason.EmptyGenerators, - diagnostics: new Map(), + const generators = this.list_generators() // requires runtime + const diagnostics = new Map() + generators.forEach((g) => { + const message = this.checkVersion(g) + if (message) { + const diagnostic: Diagnostic = { + range: { + start: { line: g.span.start_line, character: 0 }, + end: { line: g.span.end_line, character: 0 }, + }, + message: message, + severity: DiagnosticSeverity.Error, + source: 'baml', + } + diagnostics.set(URI.file(g.span.file_path).toString(), [diagnostic]) } - } - const versionMismatch = generators.find((g) => { - return !semver.satisfies(BamlWasm.version(), `${semver.major(g.version)}.${semver.minor(g.version)}.x`) }) - if (versionMismatch === undefined) { - return { isReady: true, generatorType: generatorType, diagnostics: new Map() } - } + return diagnostics + } - return { - isReady: false, - generatorType: generatorType, - disabledReason: GeneratorDisabledReason.VersionMismatch, - problematicGenerator: versionMismatch.output_type, - diagnostics: new Map([ - [ - URI.file(versionMismatch.span.file_path).toString(), - [ - { - range: { - start: { line: versionMismatch.span.start_line, character: 0 }, - end: { line: versionMismatch.span.end_line, character: 0 }, - }, - message: `The BAML Generator ${versionMismatch.output_type} version (${versionMismatch.version}), does not match the VSCode BAML Runtime version (${BamlWasm.version()}), so generation of baml_client via VSCode is disabled. \n\nA) Update this version the latest version and update your installed baml package, or \nB) configure VSCode to use the locally installed generator. \nSee https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#best-practices`, - severity: DiagnosticSeverity.Error, - source: 'baml', - }, - ], - ], - ]), + private getVSCodeGeneratorVersion() { + if (bamlConfig.config?.cliPath) { + const versionCommand = `${bamlConfig.config.cliPath} --version` + exec(versionCommand, (error: Error | null, stdout: string, stderr: string) => { + if (error) { + console.error(`Error running baml cli script: ${JSON.stringify(error, null, 2)}`) + + return + } else { + console.log(stdout) + } + }) } } @@ -145,8 +163,6 @@ class Project { .map(([path, content]) => [URI.file(path).toString(), content]), ) const diagnostics = this.wasmProject.diagnostics(this.current_runtime) - const generatorStatus = this.getGeneratorStatus() - const newErrors = [...diagnostics.errors(), generatorStatus.diagnostics] this.onSuccess(this.wasmProject.diagnostics(this.current_runtime), fileMap) } } @@ -446,9 +462,9 @@ class BamlProjectManager { }) for (const project of this.projects.values()) { - const generatorStatus = project.getGeneratorStatus() - if (generatorStatus.diagnostics && generatorStatus.diagnostics.size > 0) { - generatorStatus.diagnostics.forEach((diagnosticArray, uri) => { + const genDiags = project.getGeneratorDiagnostics() + if (genDiags) { + genDiags.forEach((diagnosticArray, uri) => { if (!diagnostics.has(uri)) { diagnostics.set(uri, []) } diff --git a/typescript/vscode-ext/packages/language-server/src/server.ts b/typescript/vscode-ext/packages/language-server/src/server.ts index 6e4e97d83..8fa1f28c0 100644 --- a/typescript/vscode-ext/packages/language-server/src/server.ts +++ b/typescript/vscode-ext/packages/language-server/src/server.ts @@ -40,6 +40,7 @@ import type { LSOptions, LSSettings } from './lib/types' import { BamlWasm } from './lib/wasm' import { bamlConfig, bamlConfigSchema } from './bamlConfig' import { cliBuild } from './baml-cli' +import { exec } from 'child_process' try { // only required on vscode versions 1.89 and below. @@ -209,6 +210,7 @@ export function startServer(options?: LSOptions): void { const configResponse = await connection.workspace.getConfiguration('baml') console.log('configResponse ' + JSON.stringify(configResponse, null, 2)) bamlConfig.config = bamlConfigSchema.parse(configResponse) + await loadBamlCLIVersion() } catch (e: any) { if (e instanceof Error) { console.log('Error getting config' + e.message + ' ' + e.stack) @@ -218,6 +220,42 @@ export function startServer(options?: LSOptions): void { } } + async function loadBamlCLIVersion(): Promise { + function parseVersion(input: string): string | null { + const versionPattern = /(\d+\.\d+\.\d+)/ + const match = input.match(versionPattern) + return match ? match[0] : null + } + + if (bamlConfig.config?.cliPath) { + const versionCommand = `${bamlConfig.config.cliPath} --version` + try { + const stdout = await new Promise((resolve, reject) => { + exec(versionCommand, (error, stdout, stderr) => { + if (error) { + reject(`Error running baml cli script: ${error}`) + } else { + resolve(stdout) + } + }) + }) + + console.log(stdout) + const version = parseVersion(stdout) + if (version) { + bamlConfig.cliVersion = version + } else { + throw new Error(`Error parsing baml cli version from output: ${stdout}`) + } + } catch (error) { + console.error(error) + connection.window.showErrorMessage(`BAML CLI Error: ${error}`) + } + } else { + throw new Error('No CLI path found in config') + } + } + function getLanguageExtension(uri: string): string | undefined { const languageExtension = uri.split('.').pop() if (!languageExtension) { @@ -304,32 +342,26 @@ export function startServer(options?: LSOptions): void { await bamlProjectManager.save_file(documentUri, change.document.getText()) console.log('baml config ' + JSON.stringify(bamlConfig.config, null, 2)) - if (bamlConfig.config?.generateCodeOnSave === 'never') { + await loadBamlCLIVersion() + + if (bamlConfig.config?.generateCodeOnSave === 'always') { return } + const proj = bamlProjectManager.getProjectById(documentUri) - const generationStatus = proj?.getGeneratorStatus() - // We need to do some of this logic in VSCode (not the CLI, since vscode needs to be able to surface warnings or even diagnostics when things mismatch) - if (!generationStatus.isReady) { - if (generationStatus.disabledReason === GeneratorDisabledReason.EmptyGenerators) { - return - } + const error = proj.checkVersionOnSave() + if (error) { connection.sendNotification('baml/message', { - type: 'warn', - message: - 'BAML: Code generation disabled. Extension version (' + - BamlWasm.version() + - ') no longer matches the version in your ' + - generationStatus.problematicGenerator + - ' BAML Generator. [See fix](https://docs.boundaryml.com/docs/calling-baml/generate-baml-client#best-practices)', + type: 'info', + message: error, durationMs: 6000, }) - return } + try { - if (generationStatus.generatorType === GeneratorType.Cli) { + if (bamlConfig.config?.cliPath) { cliBuild( bamlConfig.config?.cliPath!, URI.file(proj.rootPath()), diff --git a/typescript/vscode-ext/packages/package.json b/typescript/vscode-ext/packages/package.json index 1aece7177..e0350c757 100644 --- a/typescript/vscode-ext/packages/package.json +++ b/typescript/vscode-ext/packages/package.json @@ -29,15 +29,6 @@ "type": "object", "title": "Baml extension settings", "properties": { - "baml.versionCheckMode": { - "type": "string", - "default": "strict", - "description": "Check for updates automatically or manually", - "enum": [ - "strict" - ], - "scope": "machine-overridable" - }, "baml.cliPath": { "type": [ "string", @@ -52,18 +43,10 @@ "default": "always", "enum": [ "never", - "always", - "same-version-only", - "use-cli-path" + "always" ], "description": "Generate code on save" }, - "baml.envCommand": { - "type": "string", - "default": "env", - "description": "Command to run to retrieve environment variables (runs in workspace root)", - "scope": "machine-overridable" - }, "baml.fileWatcher": { "scope": "window", "type": "boolean",