From 88428aa1bcf601439dbf5924f1b3243c8ead9c96 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Wed, 17 Jun 2026 13:52:45 -0700 Subject: [PATCH] delete --- sdk_legacy/cs/.editorconfig | 349 - sdk_legacy/cs/.gitignore | 295 - sdk_legacy/cs/FoundryLocal.sln | 70 - sdk_legacy/cs/LICENSE.txt | 21 - sdk_legacy/cs/NuGet.config | 7 - sdk_legacy/cs/README.md | 74 - sdk_legacy/cs/samples/TestApp/Program.cs | 154 - sdk_legacy/cs/samples/TestApp/TestApp.csproj | 18 - sdk_legacy/cs/src/FoundryLocalManager.cs | 644 -- sdk_legacy/cs/src/FoundryModelInfo.cs | 157 - .../cs/src/Microsoft.AI.Foundry.Local.csproj | 29 - .../Microsoft.AI.Foundry.Local.csproj.user | 9 - .../FoundryLocal.Tests.csproj | 32 - .../FoundryLocalManagerTest.cs | 636 -- sdk_legacy/js/.eslintrc.cjs | 43 - sdk_legacy/js/.npmignore | 15 - sdk_legacy/js/.npmrc | 2 - sdk_legacy/js/.prettierignore | 2 - sdk_legacy/js/.prettierrc.json | 7 - sdk_legacy/js/LICENSE.txt | 21 - sdk_legacy/js/README.md | 147 - .../js/integration_test/integration.test.ts | 105 - sdk_legacy/js/package-lock.json | 7093 ----------------- sdk_legacy/js/package.json | 52 - sdk_legacy/js/src/base.ts | 421 - sdk_legacy/js/src/client.ts | 128 - sdk_legacy/js/src/index.ts | 83 - sdk_legacy/js/src/service.ts | 81 - sdk_legacy/js/src/types.ts | 245 - sdk_legacy/js/src/version.ts | 2 - sdk_legacy/js/test/base.test.ts | 535 -- sdk_legacy/js/test/client.test.ts | 119 - sdk_legacy/js/test/index.test.ts | 136 - sdk_legacy/js/test/service.test.ts | 149 - sdk_legacy/js/tsconfig.json | 27 - sdk_legacy/python/.lintrunner.toml | 142 - sdk_legacy/python/LICENSE.txt | 21 - sdk_legacy/python/README.md | 104 - sdk_legacy/python/foundry_local/__init__.py | 22 - sdk_legacy/python/foundry_local/api.py | 436 - sdk_legacy/python/foundry_local/client.py | 120 - sdk_legacy/python/foundry_local/logging.py | 92 - sdk_legacy/python/foundry_local/models.py | 154 - sdk_legacy/python/foundry_local/service.py | 61 - sdk_legacy/python/foundry_local/version.py | 1 - .../python/integration_test/__init__.py | 4 - .../integration_test/test_integration.py | 114 - sdk_legacy/python/pyproject.toml | 142 - sdk_legacy/python/requirements-dev.txt | 4 - sdk_legacy/python/requirements-lintrunner.txt | 8 - sdk_legacy/python/requirements.txt | 3 - sdk_legacy/python/test/__init__.py | 4 - sdk_legacy/python/test/test_api.py | 448 -- sdk_legacy/python/test/test_client.py | 144 - sdk_legacy/python/test/test_models.py | 205 - sdk_legacy/python/test/test_service.py | 89 - sdk_legacy/rust/.cargo/config.toml | 7 - sdk_legacy/rust/Cargo.toml | 33 - sdk_legacy/rust/README.md | 97 - sdk_legacy/rust/src/api.rs | 672 -- sdk_legacy/rust/src/client.rs | 217 - sdk_legacy/rust/src/lib.rs | 55 - sdk_legacy/rust/src/models.rs | 174 - sdk_legacy/rust/src/service.rs | 65 - sdk_legacy/rust/tests/README.md | 66 - sdk_legacy/rust/tests/integration_tests.rs | 106 - sdk_legacy/rust/tests/mock_service.rs | 214 - sdk_legacy/rust/tests/test_api.rs | 242 - 68 files changed, 16174 deletions(-) delete mode 100644 sdk_legacy/cs/.editorconfig delete mode 100644 sdk_legacy/cs/.gitignore delete mode 100644 sdk_legacy/cs/FoundryLocal.sln delete mode 100644 sdk_legacy/cs/LICENSE.txt delete mode 100644 sdk_legacy/cs/NuGet.config delete mode 100644 sdk_legacy/cs/README.md delete mode 100644 sdk_legacy/cs/samples/TestApp/Program.cs delete mode 100644 sdk_legacy/cs/samples/TestApp/TestApp.csproj delete mode 100644 sdk_legacy/cs/src/FoundryLocalManager.cs delete mode 100644 sdk_legacy/cs/src/FoundryModelInfo.cs delete mode 100644 sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj delete mode 100644 sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj.user delete mode 100644 sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocal.Tests.csproj delete mode 100644 sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocalManagerTest.cs delete mode 100644 sdk_legacy/js/.eslintrc.cjs delete mode 100644 sdk_legacy/js/.npmignore delete mode 100644 sdk_legacy/js/.npmrc delete mode 100644 sdk_legacy/js/.prettierignore delete mode 100644 sdk_legacy/js/.prettierrc.json delete mode 100644 sdk_legacy/js/LICENSE.txt delete mode 100644 sdk_legacy/js/README.md delete mode 100644 sdk_legacy/js/integration_test/integration.test.ts delete mode 100644 sdk_legacy/js/package-lock.json delete mode 100644 sdk_legacy/js/package.json delete mode 100644 sdk_legacy/js/src/base.ts delete mode 100644 sdk_legacy/js/src/client.ts delete mode 100644 sdk_legacy/js/src/index.ts delete mode 100644 sdk_legacy/js/src/service.ts delete mode 100644 sdk_legacy/js/src/types.ts delete mode 100644 sdk_legacy/js/src/version.ts delete mode 100644 sdk_legacy/js/test/base.test.ts delete mode 100644 sdk_legacy/js/test/client.test.ts delete mode 100644 sdk_legacy/js/test/index.test.ts delete mode 100644 sdk_legacy/js/test/service.test.ts delete mode 100644 sdk_legacy/js/tsconfig.json delete mode 100644 sdk_legacy/python/.lintrunner.toml delete mode 100644 sdk_legacy/python/LICENSE.txt delete mode 100644 sdk_legacy/python/README.md delete mode 100644 sdk_legacy/python/foundry_local/__init__.py delete mode 100644 sdk_legacy/python/foundry_local/api.py delete mode 100644 sdk_legacy/python/foundry_local/client.py delete mode 100644 sdk_legacy/python/foundry_local/logging.py delete mode 100644 sdk_legacy/python/foundry_local/models.py delete mode 100644 sdk_legacy/python/foundry_local/service.py delete mode 100644 sdk_legacy/python/foundry_local/version.py delete mode 100644 sdk_legacy/python/integration_test/__init__.py delete mode 100644 sdk_legacy/python/integration_test/test_integration.py delete mode 100644 sdk_legacy/python/pyproject.toml delete mode 100644 sdk_legacy/python/requirements-dev.txt delete mode 100644 sdk_legacy/python/requirements-lintrunner.txt delete mode 100644 sdk_legacy/python/requirements.txt delete mode 100644 sdk_legacy/python/test/__init__.py delete mode 100644 sdk_legacy/python/test/test_api.py delete mode 100644 sdk_legacy/python/test/test_client.py delete mode 100644 sdk_legacy/python/test/test_models.py delete mode 100644 sdk_legacy/python/test/test_service.py delete mode 100644 sdk_legacy/rust/.cargo/config.toml delete mode 100644 sdk_legacy/rust/Cargo.toml delete mode 100644 sdk_legacy/rust/README.md delete mode 100644 sdk_legacy/rust/src/api.rs delete mode 100644 sdk_legacy/rust/src/client.rs delete mode 100644 sdk_legacy/rust/src/lib.rs delete mode 100644 sdk_legacy/rust/src/models.rs delete mode 100644 sdk_legacy/rust/src/service.rs delete mode 100644 sdk_legacy/rust/tests/README.md delete mode 100644 sdk_legacy/rust/tests/integration_tests.rs delete mode 100644 sdk_legacy/rust/tests/mock_service.rs delete mode 100644 sdk_legacy/rust/tests/test_api.rs diff --git a/sdk_legacy/cs/.editorconfig b/sdk_legacy/cs/.editorconfig deleted file mode 100644 index a14a74133..000000000 --- a/sdk_legacy/cs/.editorconfig +++ /dev/null @@ -1,349 +0,0 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -# Don't use tabs for indentation. -[*] -indent_style = space -# (Please don't specify an indent_size here; that has too many unintended consequences.) - -# Documentation files -[*.md] -indent_size = 4 -trim_trailing_whitespace = true -insert_final_newline = true - -# Code files -[*.{cs,csx,vb,vbx,h,cpp}] -indent_size = 4 -insert_final_newline = true -charset = utf-8-bom -trim_trailing_whitespace = true - -# Adds guidelines for the EditorGuidelines VS extension. See https://github.com/pharring/EditorGuidelines. -guidelines = 80, 120 - -file_header_template = --------------------------------------------------------------------------------------------------------------------\n\n Copyright (c) Microsoft. All rights reserved.\n\n-------------------------------------------------------------------------------------------------------------------- - -# XML project files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] -indent_size = 2 - -# XML config files -[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] -indent_size = 2 - -# YAML files -[*.{yml,yaml}] -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -# JSON files -[*.json] -indent_size = 2 - -# Shell script files -[*.sh] -end_of_line = lf -indent_size = 2 - -# Dotnet code style settings: -[*.{cs,vb}] - -# Sort using and Import directives with System.* appearing first -dotnet_sort_system_directives_first = true -dotnet_separate_import_directive_groups=true -# Avoid "this." and "Me." if not necessary -dotnet_style_qualification_for_field = false:error -dotnet_style_qualification_for_property = false:error -dotnet_style_qualification_for_method = false:error -dotnet_style_qualification_for_event = false:error - -# Use language keywords instead of framework type names for type references -dotnet_style_predefined_type_for_locals_parameters_members = true:error -dotnet_style_predefined_type_for_member_access = true:error - -# Suggest more modern language features when available -dotnet_style_object_initializer = true:error -dotnet_style_collection_initializer = true:error -dotnet_style_coalesce_expression = true:error -dotnet_style_null_propagation = true:error -dotnet_style_explicit_tuple_names = true:error - -# Non-private static fields are PascalCase -dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = error -dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields -dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style - -dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field -dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected -dotnet_naming_symbols.non_private_static_fields.required_modifiers = static - -dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case - -# Non-private fields are PascalCase -dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = error -dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields -dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style - -dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected - -dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case - -# Constants are PascalCase -dotnet_naming_rule.constants_should_be_pascal_case.severity = error -dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants -dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style - -dotnet_naming_symbols.constants.applicable_kinds = field -dotnet_naming_symbols.constants.required_modifiers = const - -dotnet_naming_style.constant_style.capitalization = pascal_case - -# Static fields are camelCase and start with s_ -dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion -dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields -dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style - -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.required_modifiers = static - -dotnet_naming_style.static_field_style.capitalization = camel_case -# dotnet_naming_style.static_field_style.required_prefix = s_ - -# Instance fields are camelCase and start with _ -dotnet_naming_rule.instance_fields_should_be_camel_case.severity = error -dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields -dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style - -dotnet_naming_symbols.instance_fields.applicable_kinds = field - -dotnet_naming_style.instance_field_style.capitalization = camel_case -dotnet_naming_style.instance_field_style.required_prefix = _ - -# Locals and parameters are camelCase -dotnet_naming_rule.locals_should_be_camel_case.severity = error -dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters -dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style - -dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local - -dotnet_naming_style.camel_case_style.capitalization = camel_case - -# Local functions are PascalCase -dotnet_naming_rule.local_functions_should_be_pascal_case.severity = error -dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions -dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style - -dotnet_naming_symbols.local_functions.applicable_kinds = local_function - -dotnet_naming_style.local_function_style.capitalization = pascal_case - -# By default, name items with PascalCase -dotnet_naming_rule.members_should_be_pascal_case.severity = error -dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members -dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style - -dotnet_naming_symbols.all_members.applicable_kinds = * - -dotnet_naming_style.pascal_case_style.capitalization = pascal_case - -# CSharp code style settings: -# IDE0045: Convert to conditional expression -dotnet_diagnostic.IDE0045.severity = suggestion - -[*.cs] -# Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true -csharp_indent_switch_labels = true -csharp_indent_labels = flush_left - -# Prefer "var" nowhere -csharp_style_var_for_built_in_types = true:error -csharp_style_var_when_type_is_apparent = true:error -csharp_style_var_elsewhere = false:error -csharp_style_implicit_object_creation_when_type_is_apparent = true:error - -# Prefer method-like constructs to have a block body -csharp_style_expression_bodied_methods = false:none -csharp_style_expression_bodied_constructors = false:none -csharp_style_expression_bodied_operators = false:none - -# Code-block preferences -csharp_style_namespace_declarations = file_scoped:error - -# Unused value expressions -csharp_style_unused_value_expression_statement_preference = discard_variable:warning - -# 'using' directive preferences -csharp_using_directive_placement = inside_namespace:error - -# Prefer property-like constructs to have an expression-body -csharp_style_expression_bodied_properties = true:none -csharp_style_expression_bodied_indexers = true:none -csharp_style_expression_bodied_accessors = true:none - -# Suggest more modern language features when available -csharp_style_pattern_matching_over_is_with_cast_check = true:error -csharp_style_pattern_matching_over_as_with_null_check = true:error -csharp_style_inlined_variable_declaration = true:error -csharp_style_throw_expression = true:error -csharp_style_conditional_delegate_call = true:error - -# Newline settings -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true -csharp_new_line_before_catch = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_between_query_expression_clauses = true - -# Spacing -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_around_binary_operators = before_and_after -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false - -# Blocks are allowed -csharp_prefer_braces = true:silent -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true - -# Build severity configuration -# Everything above essentially configures IDE behavior and will not reflect in the build. -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options - -# Default severity for all analyzer diagnostics -dotnet_analyzer_diagnostic.severity = warning - -# SA1600: Elements should be documented -dotnet_diagnostic.SA1600.severity = suggestion - -# CS1591: Missing XML comment for publicly visible type or member -dotnet_diagnostic.CS1591.severity = silent - -# CA1303: Do not pass literals as localized parameters -dotnet_diagnostic.CA1303.severity = silent - -# CA2007: Consider calling ConfigureAwait on the awaited task -dotnet_diagnostic.CA2007.severity = silent - -# SA1402: File may only contain a single type -dotnet_diagnostic.SA1402.severity = none - -# SA1101: Prefix local calls with this -dotnet_diagnostic.SA1101.severity = none - -# SA1649: File name should match first type name -dotnet_diagnostic.SA1649.severity = error - -# SA1309: Field names should not begin with underscore -dotnet_diagnostic.SA1309.severity = none - -# CA1062: Validate arguments of public methods -dotnet_diagnostic.CA1062.severity = silent - -# CA1707: Identifiers should not contain underscores -dotnet_diagnostic.CA1707.severity = silent - -# CA1031: Do not catch general exception types -dotnet_diagnostic.CA1031.severity = suggestion - -# CA1822: Mark members as static -dotnet_diagnostic.CA1822.severity = suggestion - -# CA1815: Override equals and operator equals on value types -dotnet_diagnostic.CA1815.severity = suggestion - -# SA1201: Elements should appear in the correct order -dotnet_diagnostic.SA1201.severity = silent - -# SA1602: Enumeration items should be documented -dotnet_diagnostic.SA1602.severity = suggestion - -# SA1118: Parameter should not span multiple lines -dotnet_diagnostic.SA1118.severity = suggestion - -# CA2201: Do not raise reserved exception types -dotnet_diagnostic.CA2201.severity = suggestion - -# CA1050: Declare types in namespaces -dotnet_diagnostic.CA1050.severity = suggestion - -# IDE0005: Remove unnecessary import -dotnet_diagnostic.IDE0005.severity = error - -# IDE1006: Naming Styles -dotnet_diagnostic.IDE1006.severity = error - -# IDE0008: Use explicit type -dotnet_diagnostic.IDE0008.severity = silent - -# IDE0090: Use 'new(...)' -dotnet_diagnostic.IDE0090.severity = error - -# IDE0072: Add missing cases -## Suppressing this particular case due to issues in the analyzer's understanding of pattern matching. -dotnet_diagnostic.IDE0072.severity = suggestion - -# CA2000: Dispose objects before losing scope -dotnet_diagnostic.CA2000.severity = warning - -# IDE0046: Convert to conditional expression -dotnet_diagnostic.IDE0046.severity = silent - -# IDE0050: Convert to tuple -dotnet_diagnostic.IDE0050.severity = suggestion - -# IDE0066: Convert switch statement to expression -dotnet_diagnostic.IDE0066.severity = suggestion - -# IDE0130: Namespace does not match folder structure -dotnet_diagnostic.IDE0130.severity = silent - -# IDE0161: Convert to file-scoped namespace -dotnet_diagnostic.IDE0161.severity = error - -# IDE0058: Expression value is never used -dotnet_diagnostic.IDE0058.severity = none - -# VSTHRD111: Use ConfigureAwait(bool) -dotnet_diagnostic.VSTHRD111.severity = suggestion - -# IDE0042: Deconstruct variable declaration -dotnet_diagnostic.IDE0042.severity = suggestion - -# IDE0039: Use local function -dotnet_diagnostic.IDE0039.severity = suggestion - -# CA1848: Use the LoggerMessage delegates -dotnet_diagnostic.CA1848.severity = suggestion - -# CA2254: Template should be a static expression -dotnet_diagnostic.CA2254.severity = suggestion - -# IDE0290: Use primary constructor -dotnet_diagnostic.IDE0290.severity = suggestion - -# CA1711: Identifiers should not have incorrect suffix -dotnet_diagnostic.CA1711.severity = suggestion - -# IDE0305: Collection initialization can be simplified -dotnet_diagnostic.IDE0305.severity = suggestion - -# Unused value expressions -csharp_style_unused_value_expression_statement_preference = unused_local_variable:none diff --git a/sdk_legacy/cs/.gitignore b/sdk_legacy/cs/.gitignore deleted file mode 100644 index b3ed4ac3f..000000000 --- a/sdk_legacy/cs/.gitignore +++ /dev/null @@ -1,295 +0,0 @@ -# Custom -.dotnet/ -artifacts/ -.build/ -.vscode/ - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Perfview trace -*.etl.zip -*.orig -/src/BenchmarksDriver/results.md -*.trace.zip -/src/BenchmarksDriver/*.zip -eventpipe.netperf -*.netperf -*.bench.json -BenchmarkDotNet.Artifacts/ \ No newline at end of file diff --git a/sdk_legacy/cs/FoundryLocal.sln b/sdk_legacy/cs/FoundryLocal.sln deleted file mode 100644 index 2e381df37..000000000 --- a/sdk_legacy/cs/FoundryLocal.sln +++ /dev/null @@ -1,70 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.Foundry.Local", "src\Microsoft.AI.Foundry.Local.csproj", "{247537D6-CBBA-C748-B91D-AA7B236563B4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "samples\TestApp\TestApp.csproj", "{91680EDB-3D1D-47D2-BF32-58C3544EDEEC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C88DD14-F956-CE84-757C-A364CCF449FC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoundryLocal.Tests", "test\FoundryLocal.Tests\FoundryLocal.Tests.csproj", "{CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Debug|x64.ActiveCfg = Debug|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Debug|x64.Build.0 = Debug|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Debug|x86.ActiveCfg = Debug|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Debug|x86.Build.0 = Debug|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Release|Any CPU.Build.0 = Release|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Release|x64.ActiveCfg = Release|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Release|x64.Build.0 = Release|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Release|x86.ActiveCfg = Release|Any CPU - {247537D6-CBBA-C748-B91D-AA7B236563B4}.Release|x86.Build.0 = Release|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Debug|x64.ActiveCfg = Debug|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Debug|x64.Build.0 = Debug|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Debug|x86.ActiveCfg = Debug|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Debug|x86.Build.0 = Debug|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Release|Any CPU.Build.0 = Release|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Release|x64.ActiveCfg = Release|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Release|x64.Build.0 = Release|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Release|x86.ActiveCfg = Release|Any CPU - {91680EDB-3D1D-47D2-BF32-58C3544EDEEC}.Release|x86.Build.0 = Release|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Debug|x64.ActiveCfg = Debug|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Debug|x64.Build.0 = Debug|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Debug|x86.Build.0 = Debug|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Release|Any CPU.Build.0 = Release|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Release|x64.ActiveCfg = Release|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Release|x64.Build.0 = Release|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Release|x86.ActiveCfg = Release|Any CPU - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {CD75C56B-0EB9-41F4-BEE0-9D7C674894CC} = {0C88DD14-F956-CE84-757C-A364CCF449FC} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {0138DEC3-F200-43EC-A1A2-6FD8F2C609CB} - EndGlobalSection -EndGlobal diff --git a/sdk_legacy/cs/LICENSE.txt b/sdk_legacy/cs/LICENSE.txt deleted file mode 100644 index 48bc6bb49..000000000 --- a/sdk_legacy/cs/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/sdk_legacy/cs/NuGet.config b/sdk_legacy/cs/NuGet.config deleted file mode 100644 index 420497e9d..000000000 --- a/sdk_legacy/cs/NuGet.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/sdk_legacy/cs/README.md b/sdk_legacy/cs/README.md deleted file mode 100644 index 7e98000db..000000000 --- a/sdk_legacy/cs/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Foundry Local C# SDK - -## Installation - -To use the Foundry Local C# SDK, you need to install the NuGet package: - -```bash -dotnet add package Microsoft.AI.Foundry.Local -``` - -### Building from source -To build the SDK, run the following command in your terminal: - -```bash -cd sdk_legacy/cs -dotnet build -``` - -You can also load [FoundryLocal.sln](./FoundryLocal.sln) in Visual Studio 2022 or VSCode. Update your -`nuget.config` to include the local path to the generated NuGet package: - -```xml - - - - - -``` - -Then, install the package using the following command: - -```bash -dotnet add package FoundryLocal --source foundry-local -``` - -## Usage - -> [!NOTE] -> For this example, you'll need the OpenAI Nuget package installed as well: -> ```bash -> dotnet add package OpenAI -> ``` - -```csharp -using Microsoft.AI.Foundry.Local; -using OpenAI; -using OpenAI.Chat; -using System.ClientModel; -using System.Diagnostics.Metrics; - -var alias = "phi-3.5-mini"; - -var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias); - -var model = await manager.GetModelInfoAsync(aliasOrModelId: alias); -ApiKeyCredential key = new ApiKeyCredential(manager.ApiKey); -OpenAIClient client = new OpenAIClient(key, new OpenAIClientOptions -{ - Endpoint = manager.Endpoint -}); - -var chatClient = client.GetChatClient(model?.ModelId); - -var completionUpdates = chatClient.CompleteChatStreaming("Why is the sky blue'"); - -Console.Write($"[ASSISTANT]: "); -foreach (var completionUpdate in completionUpdates) -{ - if (completionUpdate.ContentUpdate.Count > 0) - { - Console.Write(completionUpdate.ContentUpdate[0].Text); - } -} -``` diff --git a/sdk_legacy/cs/samples/TestApp/Program.cs b/sdk_legacy/cs/samples/TestApp/Program.cs deleted file mode 100644 index 6e9211857..000000000 --- a/sdk_legacy/cs/samples/TestApp/Program.cs +++ /dev/null @@ -1,154 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.ClientModel; - -using Microsoft.AI.Foundry.Local; - -using OpenAI; -using OpenAI.Chat; - -public class TestApp -{ - public static async Task Main(string[] args) - { - var app = new TestApp(); // Create an instance of TestApp - - Console.WriteLine(new string('=', 80)); - Console.WriteLine("Testing catalog integration..."); - await app.TestCatalog(); // Call the instance method - - Console.WriteLine(new string('=', 80)); - Console.WriteLine("Testing cache operations..."); - await app.TestCacheOperations(); // Call the instance method - - string[] aliasesOrModelIds = new[] { "qwen2.5-0.5b", "qwen2.5-0.5b-instruct-generic-cpu:3" }; - - foreach (var aliasOrModelId in aliasesOrModelIds) - { - Console.WriteLine(new string('=', 80)); - // don't stop for now. the catalog api doesn't return register EP models afer stop, start and model list - // Console.WriteLine($"Testing OpenAI integration (from stopped service) with {aliasOrModelId}..."); - // using var manager = new FoundryLocalManager(); - // await manager.StopServiceAsync(); - // await app.TestOpenAIIntegration(aliasOrModelId); - - Console.WriteLine(new string('=', 80)); - Console.WriteLine($"Testing OpenAI integration (service running) with {aliasOrModelId}..."); - await app.TestOpenAIIntegration(aliasOrModelId); - - Console.WriteLine(new string('=', 80)); - Console.WriteLine($"Testing service operations with {aliasOrModelId}..."); - await app.TestService(); - - Console.WriteLine(new string('=', 80)); - Console.WriteLine($"Testing model (un)loading with {aliasOrModelId}..."); - await app.TestModelLoadUnload(aliasOrModelId); - - // Console.WriteLine(new string('=', 80)); - // Console.WriteLine($"Testing force downloading with {aliasOrModelId}..."); - // await app.TestDownload(aliasOrModelId); - } - } - - private async Task TestCacheOperations() - { - using var manager = new FoundryLocalManager(); - Console.WriteLine($"Model cache location at {await manager.GetCacheLocationAsync()}"); - // Print out models in the cache - var models = await manager.ListCachedModelsAsync(); - Console.WriteLine($"Found {models.Count} models in the cache:"); - foreach (var m in models) - { - Console.WriteLine($"Model: {m.Alias} ({m.ModelId})"); - } - } - - private async Task TestService() - { - using var manager = new FoundryLocalManager(); - await manager.StartServiceAsync(); - // Print out whether the service is running - Console.WriteLine($"Service running (should be true): {manager.IsServiceRunning}"); - // Print out the service endpoint and API key - Console.WriteLine($"Service Uri: {manager.ServiceUri}"); - Console.WriteLine($"Endpoint {manager.Endpoint}"); - Console.WriteLine($"ApiKey: {manager.ApiKey}"); - // don't stop for now. the catalog api doesn't return register EP models afer stop, start and model list - // // stop the service - // await manager.StopServiceAsync(); - // Console.WriteLine($"Service stopped"); - // Console.WriteLine($"Service running (should be false): {manager.IsServiceRunning}"); - } - - private async Task TestCatalog() - // First test catalog listing - { - using var manager = new FoundryLocalManager(); - foreach (var m in await manager.ListCatalogModelsAsync()) - { - Console.WriteLine($"Model: {m.Alias} ({m.ModelId})"); - } - } - - private async Task TestOpenAIIntegration(string aliasOrModelId) - { - var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId); - - var model = await manager.GetModelInfoAsync(aliasOrModelId); - var key = new ApiKeyCredential(manager.ApiKey); - var client = new OpenAIClient(key, new OpenAIClientOptions - { - Endpoint = manager.Endpoint - }); - - var chatClient = client.GetChatClient(model?.ModelId); - - CollectionResult completionUpdates = chatClient.CompleteChatStreaming("Why is the sky blue?"); - - Console.Write($"[ASSISTANT]: "); - foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates) - { - if (completionUpdate.ContentUpdate.Count > 0) - { - Console.Write(completionUpdate.ContentUpdate[0].Text); - } - } - } - - private async Task TestModelLoadUnload(string aliasOrModelId) - { - using var manager = new FoundryLocalManager(); - - // Load the model - var model = await manager.LoadModelAsync(aliasOrModelId); - Console.WriteLine($"Loaded model: {model.Alias} ({model.ModelId})"); - - // Attempt to unload without forcing - await manager.UnloadModelAsync(aliasOrModelId); - var stillLoaded = (await manager.ListLoadedModelsAsync()) - .Any(m => m.ModelId == model.ModelId); - Console.WriteLine($"Model still loaded (expected: True): {stillLoaded}"); - - // Force unload - await manager.UnloadModelAsync(aliasOrModelId, force: true); - var unloaded = (await manager.ListLoadedModelsAsync()) - .Any(m => m.ModelId == model.ModelId); - Console.WriteLine($"Model unloaded (expected: True): {!unloaded}"); - } - - - private async Task TestDownload(string aliasOrModelId) - { - using var manager = new FoundryLocalManager(); - - // Download a model - var model = await manager.DownloadModelAsync(aliasOrModelId, force: true); - - // test that the model can be loaded - Console.WriteLine($"Downloaded model: {model!.Alias} ({model.ModelId})"); - } -} diff --git a/sdk_legacy/cs/samples/TestApp/TestApp.csproj b/sdk_legacy/cs/samples/TestApp/TestApp.csproj deleted file mode 100644 index f4a5d1c71..000000000 --- a/sdk_legacy/cs/samples/TestApp/TestApp.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - - - - - - - - - - diff --git a/sdk_legacy/cs/src/FoundryLocalManager.cs b/sdk_legacy/cs/src/FoundryLocalManager.cs deleted file mode 100644 index 737c96592..000000000 --- a/sdk_legacy/cs/src/FoundryLocalManager.cs +++ /dev/null @@ -1,644 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace Microsoft.AI.Foundry.Local; - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Net.Http.Json; -using System.Net.Mime; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum DeviceType -{ - CPU, - GPU, - NPU, - Invalid -} - - -public partial class FoundryLocalManager : IDisposable, IAsyncDisposable -{ - private Uri? _serviceUri; - private HttpClient? _serviceClient; - private List? _catalogModels; - private static readonly string AssemblyVersion = typeof(FoundryLocalManager).Assembly.GetName().Version?.ToString() ?? "unknown"; - - // Gets the service URI - public Uri ServiceUri => _serviceUri ?? throw new InvalidOperationException("Service URI is not set. Call StartServiceAsync() first."); - - // Get the endpoint for model control - public Uri Endpoint => new(ServiceUri, "/v1"); - - // Retrieves the current API key to use - public string ApiKey { get; internal set; } = "OPENAI_API_KEY"; - - // Sees if the service is already running - public bool IsServiceRunning => _serviceUri != null; - - public static async Task StartModelAsync(string aliasOrModelId, DeviceType? device = null, CancellationToken ct = default) - { - var manager = new FoundryLocalManager(); - try - { - await manager.StartServiceAsync(ct); - var modelInfo = await manager.GetModelInfoAsync(aliasOrModelId, device, ct) - ?? throw new InvalidOperationException($"Model {aliasOrModelId} not found in catalog."); - await manager.DownloadModelAsync(modelInfo.ModelId, device: device, token: null, force: false,ct: ct); - await manager.LoadModelAsync(aliasOrModelId, device: device, ct: ct); - return manager; - } - catch - { - manager.Dispose(); - throw; - } - } - - public async Task StartServiceAsync(CancellationToken ct = default) - { - if (_serviceUri == null) - { - _serviceUri = await EnsureServiceRunning(ct); - _serviceClient = new HttpClient - { - BaseAddress = _serviceUri, - // set the timeout to 2 hours (for downloading large models) - Timeout = TimeSpan.FromSeconds(7200) - }; - - _serviceClient.DefaultRequestHeaders.UserAgent.ParseAdd($"foundry-local-cs-sdk/{AssemblyVersion}"); - } - } - - public async Task StopServiceAsync(CancellationToken ct = default) - { - if (_serviceUri != null) - { - await InvokeFoundry("service stop", ct); - _serviceUri = null; - _serviceClient = null; - } - } - - public void Dispose() - { - _serviceClient?.Dispose(); - GC.SuppressFinalize(this); // Ensures that the finalizer is not called for this object. - } - - public async ValueTask DisposeAsync() - { - if (_serviceClient is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.DisposeAsync(); - } - else - { - _serviceClient?.Dispose(); - } - - GC.SuppressFinalize(this); // Ensures that the finalizer is not called for this object. - } - - public async Task> ListCatalogModelsAsync(CancellationToken ct = default) - { - if (_catalogModels == null) - { - await StartServiceAsync(ct); - var results = await _serviceClient!.GetAsync("/foundry/list", ct); - var jsonResponse = await results.Content.ReadAsStringAsync(ct); - var models = JsonSerializer.Deserialize(jsonResponse, ModelGenerationContext.Default.ListModelInfo); - _catalogModels = models ?? []; - - // override ep to cuda for generic-gpu models if cuda is available - bool hasCuda = _catalogModels.Any(m => m.Runtime.ExecutionProvider == "CUDAExecutionProvider"); - if (hasCuda) - { - foreach (var m in _catalogModels) - { - if (m.ModelId.Contains("-generic-gpu:", StringComparison.OrdinalIgnoreCase)) - { - m.EpOverride = "cuda"; - } - } - } - } - - return _catalogModels; - } - - public void RefreshCatalog() - { - _catalogModels = null; - } - - public async Task GetModelInfoAsync(string aliasOrModelId, DeviceType? device = null, CancellationToken ct = default) - { - var catalog = await ListCatalogModelsAsync(ct); - if (catalog.Count == 0 || string.IsNullOrWhiteSpace(aliasOrModelId)) - { - return null; - } - - // 1) Try to match by full ID exactly (with or without ':' for backwards compatibility) - var exact = catalog.FirstOrDefault(m => - m.ModelId.Equals(aliasOrModelId, StringComparison.OrdinalIgnoreCase)); - if (exact != null) - { - return exact; - } - - // 2) Try to match by ID prefix ":" and pick the highest version - string prefix = aliasOrModelId + ":"; - int bestVersion = -1; - ModelInfo? best = null; - foreach (var m in catalog) - { - if (m.ModelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - var v = GetVersion(m.ModelId); - if (v > bestVersion) - { - bestVersion = v; - best = m; - } - } - } - if (best != null) return best; - - // 3) Match by alias, optionally filtered by device - var aliasMatches = catalog.Where(m => - !string.IsNullOrEmpty(m.Alias) && - m.Alias.Equals(aliasOrModelId, StringComparison.OrdinalIgnoreCase)); - - if (device is not null) - { - aliasMatches = aliasMatches.Where(m => m.Runtime.DeviceType == device); - } - - // Catalog/list is assumed pre-sorted by service: - // NPU → non-generic-GPU → generic-GPU → non-generic-CPU → CPU - var candidate = aliasMatches.FirstOrDefault(); - if (candidate == null) return null; - - // Windows-only fallback: if candidate is generic-GPU and has NO EpOverride, prefer CPU alias if available - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && - candidate.ModelId.Contains("-generic-gpu:", StringComparison.OrdinalIgnoreCase) && - string.IsNullOrEmpty(candidate.EpOverride)) - { - var cpuFallback = catalog.FirstOrDefault(m => - !string.IsNullOrEmpty(m.Alias) && - m.Alias.Equals(aliasOrModelId, StringComparison.OrdinalIgnoreCase) && - m.Runtime.DeviceType == DeviceType.CPU); - if (cpuFallback != null) - { - candidate = cpuFallback; - } - } - - return candidate; - } - - public async Task GetCacheLocationAsync(CancellationToken ct = default) - { - await StartServiceAsync(ct); - var response = await _serviceClient!.GetAsync("/openai/status", ct); - var json = await response.Content.ReadAsStringAsync(ct); - var jsonDocument = JsonDocument.Parse(json); - return jsonDocument.RootElement.GetProperty("modelDirPath").GetString() - ?? throw new InvalidOperationException("Model directory path not found in response."); - } - - public async Task> ListCachedModelsAsync(CancellationToken ct = default) - { - await StartServiceAsync(ct); - var results = await _serviceClient!.GetAsync("/openai/models", ct); - var jsonResponse = await results.Content.ReadAsStringAsync(ct); - var modelIds = JsonSerializer.Deserialize(jsonResponse) ?? []; - return await FetchModelInfosAsync(modelIds, ct); - } - - public async Task DownloadModelAsync(string aliasOrModelId, DeviceType? device = null, string? token = null, bool? force = false, CancellationToken ct = default) - { - var modelInfo = await GetModelInfoAsync(aliasOrModelId, device, ct) - ?? throw new InvalidOperationException($"Model {aliasOrModelId} not found in catalog."); - var localModels = await ListCachedModelsAsync(ct); - if (localModels.Any(MatchAliasOrId(aliasOrModelId)) && !force.GetValueOrDefault(false)) - { - return modelInfo; - } - - var request = new DownloadRequest - { - Model = new DownloadRequest.ModelInfo - { - Name = modelInfo.ModelId, - Uri = modelInfo.Uri, - Publisher = modelInfo.Publisher, - ProviderType = modelInfo.ProviderType + "Local", - PromptTemplate = modelInfo.PromptTemplate - }, - Token = token ?? "", - IgnorePipeReport = true - }; - - var response = await _serviceClient!.PostAsJsonAsync("/openai/download", request, ct); - response.EnsureSuccessStatusCode(); - var responseBody = await response.Content.ReadAsStringAsync(ct); - - // Find the last '{' to get the start of the JSON object - var jsonStart = responseBody.LastIndexOf('{'); - if (jsonStart == -1) - { - throw new InvalidOperationException("No JSON object found in response."); - } - - var jsonPart = responseBody[jsonStart..]; - - // Parse the JSON part - using var jsonDoc = JsonDocument.Parse(jsonPart); - var success = jsonDoc.RootElement.GetProperty("success").GetBoolean(); - var errorMessage = jsonDoc.RootElement.GetProperty("errorMessage").GetString(); - - if (!success) - { - throw new InvalidOperationException($"Failed to download model: {errorMessage}"); - } - - return modelInfo; - } - - public async Task IsModelUpgradeableAsync(string aliasOrModelId, DeviceType? device = null, CancellationToken ct = default) - { - var modelInfo = await GetLatestModelInfoAsync(aliasOrModelId, device, ct); - if (modelInfo == null) - { - return false; // Model not found in the catalog - } - - var latestVersion = GetVersion(modelInfo.ModelId); - if (latestVersion == -1) - { - return false; // Invalid version format in model ID - } - - var cachedModels = await ListCachedModelsAsync(ct); - foreach (var cachedModel in cachedModels) - { - if (cachedModel.ModelId.Equals(modelInfo.ModelId, StringComparison.OrdinalIgnoreCase) && - GetVersion(cachedModel.ModelId) == latestVersion) - { - // Cached model is already at latest version - return false; - } - } - - // Latest version not in cache - upgrade available - return true; - - } - - public async Task UpgradeModelAsync(string aliasOrModelId, DeviceType? device = null, string? token = null, CancellationToken ct = default) - { - // Get the latest model info; throw if not found - var modelInfo = await GetLatestModelInfoAsync(aliasOrModelId, device, ct) - ?? throw new ArgumentException($"Model '{aliasOrModelId}' was not found in the catalog."); - - // Attempt to download the model - try - { - return await DownloadModelAsync(modelInfo.ModelId, device, token, false, ct); - } - catch (Exception ex) - { - throw new InvalidOperationException($"Failed to upgrade model '{aliasOrModelId}'.", ex); - } - } - - public async Task LoadModelAsync(string aliasOrModelId, DeviceType? device = null, TimeSpan? timeout = null, CancellationToken ct = default) - { - var modelInfo = await GetModelInfoAsync(aliasOrModelId, device, ct) - ?? throw new InvalidOperationException($"Model {aliasOrModelId} not found in catalog."); - var localModelInfo = await ListCachedModelsAsync(ct); - if (!localModelInfo.Any(MatchAliasOrId(aliasOrModelId))) - { - throw new InvalidOperationException($"Model {aliasOrModelId} not found in local models. Please download it first."); - } - - var queryParams = new Dictionary - { - { "timeout", (timeout ?? TimeSpan.FromMinutes(10)).TotalSeconds.ToString(CultureInfo.InvariantCulture) } - }; - - // Prefer EpOverride if present (Python behavior) - if (!string.IsNullOrEmpty(modelInfo.EpOverride)) - { - queryParams["ep"] = modelInfo.EpOverride!; - } - - var uriBuilder = new UriBuilder(ServiceUri) - { - Path = $"/openai/load/{modelInfo.ModelId}", - Query = string.Join("&", queryParams.Select(kvp => $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}")) - }; - - var response = await _serviceClient!.GetAsync(uriBuilder.Uri, ct); - response.EnsureSuccessStatusCode(); - - return modelInfo; - } - - public async IAsyncEnumerable DownloadModelWithProgressAsync( - string aliasOrModelId, - DeviceType? device = null, - string? token = null, - bool? force = false, - [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct = default) - { - if (_serviceClient is null) - { - yield return ModelDownloadProgress.Error("Service not started"); - yield break; - } - - var modelInfo = await GetModelInfoAsync(aliasOrModelId, device, ct).ConfigureAwait(false); - if (modelInfo is null) - { - yield return ModelDownloadProgress.Error($"Model '{aliasOrModelId}' was not found in the catalogue"); - yield break; - } - - var localModels = await ListCachedModelsAsync(ct); - if (localModels.Any(MatchAliasOrId(aliasOrModelId)) && !force.GetValueOrDefault(false)) - { - yield return ModelDownloadProgress.Completed(modelInfo); - yield break; - } - - var payload = new DownloadRequest - { - Model = new DownloadRequest.ModelInfo - { - Name = modelInfo.ModelId, - Uri = modelInfo.Uri, - Publisher = modelInfo.Publisher, - ProviderType = modelInfo.ProviderType + "Local", - PromptTemplate = modelInfo.PromptTemplate - }, - Token = token ?? "", - IgnorePipeReport = true - }; - - var uriBuilder = new UriBuilder( - scheme: ServiceUri.Scheme, - host: ServiceUri.Host, - port: ServiceUri.Port, - pathValue: "/openai/download"); - using var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri); - request.Content = new StringContent( - JsonSerializer.Serialize(payload), - Encoding.UTF8, - MediaTypeNames.Application.Json); - - using var response = await _serviceClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct); - response.EnsureSuccessStatusCode(); - - using var stream = await response.Content.ReadAsStreamAsync(ct); - using var reader = new StreamReader(stream); - - string? line; - var completed = false; - StringBuilder jsonBuilder = new(); - var collectingJson = false; - - while (!completed && (line = await reader.ReadLineAsync(ct)) is not null) - { - // Check if this line contains download percentage - if (line.StartsWith("Total", StringComparison.CurrentCultureIgnoreCase) && line.Contains("Downloading") && line.Contains('%')) - { - // Parse percentage from line like "Total 45.67% Downloading model.onnx.data" - var percentStr = line.Split('%')[0].Split(' ').Last(); - if (double.TryParse(percentStr, out var percentage)) - { - yield return ModelDownloadProgress.Progress(percentage); - } - } - else if (line.Contains("[DONE]") || line.Contains("All Completed")) - { - // Start collecting JSON after we see the completion marker - collectingJson = true; - } - else if (collectingJson && line.Trim().StartsWith("{", StringComparison.CurrentCultureIgnoreCase)) - { - // Start of JSON object - jsonBuilder.AppendLine(line); - } - else if (collectingJson && jsonBuilder.Length > 0) - { - // Continue collecting JSON - jsonBuilder.AppendLine(line); - - // Check if we have a complete JSON object by looking for ending brace - if (line.Trim() == "}") - { - completed = true; - } - } - } - if (jsonBuilder.Length > 0) - { - var jsonPart = jsonBuilder.ToString(); - ModelDownloadProgress result; - - try - { - using var jsonDoc = JsonDocument.Parse(jsonPart); - var success = jsonDoc.RootElement.GetProperty("success").GetBoolean(); - var errorMessage = jsonDoc.RootElement.GetProperty("errorMessage").GetString(); - - result = success - ? ModelDownloadProgress.Completed(modelInfo) - : ModelDownloadProgress.Error(errorMessage ?? "Unknown error"); - } - catch (JsonException ex) - { - result = ModelDownloadProgress.Error($"Failed to parse JSON response: {ex.Message}"); - } - - yield return result; - } - else - { - yield return ModelDownloadProgress.Error("No completion response received"); - } - } - - public async Task> ListLoadedModelsAsync(CancellationToken ct = default) - { - var response = await _serviceClient!.GetAsync(new Uri(ServiceUri, "/openai/loadedmodels"), ct); - response.EnsureSuccessStatusCode(); - var names = await response.Content.ReadFromJsonAsync(ct) - ?? throw new InvalidOperationException("Failed to read loaded models."); - return await FetchModelInfosAsync(names, CancellationToken.None); - } - - public async Task UnloadModelAsync(string aliasOrModelId, DeviceType? device = null, bool force = false, CancellationToken ct = default) - { - var modelInfo = await GetModelInfoAsync(aliasOrModelId, device, ct) - ?? throw new InvalidOperationException($"Model {aliasOrModelId} not found in catalog."); - var response = await _serviceClient!.GetAsync($"/openai/unload/{modelInfo.ModelId}?force={force.ToString().ToLowerInvariant()}", ct); - - response.EnsureSuccessStatusCode(); - } - - private async Task> FetchModelInfosAsync(IEnumerable aliasesOrModelIds, CancellationToken ct = default) - { - var modelInfos = new List(); - foreach (var idOrAlias in aliasesOrModelIds) - { - var model = await GetModelInfoAsync(idOrAlias, null, ct); - if (model != null) - { - modelInfos.Add(model); - } - } - return modelInfos; - } - - public async Task GetLatestModelInfoAsync(string aliasOrModelId, DeviceType? device = null, CancellationToken ct = default) - { - if (string.IsNullOrEmpty(aliasOrModelId)) - { - return null; - } - - var withoutVersion = aliasOrModelId.Split(':')[0]; - return await GetModelInfoAsync(withoutVersion, device, ct); - } - - /// - /// Extracts the numeric version from a model ID string (e.g. "model-x:3" → 3). - /// - /// The model ID string. - /// The numeric version, or -1 if not found. - public static int GetVersion(string modelId) - { - if (string.IsNullOrEmpty(modelId)) - { - return -1; - } - - var parts = modelId.Split(':'); - if (parts.Length == 0) - { - return -1; - } - - var versionPart = parts[^1]; // last element - if (int.TryParse(versionPart, out var version)) - { - return version; - } - - return -1; - } - - private static async Task EnsureServiceRunning(CancellationToken ct = default) - { - var startInfo = new ProcessStartInfo - { - FileName = @"foundry", - Arguments = "service start", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - // This is a workaround for the issue where the - // environment variable DOTNET_ENVIRONMENT is set to "Development" by default. - startInfo.Environment["DOTNET_ENVIRONMENT"] = null; - - using var process = new Process - { - StartInfo = startInfo - }; - - process.Start(); - - await process.WaitForExitAsync(ct); - - return await StatusEndpoint(ct); - } - - private static async Task StatusEndpoint(CancellationToken ct = default) - { - var statusResult = await InvokeFoundry("service status", ct); - var status = TestIsRunning().Match(statusResult); - if (status.Success) - { - var uri = new Uri(status.Groups[1].Value); - var builder = new UriBuilder() { Scheme = uri.Scheme, Host = uri.Host, Port = uri.Port }; - return builder.Uri; - } - return status.Success ? new Uri(status.Groups[1].Value) : null; - } - - private static async Task InvokeFoundry(string args, CancellationToken ct = default) - { - var startInfo = new ProcessStartInfo - { - FileName = @"foundry", - Arguments = args, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - // This is a workaround for the issue where the - // environment variable DOTNET_ENVIRONMENT is set to "Development" by default. - startInfo.Environment["DOTNET_ENVIRONMENT"] = null; - - using Process process = Process.Start(startInfo) - ?? throw new InvalidOperationException("Failed to start the foundry process"); - - var output = new StringBuilder(); - - process.OutputDataReceived += (sender, e) => - { - if (e.Data is not null) - { - output.AppendLine(e.Data); - } - }; - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - await process.WaitForExitAsync(ct); - - return output.ToString(); - } - - private static Func MatchAliasOrId(string aliasOrModelId) - => modelInfo => modelInfo.ModelId.Equals(aliasOrModelId, StringComparison.OrdinalIgnoreCase) || modelInfo.Alias.Equals(aliasOrModelId, StringComparison.OrdinalIgnoreCase); - - [GeneratedRegex("is running on (http://.*)\\s+")] - private static partial Regex TestIsRunning(); -} diff --git a/sdk_legacy/cs/src/FoundryModelInfo.cs b/sdk_legacy/cs/src/FoundryModelInfo.cs deleted file mode 100644 index 06ac08920..000000000 --- a/sdk_legacy/cs/src/FoundryModelInfo.cs +++ /dev/null @@ -1,157 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace Microsoft.AI.Foundry.Local; - -using System.Text.Json; -using System.Text.Json.Serialization; - -public record PromptTemplate -{ - [JsonPropertyName("assistant")] - public string Assistant { get; init; } = default!; - - [JsonPropertyName("prompt")] - public string Prompt { get; init; } = default!; -} - -public record Runtime -{ - [JsonPropertyName("deviceType")] - public DeviceType DeviceType { get; init; } = default!; - - // there are many different possible values; keep it open‑ended - [JsonPropertyName("executionProvider")] - public string ExecutionProvider { get; init; } = default!; -} - -public record ModelSettings -{ - // The sample shows an empty array; keep it open‑ended. - [JsonPropertyName("parameters")] - public List Parameters { get; init; } = []; -} - -public record FoundryCachedModel(string Name, string? Id); - -public record FoundryDownloadResult(bool Success, string? ErrorMessage); - -public record ModelInfo -{ - [JsonPropertyName("name")] - public string ModelId { get; init; } = default!; - - [JsonPropertyName("displayName")] - public string DisplayName { get; init; } = default!; - - [JsonPropertyName("providerType")] - public string ProviderType { get; init; } = default!; - - [JsonPropertyName("uri")] - public string Uri { get; init; } = default!; - - [JsonPropertyName("version")] - public string Version { get; init; } = default!; - - [JsonPropertyName("modelType")] - public string ModelType { get; init; } = default!; - - [JsonPropertyName("promptTemplate")] - public PromptTemplate? PromptTemplate { get; init; } - - [JsonPropertyName("publisher")] - public string Publisher { get; init; } = default!; - - [JsonPropertyName("task")] - public string Task { get; init; } = default!; - - [JsonPropertyName("runtime")] - public Runtime Runtime { get; init; } = default!; - - [JsonPropertyName("fileSizeMb")] - public long FileSizeMb { get; init; } - - [JsonPropertyName("modelSettings")] - public ModelSettings ModelSettings { get; init; } = default!; - - [JsonPropertyName("alias")] - public string Alias { get; init; } = default!; - - [JsonPropertyName("supportsToolCalling")] - public bool SupportsToolCalling { get; init; } - - [JsonPropertyName("license")] - public string License { get; init; } = default!; - - [JsonPropertyName("licenseDescription")] - public string LicenseDescription { get; init; } = default!; - - [JsonPropertyName("parentModelUri")] - public string ParentModelUri { get; init; } = default!; - - [JsonPropertyName("maxOutputTokens")] - public long MaxOutputTokens { get; init; } - - [JsonPropertyName("minFLVersion")] - public string MinFLVersion { get; init; } = default!; - - [JsonPropertyName("epOverride")] - public string? EpOverride { get; set; } -} - -internal sealed class DownloadRequest -{ - internal sealed class ModelInfo - { - [JsonPropertyName("Name")] - public required string Name { get; set; } - [JsonPropertyName("Uri")] - public required string Uri { get; set; } - [JsonPropertyName("Publisher")] - public required string Publisher { get; set; } - [JsonPropertyName("ProviderType")] - public required string ProviderType { get; set; } - [JsonPropertyName("PromptTemplate")] - public required PromptTemplate? PromptTemplate { get; set; } - } - - [JsonPropertyName("Model")] - public required ModelInfo Model { get; set; } - - [JsonPropertyName("token")] - public required string Token { get; set; } - - [JsonPropertyName("IgnorePipeReport")] - public required bool IgnorePipeReport { get; set; } -} - -public record ModelDownloadProgress -{ - public double Percentage { get; init; } - public bool IsCompleted { get; init; } - public ModelInfo? ModelInfo { get; init; } - public string? ErrorMessage { get; init; } - - public static ModelDownloadProgress Progress(double percentage) => - new() - { Percentage = percentage, IsCompleted = false }; - - public static ModelDownloadProgress Completed(ModelInfo modelInfo) => - new() - { Percentage = 100, IsCompleted = true, ModelInfo = modelInfo }; - - public static ModelDownloadProgress Error(string errorMessage) => - new() - { IsCompleted = true, ErrorMessage = errorMessage }; -} - -[JsonSerializable(typeof(ModelInfo))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(int))] -[JsonSerializable(typeof(ModelDownloadProgress))] -public partial class ModelGenerationContext : JsonSerializerContext -{ -} diff --git a/sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj deleted file mode 100644 index e9a9aa1d8..000000000 --- a/sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - Microsoft - Microsoft Corporation - © Microsoft Corporation. All rights reserved. - LICENSE.txt - https://github.com/microsoft/Foundry-Local - Microsoft AI Foundry Local SDK for .NET - Microsoft AI Foundry SDK - https://github.com/microsoft/Foundry-Local - git - net8.0;net9.0 - enable - enable - True - 0.3.0 - - - - README.md - - - - - - - - diff --git a/sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj.user b/sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj.user deleted file mode 100644 index 41f61ea2f..000000000 --- a/sdk_legacy/cs/src/Microsoft.AI.Foundry.Local.csproj.user +++ /dev/null @@ -1,9 +0,0 @@ - - - - ProjectDebugger - - - Microsoft.AI.Foundry.Local - - \ No newline at end of file diff --git a/sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocal.Tests.csproj b/sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocal.Tests.csproj deleted file mode 100644 index 592e79c46..000000000 --- a/sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocal.Tests.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net9.0 - enable - enable - false - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - diff --git a/sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocalManagerTest.cs b/sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocalManagerTest.cs deleted file mode 100644 index f8f400363..000000000 --- a/sdk_legacy/cs/test/FoundryLocal.Tests/FoundryLocalManagerTest.cs +++ /dev/null @@ -1,636 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace Microsoft.AI.Foundry.Local.Tests; - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Reflection; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -using Microsoft.AI.Foundry.Local; - -using RichardSzalay.MockHttp; - -using Xunit; - -public class FoundryLocalManagerTests : IDisposable -{ - private readonly FoundryLocalManager _manager; - private readonly HttpClient _client; - private readonly MockHttpMessageHandler _mockHttp; - - public FoundryLocalManagerTests() - { - _mockHttp = new MockHttpMessageHandler(); - _client = _mockHttp.ToHttpClient(); - _client.BaseAddress = new Uri("http://localhost:5272"); // matches python tests - - _manager = new FoundryLocalManager(); - typeof(FoundryLocalManager) - .GetField("_serviceUri", BindingFlags.NonPublic | BindingFlags.Instance)! - .SetValue(_manager, _client.BaseAddress); - - typeof(FoundryLocalManager) - .GetField("_serviceClient", BindingFlags.NonPublic | BindingFlags.Instance)! - .SetValue(_manager, _client); - } - - private static List BuildCatalog(bool includeCuda = true) - { - // Mirrors MOCK_CATALOG_DATA ordering and fields (Python tests) - var common = new - { - ProviderType = "AzureFoundry", - Version = "1", - ModelType = "ONNX", - PromptTemplate = (PromptTemplate?)null, - Publisher = "Microsoft", - Task = "chat-completion", - FileSizeMb = 10403L, - ModelSettings = new ModelSettings { Parameters = [] }, - SupportsToolCalling = false, - License = "MIT", - LicenseDescription = "License…", - MaxOutputTokens = 1024L, - MinFLVersion = "1.0.0", - }; - - var list = new List - { - // model-1 generic-gpu, generic-cpu:2, generic-cpu:1 - new() - { - ModelId = "model-1-generic-gpu:1", - DisplayName = "model-1-generic-gpu", - Uri = "azureml://registries/azureml/models/model-1-generic-gpu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.GPU, ExecutionProvider = "WebGpuExecutionProvider" }, - Alias = "model-1", - ParentModelUri = "azureml://registries/azureml/models/model-1/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }, - new() - { - ModelId = "model-1-generic-cpu:2", - DisplayName = "model-1-generic-cpu", - Uri = "azureml://registries/azureml/models/model-1-generic-cpu/versions/2", - Runtime = new Runtime { DeviceType = DeviceType.CPU, ExecutionProvider = "CPUExecutionProvider" }, - Alias = "model-1", - ParentModelUri = "azureml://registries/azureml/models/model-1/versions/2", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }, - new() - { - ModelId = "model-1-generic-cpu:1", - DisplayName = "model-1-generic-cpu", - Uri = "azureml://registries/azureml/models/model-1-generic-cpu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.CPU, ExecutionProvider = "CPUExecutionProvider" }, - Alias = "model-1", - ParentModelUri = "azureml://registries/azureml/models/model-1/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }, - - // model-2 npu:2, npu:1, generic-cpu:1 - new() - { - ModelId = "model-2-npu:2", - DisplayName = "model-2-npu", - Uri = "azureml://registries/azureml/models/model-2-npu/versions/2", - Runtime = new Runtime { DeviceType = DeviceType.NPU, ExecutionProvider = "QNNExecutionProvider" }, - Alias = "model-2", - ParentModelUri = "azureml://registries/azureml/models/model-2/versions/2", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }, - new() - { - ModelId = "model-2-npu:1", - DisplayName = "model-2-npu", - Uri = "azureml://registries/azureml/models/model-2-npu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.NPU, ExecutionProvider = "QNNExecutionProvider" }, - Alias = "model-2", - ParentModelUri = "azureml://registries/azureml/models/model-2/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }, - new() - { - ModelId = "model-2-generic-cpu:1", - DisplayName = "model-2-generic-cpu", - Uri = "azureml://registries/azureml/models/model-2-generic-cpu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.CPU, ExecutionProvider = "CPUExecutionProvider" }, - Alias = "model-2", - ParentModelUri = "azureml://registries/azureml/models/model-2/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }, - }; - - // model-3 cuda-gpu (optional), generic-gpu, generic-cpu - if (includeCuda) - { - list.Add(new ModelInfo - { - ModelId = "model-3-cuda-gpu:1", - DisplayName = "model-3-cuda-gpu", - Uri = "azureml://registries/azureml/models/model-3-cuda-gpu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.GPU, ExecutionProvider = "CUDAExecutionProvider" }, - Alias = "model-3", - ParentModelUri = "azureml://registries/azureml/models/model-3/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }); - } - - list.AddRange(new[] - { - new ModelInfo - { - ModelId = "model-3-generic-gpu:1", - DisplayName = "model-3-generic-gpu", - Uri = "azureml://registries/azureml/models/model-3-generic-gpu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.GPU, ExecutionProvider = "WebGpuExecutionProvider" }, - Alias = "model-3", - ParentModelUri = "azureml://registries/azureml/models/model-3/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }, - new ModelInfo - { - ModelId = "model-3-generic-cpu:1", - DisplayName = "model-3-generic-cpu", - Uri = "azureml://registries/azureml/models/model-3-generic-cpu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.CPU, ExecutionProvider = "CPUExecutionProvider" }, - Alias = "model-3", - ParentModelUri = "azureml://registries/azureml/models/model-3/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = common.PromptTemplate, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - } - }); - - // model-4 generic-gpu (nullable prompt) - list.Add(new ModelInfo - { - ModelId = "model-4-generic-gpu:1", - DisplayName = "model-4-generic-gpu", - Uri = "azureml://registries/azureml/models/model-4-generic-gpu/versions/1", - Runtime = new Runtime { DeviceType = DeviceType.GPU, ExecutionProvider = "WebGpuExecutionProvider" }, - Alias = "model-4", - ParentModelUri = "azureml://registries/azureml/models/model-4/versions/1", - ProviderType = common.ProviderType, Version = common.Version, ModelType = common.ModelType, - PromptTemplate = null, Publisher = common.Publisher, Task = common.Task, - FileSizeMb = common.FileSizeMb, ModelSettings = common.ModelSettings, - SupportsToolCalling = common.SupportsToolCalling, License = common.License, - LicenseDescription = common.LicenseDescription, MaxOutputTokens = common.MaxOutputTokens, - MinFLVersion = common.MinFLVersion - }); - - return list; - } - - private void MockCatalog(bool includeCuda = true) - { - var payload = JsonSerializer.Serialize(BuildCatalog(includeCuda), ModelGenerationContext.Default.ListModelInfo); - _mockHttp.When(HttpMethod.Get, "/foundry/list").Respond("application/json", payload); - } - - private void MockLocalModels(params string[] ids) - { - var json = JsonSerializer.Serialize(ids ?? Array.Empty()); - _mockHttp.When(HttpMethod.Get, "/openai/models").Respond("application/json", json); - } - - private void MockLoadedModels(params string[] ids) - { - var json = JsonSerializer.Serialize(ids ?? Array.Empty()); - _mockHttp.When(HttpMethod.Get, "/openai/loadedmodels").Respond("application/json", json); - } - - [Fact] - public async Task ListCatalogModelsAsync_ReturnsModels_AndSetsCudaOverrideWhenCudaPresent() - { - // GIVEN - MockCatalog(includeCuda: true); - - // WHEN - var models = await _manager.ListCatalogModelsAsync(); - - // THEN - Assert.NotEmpty(models); - // Presence of any CUDAExecutionProvider should mark generic-gpu EpOverride="cuda" - Assert.Contains(models, m => m.Runtime.ExecutionProvider == "CUDAExecutionProvider"); - var gg = models.Find(m => m.ModelId == "model-4-generic-gpu:1"); - Assert.NotNull(gg); - Assert.Equal("cuda", gg!.EpOverride); - - // cache is used on second call - var again = await _manager.ListCatalogModelsAsync(); - Assert.Same(models, again); - } - - [Fact] - public void RefreshCatalog_ResetsOnlyListCache() - { - // GIVEN - typeof(FoundryLocalManager) - .GetField("_catalogModels", BindingFlags.NonPublic | BindingFlags.Instance)! - .SetValue(_manager, new List()); - - // WHEN - _manager.RefreshCatalog(); - - // THEN - var models = typeof(FoundryLocalManager) - .GetField("_catalogModels", BindingFlags.NonPublic | BindingFlags.Instance)! - .GetValue(_manager); - Assert.Null(models); - } - - [Fact] - public async Task GetModelInfoAsync_IdWithVersion_IdWithoutVersion_AliasAndDeviceFilter() - { - // GIVEN - MockCatalog(includeCuda: true); - - // WHEN/THEN - // unknown - Assert.Null(await _manager.GetModelInfoAsync("unknown-model")); - - // exact id (with version) - var m1v1 = await _manager.GetModelInfoAsync("model-1-generic-cpu:1"); - Assert.Equal("model-1-generic-cpu:1", m1v1!.ModelId); - - // id without version -> latest version among same prefix - var m1latest = await _manager.GetModelInfoAsync("model-1-generic-cpu"); - Assert.Equal("model-1-generic-cpu:2", m1latest!.ModelId); - - // alias selection (depends on service order); our list puts "model-2-npu:2" first for alias model-2 - var a2 = await _manager.GetModelInfoAsync("model-2"); - Assert.Equal("model-2-npu:2", a2!.ModelId); - - // model-3 should prefer CUDA when present - var a3 = await _manager.GetModelInfoAsync("model-3"); - Assert.Equal("model-3-cuda-gpu:1", a3!.ModelId); - - // device filter - var a1gpu = await _manager.GetModelInfoAsync("model-1", DeviceType.GPU); - Assert.Equal("model-1-generic-gpu:1", a1gpu!.ModelId); - - var a1cpu = await _manager.GetModelInfoAsync("model-1", DeviceType.CPU); - Assert.Equal("model-1-generic-cpu:2", a1cpu!.ModelId); - - var a1npu = await _manager.GetModelInfoAsync("model-1", DeviceType.NPU); - Assert.Null(a1npu); - } - - [Fact] - public async Task ListCachedModelsAsync_ReturnsMatchingInfos() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLocalModels("model-2-npu:1", "model-4-generic-gpu:1"); - - // WHEN - var local = await _manager.ListCachedModelsAsync(); - - // THEN - Assert.Equal(2, local.Count); - Assert.Equal("model-2-npu:1", local[0].ModelId); - Assert.Equal("model-4-generic-gpu:1", local[1].ModelId); - } - - [Fact] - public async Task ListLoadedModelsAsync_ReturnsModelInfoList() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLoadedModels("model-2-npu:1"); - - // WHEN - var loaded = await _manager.ListLoadedModelsAsync(); - - // THEN - var m = Assert.Single(loaded); - Assert.Equal("model-2-npu:1", m.ModelId); - } - - [Fact] - public async Task ListLoadedModelsAsync_Throws_WhenNullResponse() - { - // GIVEN - _mockHttp.When(HttpMethod.Get, "/openai/loadedmodels") - .Respond("application/json", "null"); - - // WHEN/THEN - var ex = await Assert.ThrowsAsync(() => _manager.ListLoadedModelsAsync()); - Assert.Equal("Failed to read loaded models.", ex.Message); - } - - [Fact] - public async Task DownloadModelAsync_Success_ParsesTailJson() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLocalModels(); // empty cache - - var mockJsonResponse = "log... {\"success\": true, \"errorMessage\": null}"; - _mockHttp.When("/openai/download").Respond("application/json", mockJsonResponse); - - // WHEN - var result = await _manager.DownloadModelAsync("model-3"); // resolves to cuda-gpu:1 - - // THEN - Assert.NotNull(result); - Assert.Equal("model-3-cuda-gpu:1", result!.ModelId); - } - - [Fact] - public async Task DownloadModelAsync_ThrowsWhenServiceReturnsFailure() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLocalModels(); - - var fail = "tail {\"success\": false, \"errorMessage\": \"nope\"}"; - _mockHttp.When("/openai/download").Respond("application/json", fail); - - // WHEN/THEN - var ex = await Assert.ThrowsAsync(() => _manager.DownloadModelAsync("model-1")); - Assert.Contains("Failed to download model: nope", ex.Message); - } - - [Fact] - public async Task DownloadModelAsync_SkipsWhenAlreadyCachedUnlessForce() - { - // GIVEN - MockCatalog(includeCuda: true); - // Latest version already in cache → should skip POST unless force==true - MockLocalModels("model-2-npu:2"); - - // WHEN: already cached and not forced → no POST to /openai/download - var cached = await _manager.DownloadModelAsync("model-2"); - Assert.NotNull(cached); - Assert.Equal("model-2-npu:2", cached!.ModelId); - - // AND WHEN: force download → we should POST and parse a JSON body that includes errorMessage - _mockHttp.When(HttpMethod.Post, "/openai/download") - .Respond("application/json", "{\"success\": true, \"errorMessage\": null}"); - - var forced = await _manager.DownloadModelAsync("model-2", device: null, token: null, force: true); - Assert.NotNull(forced); - Assert.Equal("model-2-npu:2", forced!.ModelId); - - // No expectations were set, but this is harmless and keeps parity with other tests - _mockHttp.VerifyNoOutstandingExpectation(); - } - - - [Fact] - public async Task DownloadModelWithProgressAsync_Success() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLocalModels(); // empty to force a download - - var stream = new MemoryStream(); - var progressLine = Encoding.UTF8.GetBytes("Total 0.00% Downloading model.onnx.data\n"); - stream.Write(progressLine, 0, progressLine.Length); - var doneLine = Encoding.UTF8.GetBytes("[DONE] All Completed!\n"); - stream.Write(doneLine, 0, doneLine.Length); - using (var writer = new Utf8JsonWriter(stream)) - { - JsonSerializer.Serialize(writer, new { success = true, errorMessage = (string?)null }); - } - stream.Position = 0; - - _mockHttp.When("/openai/download").Respond("application/json", stream); - - // WHEN - var progressList = new List(); - await foreach (var p in _manager.DownloadModelWithProgressAsync("model-3")) - { - progressList.Add(p); - } - - // THEN - Assert.Equal(2, progressList.Count); - Assert.False(progressList[0].IsCompleted); - Assert.Equal(0, progressList[0].Percentage); - - Assert.True(progressList[1].IsCompleted); - Assert.Equal(100, progressList[1].Percentage); - Assert.Equal("model-3-cuda-gpu:1", progressList[1].ModelInfo!.ModelId); - } - - [Fact] - public async Task DownloadModelWithProgressAsync_Error() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLocalModels(); - - var stream = new MemoryStream(); - var doneLine = Encoding.UTF8.GetBytes("[DONE] All Completed!\n"); - stream.Write(doneLine, 0, doneLine.Length); - using (var writer = new Utf8JsonWriter(stream)) - { - JsonSerializer.Serialize(writer, new { success = false, errorMessage = "Download error occurred." }); - } - stream.Position = 0; - - _mockHttp.When("/openai/download").Respond("application/json", stream); - - // WHEN - var progressList = new List(); - await foreach (var p in _manager.DownloadModelWithProgressAsync("model-3")) - { - progressList.Add(p); - } - - // THEN - var last = Assert.Single(progressList); - Assert.True(last.IsCompleted); - Assert.Equal("Download error occurred.", last.ErrorMessage); - } - - [Fact] - public async Task LoadModelAsync_Succeeds_AndPassesEpOverrideWhenCudaPresent() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLocalModels("model-4-generic-gpu:1"); // in cache - - // After ListCatalogModelsAsync runs, EpOverride for generic-gpu will be "cuda" - // First call ensures the override is applied - await _manager.ListCatalogModelsAsync(); - - _mockHttp - .When(HttpMethod.Get, "http://localhost:5272/openai/load/model-4-generic-gpu:1*") - .Respond("application/json", "{}"); - - // WHEN - var result = await _manager.LoadModelAsync("model-4"); - - // THEN - Assert.Equal("model-4-generic-gpu:1", result.ModelId); - } - - [Fact] - public async Task LoadModelAsync_ThrowsIfNotInCache() - { - // GIVEN - MockCatalog(includeCuda: true); - MockLocalModels(); // empty - - // WHEN/THEN - var ex = await Assert.ThrowsAsync(() => _manager.LoadModelAsync("model-3")); - Assert.Contains("not found in local models", ex.Message); - } - - [Fact] - public async Task UnloadModelAsync_CallsCorrectUri() - { - // GIVEN - MockCatalog(includeCuda: true); - var model = "model-2-npu:1"; - - _mockHttp.When("/openai/unload/model-2-npu:1") - .WithQueryString("force=false") - .Respond(HttpStatusCode.OK); - - // WHEN - await _manager.UnloadModelAsync(model); - - // THEN just no exception - Assert.True(true); - } - - [Fact] - public async Task IsModelUpgradeableAsync_ReturnsTrue_WhenCachedOlderThanLatest() - { - MockCatalog(includeCuda: true); - MockLocalModels("model-2-npu:1"); // older - Assert.True(await _manager.IsModelUpgradeableAsync("model-2")); - } - - [Fact] - public async Task IsModelUpgradeableAsync_ReturnsFalse_WhenLatestVersionCached() - { - MockCatalog(includeCuda: true); - MockLocalModels("model-2-npu:2"); // latest - Assert.False(await _manager.IsModelUpgradeableAsync("model-2")); - } - - [Fact] - public async Task IsModelUpgradeableAsync_ReturnsFalse_WhenModelMissingFromCatalog() - { - // empty catalog for this case - _mockHttp.When(HttpMethod.Get, "/foundry/list").Respond("application/json", "[]"); - // cached state doesn’t matter when it’s missing from catalog - MockLocalModels("model-2-npu:1"); - Assert.False(await _manager.IsModelUpgradeableAsync("model-2")); - } - - [Fact] - public async Task UpgradeModelAsync_HappyPath() - { - MockCatalog(includeCuda: true); - MockLocalModels(); // empty -> forces download - - _mockHttp.When(HttpMethod.Post, "/openai/download") - .Respond("application/json", "{\"success\": true, \"errorMessage\": null}"); - - var upgraded = await _manager.UpgradeModelAsync("model-3"); - Assert.Equal("model-3-cuda-gpu:1", upgraded!.ModelId); - } - - [Fact] - public async Task UpgradeModelAsync_Throws_WhenDownloadFails() - { - MockCatalog(includeCuda: true); - MockLocalModels(); // empty -> forces download - - _mockHttp.When(HttpMethod.Post, "/openai/download") - .Respond("application/json", "{\"success\": false, \"errorMessage\": \"Simulated download failure.\"}"); - - await Assert.ThrowsAsync(() => _manager.UpgradeModelAsync("model-3")); - } - - - [Fact] - public async Task UpgradeModelAsync_ThrowsWhenModelNotFound() - { - // GIVEN - _mockHttp.When(HttpMethod.Get, "/foundry/list").Respond("application/json", "[]"); - - // WHEN/THEN - var ex = await Assert.ThrowsAsync(() => _manager.UpgradeModelAsync("missing-model")); - Assert.Contains("not found", ex.Message, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void Dispose_DisposesHttpClient() - { - _manager.Dispose(); - Assert.True(true); - } - - [Fact] - public async Task DisposeAsync_DisposesHttpClient() - { - await _manager.DisposeAsync(); - Assert.True(true); - } - - public void Dispose() - { - _client.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/sdk_legacy/js/.eslintrc.cjs b/sdk_legacy/js/.eslintrc.cjs deleted file mode 100644 index 0f130039d..000000000 --- a/sdk_legacy/js/.eslintrc.cjs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict' - -module.exports = { - root: true, - ignorePatterns: ['node_modules/', 'dist/'], - env: { - commonjs: true, - es2021: true, - node: true, - }, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: './tsconfig.json', - }, - plugins: ['@typescript-eslint', 'header', 'import', 'jsdoc'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - ], - rules: { - 'header/header': [ - 2, - 'line', - [' Copyright (c) Microsoft Corporation. All rights reserved.', ' Licensed under the MIT License.'], - 2, - ], - 'import/no-extraneous-dependencies': ['error', { devDependencies: false }], - 'import/no-unassigned-import': 'error', - 'jsdoc/check-alignment': 'error', - 'jsdoc/check-indentation': 'error', - '@typescript-eslint/await-thenable': 'error', - camelcase: 'error', - curly: 'error', - 'no-debugger': 'error', - 'no-unused-vars': 'off', - }, -} diff --git a/sdk_legacy/js/.npmignore b/sdk_legacy/js/.npmignore deleted file mode 100644 index 021a39770..000000000 --- a/sdk_legacy/js/.npmignore +++ /dev/null @@ -1,15 +0,0 @@ -# Folders -node_modules -build -src -test -.vscode - -# Files -.eslintrc.cjs -.gitignore -package-lock.json -.prettierrc.json -.prettierignore -tsconfig.json -*.tgz \ No newline at end of file diff --git a/sdk_legacy/js/.npmrc b/sdk_legacy/js/.npmrc deleted file mode 100644 index 114ea2a42..000000000 --- a/sdk_legacy/js/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -registry=https://pkgs.dev.azure.com/aiinfra/PublicPackages/_packaging/ORT-Nightly/npm/registry/ -always-auth=true diff --git a/sdk_legacy/js/.prettierignore b/sdk_legacy/js/.prettierignore deleted file mode 100644 index adaaa0cb7..000000000 --- a/sdk_legacy/js/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -# ignore dist folder -dist/ diff --git a/sdk_legacy/js/.prettierrc.json b/sdk_legacy/js/.prettierrc.json deleted file mode 100644 index 14be691af..000000000 --- a/sdk_legacy/js/.prettierrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "auto", - "printWidth": 120, - "semi": false, - "singleQuote": true, - "trailingComma": "all" -} diff --git a/sdk_legacy/js/LICENSE.txt b/sdk_legacy/js/LICENSE.txt deleted file mode 100644 index 48bc6bb49..000000000 --- a/sdk_legacy/js/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/sdk_legacy/js/README.md b/sdk_legacy/js/README.md deleted file mode 100644 index 99d603f73..000000000 --- a/sdk_legacy/js/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# Foundry Local JavaScript SDK - -The Foundry Local SDK simplifies AI model management in local environments by providing control-plane operations separate from data-plane inferencing code. - -## Prerequisites - -Foundry Local must be installed and findable in your PATH. - -## Getting Started - -```bash -npm install foundry-local-sdk -``` - -## Usage - -The SDK provides a simple interface to interact with the Foundry Local API. You can use it to manage models, check the status of the service, and make requests to the models. - -### Bootstrapping - -The SDK can _bootstrap_ Foundry Local, which will initiate the following sequence: - -1. Start the Foundry Local service, if it is not already running. -1. Automatically detect the hardware and software requirements for the model. -1. Download the most performance model for the detected hardware, if it is not already downloaded. -1. Load the model into memory. - -To use the SDK with bootstrapping, you can use the following code: - -```js -// foundry-local-sdk supports both CommonJS and ES module syntax -// CommonJS -const { FoundryLocalManager } = require('foundry-local-sdk') -// ES module -// import { FoundryLocalManager } from 'foundry-local-sdk' - -const alias = 'phi-3.5-mini' -const foundryLocalManager = new FoundryLocalManager() - -// initialize the SDK with an optional alias or model ID -const modelInfo = await foundryLocalManager.init(alias) -console.log('Model Info:', modelInfo) - -// check that the service is running -const isRunning = await foundryLocalManager.isServiceRunning() -console.log(`Service running: ${isRunning}`) - -// list all available models in the catalog -const catalogModels = await foundryLocalManager.listCatalogModels() -console.log('Catalog Models:', catalogModels) - -// list all downloaded models -const localModels = await foundryLocalManager.listCachedModels() -console.log('Local Models:', localModels) -``` - -Alternatively, you can use the `FoundryLocalManager` class to manage the service and models manually. This is useful if you want to control the service and models without bootstrapping. For example, you want to present to the end user what is happening in the background. - -```js -const { FoundryLocalManager } = require('foundry-local-sdk') - -const alias = 'phi-3.5-mini' -const foundryLocalManager = new FoundryLocalManager() - -// start the service -await foundryLocalManager.startService() -// or await foundryLocalManager.init() - -// download the model -// the download api also accepts an optional event handler to track the download progress -// it must be of the signature (progress: number) => void -await foundryLocalManager.downloadModel(alias) - -// load the model -const modelInfo = await foundryLocalManager.loadModel(alias) -console.log('Model Info:', modelInfo) -``` - -## Using the SDK with OpenAI API - -Use the foundry local endpoint with an OpenAI compatible API client. For example, install the `openai` package using npm: - -```bash -npm install openai -``` - -Then copy-and-paste the following code into a file called `app.js`: - -```js -import { OpenAI } from 'openai' -import { FoundryLocalManager } from 'foundry-local-sdk' - -// By using an alias, the most suitable model will be downloaded -// to your end-user's device. -// TIP: You can find a list of available models by running the -// following command in your terminal: `foundry model list`. -const alias = 'phi-3.5-mini' - -// Create a FoundryLocalManager instance. This will start the Foundry -// Local service if it is not already running. -const foundryLocalManager = new FoundryLocalManager() - -// Initialize the manager with a model. This will download the model -// if it is not already present on the user's device. -const modelInfo = await foundryLocalManager.init(alias) -console.log('Model Info:', modelInfo) - -const openai = new OpenAI({ - baseURL: foundryLocalManager.endpoint, - apiKey: foundryLocalManager.apiKey, -}) - -async function streamCompletion() { - const stream = await openai.chat.completions.create({ - model: modelInfo.id, - messages: [{ role: 'user', content: 'What is the golden ratio?' }], - stream: true, - }) - - for await (const chunk of stream) { - if (chunk.choices[0]?.delta?.content) { - process.stdout.write(chunk.choices[0].delta.content) - } - } -} - -streamCompletion() -``` - -Run the application using Node.js: - -```bash -node app.js -``` - -## Browser Usage - -The SDK also provides a browser-compatible version. However, it requires you to provide the service URL manually. You can use the `FoundryLocalManager` class in the browser as follows: - -```js -import { FoundryLocalManager } from 'foundry-local-sdk/browser' - -const foundryLocalManager = new FoundryLocalManager({ host: 'http://localhost:8080' }) - -// the rest of the code is the same as above other than the init, isServiceRunning, and startService methods -// which are not available in the browser version. -``` diff --git a/sdk_legacy/js/integration_test/integration.test.ts b/sdk_legacy/js/integration_test/integration.test.ts deleted file mode 100644 index 825da86df..000000000 --- a/sdk_legacy/js/integration_test/integration.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft. -// Licensed under the MIT License. - -import { describe, it, expect, beforeAll } from 'vitest' -import { FoundryLocalManager } from '../src/index' -import type { FoundryModelInfo } from '../src/types' -import OpenAI from 'openai' - -const MODELS = ['qwen2.5-0.5b', 'qwen2.5-0.5b-instruct-generic-cpu:3'] as const - -let manager: FoundryLocalManager -let openai: OpenAI - -beforeAll(async () => { - manager = new FoundryLocalManager() - await manager.startService() - openai = new OpenAI({ baseURL: manager.endpoint, apiKey: manager.apiKey }) -}) - -describe('FoundryLocalManager integration', () => { - it('lists catalog models and prints unique execution providers', async () => { - const models = await manager.listCatalogModels() - expect(Array.isArray(models)).toBe(true) - expect(models.length).toBeGreaterThan(0) - - for (const m of models) { - expect(m.id).toBeTruthy() - expect(m.alias).toBeTruthy() - } - - console.log(`Found ${models.length} models in catalog: ${models.map((m) => m.id).join(', ')}`) - - const uniqueEPs = Array.from(new Set(models.map((m) => m.executionProvider))).sort() - console.log(`Unique execution providers: ${uniqueEPs.join(', ')}`) - }) - - it('cache operations', async () => { - const cacheDir = await manager.getCacheLocation() - expect(typeof cacheDir).toBe('string') - expect(cacheDir.length).toBeGreaterThan(0) - - const cached = await manager.listCachedModels() - expect(Array.isArray(cached)).toBe(true) - }) - - it('service lifecycle', async () => { - const running = await manager.isServiceRunning() - expect(running).toBe(true) - - expect(manager.serviceUrl).toBeTruthy() - expect(manager.endpoint.endsWith('/v1')).toBe(true) - expect(typeof manager.apiKey).toBe('string') - }) - - // OpenAI chat streaming as a separate test case, run for both models - it.each(MODELS)('OpenAI chat streaming for %s', async (modelId) => { - const resolved: FoundryModelInfo | null = await manager.getModelInfo(modelId, undefined, true) - expect(resolved?.id).toBeTruthy() - - // Download if not already present (no force) - await manager.downloadModel(resolved!.id, undefined, undefined, false) - - // Ensure loaded - await manager.loadModel(resolved!.id) - - // Stream a short chat completion - const stream = await openai.chat.completions.create({ - model: resolved!.id, - messages: [{ role: 'user', content: 'Why is the sky blue? Keep it brief.' }], - stream: true, - }) - - process.stdout.write(`Response from model: ${modelId}: ${resolved!.id}\n`) - let tokenCount = 0 - for await (const chunk of stream) { - const delta = chunk.choices?.[0]?.delta?.content - if (delta) { - process.stdout.write(delta) - tokenCount += delta.length - } - } - process.stdout.write('\n') - expect(tokenCount).toBeGreaterThan(0) - }) - - it.each(MODELS)('download → load → unload flow for %s', async (modelId) => { - await manager.downloadModel(modelId, undefined, undefined, false) - - const info = await manager.loadModel(modelId) - expect(info.id).toBeTruthy() - - const loaded1 = await manager.listLoadedModels() - expect(loaded1.some((m) => m.id === info.id)).toBe(true) - - // unload without force → should remain loaded (TTL) - await manager.unloadModel(modelId, undefined, false) - const loaded2 = await manager.listLoadedModels() - expect(loaded2.some((m) => m.id === info.id)).toBe(true) - - // unload with force → should disappear - await manager.unloadModel(modelId, undefined, true) - const loaded3 = await manager.listLoadedModels() - expect(loaded3.some((m) => m.id === info.id)).toBe(false) - }) -}) diff --git a/sdk_legacy/js/package-lock.json b/sdk_legacy/js/package-lock.json deleted file mode 100644 index 29f96079f..000000000 --- a/sdk_legacy/js/package-lock.json +++ /dev/null @@ -1,7093 +0,0 @@ -{ - "name": "foundry-local-sdk", - "version": "0.5.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "foundry-local-sdk", - "version": "0.5.1", - "license": "MIT", - "dependencies": { - "whatwg-fetch": "^3.6.20" - }, - "devDependencies": { - "@types/node": "^18.14.6", - "@types/whatwg-fetch": "^0.0.33", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", - "eslint": "^8.49.0", - "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsdoc": "^46.8.2", - "genversion": "^3.2.0", - "prettier": "^3.2.4", - "typescript": "^5.2.2", - "unbuild": "^3.5.0", - "vitest": "^3.1.3" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", - "integrity": "sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "comment-parser": "1.4.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rollup/plugin-alias": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", - "integrity": "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "28.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", - "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "fdir": "^6.2.0", - "is-reference": "1.2.1", - "magic-string": "^0.30.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", - "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "18.19.100", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.100.tgz", - "integrity": "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/whatwg-fetch": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/whatwg-fetch/-/whatwg-fetch-0.0.33.tgz", - "integrity": "sha512-XSWTlUwpjUyLiDu50HhtpUyhvt7QND1ANfyFfiw/TH63GC1ngZMl2rgxuH5SQKfPjMoKRXv94r7crWnJ3mg5tA==", - "deprecated": "fetch types are now provided by '--lib dom'", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/whatwg-streams": "*" - } - }, - "node_modules/@types/whatwg-streams": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@types/whatwg-streams/-/whatwg-streams-0.0.7.tgz", - "integrity": "sha512-6sDiSEP6DWcY2ZolsJ2s39ZmsoGQ7KVwBDI3sESQsEm9P2dHTcqnDIHRZFRNtLCzWp7hCFGqYbw5GyfpQnJ01A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitest/expect": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", - "integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz", - "integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.1.3", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", - "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz", - "integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.1.3", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz", - "integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.1.3", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", - "integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz", - "integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.1.3", - "loupe": "^3.1.3", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001753", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/comment-parser": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", - "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.7.tgz", - "integrity": "sha512-evKu7yiDIF7oS+EIpwFlMF730ijRyLFaM2o5cTxRGJR9OKHKkc+qP443ZEVR9kZG0syaAJJCPJyfv5pbrxlSng==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssnano-preset-default": "^7.0.7", - "lilconfig": "^3.1.3" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/cssnano-preset-default": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.7.tgz", - "integrity": "sha512-jW6CG/7PNB6MufOrlovs1TvBTEVmhY45yz+bd0h6nw3h6d+1e+/TX+0fflZ+LzvZombbT5f+KC063w9VoHeHow==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^5.0.1", - "postcss-calc": "^10.1.1", - "postcss-colormin": "^7.0.3", - "postcss-convert-values": "^7.0.5", - "postcss-discard-comments": "^7.0.4", - "postcss-discard-duplicates": "^7.0.2", - "postcss-discard-empty": "^7.0.1", - "postcss-discard-overridden": "^7.0.1", - "postcss-merge-longhand": "^7.0.5", - "postcss-merge-rules": "^7.0.5", - "postcss-minify-font-values": "^7.0.1", - "postcss-minify-gradients": "^7.0.1", - "postcss-minify-params": "^7.0.3", - "postcss-minify-selectors": "^7.0.5", - "postcss-normalize-charset": "^7.0.1", - "postcss-normalize-display-values": "^7.0.1", - "postcss-normalize-positions": "^7.0.1", - "postcss-normalize-repeat-style": "^7.0.1", - "postcss-normalize-string": "^7.0.1", - "postcss-normalize-timing-functions": "^7.0.1", - "postcss-normalize-unicode": "^7.0.3", - "postcss-normalize-url": "^7.0.1", - "postcss-normalize-whitespace": "^7.0.1", - "postcss-ordered-values": "^7.0.2", - "postcss-reduce-initial": "^7.0.3", - "postcss-reduce-transforms": "^7.0.1", - "postcss-svgo": "^7.0.2", - "postcss-unique-selectors": "^7.0.4" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/cssnano-utils": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", - "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, - "license": "MIT" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.154", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.154.tgz", - "integrity": "sha512-G4VCFAyKbp1QJ+sWdXYIRYsPGvlV5sDACfCmoMFog3rjm1syLhI41WXm/swZypwCIWIm4IFLWzHY14joWMQ5Fw==", - "dev": true, - "license": "ISC" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-regex": "^1.2.1", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", - "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.4", - "@esbuild/android-arm": "0.25.4", - "@esbuild/android-arm64": "0.25.4", - "@esbuild/android-x64": "0.25.4", - "@esbuild/darwin-arm64": "0.25.4", - "@esbuild/darwin-x64": "0.25.4", - "@esbuild/freebsd-arm64": "0.25.4", - "@esbuild/freebsd-x64": "0.25.4", - "@esbuild/linux-arm": "0.25.4", - "@esbuild/linux-arm64": "0.25.4", - "@esbuild/linux-ia32": "0.25.4", - "@esbuild/linux-loong64": "0.25.4", - "@esbuild/linux-mips64el": "0.25.4", - "@esbuild/linux-ppc64": "0.25.4", - "@esbuild/linux-riscv64": "0.25.4", - "@esbuild/linux-s390x": "0.25.4", - "@esbuild/linux-x64": "0.25.4", - "@esbuild/netbsd-arm64": "0.25.4", - "@esbuild/netbsd-x64": "0.25.4", - "@esbuild/openbsd-arm64": "0.25.4", - "@esbuild/openbsd-x64": "0.25.4", - "@esbuild/sunos-x64": "0.25.4", - "@esbuild/win32-arm64": "0.25.4", - "@esbuild/win32-ia32": "0.25.4", - "@esbuild/win32-x64": "0.25.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-header": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", - "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=7.7.0" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jsdoc": { - "version": "46.10.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.10.1.tgz", - "integrity": "sha512-x8wxIpv00Y50NyweDUpa+58ffgSAI5sqe+zcZh33xphD0AVh+1kqr1ombaTRb7Fhpove1zfUuujlX9DWWBP5ag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.41.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.1", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.5.4", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/exsolve": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz", - "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-package": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-package/-/find-package-1.0.0.tgz", - "integrity": "sha512-yVn71XCCaNgxz58ERTl8nA/8YYtIQDY9mHSrgFBfiFtdNNfY0h183Vh8BRkKxD8x9TUw3ec290uJKhDVxqGZBw==", - "dev": true, - "dependencies": { - "parents": "^1.0.1" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/genversion": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/genversion/-/genversion-3.2.0.tgz", - "integrity": "sha512-OIYSX6XYA8PHecLDCTri30hadSZfAjZ8Iq1+BBDXqLWP4dRLuJNLoNjsSWtTpw97IccK2LDWzkEstxAB8GdN7g==", - "dev": true, - "dependencies": { - "commander": "^7.2.0", - "ejs": "^3.1.9", - "find-package": "^1.0.0" - }, - "bin": { - "genversion": "bin/genversion.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jake/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/knitwork": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.2.0.tgz", - "integrity": "sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdist": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/mkdist/-/mkdist-2.3.0.tgz", - "integrity": "sha512-thkRk+pHdudjdZT3FJpPZ2+pncI6mGlH/B+KBVddlZj4MrFGW41sRIv1wZawZUHU8v7cttGaj+5nx8P+dG664A==", - "dev": true, - "license": "MIT", - "dependencies": { - "autoprefixer": "^10.4.21", - "citty": "^0.1.6", - "cssnano": "^7.0.6", - "defu": "^6.1.4", - "esbuild": "^0.25.2", - "jiti": "^1.21.7", - "mlly": "^1.7.4", - "pathe": "^2.0.3", - "pkg-types": "^2.1.0", - "postcss": "^8.5.3", - "postcss-nested": "^7.0.2", - "semver": "^7.7.1", - "tinyglobby": "^0.2.12" - }, - "bin": { - "mkdist": "dist/cli.cjs" - }, - "peerDependencies": { - "sass": "^1.85.0", - "typescript": ">=5.7.3", - "vue": "^3.5.13", - "vue-sfc-transformer": "^0.1.1", - "vue-tsc": "^1.8.27 || ^2.0.21" - }, - "peerDependenciesMeta": { - "sass": { - "optional": true - }, - "typescript": { - "optional": true - }, - "vue": { - "optional": true - }, - "vue-sfc-transformer": { - "optional": true - }, - "vue-tsc": { - "optional": true - } - } - }, - "node_modules/mkdist/node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==", - "dev": true, - "dependencies": { - "path-platform": "~0.11.15" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", - "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.1", - "exsolve": "^1.0.1", - "pathe": "^2.0.3" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-calc": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", - "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12 || ^20.9 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.38" - } - }, - "node_modules/postcss-colormin": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.3.tgz", - "integrity": "sha512-xZxQcSyIVZbSsl1vjoqZAcMYYdnJsIyG8OvqShuuqf12S88qQboxxEy0ohNCOLwVPXTU+hFHvJPACRL2B5ohTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-convert-values": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.5.tgz", - "integrity": "sha512-0VFhH8nElpIs3uXKnVtotDJJNX0OGYSZmdt4XfSfvOMrFw1jKfpwpZxfC4iN73CTM/MWakDEmsHQXkISYj4BXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-comments": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", - "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", - "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-empty": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", - "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", - "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", - "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^7.0.5" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-merge-rules": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.5.tgz", - "integrity": "sha512-ZonhuSwEaWA3+xYbOdJoEReKIBs5eDiBVLAGpYZpNFPzXZcEE5VKR7/qBEQvTZpiwjqhhqEQ+ax5O3VShBj9Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^5.0.1", - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", - "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", - "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^5.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-params": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.3.tgz", - "integrity": "sha512-vUKV2+f5mtjewYieanLX0xemxIp1t0W0H/D11u+kQV/MWdygOO7xPMkbK+r9P6Lhms8MgzKARF/g5OPXhb8tgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "cssnano-utils": "^5.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", - "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-nested": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-7.0.2.tgz", - "integrity": "sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", - "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", - "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", - "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", - "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-string": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", - "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", - "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.3.tgz", - "integrity": "sha512-EcoA29LvG3F+EpOh03iqu+tJY3uYYKzArqKJHxDhUYLa2u58aqGq16K6/AOsXD9yqLN8O6y9mmePKN5cx6krOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-url": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", - "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", - "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-ordered-values": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", - "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssnano-utils": "^5.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.3.tgz", - "integrity": "sha512-RFvkZaqiWtGMlVjlUHpaxGqEL27lgt+Q2Ixjf83CRAzqdo+TsDyGPtJUbPx2MuYIJ+sCQc2TrOvRnhcXQfgIVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", - "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.2.tgz", - "integrity": "sha512-5Dzy66JlnRM6pkdOTF8+cGsB1fnERTE8Nc+Eed++fOWo1hdsBptCsbG8UuJkgtZt75bRtMJIrPeZmtfANixdFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.3.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >= 18" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", - "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-bytes": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", - "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-dts": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.2.1.tgz", - "integrity": "sha512-sR3CxYUl7i2CHa0O7bA45mCrgADyAQ0tVtGSqi3yvH28M+eg1+g5d7kQ9hLvEz5dorK3XVsH5L2jwHLQf72DzA==", - "dev": true, - "license": "LGPL-3.0-only", - "dependencies": { - "magic-string": "^0.30.17" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/Swatinem" - }, - "optionalDependencies": { - "@babel/code-frame": "^7.26.2" - }, - "peerDependencies": { - "rollup": "^3.29.4 || ^4", - "typescript": "^4.5 || ^5.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sax": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", - "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylehacks": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.5.tgz", - "integrity": "sha512-5kNb7V37BNf0Q3w+1pxfa+oiNPS++/b4Jil9e/kPDgrk1zjEd6uR7SZeJiYaLYH6RRSC1XX2/37OTeU/4FvuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.5", - "postcss-selector-parser": "^7.1.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.32" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svgo": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", - "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0", - "sax": "^1.5.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tinypool": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", - "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unbuild": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/unbuild/-/unbuild-3.5.0.tgz", - "integrity": "sha512-DPFttsiADnHRb/K+yJ9r9jdn6JyXlsmdT0S12VFC14DFSJD+cxBnHq+v0INmqqPVPxOoUjvJFYUVIb02rWnVeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/plugin-alias": "^5.1.1", - "@rollup/plugin-commonjs": "^28.0.2", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^16.0.0", - "@rollup/plugin-replace": "^6.0.2", - "@rollup/pluginutils": "^5.1.4", - "citty": "^0.1.6", - "consola": "^3.4.0", - "defu": "^6.1.4", - "esbuild": "^0.25.0", - "fix-dts-default-cjs-exports": "^1.0.0", - "hookable": "^5.5.3", - "jiti": "^2.4.2", - "magic-string": "^0.30.17", - "mkdist": "^2.2.0", - "mlly": "^1.7.4", - "pathe": "^2.0.3", - "pkg-types": "^2.0.0", - "pretty-bytes": "^6.1.1", - "rollup": "^4.34.8", - "rollup-plugin-dts": "^6.1.1", - "scule": "^1.3.0", - "tinyglobby": "^0.2.12", - "untyped": "^2.0.0" - }, - "bin": { - "unbuild": "dist/cli.mjs" - }, - "peerDependencies": { - "typescript": "^5.7.3" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, - "node_modules/untyped": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/untyped/-/untyped-2.0.0.tgz", - "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "defu": "^6.1.4", - "jiti": "^2.4.2", - "knitwork": "^1.2.0", - "scule": "^1.3.0" - }, - "bin": { - "untyped": "dist/cli.mjs" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz", - "integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.0", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vitest": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", - "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "3.1.3", - "@vitest/mocker": "3.1.3", - "@vitest/pretty-format": "^3.1.3", - "@vitest/runner": "3.1.3", - "@vitest/snapshot": "3.1.3", - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", - "chai": "^5.2.0", - "debug": "^4.4.0", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.13", - "tinypool": "^1.0.2", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.3", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.3", - "@vitest/ui": "3.1.3", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/sdk_legacy/js/package.json b/sdk_legacy/js/package.json deleted file mode 100644 index 219cab21d..000000000 --- a/sdk_legacy/js/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "foundry-local-sdk", - "version": "0.5.1", - "description": "Foundry Local Manager Javascript SDK", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/Foundry-Local.git" - }, - "author": "Microsoft Corporation ", - "main": "dist/index.cjs", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", - "license": "MIT", - "scripts": { - "build": "npx genversion -e src/version.ts && unbuild", - "format": "prettier --write .", - "format:check": "prettier --check .", - "lint": "eslint ./src/*", - "test": "vitest --run --dir test", - "integration-test": "vitest --run --dir integration_test" - }, - "exports": { - ".": { - "require": "./dist/index.cjs", - "import": "./dist/index.mjs", - "types": "./dist/index.d.ts" - }, - "./browser": { - "require": "./dist/base.cjs", - "import": "./dist/base.mjs", - "types": "./dist/base.d.ts" - } - }, - "dependencies": { - "whatwg-fetch": "^3.6.20" - }, - "devDependencies": { - "@types/node": "^18.14.6", - "@types/whatwg-fetch": "^0.0.33", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", - "eslint": "^8.49.0", - "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsdoc": "^46.8.2", - "genversion": "^3.2.0", - "prettier": "^3.2.4", - "typescript": "^5.2.2", - "unbuild": "^3.5.0", - "vitest": "^3.1.3" - } -} diff --git a/sdk_legacy/js/src/base.ts b/sdk_legacy/js/src/base.ts deleted file mode 100644 index 76fc0d97f..000000000 --- a/sdk_legacy/js/src/base.ts +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as client from './client.js' -import { DeviceType, ExecutionProvider } from './types.js' -import type { DownloadBody, Fetch, FoundryModelInfo, FoundryListResponseModel } from './types.js' - -/** - * Utility function to detect if the platform is Windows. - * @returns {boolean} True if the platform is Windows, otherwise false. - */ -function isWindowsPlatform(): boolean { - if (typeof process !== 'undefined' && process.platform) { - return process.platform === 'win32' // Node.js context - } - if (typeof navigator !== 'undefined' && navigator.platform) { - return navigator.platform.startsWith('Win') // Browser context - } - return false // Default to non-Windows -} - -/** - * Class representing the Foundry Local Manager compatible with browser environments. - * This class needs to be constructed with a service URL and cannot manage the service itself. - */ -export class FoundryLocalManager { - /** - * The service URL for the Foundry service. - */ - protected _serviceUrl: string | null - - /** - * The fetch function to use for HTTP requests. - */ - protected readonly fetch: Fetch - - /** - * Cached list of catalog models. - */ - protected catalogList: FoundryModelInfo[] | null = null - - /** - * Constructs a new FoundryLocalManager instance. - * @param {Object} options - Configuration options for the FoundryLocalManager. - * @param {string} options.serviceUrl - The base URL of the Foundry service. - * @param {Fetch} [options.fetch] - Optional custom fetch implementation to use for HTTP requests. - * If not provided, the global fetch will be used. - */ - constructor({ serviceUrl, fetch: overriddenFetch = fetch }: { serviceUrl: string; fetch?: Fetch }) { - this._serviceUrl = serviceUrl - this.fetch = overriddenFetch - } - - /** - * Gets the service URL. - * @throws {Error} If the service URL is invalid. - * @returns {string} The service URL. - */ - get serviceUrl(): string { - if (this._serviceUrl) { - return this._serviceUrl - } - throw new Error('Service URL is invalid!') - } - - /** - * Gets the API endpoint URL. - * @returns {string} The API endpoint URL. - */ - get endpoint(): string { - return `${this.serviceUrl}/v1` - } - - /** - * Gets the API key. - * @returns {string} The API key. - */ - get apiKey(): string { - return 'OPENAI_API_KEY' - } - - /** - * Lists the catalog models. - * @returns {Promise} The list of catalog models. - */ - async listCatalogModels(): Promise { - if (this.catalogList) { - return this.catalogList - } - const response = await client.get(this.fetch, `${this.serviceUrl}/foundry/list`) - const data = (await response.json()) as FoundryListResponseModel[] - - // List should provide the model info in the future - const list = data.map((model) => ({ - alias: model.alias, - id: model.name, - version: model.version, - executionProvider: model.runtime.executionProvider, - deviceType: model.runtime.deviceType, - uri: model.uri, - modelSize: model.fileSizeMb, - supportsToolCalling: model.supportsToolCalling, - promptTemplate: model.promptTemplate, - provider: model.providerType, - publisher: model.publisher, - license: model.license, - task: model.task, - epOverride: null, - })) - - // override ep to cuda for generic-gpu models if cuda is available - const hasCudaSupport = list.some((mi) => mi.executionProvider === ExecutionProvider.CUDA) - if (hasCudaSupport) { - for (const m of list) { - if (m.id.includes('generic-gpu')) { - m.epOverride = ExecutionProvider.CUDA.replace('ExecutionProvider', '').toLowerCase() - } - } - } - this.catalogList = list - return this.catalogList - } - - /** - * Extracts numeric version from ID (e.g. model-x:3 → 3) - * @returns {number} Numeric version extracted from the model ID, or -1 if not found. - */ - private getVersion(modelId: string): number { - try { - const versionStr = modelId.split(':')[1] - const version = parseInt(versionStr, 10) - return isNaN(version) ? -1 : version - } catch { - return -1 - } - } - - /** - * Refreshes the catalog by clearing cached data. - * @returns {Promise} Resolves when the catalog is refreshed. - */ - async refreshCatalog(): Promise { - this.catalogList = null - } - - /** - * Gets the model information of the latest model that matches the given alias or ID. - * @param {string} aliasOrModelId - The alias or model ID. - * @param {DeviceType} [device] - Optional device type to filter models. - * @param {boolean} throwOnNotFound - Whether to throw an error if the model is not found. - * @returns {Promise} The model information or null if not found. - */ - async getModelInfo( - aliasOrModelId: string, - device?: DeviceType, - throwOnNotFound = false, - ): Promise { - const catalog = await this.listCatalogModels() - const key = aliasOrModelId.toLowerCase() - - // 1) Full ID match (with or without ':' for backwards compatibility) - const exact = catalog.find((m) => m.id.toLowerCase() === key) - if (exact) { - return exact - } - - // 2) ID prefix → pick highest version - const prefix = `${key}:` - let bestModel: FoundryModelInfo | null = null - let bestVersion = -1 - for (const m of catalog) { - const idLower = m.id.toLowerCase() - if (idLower.startsWith(prefix)) { - const v = this.getVersion(m.id) - if (v > bestVersion) { - bestVersion = v - bestModel = m - } - } - } - if (bestModel) { - return bestModel - } - - // 3) Alias match; filter by device if provided - let aliasMatches = catalog.filter((m) => m.alias.toLowerCase() === key) - if (device) { - aliasMatches = aliasMatches.filter((m) => m.deviceType === device) - } - - let candidate = aliasMatches[0] ?? null - - // Windows fallback: if we picked a generic-GPU with no override, prefer CPU variant if present - if ( - candidate && - !device && - isWindowsPlatform() && - candidate.id.includes('-generic-gpu:') && - candidate.epOverride == null - ) { - const cpuAlt = aliasMatches.find((m) => m.deviceType === DeviceType.CPU) - if (cpuAlt) { - candidate = cpuAlt - } - } - - if (!candidate && throwOnNotFound) { - throw new Error(`Model ${aliasOrModelId} not found in the catalog.`) - } - return candidate - } - - /** - * Gets the latest model information by alias or model ID. - * The difference from getModelInfo is that this method will return the latest version of the model - * even when you pass it a model id that contains a version suffix. - * @param {string} aliasOrModelId - The alias or model ID. - * @param {DeviceType} [device] - Optional device type to filter models. - * @param {boolean} throwOnNotFound - Whether to throw an error if the model is not found. - * @returns {Promise} The model information or null if not found. - */ - private async getLatestModelInfo( - aliasOrModelId: string, - device?: DeviceType, - throwOnNotFound = false, - ): Promise { - if (!aliasOrModelId) { - if (throwOnNotFound) { - throw new Error('The provided model alias or ID was empty.') - } - return null - } - - const base = aliasOrModelId.split(':')[0] - return this.getModelInfo(base, device, throwOnNotFound) - } - - /** - * Fetches models based on a provided endpoint. - * @param {string} path - The endpoint path. - * @returns {Promise} The list of models. - */ - private async fetchModels(path: string): Promise { - const response = await client.get(this.fetch, `${this.serviceUrl}${path}`) - const modelNames: string[] = await response.json() - - // List should provide the model info in the future - const models = await Promise.all(modelNames.map(async (name) => this.getModelInfo(name))) - - return models.filter((model): model is FoundryModelInfo => model !== null) - } - - /** - * Gets the cache location. - * @returns {Promise} The cache location. - */ - async getCacheLocation(): Promise { - const response = await client.get(this.fetch, `${this.serviceUrl}/openai/status`) - const data = await response.json() - return data.modelDirPath - } - - /** - * Lists cached models. - * @returns {Promise} The list of models downloaded to the cache. - */ - async listCachedModels(): Promise { - return await this.fetchModels('/openai/models') - } - - /** - * Downloads a model. - * @param {string} aliasOrModelId - The alias or model ID. - * @param {DeviceType} [device] - Optional device type to filter models. - * @param {string} [token] - Optional token for authentication. - * @param {boolean} force - Whether to force download an already downloaded model. - * @param {(progress: number) => void} [onProgress] - Callback for download progress percentage. - * If model is already downloaded and force is false, it will be called once with 100. - * @returns {Promise} The downloaded model information. - */ - async downloadModel( - aliasOrModelId: string, - device?: DeviceType, - token?: string, - force = false, - onProgress?: (progress: number) => void, - ): Promise { - const modelInfo = (await this.getModelInfo(aliasOrModelId, device, true)) as FoundryModelInfo - - const cachedModels = await this.listCachedModels() - if (cachedModels.some((model) => model.id === modelInfo.id)) { - if (!force) { - if (onProgress) { - onProgress(100) // Report 100% progress since model is already downloaded - } - // Model already downloaded. Skipping download. - return modelInfo - } - } - - const downloadBody: DownloadBody = { - Name: modelInfo.id, - Uri: modelInfo.uri, - Publisher: modelInfo.publisher, - ProviderType: modelInfo.provider === 'AzureFoundry' ? `${modelInfo.provider}Local` : modelInfo.provider, - PromptTemplate: modelInfo.promptTemplate, - } - const body = { - model: downloadBody, - ...(token && { token }), - IgnorePipeReport: true, - } - - const data = await client.postWithProgress(this.fetch, `${this.serviceUrl}/openai/download`, body, onProgress) - - if (!data.success) { - throw new Error( - `Failed to download model with alias '${modelInfo.alias}' and ID '${modelInfo.id}': ${data.error}`, - ) - } - - return modelInfo - } - - /** - * Checks if a newer version of a model is available. - * @param {string} aliasOrModelId - The alias or model ID. - * @param {DeviceType} [device] - Optional device type to filter models. - * @returns {Promise} True if a newer version is available, otherwise false. - */ - async isModelUpgradeable(aliasOrModelId: string, device?: DeviceType): Promise { - const modelInfo = await this.getLatestModelInfo(aliasOrModelId, device, true) - if (!modelInfo) { - return false // Model not found in the catalog - } - - const latestVersion = this.getVersion(modelInfo.id) - if (latestVersion === -1) { - return false // Invalid version format - } - - const cachedModels = await this.listCachedModels() - return !cachedModels.some((m) => m.id == modelInfo.id && this.getVersion(m.id) === latestVersion) - } - - /** - * Downloads the latest version of a model to the local cache. - * @param {string} aliasOrModelId - The alias or model ID. - * @param {DeviceType} [device] - Optional device type to filter models. - * @param {string} [token] - Optional token for authentication. - * @param {(progress: number) => void} [onProgress] - Callback for download progress percentage. - * @returns {Promise} The upgraded model information. - */ - async upgradeModel( - aliasOrModelId: string, - device?: DeviceType, - token?: string, - onProgress?: (progress: number) => void, - ): Promise { - const modelInfo = (await this.getLatestModelInfo(aliasOrModelId, device, true)) as FoundryModelInfo - return this.downloadModel(modelInfo.id, device, token, false, onProgress) - } - - /** - * Loads a model. - * @param {string} aliasOrModelId - The alias or model ID. - * @param {DeviceType} [device] - Optional device type to filter models. - * @param {number} ttl - Time-to-live for the model in seconds. Default is 600 seconds (10 minutes). - * @returns {Promise} The loaded model information. - * @throws {Error} If the model is not in the catalog or has not been downloaded yet. - */ - async loadModel(aliasOrModelId: string, device?: DeviceType, ttl = 600): Promise { - const modelInfo = (await this.getModelInfo(aliasOrModelId, device, true)) as FoundryModelInfo - - const queryParams: Record = { ttl: ttl.toString() } - if (modelInfo.epOverride) { - queryParams['ep'] = modelInfo.epOverride - } - - try { - await client.get(this.fetch, `${this.serviceUrl}/openai/load/${modelInfo.id}`, queryParams) - } catch (error) { - if (error instanceof Error && error.message.includes('No OpenAIService provider found for modelName')) { - throw new Error(`Model ${aliasOrModelId} has not been downloaded yet. Please download it first.`) - } - throw error - } - - return modelInfo - } - - /** - * Unloads a model. - * @param {string} aliasOrModelId - The alias or model ID. - * @param {DeviceType} [device] - Optional device type to filter models. - * @param {boolean} force - Whether to force unload model with TTL. - * @returns {Promise} Resolves when the model is unloaded. - */ - async unloadModel(aliasOrModelId: string, device?: DeviceType, force = false): Promise { - const modelInfo = (await this.getModelInfo(aliasOrModelId, device, true)) as FoundryModelInfo - - const loadedModels = await this.listLoadedModels() - if (!loadedModels.some((model) => model.id === modelInfo.id)) { - return - } - - await client.get(this.fetch, `${this.serviceUrl}/openai/unload/${modelInfo.id}`, { - force: force.toString(), - }) - } - - /** - * Lists loaded models. - * @returns {Promise} The list of loaded models. - */ - async listLoadedModels(): Promise { - return await this.fetchModels('/openai/loadedmodels') - } -} - -export * from './types.js' diff --git a/sdk_legacy/js/src/client.ts b/sdk_legacy/js/src/client.ts deleted file mode 100644 index 9883191d8..000000000 --- a/sdk_legacy/js/src/client.ts +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import type { Fetch } from './types.js' -import { version } from './version.js' - -/** - * Handles fetch requests with error handling. - * @param {Fetch} fetch - The fetch function to use. - * @param {string} url - The URL to fetch. - * @param {RequestInit} [options] - Optional request options. - * @returns {Promise} The fetch response. - * @throws {Error} If the fetch request fails or returns a non-OK status. - */ -async function fetchWithErrorHandling(fetch: Fetch, url: string, options?: RequestInit): Promise { - try { - const response = await fetch(url, options) - if (!response.ok) { - let responseContent = '' - try { - responseContent = await response.text() - } catch (error) { - responseContent = 'Unable to read response content' - } - return Promise.reject(new Error(`HTTP error! status: ${response.status}, response: ${responseContent}`)) - } - return response - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - throw new Error( - `Network error! Please check if the foundry service is running and the host URL is correct. Error: ${errorMessage}`, - ) - } -} - -/** - * Sends a GET request to the specified host with optional query parameters. - * @param {Fetch} fetch - The fetch function to use. - * @param {string} host - The host URL. - * @param {Record} [queryParams] - Optional query parameters. - * @returns {Promise} The fetch response. - */ -export const get = async (fetch: Fetch, host: string, queryParams?: Record): Promise => { - const endpoint = host + (queryParams ? '?' + new URLSearchParams(queryParams).toString() : '') - return await fetchWithErrorHandling(fetch, endpoint) -} - -/** - * Parses a percentage value from a string. - * @param {string} line - The string containing the percentage value. - * @returns {number | null} The parsed percentage value, or null if not found. - */ -function parsePercentage(line: string): number | null { - const match = line.match(/(\d+(?:\.\d+)?)%/) - return match ? Math.min(parseInt(match[1]), 100) : null -} - -/** - * Sends a POST request with progress updates. - * @param {Fetch} fetch - The fetch function to use. - * @param {string} host - The host URL. - * @param {Record} [body] - The request body. - * @param {(progress: number) => void} [onProgress] - Optional progress callback. - * @returns {Promise>} The parsed response body. - */ -export const postWithProgress = async ( - fetch: Fetch, - host: string, - body?: Record, - onProgress?: (progress: number) => void, -): Promise> => { - // Sending a POST request and getting a streamable response - const response = await fetchWithErrorHandling(fetch, host, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'User-Agent': `foundry-local-js-sdk/${version}` }, - body: body ? JSON.stringify(body) : undefined, - }) - - // Set up progress tracking - const reader = response.body?.getReader() - let finalJson = '' - let prevPercent = 0 - - // Read and process the response stream - // eslint-disable-next-line no-constant-condition - while (true) { - const { done, value } = (await reader?.read()) ?? { - done: true, - value: new Uint8Array(), - } - - if (done) { - break - } - - const line = new TextDecoder('utf-8').decode(value) - - // Accumulate the final JSON when we start receiving JSON data - if (finalJson || line.startsWith('{')) { - finalJson += line - continue - } - - // If onProgress is not provided, skip progress tracking - if (!onProgress) { - continue - } - - // Parse progress percentage from the line - const percent = parsePercentage(line) - if (percent !== null && percent > prevPercent) { - prevPercent = percent - onProgress(percent) // Update the progress - } - } - - // Parse and return the final JSON response - if (finalJson) { - try { - return JSON.parse(finalJson) - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - throw new Error(`Error parsing JSON response: ${errorMessage}`) - } - } else { - throw new Error('No JSON data received!') - } -} diff --git a/sdk_legacy/js/src/index.ts b/sdk_legacy/js/src/index.ts deleted file mode 100644 index 5424799e6..000000000 --- a/sdk_legacy/js/src/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { FoundryLocalManager as FoundryLocalManagerBase } from './base.js' -import * as service from './service.js' - -import type { DeviceType, FoundryModelInfo, Fetch } from './types.js' - -/** - * Class representing the Foundry Local Manager. - */ -export class FoundryLocalManager extends FoundryLocalManagerBase { - /** - * The service URL for the Foundry service. - */ - protected _serviceUrl: string | null = null - - /** - * Constructs a new FoundryLocalManager instance - * @param {Object} [options] - Optional configuration options for the FoundryLocalManager. - * @param {Fetch} [options.fetch] - Optional custom fetch implementation to use for HTTP requests. - * If not provided, the global fetch will be used. - */ - constructor({ fetch: overriddenFetch = fetch }: { fetch?: Fetch } = {}) { - service.assertFoundryAvailable() - super({ - serviceUrl: '', - fetch: overriddenFetch, - }) - } - - /** - * Gets the service URL. - * @throws {Error} If the service URL is not set. - * @returns {string} The service URL. - */ - get serviceUrl(): string { - if (this._serviceUrl) { - return this._serviceUrl - } - throw new Error('Service URL is not set. Please start the service first.') - } - - /** - * Initializes the Foundry Local Manager with a model. - * @param {string | null} aliasOrModelId - The alias or ID of the model to initialize with. - * @param {DeviceType} [device] - Optional device type to filter models. - * @returns {Promise} The model information if initialized, otherwise null. - */ - async init(aliasOrModelId: string | null, device?: DeviceType): Promise { - await this.startService() - - if (aliasOrModelId) { - await this.downloadModel(aliasOrModelId, device) - const modelInfo = await this.loadModel(aliasOrModelId, device) - return modelInfo - } - return null - } - - /** - * Checks if the Foundry service is running. - * @returns {Promise} True if the service is running, otherwise false. - */ - async isServiceRunning(): Promise { - this._serviceUrl = await service.getServiceUrl() - return this._serviceUrl !== null - } - - /** - * Starts the Foundry service. - * @throws {Error} If the service fails to start. - * @returns {Promise} Resolves when the service is successfully started. - */ - async startService(): Promise { - this._serviceUrl = await service.startService() - if (!this._serviceUrl) { - throw new Error('Failed to start the service.') - } - } -} - -export * from './types.js' diff --git a/sdk_legacy/js/src/service.ts b/sdk_legacy/js/src/service.ts deleted file mode 100644 index cf5ec4a0c..000000000 --- a/sdk_legacy/js/src/service.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { exec, execSync } from 'child_process' - -/** - * Ensures that Foundry is available in the system's PATH. - * Throws an error if Foundry is not installed or not found in the PATH. - */ -export function assertFoundryAvailable(): void { - try { - if (process.platform === 'win32') { - execSync('where foundry') - } else { - execSync('which foundry') - } - } catch (error) { - throw new Error('Foundry is not installed or not on PATH!') - } -} - -/** - * Retrieves the service URL by executing the `foundry service status` command. - * @returns {Promise} The service URL if found, otherwise null. - */ -export async function getServiceUrl(): Promise { - try { - const stdout = await new Promise((resolve, reject) => { - exec('foundry service status', (err: Error | null, stdout: string) => { - if (err) { - reject(err) - return - } - resolve(stdout) - }) - }) - const match = stdout.match(/http:\/\/(?:[a-zA-Z0-9.-]+|\d{1,3}(\.\d{1,3}){3}):\d+/) - if (match) { - return match[0] - } - return null - } catch (error) { - console.error('Error getting service status:', error) - return null - } -} - -/** - * Starts the Foundry service. - * @returns {Promise} The service URL if successfully started, otherwise null. - */ -export async function startService(): Promise { - let serviceUrl = await getServiceUrl() - if (serviceUrl !== null) { - return serviceUrl - } - - // Start the service without waiting for stdout, it never completes - exec('foundry service start', (err: Error | null) => { - if (err) { - console.error('Error starting service:', err.message) - return null - } - }) - - // Retry loop to check for service URL availability - let retries = 10 - while (retries > 0) { - serviceUrl = await getServiceUrl() - if (serviceUrl !== null) { - return serviceUrl - } - - // Wait 100ms before trying again - await new Promise((resolve) => setTimeout(resolve, 100)) - retries-- - } - - console.warn('Foundry service did not start within the expected time. May not be running.') - return null -} diff --git a/sdk_legacy/js/src/types.ts b/sdk_legacy/js/src/types.ts deleted file mode 100644 index 822325772..000000000 --- a/sdk_legacy/js/src/types.ts +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/** - * Type alias for the fetch function. - */ -export type Fetch = typeof fetch - -/** - * Enum representing the types of devices. - */ -export enum DeviceType { - CPU = 'CPU', - GPU = 'GPU', - NPU = 'NPU', -} - -/** - * Enum representing the execution providers. - */ -export enum ExecutionProvider { - CPU = 'CPUExecutionProvider', - WEBGPU = 'WebGpuExecutionProvider', - CUDA = 'CUDAExecutionProvider', -} - -/** - * Interface representing the runtime configuration of a model. - */ -export interface ModelRuntime { - /** - * The type of device used for execution. - */ - deviceType: DeviceType - - /** - * The execution provider used for running the model. - */ - executionProvider: string -} - -/** - * Interface representing the response model for a Foundry list operation. - */ -export interface FoundryListResponseModel { - /** - * The name of the model. - */ - name: string - - /** - * The display name of the model. - */ - displayName: string - - /** - * The type of the model. - */ - modelType: string - - /** - * The provider type of the model. - */ - providerType: string - - /** - * The URI of the model. - */ - uri: string - - /** - * The version of the model. - */ - version: string - - /** - * The prompt template associated with the model. - */ - promptTemplate: Record - - /** - * The publisher of the model. - */ - publisher: string - - /** - * The task the model is designed for. - */ - task: string - - /** - * The runtime configuration of the model. - */ - runtime: ModelRuntime - - /** - * The file size of the model in megabytes. - */ - fileSizeMb: number - - /** - * The settings of the model. - */ - modelSettings: Record[]> - - /** - * The alias of the model. - */ - alias: string - - /** - * Indicates whether the model supports tool calling. - */ - supportsToolCalling: boolean - - /** - * The license of the model. - */ - license: string - - /** - * The description of the license. - */ - licenseDescription: string - - /** - * The URI of the parent model. - */ - parentModelUri: string - - /** - * The maximum number of output tokens. - */ - maxOutputTokens: number - - /** - * The minimum Foundry Local version required to use this model. - */ - minFLVersion: string -} - -/** - * Interface representing information about a Foundry model. - */ -export interface FoundryModelInfo { - /** - * The alias of the model. - */ - alias: string - - /** - * The unique identifier of the model. - */ - id: string - - /** - * The version of the model. - */ - version: string - - /** - * The execution provider used for the model. - */ - executionProvider: string - - /** - * The device type used for the model. - */ - deviceType: DeviceType - - /** - * The URI of the model. - */ - uri: string - - /** - * The size of the model in megabytes. - */ - modelSize: number - - /** - * Indicates whether the model supports tool calling. - */ - supportsToolCalling: boolean - - /** - * The prompt template associated with the model. - */ - promptTemplate: Record - - /** - * The provider of the model. - */ - provider: string - - /** - * The publisher of the model. - */ - publisher: string - - /** - * The license of the model. - */ - license: string - - /** - * The task the model is designed for. - */ - task: string - - /** - * EP Override - */ - epOverride: string | null -} - -/** - * Interface representing the body of a download request. - */ -export interface DownloadBody { - /** - * The name of the model. - */ - Name: string - - /** - * The URI of the model. - */ - Uri: string - - /** - * The publisher of the model. - */ - Publisher: string - - /** - * The provider type of the model. - */ - ProviderType: string - - /** - * The prompt template associated with the model. - */ - PromptTemplate: Record -} diff --git a/sdk_legacy/js/src/version.ts b/sdk_legacy/js/src/version.ts deleted file mode 100644 index af04d914d..000000000 --- a/sdk_legacy/js/src/version.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Generated by genversion. -export const version = '0.5.1' diff --git a/sdk_legacy/js/test/base.test.ts b/sdk_legacy/js/test/base.test.ts deleted file mode 100644 index 4d2980788..000000000 --- a/sdk_legacy/js/test/base.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' -import { FoundryLocalManager } from '../src/base' -import * as client from '../src/client' -import { DeviceType, type FoundryModelInfo } from '../src/types' - -vi.mock('../src/client', () => ({ - get: vi.fn(), - postWithProgress: vi.fn(), -})) - -const MOCK_INFO = { - providerType: 'AzureFoundry', - version: '1', - modelType: 'ONNX', - promptTemplate: { prompt: '<|im_start|>user<|im_sep|>{Content}<|im_end|><|im_start|>assistant<|im_sep|>' }, - publisher: 'Microsoft', - task: 'chat-completion', - fileSizeMb: 10403, - modelSettings: { parameters: [] }, - supportsToolCalling: false, - license: 'MIT', - licenseDescription: 'License…', - maxOutputTokens: 1024, -} - -const MOCK_CATALOG_DATA = [ - // generic-gpu, generic-cpu (alias: model-1) - { - name: 'model-1-generic-gpu:1', - displayName: 'model-1-generic-gpu', - uri: 'azureml://registries/azureml/models/model-1-generic-gpu/versions/1', - runtime: { deviceType: 'GPU', executionProvider: 'WebGpuExecutionProvider' }, - alias: 'model-1', - parentModelUri: 'azureml://registries/azureml/models/model-1/versions/1', - ...MOCK_INFO, - }, - // newer CPU version also present - { - name: 'model-1-generic-cpu:2', - displayName: 'model-1-generic-cpu', - uri: 'azureml://registries/azureml/models/model-1-generic-cpu/versions/2', - runtime: { deviceType: 'CPU', executionProvider: 'CPUExecutionProvider' }, - alias: 'model-1', - parentModelUri: 'azureml://registries/azureml/models/model-1/versions/2', - ...MOCK_INFO, - }, - { - name: 'model-1-generic-cpu:1', - displayName: 'model-1-generic-cpu', - uri: 'azureml://registries/azureml/models/model-1-generic-cpu/versions/1', - runtime: { deviceType: 'CPU', executionProvider: 'CPUExecutionProvider' }, - alias: 'model-1', - parentModelUri: 'azureml://registries/azureml/models/model-1/versions/1', - ...MOCK_INFO, - }, - - // npu + generic-cpu (alias: model-2) - { - name: 'model-2-npu:2', - displayName: 'model-2-npu', - uri: 'azureml://registries/azureml/models/model-2-npu/versions/2', - runtime: { deviceType: 'NPU', executionProvider: 'QNNExecutionProvider' }, - alias: 'model-2', - parentModelUri: 'azureml://registries/azureml/models/model-2/versions/2', - ...MOCK_INFO, - }, - { - name: 'model-2-npu:1', - displayName: 'model-2-npu', - uri: 'azureml://registries/azureml/models/model-2-npu/versions/1', - runtime: { deviceType: 'NPU', executionProvider: 'QNNExecutionProvider' }, - alias: 'model-2', - parentModelUri: 'azureml://registries/azureml/models/model-2/versions/1', - ...MOCK_INFO, - }, - { - name: 'model-2-generic-cpu:1', - displayName: 'model-2-generic-cpu', - uri: 'azureml://registries/azureml/models/model-2-generic-cpu/versions/1', - runtime: { deviceType: 'CPU', executionProvider: 'CPUExecutionProvider' }, - alias: 'model-2', - parentModelUri: 'azureml://registries/azureml/models/model-2/versions/1', - ...MOCK_INFO, - }, - - // cuda-gpu + generic-gpu + generic-cpu (alias: model-3) - { - name: 'model-3-cuda-gpu:1', - displayName: 'model-3-cuda-gpu', - uri: 'azureml://registries/azureml/models/model-3-cuda-gpu/versions/1', - runtime: { deviceType: 'GPU', executionProvider: 'CUDAExecutionProvider' }, - alias: 'model-3', - parentModelUri: 'azureml://registries/azureml/models/model-3/versions/1', - ...MOCK_INFO, - }, - { - name: 'model-3-generic-gpu:1', - displayName: 'model-3-generic-gpu', - uri: 'azureml://registries/azureml/models/model-3-generic-gpu/versions/1', - runtime: { deviceType: 'GPU', executionProvider: 'WebGpuExecutionProvider' }, - alias: 'model-3', - parentModelUri: 'azureml://registries/azureml/models/model-3/versions/1', - ...MOCK_INFO, - }, - { - name: 'model-3-generic-cpu:1', - displayName: 'model-3-generic-cpu', - uri: 'azureml://registries/azureml/models/model-3-generic-cpu/versions/1', - runtime: { deviceType: 'CPU', executionProvider: 'CPUExecutionProvider' }, - alias: 'model-3', - parentModelUri: 'azureml://registries/azureml/models/model-3/versions/1', - ...MOCK_INFO, - }, - - // generic-gpu only (alias: model-4) - { - name: 'model-4-generic-gpu:1', - displayName: 'model-4-generic-gpu', - uri: 'azureml://registries/azureml/models/model-4-generic-gpu/versions/1', - runtime: { deviceType: 'GPU', executionProvider: 'WebGpuExecutionProvider' }, - alias: 'model-4', - parentModelUri: 'azureml://registries/azureml/models/model-4/versions/1', - ...MOCK_INFO, - promptTemplate: null, - newFeature: 'newValue', - minFLVersion: '1.0.0', - }, -] - -// Mock response for /openai/status -const MOCK_STATUS_RESPONSE = { modelDirPath: '/test/path/to/models' } -// Mock response for /openai/models -const MOCK_LOCAL_MODELS = ['model-2-npu:1', 'model-4-generic-gpu:1'] -// Mock response for /openai/loadedmodels -const MOCK_LOADED_MODELS = ['model-2-npu:1'] - -function makeJsonResponse(payload: any) { - return { json: vi.fn().mockResolvedValue(payload) } as any -} - -// Helper to set up /foundry/list with or without CUDA entries -function mockFoundryList(useCuda: boolean) { - const data = useCuda ? MOCK_CATALOG_DATA : MOCK_CATALOG_DATA.filter((m) => !m.name.includes('cuda')) - vi.mocked(client.get).mockImplementation(async (fetchFn, url) => { - if (url.endsWith('/foundry/list')) return makeJsonResponse(data) - if (url.endsWith('/openai/status')) return makeJsonResponse(MOCK_STATUS_RESPONSE) - if (url.endsWith('/openai/models')) return makeJsonResponse(MOCK_LOCAL_MODELS) - if (url.endsWith('/openai/loadedmodels')) return makeJsonResponse(MOCK_LOADED_MODELS) - // load/unload/download calls don’t need to return JSON in these tests - return {} as any - }) -} - -describe('FoundryLocalManager (base.ts)', () => { - let manager: FoundryLocalManager - const mockFetch = vi.fn() - - beforeEach(() => { - vi.resetAllMocks() - manager = new FoundryLocalManager({ serviceUrl: 'http://localhost:5273', fetch: mockFetch as any }) - }) - - afterEach(() => { - vi.restoreAllMocks() - }) - - describe('constructor & props', () => { - it('sets serviceUrl and fetch; endpoint/apiKey work', () => { - expect(manager['__proto__']).toBe(FoundryLocalManager.prototype) - expect(manager['fetch']).toBe(mockFetch) - expect(manager.serviceUrl).toBe('http://localhost:5273') - expect(manager.endpoint).toBe('http://localhost:5273/v1') - expect(manager.apiKey).toBe('OPENAI_API_KEY') - }) - - it('throws if serviceUrl missing', () => { - // @ts-expect-error force bad state for test - manager['_serviceUrl'] = null - expect(() => manager.serviceUrl).toThrow('Service URL is invalid') - }) - }) - - describe('listCatalogModels', () => { - it('maps Foundry list → FoundryModelInfo and caches; sets epOverride for generic-gpu when CUDA present', async () => { - mockFoundryList(true) // CUDA present - const models = await manager.listCatalogModels() - - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/foundry/list') - expect(Array.isArray(models)).toBe(true) - const ids = models.map((m) => m.id) - expect(ids).toEqual([ - 'model-1-generic-gpu:1', - 'model-1-generic-cpu:2', - 'model-1-generic-cpu:1', - 'model-2-npu:2', - 'model-2-npu:1', - 'model-2-generic-cpu:1', - 'model-3-cuda-gpu:1', - 'model-3-generic-gpu:1', - 'model-3-generic-cpu:1', - 'model-4-generic-gpu:1', - ]) - - // spot-check shape - const m0 = models[0] - expect(m0).toMatchObject>({ - alias: 'model-1', - id: 'model-1-generic-gpu:1', - version: '1', - executionProvider: 'WebGpuExecutionProvider', - deviceType: 'GPU', - provider: 'AzureFoundry', - publisher: 'Microsoft', - task: 'chat-completion', - }) - - // CUDA present → generic-gpu entries should have epOverride='cuda' - const genericGpu = models.filter((m) => m.id.includes('generic-gpu')) - expect(genericGpu.every((m) => m.epOverride === 'cuda')).toBe(true) - - // second call uses cache (no extra /foundry/list) - await manager.listCatalogModels() - expect((client.get as any).mock.calls.filter(([, url]: any[]) => url.endsWith('/foundry/list')).length).toBe(1) - }) - - it('no CUDA present → epOverride stays null', async () => { - mockFoundryList(false) - const models = await manager.listCatalogModels() - const genericGpu = models.filter((m) => m.id.includes('generic-gpu')) - expect(genericGpu.every((m) => m.epOverride == null)).toBe(true) - }) - - it('refreshCatalog clears cache', async () => { - mockFoundryList(true) - await manager.listCatalogModels() - await manager.refreshCatalog() - await manager.listCatalogModels() - // called twice total - expect((client.get as any).mock.calls.filter(([, url]: any[]) => url.endsWith('/foundry/list')).length).toBe(2) - }) - }) - - describe('getModelInfo (id/alias/device & Windows fallback)', () => { - async function setup(useCuda: boolean) { - mockFoundryList(useCuda) - await manager.listCatalogModels() - } - - it('returns exact ID if includes version; picks highest version for ID prefix', async () => { - await setup(true) - expect((await manager.getModelInfo('model-1-generic-cpu:1'))?.id).toBe('model-1-generic-cpu:1') - expect((await manager.getModelInfo('model-1-generic-cpu'))?.id).toBe('model-1-generic-cpu:2') - }) - - it('alias selection: prefers NPU > CUDA > WebGPU > CPU by availability; device filter works', async () => { - await setup(true) - - // alias default - expect((await manager.getModelInfo('model-2'))?.id).toBe('model-2-npu:2') - expect((await manager.getModelInfo('model-3'))?.id).toBe('model-3-cuda-gpu:1') - - // device filter - expect((await manager.getModelInfo('model-1', DeviceType.GPU))?.id).toBe('model-1-generic-gpu:1') - expect((await manager.getModelInfo('model-1', DeviceType.CPU))?.id).toBe('model-1-generic-cpu:2') - expect(await manager.getModelInfo('model-1', DeviceType.NPU)).toBeNull() - expect((await manager.getModelInfo('model-2', DeviceType.NPU))?.id).toBe('model-2-npu:2') - expect((await manager.getModelInfo('model-2', DeviceType.CPU))?.id).toBe('model-2-generic-cpu:1') - expect(await manager.getModelInfo('model-2', DeviceType.GPU)).toBeNull() - expect((await manager.getModelInfo('model-3', DeviceType.GPU))?.id).toBe('model-3-cuda-gpu:1') - expect((await manager.getModelInfo('model-3', DeviceType.CPU))?.id).toBe('model-3-generic-cpu:1') - expect(await manager.getModelInfo('model-3', DeviceType.NPU)).toBeNull() - }) - - it('unknown returns null; throwOnNotFound flips to error', async () => { - await setup(true) - expect(await manager.getModelInfo('unknown-model')).toBeNull() - await expect(manager.getModelInfo('unknown-model', undefined, true)).rejects.toThrow( - 'Model unknown-model not found in the catalog.', - ) - }) - - it('Windows fallback: if alias picks generic-gpu WITH NO override, prefer CPU variant when present', async () => { - // Force "no CUDA support" to keep epOverride null for generic-gpu - await setup(false) - - // Shim platform detection: process.platform = win32 - const orig = Object.getOwnPropertyDescriptor(process, 'platform') - Object.defineProperty(process, 'platform', { get: () => 'win32' }) - - try { - // alias model-1 has generic-gpu and generic-cpu; with no epOverride, Windows should pick CPU - expect((await manager.getModelInfo('model-1'))?.id).toBe('model-1-generic-cpu:2') - - // For model-3, since we removed CUDA entries, only generic-gpu/cpu remain → should pick CPU as well - expect((await manager.getModelInfo('model-3'))?.id).toBe('model-3-generic-cpu:1') - } finally { - if (orig) Object.defineProperty(process, 'platform', orig) - } - }) - }) - - describe('getCacheLocation', () => { - it('returns modelDirPath from /openai/status', async () => { - mockFoundryList(true) - const location = await manager.getCacheLocation() - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/openai/status') - expect(location).toBe('/test/path/to/models') - }) - }) - - describe('listCachedModels & listLoadedModels', () => { - beforeEach(async () => { - mockFoundryList(true) - await manager.listCatalogModels() - // spy getModelInfo to return catalog-mapped objects quickly - vi.spyOn(manager, 'getModelInfo').mockImplementation(async (idOrAlias: string) => { - const id = idOrAlias - const m = (await manager.listCatalogModels()).find((mm) => mm.id === id || mm.alias === idOrAlias) - return (m ?? null) as any - }) - }) - - it('listCachedModels returns mapped infos', async () => { - const models = await manager.listCachedModels() - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/openai/models') - const ids = models.map((m) => m.id) - expect(ids).toEqual(['model-2-npu:1', 'model-4-generic-gpu:1']) - }) - - it('listLoadedModels returns mapped infos', async () => { - const models = await manager.listLoadedModels() - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/openai/loadedmodels') - const ids = models.map((m) => m.id) - expect(ids).toEqual(['model-2-npu:1']) - }) - }) - - describe('downloadModel', () => { - beforeEach(async () => { - mockFoundryList(true) - await manager.listCatalogModels() - }) - - it('skips when already cached and force=false (calls onProgress(100))', async () => { - // Mock cache contains the target ID - vi.spyOn(manager, 'listCachedModels').mockResolvedValue([{ id: 'model-3-cuda-gpu:1' } as any]) - const onProgress = vi.fn() - const info = await manager.downloadModel('model-3', undefined, undefined, false, onProgress) - expect(info.id).toBe('model-3-cuda-gpu:1') - expect(onProgress).toHaveBeenCalledWith(100) - expect(client.postWithProgress).not.toHaveBeenCalled() - }) - - it('posts to /openai/download when not cached; providerType AzureFoundry → AzureFoundryLocal', async () => { - vi.spyOn(manager, 'listCachedModels').mockResolvedValue([]) - vi.mocked(client.postWithProgress).mockResolvedValue({ success: true } as any) - - const info = await manager.downloadModel('model-1-generic-cpu:2') - expect(info.id).toBe('model-1-generic-cpu:2') - - expect(client.postWithProgress).toHaveBeenCalledWith( - mockFetch, - 'http://localhost:5273/openai/download', - { - model: { - Name: 'model-1-generic-cpu:2', - Uri: 'azureml://registries/azureml/models/model-1-generic-cpu/versions/2', - Publisher: 'Microsoft', - ProviderType: 'AzureFoundryLocal', - PromptTemplate: MOCK_INFO.promptTemplate, - }, - IgnorePipeReport: true, - }, - undefined, - ) - }) - - it('throws on download error', async () => { - vi.spyOn(manager, 'listCachedModels').mockResolvedValue([]) - vi.mocked(client.postWithProgress).mockResolvedValue({ success: false, error: 'Download failed' } as any) - await expect(manager.downloadModel('model-1')).rejects.toThrow( - "Failed to download model with alias 'model-1' and ID 'model-1-generic-gpu:1': Download failed", - ) - }) - }) - - describe('isModelUpgradeable', () => { - beforeEach(async () => { - mockFoundryList(true) - await manager.listCatalogModels() - }) - - it('true when latest ID not present in cache', async () => { - // latest for model-1 alias is CPU:2 (highest version for that ID prefix) - vi.spyOn(manager, 'listCachedModels').mockResolvedValue([]) - expect(await manager.isModelUpgradeable('model-1')).toBe(true) - }) - - it('false when cached contains the exact latest ID', async () => { - vi.spyOn(manager, 'listCachedModels').mockResolvedValue([{ id: 'model-4-generic-gpu:1' } as any]) - expect(await manager.isModelUpgradeable('model-4')).toBe(false) - }) - - it('true when cached has an older version', async () => { - vi.spyOn(manager, 'listCachedModels').mockResolvedValue([{ id: 'model-2-npu:1' } as any]) - expect(await manager.isModelUpgradeable('model-2')).toBe(true) - }) - - it('false when version cannot be parsed', async () => { - // Spy getLatestModelInfo to return an ID with no version suffix - // @ts-expect-error accessing private for test via cast - vi.spyOn(manager as any, 'getLatestModelInfo').mockResolvedValue({ - id: 'model-invalid-version', - alias: 'model-invalid', - } as FoundryModelInfo) - vi.spyOn(manager, 'listCachedModels').mockResolvedValue([]) - expect(await manager.isModelUpgradeable('model-invalid-version')).toBe(false) - }) - }) - - describe('upgradeModel', () => { - beforeEach(async () => { - mockFoundryList(true) - await manager.listCatalogModels() - }) - - it('downloads latest (not in cache case)', async () => { - const latest = (await manager.getModelInfo('model-3')) as FoundryModelInfo // cuda - const dl = vi.spyOn(manager, 'downloadModel').mockResolvedValue(latest) - // @ts-expect-error test private by behavior - const res = await manager.upgradeModel('model-3') - expect(dl).toHaveBeenCalledWith(latest.id, undefined, undefined, false, undefined) - expect(res.id).toBe('model-3-cuda-gpu:1') - }) - - it('uses latest when older version in cache', async () => { - const latest = (await manager.getModelInfo('model-2')) as FoundryModelInfo // npu:2 - const dl = vi.spyOn(manager, 'downloadModel').mockResolvedValue(latest) - const res = await manager.upgradeModel('model-2-npu:1') - expect(dl).toHaveBeenCalledWith('model-2-npu:2', undefined, undefined, false, undefined) - expect(res.id).toBe('model-2-npu:2') - }) - - it('no re-download if already latest (call still targets latest id)', async () => { - const latest = (await manager.getModelInfo('model-4')) as FoundryModelInfo // single version - const dl = vi.spyOn(manager, 'downloadModel').mockResolvedValue(latest) - const res = await manager.upgradeModel('model-4') - expect(dl).toHaveBeenCalledWith('model-4-generic-gpu:1', undefined, undefined, false, undefined) - expect(res.id).toBe('model-4-generic-gpu:1') - }) - }) - - describe('loadModel', () => { - beforeEach(async () => { - vi.resetAllMocks() - }) - - it('loads with TTL only; no ep param when no override', async () => { - // No CUDA present → epOverride stays null for generic-gpu - mockFoundryList(false) - const info = await new FoundryLocalManager({ - serviceUrl: 'http://localhost:5273', - fetch: mockFetch as any, - }).loadModel('model-2') // picks model-2-npu:2 - expect(info.id).toBe('model-2-npu:2') - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/openai/load/model-2-npu:2', { - ttl: '600', - }) - }) - - it('adds ep=cuda for generic-gpu when CUDA support present (epOverride set by listCatalogModels)', async () => { - mockFoundryList(true) - // Force a fresh manager so it recomputes catalog and overrides - const mgr = new FoundryLocalManager({ serviceUrl: 'http://localhost:5273', fetch: mockFetch as any }) - // load alias 'model-4' → generic-gpu with epOverride='cuda' - const info = await mgr.loadModel('model-4') - expect(info.id).toBe('model-4-generic-gpu:1') - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/openai/load/model-4-generic-gpu:1', { - ttl: '600', - ep: 'cuda', - }) - }) - - it('throws a friendly message when not downloaded', async () => { - mockFoundryList(true) - - // Capture whatever behavior is currently configured for client.get - const original = vi.mocked(client.get).getMockImplementation() - - // Intercept only the load URL; delegate all else to original - vi.mocked(client.get).mockImplementation((fetchFn: any, url: string, query?: any) => { - if (url.includes('/openai/load/')) { - return Promise.reject(new Error('No OpenAIService provider found for modelName')) - } - return original ? original(fetchFn, url, query) : Promise.resolve({ json: async () => ({}) } as any) - }) - - await expect(manager.loadModel('model-3')).rejects.toThrow( - 'Model model-3 has not been downloaded yet. Please download it first.', - ) - }) - }) - - describe('unloadModel', () => { - beforeEach(async () => { - mockFoundryList(true) - await manager.listCatalogModels() - }) - - it('unloads when loaded', async () => { - // model-2-npu:1 is in MOCK_LOADED_MODELS - await manager.unloadModel('model-2-npu:1') - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/openai/unload/model-2-npu:1', { - force: 'false', - }) - }) - - it('no-op when not loaded', async () => { - vi.mocked(client.get).mockClear() - await manager.unloadModel('model-4') // not in loaded list - const calls = (client.get as any).mock.calls.map(([, url]: any[]) => url) - expect(calls.some((u: string) => u.endsWith('/openai/unload/model-4-generic-gpu:1'))).toBe(false) - }) - - it('force unload', async () => { - await manager.unloadModel('model-2-npu:1', undefined, true as any) - expect(client.get).toHaveBeenCalledWith(mockFetch, 'http://localhost:5273/openai/unload/model-2-npu:1', { - force: 'true', - }) - }) - }) -}) diff --git a/sdk_legacy/js/test/client.test.ts b/sdk_legacy/js/test/client.test.ts deleted file mode 100644 index ab9c0405a..000000000 --- a/sdk_legacy/js/test/client.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { describe, expect, it, vi, beforeEach } from 'vitest' -import * as client from '../src/client' -import type { Fetch } from '../src/types' -import { version } from '../src/version' - -describe('Client', () => { - const mockFetch = vi.fn() as unknown as Fetch - const mockResponse = { - ok: true, - json: vi.fn(), - text: vi.fn(), - body: { getReader: vi.fn() }, - status: 200, - } - - beforeEach(() => { - vi.restoreAllMocks() - mockFetch.mockReset() - mockResponse.ok = true - mockResponse.status = 200 - mockResponse.text.mockReset() - mockResponse.json.mockReset() - mockResponse.body.getReader.mockReset() - mockFetch.mockResolvedValue(mockResponse as unknown as Response) - }) - - describe('get', () => { - it('calls fetch with correct URL', async () => { - await client.get(mockFetch, 'http://example.com') - expect(mockFetch).toHaveBeenCalledWith('http://example.com', undefined) - }) - - it('appends query params', async () => { - await client.get(mockFetch, 'http://example.com', { a: '1', b: '2' }) - expect(mockFetch).toHaveBeenCalledWith('http://example.com?a=1&b=2', undefined) - }) - }) - - describe('postWithProgress', () => { - let mockReader: any - let onProgress: ReturnType - - beforeEach(() => { - onProgress = vi.fn() - mockReader = { read: vi.fn() } - mockResponse.body.getReader.mockReturnValue(mockReader) - }) - - it('sends POST with JSON', async () => { - const body = { key: 'value' } - - mockReader.read - .mockResolvedValueOnce({ done: false, value: new TextEncoder().encode('{"ok":true}') }) // valid JSON - .mockResolvedValueOnce({ done: true, value: undefined }) - - const result = await client.postWithProgress(mockFetch, 'http://example.com', body) - - expect(result).toEqual({ ok: true }) - expect(mockFetch).toHaveBeenCalledWith('http://example.com', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'User-Agent': `foundry-local-js-sdk/${version}`, - }, - body: JSON.stringify(body), - }) - }) - - it('reports progress if onProgress is given', async () => { - const mockOnProgress = vi.fn() - const progress1 = new TextEncoder().encode('Progress: 50%') - const progress2 = new TextEncoder().encode('Progress: 100%') - const finalJson1 = new TextEncoder().encode('{"done"') - const finalJson2 = new TextEncoder().encode(':true}') - - mockReader.read - .mockResolvedValueOnce({ done: false, value: progress1 }) - .mockResolvedValueOnce({ done: false, value: progress2 }) - .mockResolvedValueOnce({ done: false, value: finalJson1 }) - .mockResolvedValueOnce({ done: false, value: finalJson2 }) - .mockResolvedValueOnce({ done: true, value: undefined }) - - const result = await client.postWithProgress(mockFetch, 'http://example.com', {}, mockOnProgress) - - expect(mockOnProgress).toHaveBeenCalledWith(50) - expect(mockOnProgress).toHaveBeenCalledWith(100) - expect(result).toEqual({ done: true }) - }) - - it('parses final JSON response', async () => { - const jsonChunk = new TextEncoder().encode('{"message":"ok"}') - mockReader.read - .mockResolvedValueOnce({ done: false, value: jsonChunk }) - .mockResolvedValueOnce({ done: true, value: undefined }) - - const result = await client.postWithProgress(mockFetch, 'http://example.com') - expect(result).toEqual({ message: 'ok' }) - }) - - it('throws on invalid JSON', async () => { - mockReader.read - .mockResolvedValueOnce({ done: false, value: new TextEncoder().encode('{invalid') }) - .mockResolvedValueOnce({ done: true, value: undefined }) - - await expect(client.postWithProgress(mockFetch, 'http://example.com')).rejects.toThrow( - /Error parsing JSON response/, - ) - }) - - it('throws if no JSON is received', async () => { - mockReader.read.mockResolvedValueOnce({ done: true, value: undefined }) - - await expect(client.postWithProgress(mockFetch, 'http://example.com')).rejects.toThrow('No JSON data received!') - }) - }) -}) diff --git a/sdk_legacy/js/test/index.test.ts b/sdk_legacy/js/test/index.test.ts deleted file mode 100644 index 2f57bcd9a..000000000 --- a/sdk_legacy/js/test/index.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { describe, expect, it, vi, beforeEach } from 'vitest' -import { FoundryLocalManager } from '../src/index' -import * as service from '../src/service' -import { FoundryModelInfo, ExecutionProvider } from '../src/types' - -// Mock service module -vi.mock('../src/service', () => ({ - assertFoundryAvailable: vi.fn(), - getServiceUrl: vi.fn(), - startService: vi.fn(), -})) - -// Mock the base class -vi.mock('../src/base', () => { - return { - FoundryLocalManager: vi.fn().mockImplementation(() => ({ - downloadModel: vi.fn(), - loadModel: vi.fn(), - })), - } -}) - -describe('FoundryLocalManager (index)', () => { - let manager: FoundryLocalManager - - beforeEach(() => { - vi.resetAllMocks() - manager = new FoundryLocalManager() - - // Ensure these are spies in all tests that reference them - manager.downloadModel = vi.fn() - manager.loadModel = vi.fn() - }) - - describe('constructor', () => { - it('should check if foundry is available', () => { - expect(service.assertFoundryAvailable).toHaveBeenCalled() - }) - }) - - describe('serviceUrl', () => { - it('should return the service URL if set', () => { - // Accessing private field for testing - manager['_serviceUrl'] = 'http://localhost:5273' - expect(manager.serviceUrl).toBe('http://localhost:5273') - }) - - it('should throw error if service URL is not set', () => { - manager['_serviceUrl'] = null - expect(() => manager.serviceUrl).toThrow('Service URL is not set') - }) - }) - - describe('isServiceRunning', () => { - it('should return true if service URL is found', async () => { - vi.mocked(service.getServiceUrl).mockResolvedValue('http://localhost:5273') - - const result = await manager.isServiceRunning() - - expect(service.getServiceUrl).toHaveBeenCalled() - expect(manager['_serviceUrl']).toBe('http://localhost:5273') - expect(result).toBe(true) - }) - - it('should return false if service URL is not found', async () => { - vi.mocked(service.getServiceUrl).mockResolvedValue(null) - - const result = await manager.isServiceRunning() - - expect(service.getServiceUrl).toHaveBeenCalled() - expect(manager['_serviceUrl']).toBeNull() - expect(result).toBe(false) - }) - }) - - describe('startService', () => { - it('should set service URL if service starts successfully', async () => { - vi.mocked(service.startService).mockResolvedValue('http://localhost:5273') - - await manager.startService() - - expect(service.startService).toHaveBeenCalled() - expect(manager['_serviceUrl']).toBe('http://localhost:5273') - }) - - it('should throw if service fails to start', async () => { - vi.mocked(service.startService).mockResolvedValue(null) - - await expect(manager.startService()).rejects.toThrow('Failed to start the service') - }) - }) - - describe('init', () => { - it('should initialize with a model', async () => { - vi.mocked(service.startService).mockResolvedValue('http://localhost:5273') - - const mockModelInfo: FoundryModelInfo = { - alias: 'model_alias', - id: 'model_name', - version: '1', - runtime: ExecutionProvider.CPU, - uri: 'azureml://registries/azureml/models/model_name/versions/1', - modelSize: 10403, - promptTemplate: { prompt: '<|start|>{Content}<|end|>' }, - provider: 'AzureFoundry', - publisher: 'Microsoft', - license: 'MIT', - task: 'chat-completion', - } - - vi.mocked(manager.downloadModel).mockResolvedValue(undefined) - vi.mocked(manager.loadModel).mockResolvedValue(mockModelInfo) - - const result = await manager.init('model_alias') - - expect(service.startService).toHaveBeenCalled() - expect(manager.downloadModel).toHaveBeenCalledWith('model_alias', undefined) - expect(manager.loadModel).toHaveBeenCalledWith('model_alias', undefined) - expect(result).toEqual(mockModelInfo) - }) - - it('should initialize without a model', async () => { - vi.mocked(service.startService).mockResolvedValue('http://localhost:5273') - - const result = await manager.init(null) - - expect(service.startService).toHaveBeenCalled() - expect(manager.downloadModel).not.toHaveBeenCalled() - expect(manager.loadModel).not.toHaveBeenCalled() - expect(result).toBeNull() - }) - }) -}) diff --git a/sdk_legacy/js/test/service.test.ts b/sdk_legacy/js/test/service.test.ts deleted file mode 100644 index 9f9c6924e..000000000 --- a/sdk_legacy/js/test/service.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { describe, expect, it, vi, beforeEach } from 'vitest' -import * as service from '../src/service' -import * as child_process from 'child_process' - -vi.mock('child_process', () => ({ - exec: vi.fn(), - execSync: vi.fn(), -})) - -describe('Service', () => { - beforeEach(() => { - vi.restoreAllMocks() - vi.resetAllMocks() - }) - - describe('assertFoundryAvailable', () => { - it('should not throw error if foundry is available on Windows', () => { - vi.spyOn(process, 'platform', 'get').mockReturnValue('win32') - vi.mocked(child_process.execSync).mockReturnValue(Buffer.from('C:\\foundry.exe')) - - expect(() => service.assertFoundryAvailable()).not.toThrow() - expect(child_process.execSync).toHaveBeenCalledWith('where foundry') - }) - - it('should not throw error if foundry is available on non-Windows', () => { - vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin') - vi.mocked(child_process.execSync).mockReturnValue(Buffer.from('/usr/local/bin/foundry')) - - expect(() => service.assertFoundryAvailable()).not.toThrow() - expect(child_process.execSync).toHaveBeenCalledWith('which foundry') - }) - - it('should throw error if foundry is not available', () => { - vi.mocked(child_process.execSync).mockImplementation(() => { - throw new Error('Command failed') - }) - - expect(() => service.assertFoundryAvailable()).toThrow('Foundry is not installed or not on PATH') - }) - }) - - describe('getServiceUrl', () => { - it('should extract service URL from command output', async () => { - vi.mocked(child_process.exec).mockImplementation((cmd, callback) => { - callback(null, 'Model management service is running on http://localhost:5273/openai/status') - }) - - const url = await service.getServiceUrl() - expect(url).toBe('http://localhost:5273') - }) - - it('should handle IP address in service URL', async () => { - vi.mocked(child_process.exec).mockImplementation((cmd, callback) => { - callback(null, 'Model management service is running on http://127.0.0.1:5273/openai/status') - }) - - const url = await service.getServiceUrl() - expect(url).toBe('http://127.0.0.1:5273') - }) - - it('should return null if no URL is found in output', async () => { - vi.mocked(child_process.exec).mockImplementation((cmd, callback) => { - callback(null, 'Model management service is not running!') - }) - - const url = await service.getServiceUrl() - expect(url).toBeNull() - }) - - it('should return null if command fails', async () => { - vi.spyOn(console, 'error').mockImplementation(() => {}) - vi.mocked(child_process.exec).mockImplementation((cmd, callback) => { - callback(new Error('Command failed'), '') - }) - - const url = await service.getServiceUrl() - expect(url).toBeNull() - expect(console.error).toHaveBeenCalled() - }) - }) - - describe('startService', () => { - it('should return existing service URL if service is already running', async () => { - vi.mocked(child_process.exec).mockImplementation((cmd, callback) => { - callback(null, 'Model management service is running on http://localhost:5273/openai/status') - }) - - const url = await service.startService() - - expect(url).toBe('http://localhost:5273') - expect(child_process.exec).not.toHaveBeenCalledWith('foundry service start', expect.any(Function)) - }) - - it('should start service and return URL after retry', async () => { - vi.useFakeTimers() - - let callCount = 0 - vi.mocked(child_process.exec).mockImplementation((cmd, callback) => { - if (cmd === 'foundry service status') { - if (callCount++ === 0) { - callback(null, 'Model management service is not running!') - } else { - callback(null, 'Model management service is running on http://localhost:5273/openai/status') - } - } else if (cmd === 'foundry service start') { - callback(null) - } - }) - - const promise = service.startService() - await vi.advanceTimersByTimeAsync(100) - const url = await promise - - expect(url).toBe('http://localhost:5273') - expect(child_process.exec).toHaveBeenCalledWith('foundry service start', expect.any(Function)) - - vi.useRealTimers() - }) - - it('should return null if service fails to start after retries', async () => { - vi.useFakeTimers() - vi.spyOn(console, 'warn').mockImplementation(() => {}) - - // Always return status output with no URL - vi.mocked(child_process.exec).mockImplementation((cmd, callback) => { - if (cmd === 'foundry service status') { - callback(null, 'Model management service is not running!') - } else if (cmd === 'foundry service start') { - callback(null) - } - }) - - const promise = service.startService() - await vi.advanceTimersByTimeAsync(1000) // 10 * 100ms retry delay - const url = await promise - - expect(url).toBeNull() - expect(child_process.exec).toHaveBeenCalledWith('foundry service start', expect.any(Function)) - expect(console.warn).toHaveBeenCalledWith( - 'Foundry service did not start within the expected time. May not be running.', - ) - - vi.useRealTimers() - }) - }) -}) diff --git a/sdk_legacy/js/tsconfig.json b/sdk_legacy/js/tsconfig.json deleted file mode 100644 index 7ec286f25..000000000 --- a/sdk_legacy/js/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "noImplicitAny": false, - "noImplicitThis": true, - "strictNullChecks": true, - "esModuleInterop": true, - "declaration": true, - "declarationMap": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "module": "ES2022", - "outDir": "./dist", - "target": "ES6", - "lib": ["es6", "es2018.asyncgenerator", "dom"] - }, - - "ts-node": { - "swc": true, - "esm": true - }, - - "include": ["./src/**/*.ts"], - - "exclude": ["node_modules"] -} diff --git a/sdk_legacy/python/.lintrunner.toml b/sdk_legacy/python/.lintrunner.toml deleted file mode 100644 index d4ed9e796..000000000 --- a/sdk_legacy/python/.lintrunner.toml +++ /dev/null @@ -1,142 +0,0 @@ -# Configuration for lintrunner https://github.com/suo/lintrunner -# You can install the dependencies and initialize with -# -# ```sh -# pip install lintrunner lintrunner-adapters -# lintrunner init -# ``` -# -# This will install lintrunner on your system and download all the necessary -# dependencies to run linters locally. -# If you want to see what lintrunner init will install, run -# `lintrunner init --dry-run`. -# -# To lint local changes: -# -# ```bash -# lintrunner -# ``` -# -# To lint all files: -# -# ```bash -# lintrunner --all-files -# ``` -# -# To format files: -# -# ```bash -# lintrunner f --all-files -# ``` -# -# To read more about lintrunner, see [wiki](https://github.com/pytorch/pytorch/wiki/lintrunner). - -merge_base_with = 'main' - -[[linter]] -code = 'RUFF' -include_patterns = [ - '**/*.py', - '**/*.pyi', -] -exclude_patterns = [ -] -command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'ruff_linter', - '--config=pyproject.toml', - '@{{PATHSFILE}}' -] -init_command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'pip_init', - '--dry-run={{DRYRUN}}', - '--requirement=requirements-lintrunner.txt', -] -is_formatter = true - - -[[linter]] -code = 'RUFF-FORMAT' -include_patterns = [ - '**/*.py', -] -exclude_patterns = [ -] -command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'ruff_format_linter', - '--', - '@{{PATHSFILE}}' -] -init_command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'pip_init', - '--dry-run={{DRYRUN}}', - '--requirement=requirements-lintrunner.txt', -] -is_formatter = true - -[[linter]] -code = 'PYLINT' -include_patterns = [ - '**/*.py', -] -exclude_patterns = [ -] -command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'pylint_linter', - '--rcfile=pyproject.toml', - '--show-disable', - '--', - '@{{PATHSFILE}}' -] -init_command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'pip_init', - '--dry-run={{DRYRUN}}', - '--requirement=requirements-lintrunner.txt', -] - -[[linter]] -code = 'EDITORCONFIG-CHECKER' -include_patterns = ['**'] -exclude_patterns = [ -] -command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'editorconfig_checker_linter', - '--', - '@{{PATHSFILE}}' -] -init_command = [ - 'python', - '-m', - 'lintrunner_adapters', - 'run', - 'pip_init', - '--dry-run={{DRYRUN}}', - '--requirement=requirements-lintrunner.txt', -] diff --git a/sdk_legacy/python/LICENSE.txt b/sdk_legacy/python/LICENSE.txt deleted file mode 100644 index 48bc6bb49..000000000 --- a/sdk_legacy/python/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/sdk_legacy/python/README.md b/sdk_legacy/python/README.md deleted file mode 100644 index 9642411de..000000000 --- a/sdk_legacy/python/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Foundry Local Python SDK -The Foundry Local SDK simplifies AI model management in local environments by providing control-plane operations separate from data-plane inferencing code. - -## Prerequisites -Foundry Local must be installed and findable in your PATH. - -## Getting Started -``` -pip install foundry-local-sdk -``` - -## Usage - -The SDK provides a simple interface to interact with the Foundry Local API. You can use it to manage models, check the status of the service, and make requests to the models. - -### Bootstrapping - -The SDK can *bootstrap* Foundry Local, which will initiate the following sequence: - -1. Start the Foundry Local service, if it is not already running. -1. Automatically detect the hardware and software requirements for the model. -1. Download the highest-performance model for the detected hardware, if it is not already downloaded. -1. Load the model into memory. - -To use the SDK with bootstrapping, you can use the following code: - -```python -from foundry_local import FoundryLocalManager - -# Provide the alias of the model you want to use -# The alias is a string that identifies the model in the Foundry Local catalog. -# The alias can be found in the Foundry Local catalog. -# For example, "phi-3.5-mini" is the alias for the Phi 3.5 model. -# Foundry local will automatically download the most suitable model for your hardware. -alias = "phi-3.5-mini" -fl_manager = FoundryLocalManager(alias) - -# check that the service is running -print(fl_manager.is_service_running()) - -# list all available models in the catalog -print(fl_manager.list_catalog_models()) - -# list all downloaded models -print(fl_manager.list_cached_models()) - -# get information on the selected model -model_info = fl_manager.get_model_info(alias) -print(model_info) -``` - -Alternatively, you can use the `FoundryLocalManager` class to manage the service and models manually. This is useful if you want to control the service and models without bootstrapping. For example, you want to present to the end user what is happening in the background. - -```python -from foundry_local import FoundryLocalManager - -alias = "phi-3.5-mini" -fl_manager = FoundryLocalManager() - -# start the service -fl_manager.start_service() - -# download the model -fl_manager.download_model(alias) - -# load the model -model_info = fl_manager.load_model(alias) -print(model_info) -``` - -Use the foundry local endpoint with an OpenAI compatible API client. For example, using the `openai` package: - -```python -import openai -from foundry_local import FoundryLocalManager - -# By using an alias, the most suitable model will be downloaded -# to your end-user's device. -alias = "phi-3.5-mini" - -# Create a FoundryLocalManager instance. This will start the Foundry -# Local service if it is not already running and load the specified model. -manager = FoundryLocalManager(alias) - -# The remaining code uses the OpenAI Python SDK to interact with the local model. - -# Configure the client to use the local Foundry service -client = openai.OpenAI( - base_url=manager.endpoint, - api_key=manager.api_key # API key is not required for local usage -) - -# Set the model to use and generate a streaming response -stream = client.chat.completions.create( - model=manager.get_model_info(alias).id, - messages=[{"role": "user", "content": "What is the golden ratio?"}], - stream=True -) - -# Print the streaming response -for chunk in stream: - if chunk.choices[0].delta.content is not None: - print(chunk.choices[0].delta.content, end="", flush=True) -``` diff --git a/sdk_legacy/python/foundry_local/__init__.py b/sdk_legacy/python/foundry_local/__init__.py deleted file mode 100644 index d05ca567c..000000000 --- a/sdk_legacy/python/foundry_local/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -import logging -import sys - -from foundry_local.api import FoundryLocalManager -from foundry_local.version import __version__ - -_logger = logging.getLogger(__name__) -_logger.setLevel(logging.WARNING) - -_sc = logging.StreamHandler(stream=sys.stdout) -_formatter = logging.Formatter( - "[foundry-local] | %(asctime)s | %(levelname)-8s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S" -) -_sc.setFormatter(_formatter) -_logger.addHandler(_sc) -_logger.propagate = False - -__all__ = ["FoundryLocalManager", "__version__"] diff --git a/sdk_legacy/python/foundry_local/api.py b/sdk_legacy/python/foundry_local/api.py deleted file mode 100644 index aefb432a5..000000000 --- a/sdk_legacy/python/foundry_local/api.py +++ /dev/null @@ -1,436 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from __future__ import annotations - -import logging -import os -import platform - -from httpx import Timeout - -from foundry_local.client import HttpResponseError, HttpxClient -from foundry_local.models import DeviceType, ExecutionProvider, FoundryModelInfo -from foundry_local.service import assert_foundry_installed, get_service_uri, start_service - -logger = logging.getLogger(__name__) - - -class FoundryLocalManager: - """Manager for Foundry Local SDK operations.""" - - def __init__( - self, - alias_or_model_id: str | None = None, - device: DeviceType | None = None, - bootstrap: bool = True, - timeout: float | Timeout | None = None, - ): - """ - Initialize the Foundry Local SDK. - - Args: - alias_or_model_id (str | None): Alias or Model ID to download and load. Only used if bootstrap is True. - device (DeviceType | None): Optional device type to filter the models. Only used if bootstrap is True. - bootstrap (bool): If True, start the service if it is not running. - timeout (float | Timeout | None): Timeout for the HTTP client. Default is None. - """ - assert_foundry_installed() - self._timeout = timeout - self._service_uri = None - self._httpx_client = None - self._set_service_uri_and_client(get_service_uri()) - self._catalog_list = None - if bootstrap: - self.start_service() - if alias_or_model_id is not None: - self.download_model(alias_or_model_id, device=device) - self.load_model(alias_or_model_id, device=device) - - def _set_service_uri_and_client(self, service_uri: str | None): - """ - Set the service URI and HTTP client. - - Args: - service_uri (str | None): URI of the Foundry service. - """ - self._service_uri = service_uri - self._httpx_client = HttpxClient(service_uri, timeout=self._timeout) if service_uri else None - - @property - def service_uri(self) -> str: - """ - Get the service URI. - - Returns: - str: URI of the Foundry service. - - Raises: - RuntimeError: If the service URI is not set. - """ - if self._service_uri is None: - raise RuntimeError("Service URI is not set. Please start the service first.") - return self._service_uri - - @property - def httpx_client(self) -> HttpxClient: - """ - Get the HTTP client. - - Returns: - HttpxClient: HTTP client instance. - - Raises: - RuntimeError: If the HTTP client is not set. - """ - if self._httpx_client is None: - raise RuntimeError("Httpx client is not set. Please start the service first.") - return self._httpx_client - - @property - def endpoint(self) -> str: - """ - Get the endpoint for the service. - - Returns: - str: Endpoint URL. - """ - return f"{self.service_uri}/v1" - - @property - def api_key(self) -> str: - """ - Get the API key for authentication. - - Returns: - str: API key. - """ - return os.getenv("OPENAI_API_KEY") or "OPENAI_API_KEY" - - # Service management api - def is_service_running(self) -> bool: - """ - Check if the service is running. Will also set the service URI if it is not set. - - Returns: - bool: True if the service is running, False otherwise. - """ - self._set_service_uri_and_client(get_service_uri()) - return self._service_uri is not None - - def start_service(self): - """Start the service.""" - self._set_service_uri_and_client(start_service()) - - # Catalog api - def list_catalog_models(self) -> list[FoundryModelInfo]: - """ - Get a list of available models in the catalog. - - Returns: - list[FoundryModelInfo]: List of catalog models. - """ - if self._catalog_list is None: - self._catalog_list = [ - FoundryModelInfo.from_list_response(model) for model in self.httpx_client.get("/foundry/list") - ] - # override ep to cuda for generic-gpu models if cuda is available - if any(mi.execution_provider == ExecutionProvider.CUDA for mi in self._catalog_list): - for model in self._catalog_list: - if "-generic-gpu:" in model.id: - model.ep_override = ExecutionProvider.CUDA.get_alias() - return self._catalog_list - - def _get_version(self, model_id: str) -> int: - """ - Extract numeric version from ID (e.g. model-x:3 → 3) - - Args: - model_id (str): Model ID. - - Returns: - int: Numeric version extracted from the model ID, or -1 if not found. - """ - try: - return int(model_id.split(":")[-1]) - except (ValueError, IndexError): - return -1 - - def refresh_catalog(self): - """Refresh the catalog.""" - self._catalog_list = None - - def get_model_info( - self, alias_or_model_id: str, device: DeviceType | None = None, raise_on_not_found: bool = False - ) -> FoundryModelInfo | None: - """ - Get the model information of the latest model that matches the given alias or ID. - - Args: - alias_or_model_id (str): Alias or Model ID. If it is an alias, the most preferred model will be returned. - If it is a model ID, it can contain a ":" suffix or not. - device (DeviceType | None): Optional device type to filter the models. If None, no filtering is applied. - raise_on_not_found (bool): If True, raise an error if the model is not found. Default is False. - - Returns: - FoundryModelInfo | None: Model information or None if not found. - - Raises: - ValueError: If the model is not found and raise_on_not_found is True. - """ - catalog = self.list_catalog_models() - key = alias_or_model_id.lower() - - # 1) Try to match by full ID exactly (with or without ':' for backwards compatibility) - model_info = next((m for m in catalog if m.id.lower() == key), None) - if model_info is not None: - return model_info - - # 2) Try to match by ID prefix ":" and pick the highest version - prefix = f"{key}:" - best_version = -1 - best_model: FoundryModelInfo | None = None - for m in catalog: - if m.id.lower().startswith(prefix): - version = self._get_version(m.id) - if version > best_version: - best_version = version - best_model = m - if best_model is not None: - return best_model - - # 3) Match by alias, optionally filtered by device - alias_matches = [m for m in catalog if m.alias.lower() == key] - if device is not None: - alias_matches = [m for m in alias_matches if m.device_type == device] - - # catalog/list is pre-sorted by: NPU → non-generic-GPU → generic-GPU → non-generic-CPU → CPU - candidate = alias_matches[0] if alias_matches else None - - # If device not specified and first pick is generic-GPU with no EP override on Windows, try CPU instead - if ( - candidate is not None - and device is None - and platform.system() == "Windows" - and "-generic-gpu:" in candidate.id - and candidate.ep_override is None - ): - cpu_fallback = next((m for m in alias_matches if m.device_type == DeviceType.CPU), None) - if cpu_fallback is not None: - candidate = cpu_fallback - - if candidate is None and raise_on_not_found: - raise ValueError(f"Model {alias_or_model_id} not found in the catalog.") - - return candidate - - def _get_latest_model_info( - self, alias_or_model_id: str, device: DeviceType | None = None, raise_on_not_found: bool = False - ) -> FoundryModelInfo | None: - """ - Get the latest model information by alias or model ID. - The difference from get_model_info is that this method will return the latest version of the model - even when you pass it a model id that contains a version suffix. - - Args: - alias_or_model_id (str): Alias or Model ID. If it is an alias, the most preferred model will be returned. - device (DeviceType | None): Optional device type to filter the models. If None, no filtering is applied. - raise_on_not_found (bool): If True, raise an error if the model is not found. Default is False. - - Returns: - FoundryModelInfo | None: Latest model information or None if not found. - - Raises: - ValueError: If the model is not found and raise_on_not_found is True. - """ - if not alias_or_model_id: - if raise_on_not_found: - raise ValueError("The provided nodel alias or ID was empty.") - return None - - # remove the ":" suffix if it exists, and use it to get the latest model - alias_or_name_without_version = alias_or_model_id.split(":")[0] - return self.get_model_info(alias_or_name_without_version, device=device, raise_on_not_found=raise_on_not_found) - - # Cache management api - def get_cache_location(self): - """ - Get the cache location. - - Returns: - str: Path to the cache location. - """ - return self.httpx_client.get("/openai/status")["modelDirPath"] - - def _fetch_model_infos(self, model_ids: list[str]) -> list[FoundryModelInfo]: - """ - Fetch model information for a list of model IDs. - - Args: - model_ids (list[str]): List of model IDs. - - Returns: - list[FoundryModelInfo]: List of model information. - """ - model_infos = [] - for model_id in model_ids: - if (model_info := self.get_model_info(model_id)) is not None: - model_infos.append(model_info) - else: - logger.debug("Model %s not found in the catalog.", model_id) - return model_infos - - def list_cached_models(self) -> list[FoundryModelInfo]: - """ - Get a list of cached models. - - Returns: - list[FoundryModelInfo]: List of models downloaded to the cache. - """ - return self._fetch_model_infos(self.httpx_client.get("/openai/models")) - - # Model management api - def download_model( - self, alias_or_model_id: str, device: DeviceType | None = None, token: str | None = None, force: bool = False - ) -> FoundryModelInfo: - """ - Download a model. - - Args: - alias_or_model_id (str): Alias or Model ID. If it is an alias, the most preferred model will be downloaded. - device (DeviceType | None): Optional device type to filter the models. If None, no filtering is applied. - token (str | None): Optional token for authentication. - force (bool): If True, force download the model even if it is already downloaded. - - Returns: - FoundryModelInfo: Model information. - - Raises: - RuntimeError: If the model download fails. - """ - model_info = self.get_model_info(alias_or_model_id, device=device, raise_on_not_found=True) - if model_info in self.list_cached_models() and not force: - logger.info( - "Model with alias '%s' and ID '%s' is already downloaded. Use force=True to download it again.", - model_info.alias, - model_info.id, - ) - return model_info - logger.info("Downloading model with alias '%s' and ID '%s'...", model_info.alias, model_info.id) - response_body = self.httpx_client.post_with_progress( - "/openai/download", - body={ - "model": model_info.to_download_body(), - "token": token, - "IgnorePipeReport": True, - }, - ) - if not response_body.get("success", False): - raise RuntimeError( - f"Failed to download model with error: {response_body.get('errorMessage', 'Unknown error')}" - ) - return model_info - - def is_model_upgradeable(self, alias_or_model_id: str, device: DeviceType | None = None) -> bool: - """ - Check if a newer version of a model is available. - - Args: - alias_or_model_id (str): Alias or Model ID. - device (DeviceType | None): Optional device type to filter the models. If None, no filtering is applied. - - Returns: - bool: True if a newer version is available, False otherwise. - - Raises: - ValueError: If the model is not found in the catalog. - """ - logger.info("Checking if model '%s' is upgradeable...", alias_or_model_id) - model_info = self._get_latest_model_info(alias_or_model_id, device=device, raise_on_not_found=True) - if model_info is None: - return False # Model not found in the catalog - - latest_version = self._get_version(model_info.id) - if latest_version == -1: - return False # Invalid model ID format - - cached_models = self.list_cached_models() - for cached_model in cached_models: - if cached_model.id == model_info.id and self._get_version(cached_model.id) == latest_version: - return False # Cached model is already at the latest version - - return True # The latest version is not in the cache - - def upgrade_model(self, alias_or_model_id: str, device: DeviceType | None = None, token: str | None = None) -> None: - """ - Download the latest version of a model to the local cache, if the latest version is not already cached. - - Args: - alias_or_model_id (str): Alias or Model ID. - device (DeviceType | None): Optional device type to filter the models. If None, no filtering is applied. - token (str | None): Optional token for authentication. - - Raises: - ValueError: If the model is not found in the catalog. - RuntimeError: If the model upgrade fails. - """ - model_info = self._get_latest_model_info(alias_or_model_id, device=device, raise_on_not_found=True) - return self.download_model(model_info.id, token=token) - - def load_model(self, alias_or_model_id: str, device: DeviceType | None = None, ttl: int = 600) -> FoundryModelInfo: - """ - Load a model. - - Args: - alias_or_model_id (str): Alias or Model ID. If it is an alias, the most preferred model will be loaded. - device (DeviceType | None): Optional device type to filter the models. If None, no filtering is applied. - ttl (int): Time to live for the model in seconds. Default is 600 seconds (10 minutes). - - - Returns: - FoundryModelInfo: Model information. - - Raises: - ValueError: If the model is not in the catalog or has not been downloaded yet. - """ - model_info = self.get_model_info(alias_or_model_id, device=device, raise_on_not_found=True) - logger.info("Loading model with alias '%s' and ID '%s'...", model_info.alias, model_info.id) - query_params = {"ttl": ttl, "ep": model_info.ep_override} - try: - self.httpx_client.get(f"/openai/load/{model_info.id}", query_params=query_params) - except HttpResponseError as e: - if "No OpenAIService provider found for modelName" in str(e): - raise ValueError( - f"Model {alias_or_model_id} has not been downloaded yet. Please download it first." - ) from None - raise - return model_info - - def unload_model(self, alias_or_model_id: str, device: DeviceType | None = None, force: bool = False): - """ - Unload a model. - - Args: - alias_or_model_id (str): Alias or Model ID. - device (DeviceType | None): Optional device type to filter the models. If None, no filtering is applied. - force (bool): If True, force unload a model with TTL. - """ - model_info = self.get_model_info(alias_or_model_id, device=device, raise_on_not_found=True) - if model_info not in self.list_loaded_models(): - # safest since unload fails if model is not downloaded, easier to check if loaded - logger.info( - "Model with alias '%s' and ID '%s' is not loaded. No need to unload.", model_info.alias, model_info.id - ) - return - logger.info("Unloading model with alias '%s' and ID '%s'...", model_info.alias, model_info.id) - self.httpx_client.get(f"/openai/unload/{model_info.id}", query_params={"force": force}) - - def list_loaded_models(self) -> list[FoundryModelInfo]: - """ - Get a list of loaded models. - - Returns: - list[FoundryModelInfo]: List of loaded models. - """ - return self._fetch_model_infos(self.httpx_client.get("/openai/loadedmodels")) diff --git a/sdk_legacy/python/foundry_local/client.py b/sdk_legacy/python/foundry_local/client.py deleted file mode 100644 index 312fd06c4..000000000 --- a/sdk_legacy/python/foundry_local/client.py +++ /dev/null @@ -1,120 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from __future__ import annotations - -import json -import logging -import re - -import httpx -from tqdm import tqdm - -from foundry_local.version import __version__ as sdk_version - -logger = logging.getLogger(__name__) - - -class HttpResponseError(Exception): - pass - - -class HttpxClient: - """ - Client for Foundry Local SDK. - - Attributes: - _client (httpx.Client): HTTP client instance. - """ - - def __init__(self, host: str, timeout: float | httpx.Timeout | None = None) -> None: - """ - Initialize the HttpxClient with the host. - - Args: - host (str): Base URL of the host. - timeout (float | httpx.Timeout | None): Timeout for the HTTP client. - """ - headers = {"user-agent": f"foundry-local-python-sdk/{sdk_version}"} - self._client = httpx.Client(base_url=host, timeout=timeout, headers=headers) - - def _request(self, *args, **kwargs) -> httpx.Response: - """ - Send an HTTP request. - - Args: - *args: Positional arguments for the request. - **kwargs: Keyword arguments for the request. - - Returns: - httpx.Response: HTTP response object. - - Raises: - RuntimeError: If an HTTP error or connection error occurs. - """ - try: - response = self._client.request(*args, **kwargs) - response.raise_for_status() - except httpx.HTTPStatusError as e: - raise HttpResponseError(f"{e.response.status_code} - {e.response.text}") from None - except httpx.ConnectError: - raise ConnectionError( - "Could not connect to Foundry Local! Please check if the Foundry Local service is running and the host" - " URL is correct." - ) from None - return response - - def get(self, path: str, query_params: dict[str, str] | None = None) -> dict | list | None: - """ - Send a GET request to the specified path with optional query parameters. - - Args: - path (str): Path for the GET request. - query_params (dict[str, str] | None): Query parameters for the request. - - Returns: - dict | list | None: JSON response or None if no content. - """ - response = self._request("GET", path, params=query_params) - return response.json() if response.text else None - - def post_with_progress(self, path: str, body: dict | None = None) -> dict: - """ - Send a POST request to the specified path with optional request body and show progress. - - Args: - path (str): Path for the POST request. - body (dict | None): Request body in JSON format. - - Returns: - dict: JSON response. - - Raises: - ValueError: If the JSON response is invalid. - """ - with self._client.stream("POST", path, json=body, timeout=None) as response: - progress_bar = None - prev_percent = 0.0 - if logger.isEnabledFor(logging.INFO): - progress_bar = tqdm(total=100.0) - final_json = "" - for line in response.iter_lines(): - if final_json or line.startswith("{"): - final_json += line - continue - if not progress_bar: - continue - if match := re.search(r"(\d+(?:\.\d+)?)%", line): - percent = min(float(match.group(1)), 100.0) - delta = percent - prev_percent - if delta > 0: - progress_bar.update(delta) - prev_percent = percent - if progress_bar: - progress_bar.close() - - if not final_json.endswith("}"): - raise ValueError(f"Invalid JSON response: {final_json}") - - return json.loads(final_json) diff --git a/sdk_legacy/python/foundry_local/logging.py b/sdk_legacy/python/foundry_local/logging.py deleted file mode 100644 index 25d5a7af2..000000000 --- a/sdk_legacy/python/foundry_local/logging.py +++ /dev/null @@ -1,92 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -import logging - - -def get_logger(): - """ - Get the logger for the sdk. - - Returns: - logging.Logger: Logger instance for the module. - """ - return logging.getLogger(__name__.split(".", maxsplit=1)[0]) - - -def set_verbosity(verbose): - """ - Set the verbosity level for the logger. - - Args: - verbose (int): Verbosity level (e.g., logging.INFO, logging.DEBUG). - """ - get_logger().setLevel(verbose) - - -def set_verbosity_info(): - """Set the verbosity level to INFO.""" - set_verbosity(logging.INFO) - - -def set_verbosity_warning(): - """Set the verbosity level to WARNING.""" - set_verbosity(logging.WARNING) - - -def set_verbosity_debug(): - """Set the verbosity level to DEBUG.""" - set_verbosity(logging.DEBUG) - - -def set_verbosity_error(): - """Set the verbosity level to ERROR.""" - set_verbosity(logging.ERROR) - - -def set_verbosity_critical(): - """Set the verbosity level to CRITICAL.""" - set_verbosity(logging.CRITICAL) - - -def get_verbosity() -> int: - """ - Get the current verbosity level of the logger. - - Returns: - int: Verbosity level as an integer. - """ - return get_logger().getEffectiveLevel() - - -def get_logger_level(level): - """ - Get Python logging level for the integer level. - - Args: - level (int): Verbosity level (0: DEBUG, 1: INFO, 2: WARNING, 3: ERROR, 4: CRITICAL). - - Returns: - int: Corresponding Python logging level. - - Raises: - ValueError: If the level is invalid. - """ - level_map = {0: logging.DEBUG, 1: logging.INFO, 2: logging.WARNING, 3: logging.ERROR, 4: logging.CRITICAL} - # check if level is valid - if level not in level_map: - raise ValueError(f"Invalid level {level}, should be one of {list(level_map.keys())}") - - return level_map[level] - - -def set_default_logger_severity(level): - """ - Set the default log level for the logger. - - Args: - level (int): Verbosity level (0: DEBUG, 1: INFO, 2: WARNING, 3: ERROR, 4: CRITICAL). - """ - # set logger level - set_verbosity(get_logger_level(level)) diff --git a/sdk_legacy/python/foundry_local/models.py b/sdk_legacy/python/foundry_local/models.py deleted file mode 100644 index 5895de729..000000000 --- a/sdk_legacy/python/foundry_local/models.py +++ /dev/null @@ -1,154 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from __future__ import annotations - -import sys - -from pydantic import BaseModel, Field - -if sys.version_info >= (3, 11): - from enum import StrEnum - -else: - from enum import Enum - - class StrEnum(str, Enum): - def __str__(self) -> str: - return self.value - - -# ruff: noqa: N815 - - -class DeviceType(StrEnum): - """Enumeration of devices supported by the model.""" - - CPU = "CPU" - GPU = "GPU" - NPU = "NPU" - - -class ExecutionProvider(StrEnum): - """Enumeration of common execution providers supported by the model.""" - - CPU = "CPUExecutionProvider" - WEBGPU = "WebGpuExecutionProvider" - CUDA = "CUDAExecutionProvider" - - def get_alias(self) -> str: - """ - Get the alias for the execution provider. - - Returns: - str: Alias of the execution provider. - """ - return self.value.replace("ExecutionProvider", "").lower() - - -class ModelRuntime(BaseModel): - """Model runtime information.""" - - deviceType: DeviceType = Field(..., description="Device type supported by the model") - # use a string since we don't want to hardcode all possible execution providers - executionProvider: str = Field( - ..., - description="Execution provider supported by the model", - ) - - -class FoundryListResponseModel(BaseModel): - """Response model for listing models.""" - - name: str = Field(..., description="Name of the model") - displayName: str = Field(..., description="Display name of the model") - modelType: str = Field(..., description="Type of the model") - providerType: str = Field(..., description="Provider type of the model") - uri: str = Field(..., description="URI of the model") - version: str = Field(..., description="Version of the model") - promptTemplate: dict | None = Field(..., description="Prompt template for the model") - publisher: str = Field(..., description="Publisher of the model") - task: str = Field(..., description="Task of the model") - runtime: ModelRuntime = Field(..., description="Runtime information of the model") - fileSizeMb: int = Field(..., description="File size of the model in MB") - modelSettings: dict = Field(..., description="Model settings") - alias: str = Field(..., description="Alias name of the model") - supportsToolCalling: bool = Field(..., description="Whether the model supports tool calling") - license: str = Field(..., description="License of the model") - licenseDescription: str = Field(..., description="License description of the model") - parentModelUri: str = Field(..., description="Parent model URI of the model") - maxOutputTokens: int | None = Field(..., description="Maximum output tokens for the model") - minFLVersion: str | None = Field(None, description="Minimum Foundry Local version required for the model") - - -class FoundryModelInfo(BaseModel): - """Model information.""" - - alias: str = Field(..., description="Alias of the model") - id: str = Field(..., description="Unique identifier of the model") - version: str = Field(..., description="Version of the model") - execution_provider: str = Field(..., description="Execution provider of the model") - device_type: DeviceType = Field(..., description="Device type of the model") - uri: str = Field(..., description="URI of the model") - file_size_mb: int = Field(..., description="Size of the model on disk in MB") - supports_tool_calling: bool = Field(..., description="Whether the model supports tool calling") - prompt_template: dict | None = Field(..., description="Prompt template for the model") - provider: str = Field(..., description="Provider of the model") - publisher: str = Field(..., description="Publisher of the model") - license: str = Field(..., description="License of the model") - task: str = Field(..., description="Task of the model") - ep_override: str | None = Field( - None, description="Override for the execution provider, if different from the model's default" - ) - - def __repr__(self) -> str: - return ( - f"FoundryModelInfo(alias={self.alias}, id={self.id}," - f" execution_provider={self.execution_provider}, device_type={self.device_type}," - f" file_size={self.file_size_mb} MB, license={self.license})" - ) - - @classmethod - def from_list_response(cls, response: dict | FoundryListResponseModel) -> FoundryModelInfo: - """ - Create a FoundryModelInfo object from a FoundryListResponseModel object. - - Args: - response (dict | FoundryListResponseModel): Response data. - - Returns: - FoundryModelInfo: Instance of FoundryModelInfo. - """ - if isinstance(response, dict): - response = FoundryListResponseModel.model_validate(response) - return cls( - alias=response.alias, - id=response.name, - version=response.version, - execution_provider=response.runtime.executionProvider, - device_type=response.runtime.deviceType, - uri=response.uri, - file_size_mb=response.fileSizeMb, - supports_tool_calling=response.supportsToolCalling, - prompt_template=response.promptTemplate, - provider=response.providerType, - publisher=response.publisher, - license=response.license, - task=response.task, - ) - - def to_download_body(self) -> dict: - """ - Convert the FoundryModelInfo object to a dictionary for download. - - Returns: - dict: Dictionary representation for download. - """ - return { - "Name": self.id, - "Uri": self.uri, - "Publisher": self.publisher, - "ProviderType": f"{self.provider}Local" if self.provider == "AzureFoundry" else self.provider, - "PromptTemplate": self.prompt_template, - } diff --git a/sdk_legacy/python/foundry_local/service.py b/sdk_legacy/python/foundry_local/service.py deleted file mode 100644 index c986a43a8..000000000 --- a/sdk_legacy/python/foundry_local/service.py +++ /dev/null @@ -1,61 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from __future__ import annotations - -import logging -import re -import shutil -import subprocess -import time - -logger = logging.getLogger(__name__) - - -def assert_foundry_installed(): - """ - Assert that Foundry is installed. - - Raises: - RuntimeError: If Foundry is not installed or not on PATH. - """ - if shutil.which("foundry") is None: - raise RuntimeError("Foundry is not installed or not on PATH!") - - -def get_service_uri() -> str | None: - """ - Get the service URI if the service is running. - - Returns: - str | None: URI of the running Foundry service, or None if not running. - """ - with subprocess.Popen(["foundry", "service", "status"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: - stdout, _ = proc.communicate() - match = re.search(r"http://(?:[a-zA-Z0-9.-]+|\d{1,3}(\.\d{1,3}){3}):\d+", stdout.decode()) - if match: - return match.group(0) - return None - - -def start_service() -> str | None: - """ - Start the Foundry service. - - Returns: - str | None: URI of the started Foundry service, or None if it failed to start. - """ - if (service_url := get_service_uri()) is not None: - logger.info("Foundry service is already running at %s", service_url) - return service_url - - with subprocess.Popen(["foundry", "service", "start"]): - # not checking the process output since it never finishes communication - for _ in range(10): - if (service_url := get_service_uri()) is not None: - logger.info("Foundry service started successfully at %s", service_url) - return service_url - time.sleep(0.1) - logger.warning("Foundry service did not start within the expected time. May not be running.") - return None diff --git a/sdk_legacy/python/foundry_local/version.py b/sdk_legacy/python/foundry_local/version.py deleted file mode 100644 index dd9b22ccc..000000000 --- a/sdk_legacy/python/foundry_local/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.5.1" diff --git a/sdk_legacy/python/integration_test/__init__.py b/sdk_legacy/python/integration_test/__init__.py deleted file mode 100644 index 862c45ce3..000000000 --- a/sdk_legacy/python/integration_test/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- diff --git a/sdk_legacy/python/integration_test/test_integration.py b/sdk_legacy/python/integration_test/test_integration.py deleted file mode 100644 index 42c9548db..000000000 --- a/sdk_legacy/python/integration_test/test_integration.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from __future__ import annotations - -import pytest -from openai import OpenAI - -from foundry_local.api import FoundryLocalManager -from foundry_local.logging import set_verbosity_debug - -set_verbosity_debug() - -MODELS = [ - "qwen2.5-0.5b", - "qwen2.5-0.5b-instruct-generic-cpu:3", -] - - -def _openai_client(m: FoundryLocalManager) -> OpenAI: - return OpenAI(base_url=m.endpoint, api_key=m.api_key) - - -@pytest.fixture(scope="session") -def manager_bootstrapped() -> FoundryLocalManager: - return FoundryLocalManager(bootstrap=True) - - -@pytest.fixture(scope="function") -def fresh_manager_no_bootstrap() -> FoundryLocalManager: - return FoundryLocalManager(bootstrap=False) - - -# Catalog & Cache (not model-specific) - - -def test_catalog_lists_models(manager_bootstrapped: FoundryLocalManager): - models = manager_bootstrapped.list_catalog_models() - assert isinstance(models, list) - for m in models: - assert getattr(m, "id", None) - assert getattr(m, "alias", None) - print(f"Found {len(models)} models in catalog: {[m.id for m in models]}") - all_eps = {m.execution_provider for m in models} - print(f"Found {len(all_eps)} execution providers in catalog: {all_eps}") - - -def test_cache_operations(manager_bootstrapped: FoundryLocalManager): - cache_dir = manager_bootstrapped.get_cache_location() - assert isinstance(cache_dir, str) and cache_dir - cached = manager_bootstrapped.list_cached_models() - assert isinstance(cached, list) - - -# Service lifecycle (not model-specific) - - -def test_service_start_stop(fresh_manager_no_bootstrap: FoundryLocalManager): - fresh_manager_no_bootstrap.start_service() - assert fresh_manager_no_bootstrap.is_service_running() is True - assert fresh_manager_no_bootstrap.service_uri - assert fresh_manager_no_bootstrap.endpoint.endswith("/v1") - assert isinstance(fresh_manager_no_bootstrap.api_key, str) - - -# OpenAI integration & model mgmt - - -@pytest.mark.parametrize("model_id", MODELS, ids=lambda s: f"model={s}") -def test_openai_chat_stream(model_id: str): - mgr = FoundryLocalManager(alias_or_model_id=model_id, bootstrap=True) - resolved = mgr.get_model_info(model_id, raise_on_not_found=True) - client = _openai_client(mgr) - - stream = client.chat.completions.create( - model=resolved.id, - messages=[{"role": "user", "content": "Why is the sky blue?"}], - stream=True, - ) - - token_count = 0 - for ch in stream: - choice = ch.choices[0] - delta = getattr(choice, "delta", None) - if delta is not None and getattr(delta, "content", None): - print(delta.content, end="") - token_count += len(delta.content) - assert token_count > 0 - - -# this takes too long, don't need to run every time -# @pytest.mark.parametrize("model_id", MODELS, ids=lambda s: f"model={s}") -# def test_download_force(model_id: str, manager_bootstrapped: FoundryLocalManager): -# # force unload model first -# manager_bootstrapped.unload_model(model_id, force=True) -# info = manager_bootstrapped.download_model(model_id, force=True) -# assert info.id and info.alias - - -@pytest.mark.parametrize("model_id", MODELS, ids=lambda s: f"model={s}") -def test_load_then_unload(model_id: str, manager_bootstrapped: FoundryLocalManager): - manager_bootstrapped.download_model(model_id, force=False) - info = manager_bootstrapped.load_model(model_id) - assert info.id and info.alias - loaded = {m.id for m in manager_bootstrapped.list_loaded_models()} - assert info.id in loaded - # unload with no force should keep it since it's in ttl - manager_bootstrapped.unload_model(model_id, force=False) - loaded_after = {m.id for m in manager_bootstrapped.list_loaded_models()} - assert info.id in loaded_after - # unload with force should remove it - manager_bootstrapped.unload_model(model_id, force=True) - loaded_final = {m.id for m in manager_bootstrapped.list_loaded_models()} - assert info.id not in loaded_final diff --git a/sdk_legacy/python/pyproject.toml b/sdk_legacy/python/pyproject.toml deleted file mode 100644 index fac135589..000000000 --- a/sdk_legacy/python/pyproject.toml +++ /dev/null @@ -1,142 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "foundry-local-sdk" -dynamic = ["version", "dependencies"] -description = "Foundry Local Manager Python SDK: Control-plane SDK for Foundry Local." -readme = "README.md" -requires-python = ">=3.9" -license = {file = "LICENSE.txt"} -authors = [ - {name = "Microsoft Corporation", email = "foundrylocaldevs@microsoft.com"}, -] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Software Development", - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Libraries :: Python Modules", - "Programming Language :: Python", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] - -[project.urls] -Homepage = "https://github.com/microsoft/Foundry-Local" - -[tool.setuptools] -packages = ["foundry_local"] - -[tool.setuptools.dynamic] -version = {attr = "foundry_local.version.__version__"} -dependencies = {file = ["requirements.txt"]} - -[tool.pylint.messages_control] -disable = [ - "consider-using-from-import", - "cyclic-import", - "duplicate-code", - "fixme", - "format", - "import-error", - "invalid-name", - "line-too-long", - "missing-docstring", - "no-else-return", - "no-member", - "no-name-in-module", - "too-few-public-methods", - "too-many-ancestors", - "too-many-arguments", - "too-many-branches", - "too-many-instance-attributes", - "too-many-lines", - "too-many-locals", - "too-many-positional-arguments", - "too-many-public-methods", - "too-many-return-statements", - "too-many-statements", - "unnecessary-ellipsis" -] - -[tool.ruff] -line-length = 120 -target-version = "py39" - -[tool.ruff.lint] -select = [ - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "D", # pydocstyle - "E", # pycodestyle - "F", # Pyflakes - "G", # flake8-logging-format - "I", # isort - "ISC", # flake8-implicit-str-concat - "LOG", # flake8-logging - "N", # pep8-naming - "NPY", # modern numpy - "PERF", # Perflint - "PIE", # flake8-pie - "PYI", # flake8-pyi - "RUF", # Ruff-specific rules - "SIM", # flake8-simplify - "SLOT", # flake8-slot - "T10", # flake8-debugger - "TID", # Disallow relative imports - "TRY", # flake8-try-except-raise - "UP", # pyupgrade - "W", # pycodestyle - "YTT", # flake8-2020 -] -ignore = [ - "B9", # Opinionated bugbear rules - "C408", # Sometimes it is preferable when we construct kwargs - "D1", # D1 is for missing docstrings, which is not yet enforced. - "D202", # D202 Too strict. "No blank lines allowed after function docstring" - "D205", # D205 Too strict. "1 blank line required between summary line and description" - "D212", - "D400", - "D401", # First line of docstring should be in imperative mood - "D415", # D415 Not yet enforced. "First line should end with a period, question mark, or exclamation point" - "E1", "E2", "E3", # Pycodestyle formatting rules that conflicts with the formatter - "E501", # Line length. Not enforced because black will handle formatting - "SIM103", # "Return the condition directly" obscures logic sometimes - "N802", # Nxx: ONNX Script function sometimes use upper case for names. - "N803", - "N806", - "N999", # Invalid module name - "NPY002", # We may not always need a generator - "PERF203", # try-except in loops sometimes necessary - "PERF401", # List comprehension is not always readable - "PYI041", # int | float is more clear - "RUF022", # We don't need to sort __all__ for elements to be grouped - "RUF027", # This is used for creating rest API urls - "RUF031", # Parentheses for tuple in subscripts is more readable - "RUF052", # Variables with `_` prefix may not be dummy variables in all cases - "SIM102", # Collapible if statements are not always more readable - "SIM108", # We don't always encourage ternary operators - "SIM114", # Don't always combine if branches for debugability - "SIM116", # Don't use dict lookup to replace if-else - "TRY002", # Create your own exception - "TRY003", # Messages can be constructed in the exception - "UP006", # keep-runtime-typing - "UP007", # keep-runtime-typing - "UP045", # TODO: Support new style type annotations -] -ignore-init-module-imports = true - -[tool.ruff.lint.flake8-tidy-imports] -# Disallow all relative imports. -ban-relative-imports = "all" - -[tool.ruff.lint.pydocstyle] -convention = "google" diff --git a/sdk_legacy/python/requirements-dev.txt b/sdk_legacy/python/requirements-dev.txt deleted file mode 100644 index ca4aff8a3..000000000 --- a/sdk_legacy/python/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ --r requirements.txt -build -coverage -pytest diff --git a/sdk_legacy/python/requirements-lintrunner.txt b/sdk_legacy/python/requirements-lintrunner.txt deleted file mode 100644 index 3a07a912e..000000000 --- a/sdk_legacy/python/requirements-lintrunner.txt +++ /dev/null @@ -1,8 +0,0 @@ -lintrunner==0.12.7 -lintrunner-adapters>=0.8.0 -# RUFF, RUFF-FIX -ruff==0.11.4 -# PYLINT -pylint==3.3.6 -# EDITORCONFIG-CHECKER -editorconfig-checker==3.2.0 \ No newline at end of file diff --git a/sdk_legacy/python/requirements.txt b/sdk_legacy/python/requirements.txt deleted file mode 100644 index a23242ab0..000000000 --- a/sdk_legacy/python/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -httpx -pydantic>=2.0.0 -tqdm diff --git a/sdk_legacy/python/test/__init__.py b/sdk_legacy/python/test/__init__.py deleted file mode 100644 index 862c45ce3..000000000 --- a/sdk_legacy/python/test/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- diff --git a/sdk_legacy/python/test/test_api.py b/sdk_legacy/python/test/test_api.py deleted file mode 100644 index 287d42756..000000000 --- a/sdk_legacy/python/test/test_api.py +++ /dev/null @@ -1,448 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -import os -from unittest import mock - -import pytest - -from foundry_local.api import FoundryLocalManager -from foundry_local.client import HttpResponseError -from foundry_local.models import FoundryModelInfo - -# pylint: disable=unused-argument, redefined-outer-name - -MOCK_INFO = { - "providerType": "AzureFoundry", - "version": "1", - "modelType": "ONNX", - "promptTemplate": {"prompt": "<|im_start|>user<|im_sep|>{Content}<|im_end|><|im_start|>assistant<|im_sep|>"}, - "publisher": "Microsoft", - "task": "chat-completion", - "fileSizeMb": 10403, - "modelSettings": {"parameters": []}, - "supportsToolCalling": False, - "license": "MIT", - "licenseDescription": "This model is provided under the License Terms available at ...", - "maxOutputTokens": 1024, -} - -# Sample catalog with 3 aliases with different combos -MOCK_CATALOG_DATA = [ - # generic-gpu, generic-cpu - { - "name": "model-1-generic-gpu:1", - "displayName": "model-1-generic-gpu", - "uri": "azureml://registries/azureml/models/model-1-generic-gpu/versions/1", - "runtime": {"deviceType": "GPU", "executionProvider": "WebGpuExecutionProvider"}, - "alias": "model-1", - "parentModelUri": "azureml://registries/azureml/models/model-1/versions/1", - **MOCK_INFO, - }, - # put newer version first, unsure how the service will return them both and/or it it's sorted - { - "name": "model-1-generic-cpu:2", - "displayName": "model-1-generic-cpu", - "uri": "azureml://registries/azureml/models/model-1-generic-cpu/versions/2", - "runtime": {"deviceType": "CPU", "executionProvider": "CPUExecutionProvider"}, - "alias": "model-1", - "parentModelUri": "azureml://registries/azureml/models/model-1/versions/2", - **MOCK_INFO, - }, - { - "name": "model-1-generic-cpu:1", - "displayName": "model-1-generic-cpu", - "uri": "azureml://registries/azureml/models/model-1-generic-cpu/versions/1", - "runtime": {"deviceType": "CPU", "executionProvider": "CPUExecutionProvider"}, - "alias": "model-1", - "parentModelUri": "azureml://registries/azureml/models/model-1/versions/1", - **MOCK_INFO, - }, - # npu, generic-cpu - { - "name": "model-2-npu:2", - "displayName": "model-2-npu", - "uri": "azureml://registries/azureml/models/model-2-npu/versions/2", - "runtime": {"deviceType": "NPU", "executionProvider": "QNNExecutionProvider"}, - "alias": "model-2", - "parentModelUri": "azureml://registries/azureml/models/model-2/versions/2", - **MOCK_INFO, - }, - { - "name": "model-2-npu:1", - "displayName": "model-2-npu", - "uri": "azureml://registries/azureml/models/model-2-npu/versions/1", - "runtime": {"deviceType": "NPU", "executionProvider": "QNNExecutionProvider"}, - "alias": "model-2", - "parentModelUri": "azureml://registries/azureml/models/model-2/versions/1", - **MOCK_INFO, - }, - { - "name": "model-2-generic-cpu:1", - "displayName": "model-2-generic-cpu", - "uri": "azureml://registries/azureml/models/model-2-generic-cpu/versions/1", - "runtime": {"deviceType": "CPU", "executionProvider": "CPUExecutionProvider"}, - "alias": "model-2", - "parentModelUri": "azureml://registries/azureml/models/model-2/versions/1", - **MOCK_INFO, - }, - # cuda-gpu, generic-gpu, generic-cpu - { - "name": "model-3-cuda-gpu:1", - "displayName": "model-3-cuda-gpu", - "uri": "azureml://registries/azureml/models/model-3-cuda-gpu/versions/1", - "runtime": {"deviceType": "GPU", "executionProvider": "CUDAExecutionProvider"}, - "alias": "model-3", - "parentModelUri": "azureml://registries/azureml/models/model-3/versions/1", - **MOCK_INFO, - }, - { - "name": "model-3-generic-gpu:1", - "displayName": "model-3-generic-gpu", - "uri": "azureml://registries/azureml/models/model-3-generic-gpu/versions/1", - "runtime": {"deviceType": "GPU", "executionProvider": "WebGpuExecutionProvider"}, - "alias": "model-3", - "parentModelUri": "azureml://registries/azureml/models/model-3/versions/1", - **MOCK_INFO, - }, - { - "name": "model-3-generic-cpu:1", - "displayName": "model-3-generic-cpu", - "uri": "azureml://registries/azureml/models/model-3-generic-cpu/versions/1", - "runtime": {"deviceType": "CPU", "executionProvider": "CPUExecutionProvider"}, - "alias": "model-3", - "parentModelUri": "azureml://registries/azureml/models/model-3/versions/1", - **MOCK_INFO, - }, - # generic-cpu - { - "name": "model-4-generic-gpu:1", - "displayName": "model-4-generic-gpu", - "uri": "azureml://registries/azureml/models/model-4-generic-gpu/versions/1", - "runtime": {"deviceType": "GPU", "executionProvider": "WebGpuExecutionProvider"}, - "alias": "model-4", - "parentModelUri": "azureml://registries/azureml/models/model-4/versions/1", - **MOCK_INFO, - "promptTemplate": None, # to test nullable fields - "newFeature": "newValue", # to test extra fields - "minFLVersion": "1.0.0", # to test optional fields - }, -] - -# Mock response for /openai/status -MOCK_STATUS_RESPONSE = {"modelDirPath": "/test/path/to/models"} - -# Mock response for /openai/models -MOCK_LOCAL_MODELS = ["model-2-npu:1", "model-4-generic-gpu:1"] - -# Mock response for /openai/loadedmodels -MOCK_LOADED_MODELS = ["model-2-npu:1"] - - -@pytest.fixture(scope="module", autouse=True) -def mock_service_check(): - """Mock subprocess calls for service status check.""" - with mock.patch("subprocess.Popen") as mock_popen: - mock_process = mock.MagicMock() - mock_process.communicate.return_value = (b"Service is running at http://localhost:5272", b"") - mock_popen.return_value.__enter__.return_value = mock_process - yield mock_popen - - -@pytest.fixture(scope="module", autouse=True) -def mock_foundry_installed(): - """Mock check for foundry being installed.""" - with mock.patch("shutil.which") as mock_which: - mock_which.return_value = "/usr/local/bin/foundry" - yield mock_which - - -@pytest.fixture -def mock_http_client(request): - """Mock HTTP client for API calls.""" - use_cuda = getattr(request, "param", {}).get("use_cuda", True) - - with mock.patch("foundry_local.api.HttpxClient", autospec=True) as mock_client: - mock_instance = mock_client.return_value - - # Mock GET /foundry/list - mock_instance.get.side_effect = lambda path, query_params=None: ( - (MOCK_CATALOG_DATA if use_cuda else [m for m in MOCK_CATALOG_DATA if "cuda" not in m["name"]]) - if path == "/foundry/list" - else ( - MOCK_STATUS_RESPONSE - if path == "/openai/status" - else ( - MOCK_LOCAL_MODELS - if path == "/openai/models" - else MOCK_LOADED_MODELS - if path == "/openai/loadedmodels" - else None - ) - ) - ) - - # Mock POST /openai/download - mock_instance.post_with_progress.return_value = {"success": True} - - yield mock_instance - - -def test_initialization(mock_http_client): - """Test FoundryLocalManager initialization.""" - # Test with no bootstrap - manager = FoundryLocalManager(bootstrap=False) - assert manager.service_uri == "http://localhost:5272" - assert manager.endpoint == "http://localhost:5272/v1" - assert manager.api_key == (os.getenv("OPENAI_API_KEY") or "OPENAI_API_KEY") - - # Test with bootstrap and model_id - with mock.patch("foundry_local.api.start_service") as mock_start: - mock_start.return_value = "http://localhost:5272" - manager = FoundryLocalManager(alias_or_model_id="model-4", bootstrap=True) - mock_start.assert_called_once() - mock_http_client.get.assert_any_call("/foundry/list") - # in local models - mock_http_client.post_with_progress.assert_not_called() - - -def test_list_catalog_models(mock_http_client): - """Test listing catalog models.""" - manager = FoundryLocalManager(bootstrap=False) - models = manager.list_catalog_models() - mock_http_client.get.assert_called_once_with("/foundry/list") - assert len(models) == len(MOCK_CATALOG_DATA) - assert all(isinstance(model, FoundryModelInfo) for model in models) - assert [model.id for model in models] == [ - "model-1-generic-gpu:1", - "model-1-generic-cpu:2", - "model-1-generic-cpu:1", - "model-2-npu:2", - "model-2-npu:1", - "model-2-generic-cpu:1", - "model-3-cuda-gpu:1", - "model-3-generic-gpu:1", - "model-3-generic-cpu:1", - "model-4-generic-gpu:1", - ] - assert [model.alias for model in models] == [ - "model-1", - "model-1", - "model-1", - "model-2", - "model-2", - "model-2", - "model-3", - "model-3", - "model-3", - "model-4", - ] - - # cached model catalog - manager.list_catalog_models() - mock_http_client.get.assert_called_once_with("/foundry/list") - - # refresh model catalog - manager.refresh_catalog() - manager.list_catalog_models() - assert mock_http_client.get.call_count == 2 - - -@pytest.mark.parametrize( - "mock_http_client", - [ - {"use_cuda": True}, - {"use_cuda": False}, - ], - indirect=True, -) -@pytest.mark.parametrize("platform", ["Windows", "Linux", "Darwin"]) -def test_get_model_info(platform, mock_http_client, request): - use_cuda = request.node.callspec.params["mock_http_client"]["use_cuda"] - with mock.patch("platform.system", return_value=platform): - manager = FoundryLocalManager(bootstrap=False) - - # unknown model - # no raise - assert manager.get_model_info("unknown-model") is None - # raise - with pytest.raises(ValueError): - manager.get_model_info("unknown-model", raise_on_not_found=True) - - # with id that contains version - assert manager.get_model_info("model-1-generic-cpu:1").id == "model-1-generic-cpu:1" - - # with id that does not contain version - assert manager.get_model_info("model-1-generic-cpu").id == "model-1-generic-cpu:2" - - # with alias - # generic-cpu preferred on Windows - assert ( - manager.get_model_info("model-1").id == "model-1-generic-cpu:2" - if platform == "Windows" and (not use_cuda) - else "model-1-generic-gpu:1" - ) - assert manager.get_model_info("model-2").id == "model-2-npu:2" # latest version, even if not in cache - assert ( - manager.get_model_info("model-3").id == "model-3-cuda-gpu:1" - if use_cuda - else ("model-3-generic-gpu:1" if platform != "Windows" else "model-3-generic-cpu:1") - ) - - # with device filter - assert manager.get_model_info("model-1", device="GPU").id == "model-1-generic-gpu:1" - assert manager.get_model_info("model-1", device="CPU").id == "model-1-generic-cpu:2" - assert manager.get_model_info("model-1", device="NPU") is None - assert manager.get_model_info("model-2", device="NPU").id == "model-2-npu:2" - assert manager.get_model_info("model-2", device="CPU").id == "model-2-generic-cpu:1" - assert manager.get_model_info("model-2", device="GPU") is None - assert manager.get_model_info("model-3", device="GPU").id == ( - "model-3-cuda-gpu:1" if use_cuda else "model-3-generic-gpu:1" - ) - assert manager.get_model_info("model-3", device="CPU").id == "model-3-generic-cpu:1" - assert manager.get_model_info("model-3", device="NPU") is None - - -def test_list_cached_models(mock_http_client): - """Test listing local models.""" - manager = FoundryLocalManager(bootstrap=False) - local_models = manager.list_cached_models() - assert len(local_models) == 2 - assert local_models[0].id == "model-2-npu:1" - assert local_models[1].id == "model-4-generic-gpu:1" - - -def test_list_loaded_models(mock_http_client): - """Test listing loaded models.""" - manager = FoundryLocalManager(bootstrap=False) - loaded_models = manager.list_loaded_models() - assert len(loaded_models) == 1 - assert loaded_models[0].id == "model-2-npu:1" - - -def test_download_model(mock_http_client): - """Test downloading a model.""" - manager = FoundryLocalManager(bootstrap=False) - - # Test downloading a new model - model_info = manager.download_model("model-3") - assert model_info.id == "model-3-cuda-gpu:1" - mock_http_client.post_with_progress.assert_called_once() - mock_http_client.post_with_progress.reset_mock() # Reset mock for next test - - # Test downloading an already cached model - model_info = manager.download_model("model-2-npu:1") - assert model_info.id == "model-2-npu:1" - mock_http_client.post_with_progress.assert_not_called() - - # Test download a model that is not at the latest version - model_info = manager.download_model("model-2") - assert model_info.id == "model-2-npu:2" - mock_http_client.post_with_progress.assert_called_once() - mock_http_client.post_with_progress.reset_mock() # Reset mock for next test - - # Test force download - model_info = manager.download_model("model-2", force=True) - assert model_info.id == "model-2-npu:2" - mock_http_client.post_with_progress.assert_called_once() - - # Test download failure - mock_http_client.post_with_progress.return_value = {"success": False, "errorMessage": "Download failed"} - with pytest.raises(RuntimeError, match="Download failed"): - manager.download_model("model-1") - - -def test_is_model_upgradeable(mock_http_client): - manager = FoundryLocalManager(bootstrap=False) - - # Not in cache, even if at the latest version - assert manager.is_model_upgradeable("model-1") is True - assert manager.is_model_upgradeable("model-1-generic-cpu:1") is True - assert manager.is_model_upgradeable("model-1-generic-cpu:2") is True - assert manager.is_model_upgradeable("model-1-generic-gpu:1") is True - assert manager.is_model_upgradeable("model-2-npu:2") is True - - # In cache, at the latest version - assert manager.is_model_upgradeable("model-4-generic-gpu:1") is False - - # In cache, not at the latest version - assert manager.is_model_upgradeable("model-2-npu:1") is True - - -def test_upgrade_model(mock_http_client): - """Test downloading a model.""" - manager = FoundryLocalManager(bootstrap=False) - - # Test upgrading a model that is not in the cache at all - model_info = manager.upgrade_model("model-3") - assert model_info.id == "model-3-cuda-gpu:1" - mock_http_client.post_with_progress.assert_called_once() - mock_http_client.post_with_progress.reset_mock() # Reset mock for next test - - # Test upgrading a model that has an older version in the cache - model_info = manager.upgrade_model("model-2-npu:1") - assert model_info.id == "model-2-npu:2" - mock_http_client.post_with_progress.assert_called_once() - mock_http_client.post_with_progress.reset_mock() # Reset mock for next test - - # Test upgrading a model that has the latest version in the cache - model_info = manager.upgrade_model("model-4") - assert model_info.id == "model-4-generic-gpu:1" - mock_http_client.post_with_progress.assert_not_called() - - -@pytest.mark.parametrize( - "mock_http_client", - [ - {"use_cuda": True}, - {"use_cuda": False}, - ], - indirect=True, -) -def test_load_model(mock_http_client, request): - """Test loading a model.""" - use_cuda = request.node.callspec.params["mock_http_client"]["use_cuda"] - manager = FoundryLocalManager(bootstrap=False) - - # already loaded model - model_info = manager.load_model("model-2") - assert model_info.id == "model-2-npu:2" - mock_http_client.get.assert_any_call("/openai/load/model-2-npu:2", query_params={"ttl": 600, "ep": None}) - - # not loaded model - model_info = manager.load_model("model-4") - assert model_info.id == "model-4-generic-gpu:1" - # ep override, should be cuda since there is cuda support - mock_http_client.get.assert_any_call( - "/openai/load/model-4-generic-gpu:1", query_params={"ttl": 600, "ep": "cuda" if use_cuda else None} - ) - - # Test loading a non-downloaded model - def mock_get(path, query_params=None): - raise HttpResponseError("No OpenAIService provider found for modelName") - - mock_http_client.get = mock_get - with pytest.raises(ValueError, match="has not been downloaded yet"): - manager.load_model("model-3") - - -def test_unload_model(mock_http_client): - """Test unloading a model.""" - manager = FoundryLocalManager(bootstrap=False) - - # Test unloading a loaded model - manager.unload_model("model-2-npu:1") - mock_http_client.get.assert_any_call("/openai/unload/model-2-npu:1", query_params={"force": False}) - - # Test unloading a model that's not loaded - mock_http_client.get.reset_mock() - manager.unload_model("model-4") - assert ( - mock.call("/openai/unload/model-4-generic-gpu:1", query_params={"force": False}) - not in mock_http_client.get.call_args_list - ) - - # Test force unloading - manager.unload_model("model-2-npu:1", force=True) - mock_http_client.get.assert_any_call("/openai/unload/model-2-npu:1", query_params={"force": True}) diff --git a/sdk_legacy/python/test/test_client.py b/sdk_legacy/python/test/test_client.py deleted file mode 100644 index 28fb8ca89..000000000 --- a/sdk_legacy/python/test/test_client.py +++ /dev/null @@ -1,144 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from unittest import mock - -import httpx -import pytest - -from foundry_local.client import HttpResponseError, HttpxClient -from foundry_local.version import __version__ as sdk_version - - -def test_initialization(): - """Test initialization of HttpxClient.""" - with mock.patch("httpx.Client") as mock_client: - HttpxClient("http://localhost:5273") - mock_client.assert_called_once_with( - base_url="http://localhost:5273", - timeout=None, - headers={"user-agent": f"foundry-local-python-sdk/{sdk_version}"}, - ) - - # Test with timeout - HttpxClient("http://localhost:5273", timeout=30.0) - mock_client.assert_called_with( - base_url="http://localhost:5273", - timeout=30.0, - headers={"user-agent": f"foundry-local-python-sdk/{sdk_version}"}, - ) - - -# pylint: disable=protected-access -def test_request(): - """Test _request method.""" - with mock.patch("httpx.Client") as mock_client: - mock_instance = mock_client.return_value - client = HttpxClient("http://localhost:5273") - - # Test successful request - mock_response = mock.MagicMock() - mock_response.raise_for_status.return_value = None - mock_instance.request.return_value = mock_response - - result = client._request("GET", "/test") - mock_instance.request.assert_called_once_with("GET", "/test") - assert result == mock_response - - # Test HTTP error - mock_response = mock.MagicMock() - mock_response.raise_for_status.side_effect = httpx.HTTPStatusError( - "Error", request=mock.MagicMock(), response=mock.MagicMock() - ) - mock_response.status_code = 404 - mock_response.text = "Not Found" - mock_instance.request.return_value = mock_response - - with pytest.raises(HttpResponseError): - client._request("GET", "/test") - - # Test connection error - mock_instance.request.side_effect = httpx.ConnectError("Connection failed") - - with pytest.raises(ConnectionError, match="Could not connect to Foundry Local"): - client._request("GET", "/test") - - -def test_get(): - """Test get method.""" - with mock.patch.object(HttpxClient, "_request") as mock_request: - client = HttpxClient("http://localhost:5273") - - # Test with JSON response - mock_response = mock.MagicMock() - mock_response.text = '{"key": "value"}' - mock_response.json.return_value = {"key": "value"} - mock_request.return_value = mock_response - - result = client.get("/test") - mock_request.assert_called_once_with("GET", "/test", params=None) - assert result == {"key": "value"} - - # Test with empty response - mock_response.text = "" - result = client.get("/test") - assert result is None - - # Test with query parameters - mock_response.text = '{"key": "value"}' - result = client.get("/test", query_params={"param": "value"}) - mock_request.assert_called_with("GET", "/test", params={"param": "value"}) - assert result == {"key": "value"} - - -def test_post_with_progress(): - """Test post_with_progress method.""" - with ( - mock.patch("httpx.Client") as mock_client, - mock.patch("foundry_local.client.tqdm") as mock_tqdm, - mock.patch("foundry_local.client.logger") as mock_logger, - ): - mock_instance = mock_client.return_value - mock_logger_instance = mock.MagicMock() - mock_logger_instance.isEnabledFor.return_value = True - mock_logger.return_value = mock_logger_instance - - # Mock the stream context manager - mock_response = mock.MagicMock() - mock_instance.stream.return_value.__enter__.return_value = mock_response - - # Mock tqdm progress bar - mock_progress_bar = mock.MagicMock() - mock_tqdm.return_value = mock_progress_bar - - # Mock iter_lines to return progress and JSON - mock_response.iter_lines.return_value = [ - "Progress: 10.1%", - "Progress: 50%", - "Progress: 100%", - '{"status": "complete"}', - ] - - client = HttpxClient("http://localhost:5273") - result = client.post_with_progress("/test", body={"key": "value"}) - - mock_instance.stream.assert_called_once_with("POST", "/test", json={"key": "value"}, timeout=None) - mock_progress_bar.update.assert_any_call(10.1) # 10.1% - 0% - mock_progress_bar.update.assert_any_call(39.9) # 50% - 10.1% - mock_progress_bar.update.assert_any_call(50.0) # 100% - 50% - mock_progress_bar.close.assert_called_once() - assert result == {"status": "complete"} - - # Test invalid JSON response - mock_response.iter_lines.return_value = ["Progress: 100%", '{"status": "incomplete'] - - with pytest.raises(ValueError, match="Invalid JSON response"): - client.post_with_progress("/test", body={"key": "value"}) - - # Test with no progress bar (logging disabled) - mock_logger_instance.isEnabledFor.return_value = False - mock_response.iter_lines.return_value = ['{"status": "complete"}'] - - result = client.post_with_progress("/test", body={"key": "value"}) - assert result == {"status": "complete"} diff --git a/sdk_legacy/python/test/test_models.py b/sdk_legacy/python/test/test_models.py deleted file mode 100644 index 69bbdda25..000000000 --- a/sdk_legacy/python/test/test_models.py +++ /dev/null @@ -1,205 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from foundry_local.models import DeviceType, ExecutionProvider, FoundryListResponseModel, FoundryModelInfo, ModelRuntime - - -def test_device_type_enum(): - """Test DeviceType enum.""" - assert DeviceType.CPU == "CPU" - assert DeviceType.GPU == "GPU" - assert DeviceType.NPU == "NPU" - assert str(DeviceType.CPU) == "CPU" - - -def test_execution_provider_enum(): - """Test ExecutionProvider enum.""" - assert ExecutionProvider.CPU == "CPUExecutionProvider" - assert ExecutionProvider.WEBGPU == "WebGpuExecutionProvider" - assert ExecutionProvider.CUDA == "CUDAExecutionProvider" - - # Test get_alias method - assert ExecutionProvider.CPU.get_alias() == "cpu" - assert ExecutionProvider.WEBGPU.get_alias() == "webgpu" - assert ExecutionProvider.CUDA.get_alias() == "cuda" - - -def test_model_runtime(): - """Test ModelRuntime class.""" - runtime = ModelRuntime(deviceType=DeviceType.CPU, executionProvider=ExecutionProvider.CPU) - assert runtime.deviceType == DeviceType.CPU - assert runtime.executionProvider == ExecutionProvider.CPU - - -def test_foundry_list_response_model(): - """Test FoundryListResponseModel class.""" - response_data = { - "name": "test-model", - "displayName": "Test Model", - "modelType": "ONNX", - "providerType": "AzureFoundry", - "uri": "azureml://test", - "version": "1", - "promptTemplate": {"prompt": "test"}, - "publisher": "Test", - "task": "chat-completion", - "runtime": {"deviceType": "CPU", "executionProvider": "CPUExecutionProvider"}, - "fileSizeMb": 1000, - "modelSettings": {}, - "alias": "test", - "supportsToolCalling": False, - "license": "MIT", - "licenseDescription": "Test license", - "parentModelUri": "azureml://parent", - "maxOutputTokens": 1024, - "minFLVersion": "1.0.0", - } - - model = FoundryListResponseModel.model_validate(response_data) - assert model.name == "test-model" - assert model.displayName == "Test Model" - assert model.modelType == "ONNX" - assert model.providerType == "AzureFoundry" - assert model.uri == "azureml://test" - assert model.version == "1" - assert model.promptTemplate == {"prompt": "test"} - assert model.publisher == "Test" - assert model.task == "chat-completion" - assert model.runtime.deviceType == DeviceType.CPU - assert model.runtime.executionProvider == ExecutionProvider.CPU - assert model.fileSizeMb == 1000 - assert model.modelSettings == {} - assert model.alias == "test" - assert model.supportsToolCalling is False - assert model.license == "MIT" - assert model.licenseDescription == "Test license" - assert model.parentModelUri == "azureml://parent" - - -def test_foundry_model_info(): - """Test FoundryModelInfo class.""" - model_info = FoundryModelInfo( - alias="test", - id="test-model", - version="1", - execution_provider=ExecutionProvider.CPU, - device_type=DeviceType.CPU, - uri="azureml://test", - file_size_mb=1000, - supports_tool_calling=False, - prompt_template={"prompt": "test"}, - provider="AzureFoundry", - publisher="Test", - license="MIT", - task="chat-completion", - ) - - assert model_info.alias == "test" - assert model_info.id == "test-model" - assert model_info.version == "1" - assert model_info.execution_provider == ExecutionProvider.CPU - assert model_info.device_type == DeviceType.CPU - assert model_info.uri == "azureml://test" - assert model_info.file_size_mb == 1000 - assert model_info.prompt_template == {"prompt": "test"} - assert model_info.provider == "AzureFoundry" - assert model_info.publisher == "Test" - assert model_info.license == "MIT" - assert model_info.task == "chat-completion" - - # Test __repr__ - repr_str = repr(model_info) - assert "alias=test" in repr_str - assert "id=test-model" in repr_str - assert "provider=CPUExecutionProvider" in repr_str - assert "device_type=CPU" in repr_str - assert "file_size=1000 MB" in repr_str - assert "license=MIT" in repr_str - - -def test_from_list_response(): - """Test from_list_response class method.""" - response_data = { - "name": "test-model", - "displayName": "Test Model", - "modelType": "ONNX", - "providerType": "AzureFoundry", - "uri": "azureml://test", - "version": "1", - "promptTemplate": {"prompt": "test"}, - "publisher": "Test", - "task": "chat-completion", - "runtime": {"deviceType": "CPU", "executionProvider": "CPUExecutionProvider"}, - "fileSizeMb": 1000, - "modelSettings": {}, - "alias": "test", - "supportsToolCalling": False, - "license": "MIT", - "licenseDescription": "Test license", - "parentModelUri": "azureml://parent", - "maxOutputTokens": 1024, - "minFLVersion": "1.0.0", - } - - # Test with dict - model_info = FoundryModelInfo.from_list_response(response_data) - assert model_info.alias == "test" - assert model_info.id == "test-model" - assert model_info.version == "1" - assert model_info.execution_provider == ExecutionProvider.CPU - assert model_info.device_type == DeviceType.CPU - assert model_info.uri == "azureml://test" - assert model_info.file_size_mb == 1000 - assert model_info.prompt_template == {"prompt": "test"} - assert model_info.provider == "AzureFoundry" - assert model_info.publisher == "Test" - assert model_info.license == "MIT" - assert model_info.task == "chat-completion" - - # Test with FoundryListResponseModel instance - model = FoundryListResponseModel.model_validate(response_data) - model_info = FoundryModelInfo.from_list_response(model) - assert model_info.alias == "test" - assert model_info.id == "test-model" - assert model_info.version == "1" - assert model_info.execution_provider == ExecutionProvider.CPU - assert model_info.device_type == DeviceType.CPU - assert model_info.uri == "azureml://test" - assert model_info.file_size_mb == 1000 - assert model_info.prompt_template == {"prompt": "test"} - assert model_info.provider == "AzureFoundry" - assert model_info.publisher == "Test" - assert model_info.license == "MIT" - assert model_info.task == "chat-completion" - - -def test_to_download_body(): - """Test to_download_body method.""" - model_info = FoundryModelInfo( - alias="test", - id="test-model", - version="1", - execution_provider=ExecutionProvider.CPU, - device_type=DeviceType.CPU, - uri="azureml://test", - file_size_mb=1000, - supports_tool_calling=False, - prompt_template={"prompt": "test"}, - provider="AzureFoundry", - publisher="Test", - license="MIT", - task="chat-completion", - ) - - download_body = model_info.to_download_body() - assert download_body["Name"] == "test-model" - assert download_body["Uri"] == "azureml://test" - assert download_body["Publisher"] == "Test" - assert download_body["ProviderType"] == "AzureFoundryLocal" # Note the "Local" suffix - assert download_body["PromptTemplate"] == {"prompt": "test"} - - # Test with different provider - model_info.provider = "OtherProvider" - download_body = model_info.to_download_body() - assert download_body["ProviderType"] == "OtherProvider" # No "Local" suffix for other providers diff --git a/sdk_legacy/python/test/test_service.py b/sdk_legacy/python/test/test_service.py deleted file mode 100644 index 2701205b0..000000000 --- a/sdk_legacy/python/test/test_service.py +++ /dev/null @@ -1,89 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# -------------------------------------------------------------------------- -from unittest import mock - -import pytest - -from foundry_local.service import assert_foundry_installed, get_service_uri, start_service - - -def test_assert_foundry_installed(): - """Test checking if foundry is installed.""" - # Test when foundry is installed - with mock.patch("shutil.which") as mock_which: - mock_which.return_value = "/usr/local/bin/foundry" - assert_foundry_installed() # Should not raise an exception - - # Test when foundry is not installed - with mock.patch("shutil.which") as mock_which: - mock_which.return_value = None - with pytest.raises(RuntimeError, match="Foundry is not installed or not on PATH!"): - assert_foundry_installed() - - -def test_get_service_uri(): - """Test getting service URI.""" - # Test when service is running - with mock.patch("subprocess.Popen") as mock_popen: - mock_process = mock.MagicMock() - mock_process.communicate.return_value = ( - b"Model management service is running on http://localhost:5273/openai/status", - b"", - ) - mock_popen.return_value.__enter__.return_value = mock_process - assert get_service_uri() == "http://localhost:5273" - - # Test when service is not running - with mock.patch("subprocess.Popen") as mock_popen: - mock_process = mock.MagicMock() - mock_process.communicate.return_value = (b"Model management service is not running!", b"") - mock_popen.return_value.__enter__.return_value = mock_process - assert get_service_uri() is None - - # Test with IPv4 address - with mock.patch("subprocess.Popen") as mock_popen: - mock_process = mock.MagicMock() - mock_process.communicate.return_value = ( - b"Model management service is running on http://127.0.0.1:5273/openai/status", - b"", - ) - mock_popen.return_value.__enter__.return_value = mock_process - assert get_service_uri() == "http://127.0.0.1:5273" - - -def test_start_service(): - """Test starting the service.""" - # Test when service is already running - with mock.patch("foundry_local.service.get_service_uri") as mock_get_uri: - mock_get_uri.return_value = "http://localhost:5273" - assert start_service() == "http://localhost:5273" - mock_get_uri.assert_called_once() - - # Test when service needs to be started and starts successfully - with ( - mock.patch("foundry_local.service.get_service_uri") as mock_get_uri, - mock.patch("subprocess.Popen") as mock_popen, - mock.patch("time.sleep") as mock_sleep, - ): - # First call returns None (not running), second call returns URI (started) - mock_get_uri.side_effect = [None, "http://localhost:5273"] - assert start_service() == "http://localhost:5273" - assert mock_get_uri.call_count == 2 - mock_popen.assert_called_once_with(["foundry", "service", "start"]) - mock_sleep.assert_not_called() - - # Test when service fails to start - with ( - mock.patch("foundry_local.service.get_service_uri") as mock_get_uri, - mock.patch("subprocess.Popen") as mock_popen, - mock.patch("foundry_local.service.time.sleep") as mock_sleep, - ): - # Always return None (never starts) - num_sleeps = 10 - mock_get_uri.return_value = None - assert start_service() is None - assert mock_get_uri.call_count == num_sleeps + 1 - mock_popen.assert_called_once_with(["foundry", "service", "start"]) - assert mock_sleep.call_count == num_sleeps diff --git a/sdk_legacy/rust/.cargo/config.toml b/sdk_legacy/rust/.cargo/config.toml deleted file mode 100644 index 84c574452..000000000 --- a/sdk_legacy/rust/.cargo/config.toml +++ /dev/null @@ -1,7 +0,0 @@ -[registries] - -[source.crates-io] -replace-with = "ORT-Nightly" - -[source.ORT-Nightly] -registry = "sparse+https://pkgs.dev.azure.com/aiinfra/PublicPackages/_packaging/ORT-Nightly/Cargo/index/" diff --git a/sdk_legacy/rust/Cargo.toml b/sdk_legacy/rust/Cargo.toml deleted file mode 100644 index 0fffaa0b1..000000000 --- a/sdk_legacy/rust/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "foundry-local" -version = "0.2.0" -edition = "2021" -description = "SDK for Microsoft Foundry Local service" -license = "MIT" -repository = "https://github.com/microsoft/Foundry-Local" -readme = "README.md" - -[features] -integration-tests = [] - -[dependencies] -reqwest = { version = "0.11", features = ["json", "stream", "rustls-tls"], default-features = false } -tokio = { version = "1", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" -log = "0.4" -env_logger = "0.10" -anyhow = "1.0" -which = "4.4" -regex = "1.9" -dirs = "5.0" -indicatif = "0.17" -async-trait = "0.1" -futures-util = "0.3" -url = "=2.4.1" - -[dev-dependencies] -axum = "0.7" -tower = "0.4" -tower-http = { version = "0.5", features = ["trace"] } diff --git a/sdk_legacy/rust/README.md b/sdk_legacy/rust/README.md deleted file mode 100644 index f9e610ec9..000000000 --- a/sdk_legacy/rust/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# Foundry Local Rust SDK - -A Rust SDK for interacting with the Microsoft Foundry Local service. This SDK allows you to manage and use AI models locally on your device. - -## Features -- Start and manage the Foundry Local service -- Download models from the Foundry catalog -- Load and unload models -- List available, cached, and loaded models -- Interact with loaded models using a simple API - -## Usage - -### Prerequisites - -To use this SDK, ensure you have the following prerequisites: - -- Foundry Local must be installed and available on the PATH -- Rust 1.70.0 or later - -### Create a new Rust project: - -```bash -cargo new hello-foundry-local -cd hello-foundry-local -``` - -### Install crates - -Install the following Rust crates using Cargo: - -```bash -cargo add foundry-local anyhow env_logger serde_json -cargo add reqwest --features json -cargo add tokio --features full -``` - -Alternatively, you can add these dependencies manually to your `Cargo.toml` file. - -```toml -[dependencies] -anyhow = "1.0.98" -env_logger = "0.11.8" -foundry-local = "0.1.0" -reqwest = { version = "0.12.19", features = ["json"] } -serde_json = "1.0.140" -tokio = { version = "1.45.1", features = ["full"] } -``` - -### Update the `main.rs` file - -Replace the contents of `src/main.rs` with the following code to create a simple application that interacts with the Foundry Local service: - -```rust -use foundry_local::FoundryLocalManager; -use anyhow::Result; - -#[tokio::main] -async fn main() -> Result<()> { - // Create a FoundryLocalManager instance with default options - let mut manager = FoundryLocalManager::builder() - .alias_or_model_id("phi-3.5-mini") // Specify the model to use - .bootstrap(true) // Start the service if not running - .build() - .await?; - - // Use the OpenAI compatible API to interact with the model - let client = reqwest::Client::new(); - let endpoint = manager.endpoint()?; - let response = client.post(&format!("{}/chat/completions", endpoint)) - .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", manager.api_key())) - .json(&serde_json::json!({ - "model": manager.get_model_info("phi-3.5-mini", true).await?.id, - "messages": [{"role": "user", "content": "What is the golden ratio?"}], - })) - .send() - .await?; - - let result = response.json::().await?; - println!("{}", result["choices"][0]["message"]["content"]); - - Ok(()) -} -``` - -### Run the application - -To run the application, execute the following command in your terminal: - -```bash -cargo run -``` - -## License - -Licensed under the MIT License. \ No newline at end of file diff --git a/sdk_legacy/rust/src/api.rs b/sdk_legacy/rust/src/api.rs deleted file mode 100644 index a2762384a..000000000 --- a/sdk_legacy/rust/src/api.rs +++ /dev/null @@ -1,672 +0,0 @@ -use std::{collections::HashMap, env}; - -use anyhow::{anyhow, Result}; -use log::{debug, info}; -use serde_json::Value; - -use crate::{ - client::HttpClient, - models::{ExecutionProvider, FoundryListResponseModel, FoundryModelInfo}, - service::{check_foundry_installed, get_service_uri, start_service}, -}; - -/// Manager for Foundry Local SDK operations. -pub struct FoundryLocalManager { - service_uri: Option, - client: Option, - catalog_list: Option>, - catalog_dict: Option>, - timeout: Option, -} - -/// Builder for creating a FoundryLocalManager instance. -pub struct FoundryLocalManagerBuilder { - alias_or_model_id: Option, - bootstrap: bool, - timeout_secs: Option, -} - -impl FoundryLocalManagerBuilder { - /// Create a new builder instance. - pub fn new() -> Self { - Self { - alias_or_model_id: None, - bootstrap: false, - timeout_secs: None, - } - } - - /// Set the alias or model ID to download and load. - pub fn alias_or_model_id(mut self, alias_or_model_id: impl Into) -> Self { - self.alias_or_model_id = Some(alias_or_model_id.into()); - self - } - - /// Set whether to start the service if it is not running. - pub fn bootstrap(mut self, bootstrap: bool) -> Self { - self.bootstrap = bootstrap; - self - } - - /// Set the timeout for the HTTP client in seconds. - pub fn timeout_secs(mut self, timeout_secs: u64) -> Self { - self.timeout_secs = Some(timeout_secs); - self - } - - /// Build the FoundryLocalManager instance. - pub async fn build(self) -> Result { - check_foundry_installed()?; - - let mut manager = FoundryLocalManager { - service_uri: None, - client: None, - catalog_list: None, - catalog_dict: None, - timeout: self.timeout_secs, - }; - - if let Some(uri) = get_service_uri() { - manager.set_service_uri_and_client(Some(uri)); - } - - if self.bootstrap { - manager.start_service()?; - - if let Some(model) = self.alias_or_model_id { - manager.download_model(&model, None, false).await?; - manager.load_model(&model, Some(600)).await?; - } - } - - Ok(manager) - } -} - -/// Extract numeric version from ID (e.g. model-x:3 → 3) -/// -/// # Returns -/// -/// Numeric version as i32, or -1 if not found. -fn get_version(model_id: &str) -> i32 { - match model_id.rsplit(':').next() { - Some(version_str) => version_str.parse::().unwrap_or(-1), - None => -1, - } -} - -impl FoundryLocalManager { - /// Create a new builder for FoundryLocalManager. - pub fn builder() -> FoundryLocalManagerBuilder { - FoundryLocalManagerBuilder::new() - } - - fn set_service_uri_and_client(&mut self, service_uri: Option) { - self.service_uri = service_uri.clone(); - self.client = service_uri.map(|uri| HttpClient::new(&uri, self.timeout)); - } - - /// Get the service URI. - /// - /// # Returns - /// - /// URI of the Foundry service. - pub fn service_uri(&self) -> Result<&str> { - self.service_uri - .as_deref() - .ok_or_else(|| anyhow!("Service URI is not set. Please start the service first.")) - } - - /// Get the HTTP client. - /// - /// # Returns - /// - /// HTTP client instance. - fn client(&self) -> Result<&HttpClient> { - self.client - .as_ref() - .ok_or_else(|| anyhow!("HTTP client is not set. Please start the service first.")) - } - - /// Get the endpoint for the service. - /// - /// # Returns - /// - /// Endpoint URL. - pub fn endpoint(&self) -> Result { - Ok(format!("{}/v1", self.service_uri()?)) - } - - /// Get the API key for authentication. - /// - /// # Returns - /// - /// API key. - pub fn api_key(&self) -> String { - env::var("OPENAI_API_KEY").unwrap_or_else(|_| "OPENAI_API_KEY".to_string()) - } - - // Service management API - - /// Check if the service is running. Will also set the service URI if it is not set. - /// - /// # Returns - /// - /// True if the service is running, False otherwise. - pub fn is_service_running(&mut self) -> bool { - if let Some(uri) = get_service_uri() { - self.set_service_uri_and_client(Some(uri)); - true - } else { - false - } - } - - /// Start the service. - /// - /// # Returns - /// - /// Result indicating success or failure. - pub fn start_service(&mut self) -> Result<()> { - let uri = start_service()?; - self.set_service_uri_and_client(Some(uri)); - Ok(()) - } - - // Catalog API - - /// Get a list of available models in the catalog. - /// - /// # Returns - /// - /// List of catalog models. - pub async fn list_catalog_models(&mut self) -> Result<&Vec> { - if self.catalog_list.is_none() { - let models: Vec = self - .client()? - .get("/foundry/list", None) - .await? - .ok_or_else(|| anyhow!("Failed to get catalog models"))?; - - self.catalog_list = Some( - models - .iter() - .map(FoundryModelInfo::from_list_response) - .collect(), - ); - } - - Ok(self.catalog_list.as_ref().unwrap()) - } - - /// Get a dictionary of available models. Keyed by model ID and alias. - /// Alias points to the most preferred model. - /// - /// # Returns - /// - /// Dictionary of catalog models. - async fn get_catalog_dict(&mut self) -> Result<&HashMap> { - if let Some(ref dict) = self.catalog_dict { - return Ok(dict); - } - - let catalog_models = self.list_catalog_models().await?; - let mut catalog_dict = HashMap::new(); - let mut alias_candidates: HashMap> = HashMap::new(); - - for model in catalog_models.iter() { - catalog_dict.insert(model.id.clone(), model.clone()); - alias_candidates - .entry(model.alias.clone()) - .or_default() - .push(model.clone()); - } - - // Set up priority order - let mut preferred_order = vec![ - ExecutionProvider::QNN, - ExecutionProvider::CUDA, - ExecutionProvider::CPU, - ExecutionProvider::WebGPU, - ]; - - if cfg!(not(target_os = "windows")) { - // Move CPU to the end for non-Windows - preferred_order.retain(|p| *p != ExecutionProvider::CPU); - preferred_order.push(ExecutionProvider::CPU); - } - - let priority_map: HashMap = preferred_order - .into_iter() - .enumerate() - .map(|(i, provider)| (provider, i)) - .collect(); - - // Choose the best candidate per alias - for (alias, candidates) in alias_candidates { - if let Some(best) = candidates.into_iter().max_by(|a, b| { - // Compare priority (lower index = higher priority) - let pa = priority_map.get(&a.runtime).copied().unwrap_or(usize::MAX); - let pb = priority_map.get(&b.runtime).copied().unwrap_or(usize::MAX); - match pa.cmp(&pb) { - std::cmp::Ordering::Less => std::cmp::Ordering::Greater, - std::cmp::Ordering::Greater => std::cmp::Ordering::Less, - std::cmp::Ordering::Equal => { - // Same priority → compare version - get_version(&a.id).cmp(&get_version(&b.id)) - } - } - }) { - catalog_dict.insert(alias, best); - } - } - - self.catalog_dict = Some(catalog_dict); - Ok(self.catalog_dict.as_ref().unwrap()) - } - - /// Refresh the catalog. - pub fn refresh_catalog(&mut self) { - self.catalog_list = None; - self.catalog_dict = None; - } - - /// Get the model information by alias or ID. - /// - /// # Arguments - /// - /// * `alias_or_model_id` - Alias or Model ID. If it is an alias, the most preferred model will be returned. - /// * `raise_on_not_found` - If true, raise an error if the model is not found. Default is false. - /// - /// # Returns - /// - /// Model information, or None if not found and raise_on_not_found is false. - pub async fn get_model_info( - &mut self, - alias_or_model_id: &str, - raise_on_not_found: bool, - ) -> Result { - let catalog_dict = self.get_catalog_dict().await?; - - // Try to find exact match (alias or id) - if let Some(model_info) = catalog_dict.get(alias_or_model_id) { - return Ok(model_info.clone()); - } - - // If alias_or_model_id does NOT contain version suffix (":") - if !alias_or_model_id.contains(':') { - // Search for model IDs starting with alias_or_model_id + ":" - let prefix = format!("{}:", alias_or_model_id); - let mut best_version = -1; - let mut best_model: Option = None; - - for (key, info) in catalog_dict.iter() { - if key.starts_with(&prefix) { - let version = get_version(key); - if version > best_version { - best_version = version; - best_model = Some(info.clone()); - } - } - } - - if let Some(model) = best_model { - return Ok(model); - } - } - - if raise_on_not_found { - Err(anyhow!( - "Model {} not found in the catalog", - alias_or_model_id - )) - } else { - Err(anyhow!( - "Model {} not found in the catalog", - alias_or_model_id - )) - } - } - - /// Get the latest model information by alias or model ID. - /// The difference from get_model_info is that this method will return the latest version of the model - /// even when you pass it a model id that contains a version suffix. - /// - /// # Arguments - /// - /// * `alias_or_model_id` - Alias or Model ID. If it is an alias, the most preferred model will be returned. - /// * `raise_on_not_found` - If true, raise an error if the model is not found. Default is false. - /// - /// # Returns - /// - /// Latest model information, or None if not found and raise_on_not_found is false. - pub async fn get_latest_model_info( - &mut self, - alias_or_model_id: &str, - raise_on_not_found: bool, - ) -> Result { - if alias_or_model_id.is_empty() { - if raise_on_not_found { - return Err(anyhow!("The provided model alias or ID was empty.")); - } else { - return Err(anyhow!( - "Model alias or ID was empty and raise_on_not_found is false." - )); - } - } - - let catalog = self.get_catalog_dict().await?; - - if !alias_or_model_id.contains(':') { - // Alias or ID without version suffix - if let Some(model) = catalog.get(alias_or_model_id) { - // Found directly in catalog (alias or id) - Ok(model.clone()) - } else { - // Fallback: try get_model_info which can look for latest version of ID without version - self.get_model_info(alias_or_model_id, raise_on_not_found) - .await - } - } else { - // ID with version suffix: strip version and get latest by ID without version - let id_without_version = alias_or_model_id.split(':').next().unwrap_or(""); - self.get_model_info(id_without_version, raise_on_not_found) - .await - } - } - - // Cache management API - - /// Get the cache location. - /// - /// # Returns - /// - /// Cache location as a string. - pub async fn get_cache_location(&self) -> Result { - let response: Value = self - .client()? - .get("/foundry/cache", None) - .await? - .ok_or_else(|| anyhow!("Failed to get cache location"))?; - - response["location"] - .as_str() - .map(|s| s.to_string()) - .ok_or_else(|| anyhow!("Invalid cache location response")) - } - - /// List cached models. - /// - /// # Returns - /// - /// List of cached models. - pub async fn list_cached_models(&mut self) -> Result> { - let response: Value = self - .client()? - .get("/openai/models", None) - .await? - .ok_or_else(|| anyhow!("Failed to list cached models - no response"))?; - - // Handle both direct array response and object with models field - let model_ids = if response.is_array() { - response - .as_array() - .ok_or_else(|| anyhow!("Invalid models response - expected array"))? - .iter() - .filter_map(|v| v.as_str()) - .map(|s| s.to_string()) - .collect::>() - } else { - response["models"] - .as_array() - .ok_or_else(|| anyhow!("Invalid models response - expected models field"))? - .iter() - .filter_map(|v| v.as_str()) - .map(|s| s.to_string()) - .collect::>() - }; - - self.fetch_model_infos(&model_ids).await - } - - async fn fetch_model_infos(&mut self, model_ids: &[String]) -> Result> { - let mut results = Vec::new(); - let catalog_dict = self.get_catalog_dict().await?; - - for id in model_ids { - if let Some(model) = catalog_dict.get(id) { - results.push(model.clone()); - } else { - debug!("Model {id} not found in the catalog"); - } - } - - Ok(results) - } - - /// Download a model. - /// - /// # Arguments - /// - /// * `alias_or_model_id` - Alias or Model ID. - /// * `token` - Optional token for authentication. - /// * `force` - If true, force re-download even if the model is already cached. - /// - /// # Returns - /// - /// Downloaded model information. - pub async fn download_model( - &mut self, - alias_or_model_id: &str, - token: Option<&str>, - force: bool, - ) -> Result { - let model_info = self.get_model_info(alias_or_model_id, true).await?; - info!( - "Downloading model: {} ({}) - {} MB", - model_info.alias, model_info.id, model_info.file_size_mb - ); - - let mut body = model_info.to_download_body(); - - if let Some(t) = token { - body["token"] = Value::String(t.to_string()); - } - - if force { - body["Force"] = Value::Bool(true); - } - - let client = self.client()?; - let _response: Value = client - .post_with_progress("/openai/download", Some(body)) - .await?; - - Ok(model_info) - } - - /// Check if a newer version of a model is available. - /// - /// # Arguments - /// - /// * `alias_or_model_id` - Alias or Model ID. - /// - /// # Returns - /// - /// bool: True if a newer version is available, False otherwise. - pub async fn is_model_upgradeable(&mut self, alias_or_model_id: &str) -> Result { - info!("Checking if model '{}' is upgradeable", alias_or_model_id); - - // Get the latest model info (throws if not found) - let latest_model_info = self.get_latest_model_info(alias_or_model_id, true).await?; - - let latest_version = get_version(&latest_model_info.id); - if latest_version == -1 { - return Ok(false); // Invalid version format - } - - let cached_models = self.list_cached_models().await?; - for cached_model in cached_models { - if cached_model.id == latest_model_info.id - && get_version(&cached_model.id) == latest_version - { - return Ok(false); // Already cached latest version - } - } - - Ok(true) // Latest version not in cache - } - - /// Downloads the latest version of a model to the local cache, if it is not already cached. - /// - /// # Arguments - /// - /// * `alias_or_model_id` - The alias or model ID to upgrade. - /// * `token` - Optional authentication token. - /// - /// # Errors - /// - /// Downloaded model information. - pub async fn upgrade_model( - &mut self, - alias_or_model_id: &str, - token: Option<&str>, - ) -> Result { - // Get the latest model info (this also validates existence) - let model_info = self.get_latest_model_info(alias_or_model_id, true).await?; - - // Download the model and discard the result - self.download_model(&model_info.id, token, false).await - } - - /// Load a model. - /// - /// # Arguments - /// - /// * `alias_or_model_id` - Alias or Model ID. - /// * `ttl` - Optional time-to-live in seconds. Default is 10 minutes (600 seconds). - /// - /// # Returns - /// - /// Loaded model information. - pub async fn load_model( - &mut self, - alias_or_model_id: &str, - ttl: Option, - ) -> Result { - let model_info = self.get_model_info(alias_or_model_id, true).await?; - info!("Loading model: {} ({})", model_info.alias, model_info.id); - - let url = format!("/openai/load/{}", model_info.id); - let ttl_str = ttl.unwrap_or(600).to_string(); - let mut query_params = vec![("ttl", ttl_str.as_str())]; - - // Handle execution provider selection for WEBGPU and CUDA models - let ep_str = if matches!( - model_info.runtime, - ExecutionProvider::WebGPU | ExecutionProvider::CUDA - ) { - let has_cuda_support = self - .list_catalog_models() - .await? - .iter() - .any(|mi| mi.runtime == ExecutionProvider::CUDA); - - if has_cuda_support { - ExecutionProvider::CUDA.get_alias().to_string() - } else { - model_info.runtime.get_alias().to_string() - } - } else { - String::new() - }; - - if !ep_str.is_empty() { - query_params.push(("ep", ep_str.as_str())); - } - - let client = self.client()?; - let _response: Option = client.get(&url, Some(&query_params)).await?; - - Ok(model_info) - } - - /// Unload a model. - /// - /// # Arguments - /// - /// * `alias_or_model_id` - Alias or Model ID. - /// * `force` - If true, force unload even if the model is in use. - /// - /// # Returns - /// - /// Result indicating success or failure. - pub async fn unload_model(&mut self, alias_or_model_id: &str, force: bool) -> Result<()> { - let model_info = self.get_model_info(alias_or_model_id, true).await?; - info!("Unloading model: {} ({})", model_info.alias, model_info.id); - - let url = format!("/openai/unload/{}", model_info.id); - let force_str = force.to_string(); - let query_params = vec![("force", force_str.as_str())]; - - let client = self.client()?; - let _response: Option = client.get(&url, Some(&query_params)).await?; - - Ok(()) - } - - /// List loaded models. - /// - /// # Returns - /// - /// List of loaded models. - pub async fn list_loaded_models(&mut self) -> Result> { - let response: Value = self - .client()? - .get("/openai/loadedmodels", None) - .await? - .ok_or_else(|| anyhow!("Failed to list loaded models - no response"))?; - - // Handle both direct array response and object with models field - let model_ids = if response.is_array() { - response - .as_array() - .ok_or_else(|| anyhow!("Invalid models response - expected array"))? - .iter() - .filter_map(|v| v.as_str()) - .map(|s| s.to_string()) - .collect::>() - } else { - response["models"] - .as_array() - .ok_or_else(|| anyhow!("Invalid models response - expected models field"))? - .iter() - .filter_map(|v| v.as_str()) - .map(|s| s.to_string()) - .collect::>() - }; - - self.fetch_model_infos(&model_ids).await - } - - /// Set a custom service URI and client for testing purposes. - #[doc(hidden)] - pub fn set_test_service_uri(&mut self, uri: &str) { - self.service_uri = Some(uri.to_string()); - self.client = Some(HttpClient::new(uri, self.timeout)); - self.catalog_list = None; - self.catalog_dict = None; - } - - /// Create a new FoundryLocalManager instance for testing without checking if Foundry is installed. - #[doc(hidden)] - pub async fn new_for_testing() -> Result { - Ok(Self { - service_uri: None, - client: None, - catalog_list: None, - catalog_dict: None, - timeout: None, - }) - } -} diff --git a/sdk_legacy/rust/src/client.rs b/sdk_legacy/rust/src/client.rs deleted file mode 100644 index 68684fae1..000000000 --- a/sdk_legacy/rust/src/client.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::time::Duration; - -use anyhow::Result; -use indicatif::{ProgressBar, ProgressStyle}; -use log::{debug, error}; -use regex::Regex; -use reqwest::{Client, Response, StatusCode}; -use serde::de::DeserializeOwned; -use serde_json::Value; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum ClientError { - #[error("HTTP response error: {0} - {1}")] - HttpResponseError(StatusCode, String), - - #[error("Connection error: {0}")] - ConnectionError(String), - - #[error("JSON parsing error: {0}")] - JsonError(#[from] serde_json::Error), - - #[error("Request error: {0}")] - RequestError(#[from] reqwest::Error), - - #[error("{0}")] - Other(String), -} - -/// Client for Foundry Local SDK. -pub struct HttpClient { - client: Client, - base_url: String, -} - -impl HttpClient { - /// Create a new HTTP client for the Foundry Local SDK. - /// - /// # Arguments - /// - /// * `host` - Base URL of the host. - /// * `timeout` - Optional timeout for the HTTP client in seconds. - /// - /// # Returns - /// - /// A new HttpClient instance. - pub fn new(host: &str, timeout_secs: Option) -> Self { - let timeout = timeout_secs.map(Duration::from_secs); - const VERSION: &str = env!("CARGO_PKG_VERSION"); - let mut client_builder = - Client::builder().user_agent(&format!("foundry-local-rust-sdk/{}", VERSION)); - - if let Some(timeout) = timeout { - client_builder = client_builder.timeout(timeout); - } - - let client = client_builder.build().expect("Failed to build HTTP client"); - - Self { - client, - base_url: host.to_string(), - } - } - - /// Send a GET request to the specified path with optional query parameters. - /// - /// # Arguments - /// - /// * `path` - Path for the GET request. - /// * `query_params` - Optional query parameters for the request. - /// - /// # Returns - /// - /// JSON response or None if no content. - pub async fn get( - &self, - path: &str, - query_params: Option<&[(&str, &str)]>, - ) -> Result, ClientError> { - let url = format!("{}{}", self.base_url, path); - debug!("GET {url}"); - - let mut request_builder = self.client.get(&url); - if let Some(params) = query_params { - request_builder = request_builder.query(params); - } - - let response = request_builder.send().await.map_err(|e| { - if e.is_connect() { - ClientError::ConnectionError( - "Could not connect to Foundry Local! Please check if the Foundry Local service is running and the host URL is correct." - .to_string() - ) - } else { - ClientError::RequestError(e) - } - })?; - - self.handle_response(response).await - } - - /// Send a POST request to the specified path with optional request body and show progress. - /// - /// # Arguments - /// - /// * `path` - Path for the POST request. - /// * `body` - Optional request body in JSON format. - /// - /// # Returns - /// - /// JSON response. - pub async fn post_with_progress( - &self, - path: &str, - body: Option, - ) -> Result { - let url = format!("{}{}", self.base_url, path); - debug!("POST with progress: {url}"); - - let mut request_builder = self.client.post(&url); - if let Some(ref json_body) = body { - request_builder = request_builder.json(json_body); - } - - let response = request_builder.send().await.map_err(|e| { - if e.is_connect() { - ClientError::ConnectionError( - "Could not connect to Foundry Local! Please check if the Foundry Local service is running and the host URL is correct." - .to_string() - ) - } else { - ClientError::RequestError(e) - } - })?; - - let pb = ProgressBar::new(100); - pb.set_style( - ProgressStyle::default_bar() - .template("[{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent}%") - .unwrap() - .progress_chars("█▉▊▋▌▍▎▏ "), - ); - - let mut final_json = String::new(); - let status = response.status(); - let mut stream = response.bytes_stream(); - - use futures_util::StreamExt; - let re = Regex::new(r"(\d+(?:\.\d+)?)%").unwrap(); - let mut prev_percent = 0.0; - - while let Some(chunk_result) = stream.next().await { - match chunk_result { - Ok(chunk) => { - let chunk_str = String::from_utf8_lossy(&chunk); - - if chunk_str.starts_with("{") || !final_json.is_empty() { - final_json.push_str(&chunk_str); - } else if let Some(captures) = re.captures(&chunk_str) { - if let Some(percent_match) = captures.get(1) { - if let Ok(percent) = percent_match.as_str().parse::() { - if percent > prev_percent { - pb.set_position((percent) as u64); - prev_percent = percent; - } - } - } - } - } - Err(e) => { - error!("Error reading response chunk: {e}"); - return Err(ClientError::RequestError(e)); - } - } - } - - pb.finish_and_clear(); - - if !status.is_success() { - return Err(ClientError::HttpResponseError(status, final_json)); - } - - if final_json.is_empty() { - return Err(ClientError::Other("Empty response body".to_string())); - } - - if !final_json.ends_with("}") { - return Err(ClientError::Other(format!( - "Invalid JSON response: {final_json}" - ))); - } - - serde_json::from_str(&final_json).map_err(ClientError::JsonError) - } - - async fn handle_response( - &self, - response: Response, - ) -> Result, ClientError> { - let status = response.status(); - if !status.is_success() { - let text = response - .text() - .await - .unwrap_or_else(|_| "No response text".to_string()); - return Err(ClientError::HttpResponseError(status, text)); - } - - let text = response.text().await.map_err(ClientError::RequestError)?; - if text.is_empty() { - return Ok(None); - } - - let parsed = serde_json::from_str(&text)?; - Ok(Some(parsed)) - } -} diff --git a/sdk_legacy/rust/src/lib.rs b/sdk_legacy/rust/src/lib.rs deleted file mode 100644 index a563c9807..000000000 --- a/sdk_legacy/rust/src/lib.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! # Foundry Local SDK -//! -//! A Rust SDK for interacting with the Microsoft Foundry Local service. -//! This SDK allows you to manage and use AI models locally on your device. -//! -//! ## Features -//! - Start and manage the Foundry Local service -//! - Download models from the Foundry catalog -//! - Load and unload models -//! - List available, cached, and loaded models -//! - Interact with loaded models using a simple API -//! -//! ## Example -//! -//! ```rust, ignore -//! use foundry_local::FoundryLocalManager; -//! use anyhow::Result; -//! -//! #[tokio::main] -//! async fn main() -> Result<()> { -//! // Create a FoundryLocalManager instance for a model with default options -//! let mut manager = FoundryLocalManager::builder() -//! .alias_or_model_id("phi-4-mini") -//! .build() -//! .await?; -//! -//! // Use the OpenAI compatible API to interact with the model -//! let client = reqwest::Client::new(); -//! let response = client.post(&format!("{}/chat/completions", manager.endpoint()?)) -//! .header("Content-Type", "application/json") -//! .header("Authorization", format!("Bearer {}", manager.api_key())) -//! .json(&serde_json::json!({ -//! "model": manager.get_model_info("phi-4-mini", true).await?.id, -//! "messages": [{"role": "user", "content": "What is the golden ratio?"}], -//! })) -//! .send() -//! .await?; -//! -//! let result = response.json::().await?; -//! if let Some(content) = result["choices"][0]["message"]["content"].as_str() { -//! println!("{}", content); -//! } else { -//! println!("No content found in response."); -//! } -//! -//! Ok(()) -//! } -//! ``` - -pub mod api; -mod client; -pub mod models; -mod service; - -pub use api::FoundryLocalManager; diff --git a/sdk_legacy/rust/src/models.rs b/sdk_legacy/rust/src/models.rs deleted file mode 100644 index b6aa6fe64..000000000 --- a/sdk_legacy/rust/src/models.rs +++ /dev/null @@ -1,174 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::fmt; - -/// Enumeration of devices supported by the model. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "UPPERCASE")] -pub enum DeviceType { - CPU, - GPU, - NPU, -} - -impl fmt::Display for DeviceType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DeviceType::CPU => write!(f, "CPU"), - DeviceType::GPU => write!(f, "GPU"), - DeviceType::NPU => write!(f, "NPU"), - } - } -} - -/// Enumeration of execution providers supported by the model. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum ExecutionProvider { - #[serde(rename = "CPUExecutionProvider")] - CPU, - #[serde(rename = "WebGpuExecutionProvider")] - WebGPU, - #[serde(rename = "CUDAExecutionProvider")] - CUDA, - #[serde(rename = "QNNExecutionProvider")] - QNN, -} - -impl ExecutionProvider { - /// Get the alias for the execution provider. - pub fn get_alias(&self) -> String { - match self { - ExecutionProvider::CPU => "cpu".to_string(), - ExecutionProvider::WebGPU => "webgpu".to_string(), - ExecutionProvider::CUDA => "cuda".to_string(), - ExecutionProvider::QNN => "qnn".to_string(), - } - } -} - -impl fmt::Display for ExecutionProvider { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - ExecutionProvider::CPU => "CPUExecutionProvider", - ExecutionProvider::WebGPU => "WebGpuExecutionProvider", - ExecutionProvider::CUDA => "CUDAExecutionProvider", - ExecutionProvider::QNN => "QNNExecutionProvider", - } - ) - } -} - -/// Model runtime information. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ModelRuntime { - #[serde(rename = "deviceType")] - pub device_type: DeviceType, - #[serde(rename = "executionProvider")] - pub execution_provider: ExecutionProvider, -} - -/// Response model for listing models. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FoundryListResponseModel { - pub name: String, - #[serde(rename = "displayName")] - pub display_name: String, - #[serde(rename = "modelType")] - pub model_type: String, - #[serde(rename = "providerType")] - pub provider_type: String, - pub uri: String, - pub version: String, - #[serde(rename = "promptTemplate")] - pub prompt_template: serde_json::Value, - pub publisher: String, - pub task: String, - pub runtime: ModelRuntime, - #[serde(rename = "fileSizeMb")] - pub file_size_mb: i32, - #[serde(rename = "modelSettings")] - pub model_settings: serde_json::Value, - pub alias: String, - #[serde(rename = "supportsToolCalling")] - pub supports_tool_calling: bool, - pub license: String, - #[serde(rename = "licenseDescription")] - pub license_description: String, - #[serde(rename = "parentModelUri")] - pub parent_model_uri: String, - #[serde(rename = "maxOutputTokens")] - pub max_output_tokens: i32, - #[serde(rename = "minFLVersion")] - pub min_fl_version: String, -} - -/// Model information. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FoundryModelInfo { - pub alias: String, - pub id: String, - pub version: String, - pub runtime: ExecutionProvider, - pub uri: String, - pub file_size_mb: i32, - pub prompt_template: serde_json::Value, - pub provider: String, - pub publisher: String, - pub license: String, - pub task: String, -} - -impl FoundryModelInfo { - /// Create a FoundryModelInfo object from a FoundryListResponseModel object. - pub fn from_list_response(response: &FoundryListResponseModel) -> Self { - Self { - alias: response.alias.clone(), - id: response.name.clone(), - version: response.version.clone(), - runtime: response.runtime.execution_provider.clone(), - uri: response.uri.clone(), - file_size_mb: response.file_size_mb, - prompt_template: response.prompt_template.clone(), - provider: response.provider_type.clone(), - publisher: response.publisher.clone(), - license: response.license.clone(), - task: response.task.clone(), - } - } - - /// Convert the FoundryModelInfo object to a dictionary for download. - pub fn to_download_body(&self) -> serde_json::Value { - let provider_type = if self.provider == "AzureFoundry" { - format!("{}Local", self.provider) - } else { - self.provider.clone() - }; - - serde_json::json!({ - "model": { - "Name": self.id, - "Uri": self.uri, - "Publisher": self.publisher, - "ProviderType": provider_type, - "PromptTemplate": self.prompt_template, - }, - "IgnorePipeReport": true - }) - } -} - -impl fmt::Display for FoundryModelInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "FoundryModelInfo(alias={}, id={}, runtime={}, file_size={} MB, license={})", - self.alias, - self.id, - self.runtime.get_alias(), - self.file_size_mb, - self.license - ) - } -} diff --git a/sdk_legacy/rust/src/service.rs b/sdk_legacy/rust/src/service.rs deleted file mode 100644 index ecae34703..000000000 --- a/sdk_legacy/rust/src/service.rs +++ /dev/null @@ -1,65 +0,0 @@ -use anyhow::{anyhow, Result}; -use log::{info, warn}; -use regex::Regex; -use std::{ - process::{Command, Stdio}, - thread, - time::Duration, -}; -use which::which; - -/// Checks that Foundry is installed. -/// -/// # Errors -/// -/// Returns an error if Foundry is not installed or not on PATH. -pub fn check_foundry_installed() -> Result<()> { - which("foundry").map_err(|_| anyhow!("Foundry is not installed or not on PATH!"))?; - Ok(()) -} - -/// Get the service URI if the service is running. -/// -/// # Returns -/// -/// URI of the running Foundry service, or None if not running. -pub fn get_service_uri() -> Option { - let output = Command::new("foundry") - .args(["service", "status"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .ok()?; - - let stdout = String::from_utf8_lossy(&output.stdout); - let re = Regex::new(r"http://(?:[a-zA-Z0-9.-]+|\d{1,3}(\.\d{1,3}){3}):\d+").ok()?; - - re.find(&stdout).map(|m| m.as_str().to_string()) -} - -/// Start the Foundry service. -/// -/// # Returns -/// -/// URI of the started Foundry service, or None if it failed to start. -pub fn start_service() -> Result { - if let Some(service_url) = get_service_uri() { - info!("Foundry service is already running at {service_url}"); - return Ok(service_url); - } - - // Start the service in the background - let _child = Command::new("foundry").args(["service", "start"]).spawn()?; - - // Check for service availability - for _ in 0..10 { - if let Some(service_url) = get_service_uri() { - info!("Foundry service started successfully at {service_url}"); - return Ok(service_url); - } - thread::sleep(Duration::from_millis(100)); - } - - warn!("Foundry service did not start within the expected time. May not be running."); - Err(anyhow!("Failed to start Foundry service")) -} diff --git a/sdk_legacy/rust/tests/README.md b/sdk_legacy/rust/tests/README.md deleted file mode 100644 index ffca2447e..000000000 --- a/sdk_legacy/rust/tests/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Foundry Local SDK Tests - -This directory contains tests for the Foundry Local SDK, with a focus on testing the API without requiring an actual Foundry Local service to be running. - -## Mock Service - -The `mock_service.rs` file contains a mock implementation of the Foundry Local service that can be used for testing. The mock service provides endpoints that mimic the behavior of the real service, but with predictable responses and no actual model loading or execution. - -Key features: -- In-memory state management for models, cache, etc. -- HTTP endpoints that match the real service -- Configurable responses - -## Running Tests - -To run all tests: - -```bash -cargo test --package foundry-local -``` - -To run a specific test: - -```bash -cargo test --package foundry-local test_list_catalog_models -``` - -To run the integration tests: -```bash -cargo test --features integration-tests -``` - -## Adding New Tests - -When adding new tests: - -1. Add test functions to `test_api.rs` -2. Make sure each test starts its own mock server and shuts it down when done -3. Use the `TestableFoundryLocalManager` trait to create a manager that uses the mock server - -Example: - -```rust -#[tokio::test] -async fn test_my_new_feature() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let mut manager = FoundryLocalManager::with_test_uri(&server_uri); - - // Test your feature - // ... - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} -``` - -## Extending the Mock Service - -To add new endpoints or behaviors to the mock service: - -1. Add new state fields to `MockState` if needed -2. Implement handler functions for the new endpoints -3. Add routes to the router in `start_mock_server` \ No newline at end of file diff --git a/sdk_legacy/rust/tests/integration_tests.rs b/sdk_legacy/rust/tests/integration_tests.rs deleted file mode 100644 index 8cdf28362..000000000 --- a/sdk_legacy/rust/tests/integration_tests.rs +++ /dev/null @@ -1,106 +0,0 @@ -#[cfg(feature = "integration-tests")] -mod integration_tests { - use foundry_local::FoundryLocalManager; - - async fn setup_manager() -> FoundryLocalManager { - FoundryLocalManager::builder() - .bootstrap(true) - .build() - .await - .expect("Failed to create manager") - } - - #[tokio::test] - async fn test_real_service_workflow() { - // Create manager - let mut manager = setup_manager().await; - - // List catalog models and get the first model ID - let model_id = { - let catalog_models = manager - .list_catalog_models() - .await - .expect("Failed to list catalog models"); - - assert!(!catalog_models.is_empty(), "No models found in catalog"); - - let first_model = &catalog_models[0]; - println!( - "Testing with model: {} ({})", - first_model.alias, first_model.id - ); - first_model.id.clone() - }; - - // Download the model - let model_info = manager - .download_model(&model_id, None, false) - .await - .expect("Failed to download model"); - assert_eq!(model_info.id, model_id); - - // Verify model is cached - let cached_models = manager - .list_cached_models() - .await - .expect("Failed to list cached models"); - assert!( - cached_models.iter().any(|m| m.id == model_id), - "Downloaded model not found in cache" - ); - - // Load the model - let model_info = manager - .load_model(&model_id, Some(300)) - .await - .expect("Failed to load model"); - assert_eq!(model_info.id, model_id); - - // Verify model is loaded - let loaded_models = manager - .list_loaded_models() - .await - .expect("Failed to list loaded models"); - assert!(loaded_models.iter().any(|m| m.id == model_id)); - - // Get the endpoint before making the request - let endpoint = manager.endpoint().expect("Failed to get endpoint"); - - // Use the OpenAI compatible API to interact with the model - let client = reqwest::Client::new(); - let response = client - .post(&format!("{}/chat/completions", endpoint)) - .json(&serde_json::json!({ - "model": model_info.id, - "messages": [{"role": "user", "content": "What is 2+2?"}], - })) - .send() - .await - .expect("Failed to send request"); - - // Parse and display the response - let result = response - .json::() - .await - .expect("Failed to parse response"); - if let Some(content) = result["choices"][0]["message"]["content"].as_str() { - println!("\nResponse:\n{}", content); - } else { - println!("\nError: Failed to extract response content from API result"); - println!("Full API response: {}", result); - } - - // Unload the model - manager - .unload_model(&model_id, true) - .await - .expect("Failed to unload model"); - - // Verify model is unloaded - let loaded_models = manager - .list_loaded_models() - .await - .expect("Failed to list loaded models"); - assert!(!loaded_models.iter().any(|m| m.id == model_id)); - } -} diff --git a/sdk_legacy/rust/tests/mock_service.rs b/sdk_legacy/rust/tests/mock_service.rs deleted file mode 100644 index e7233b1d3..000000000 --- a/sdk_legacy/rust/tests/mock_service.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::net::SocketAddr; -use std::sync::{Arc, Mutex}; - -use axum::{ - extract::State, - response::IntoResponse, - routing::{get, post}, - Json, Router, -}; -use serde_json::{json, Value}; -use tokio::net::TcpListener; -use tokio::sync::oneshot; - -use foundry_local::models::{ExecutionProvider, FoundryModelInfo}; - -// MockState represents the state of the mock Foundry Local service -pub struct MockState { - catalog_models: Vec, - cached_models: Vec, - loaded_models: Vec, - cache_location: String, -} - -impl Default for MockState { - fn default() -> Self { - Self { - catalog_models: vec![ - FoundryModelInfo { - id: "Phi-4-mini-instruct-generic-cpu:1".to_string(), - alias: "phi-4-mini".to_string(), - runtime: ExecutionProvider::CPU, - file_size_mb: 100, - uri: "azureml://registries/azureml/models/Phi-4-mini-instruct-generic-cpu/versions/1".to_string(), - version: "1.0".to_string(), - prompt_template: serde_json::json!({}), - provider: "MockProvider".to_string(), - publisher: "MockPublisher".to_string(), - license: "MIT".to_string(), - task: "text-generation".to_string(), - }, - FoundryModelInfo { - id: "qwen2.5-0.5b-instruct-cuda-gpu:1".to_string(), - alias: "qwen2.5-0.5b".to_string(), - runtime: ExecutionProvider::CUDA, - file_size_mb: 500, - uri: "azureml://registries/azureml/models/qwen2.5-0.5b-instruct-cuda-gpu/versions/1".to_string(), - version: "1.0".to_string(), - prompt_template: serde_json::json!({}), - provider: "MockProvider".to_string(), - publisher: "MockPublisher".to_string(), - license: "MIT".to_string(), - task: "text-generation".to_string(), - }, - FoundryModelInfo { - id: "qwen2.5-0.5b-instruct-cuda-gpu:2".to_string(), - alias: "qwen2.5-0.5b".to_string(), - runtime: ExecutionProvider::CUDA, - file_size_mb: 600, - uri: "azureml://registries/azureml/models/qwen2.5-0.5b-instruct-cuda-gpu/versions/2".to_string(), - version: "2.0".to_string(), - prompt_template: serde_json::json!({}), - provider: "MockProvider".to_string(), - publisher: "MockPublisher".to_string(), - license: "MIT".to_string(), - task: "text-generation".to_string(), - }, - ], - cached_models: vec!["qwen2.5-0.5b-instruct-cuda-gpu:1".to_string()], - loaded_models: vec![], - cache_location: "/tmp/mock-cache".to_string(), - } - } -} - -type AppState = Arc>; - -// Handler for /foundry/list endpoint -async fn list_catalog(State(state): State) -> impl IntoResponse { - let state = state.lock().unwrap(); - let models = state.catalog_models.iter() - .map(|model| { - json!({ - "name": model.id, - "displayName": model.id, - "modelType": "LLM", - "providerType": model.provider, - "uri": model.uri, - "version": model.version, - "promptTemplate": model.prompt_template, - "publisher": model.publisher, - "task": model.task, - "runtime": { - "deviceType": if model.runtime == ExecutionProvider::CPU { "CPU" } else { "GPU" }, - "executionProvider": model.runtime.to_string() - }, - "fileSizeMb": model.file_size_mb, - "modelSettings": json!({}), - "alias": model.alias, - "supportsToolCalling": false, - "license": model.license, - "licenseDescription": "", - "parentModelUri": "", - "maxOutputTokens": 1024, - "minFLVersion": "1.0.0" - }) - }) - .collect::>(); - - Json(models) -} - -// Handler for /openai/models endpoint -async fn list_cached_models(State(state): State) -> impl IntoResponse { - let state = state.lock().unwrap(); - Json(json!({ - "models": state.cached_models - })) -} - -// Handler for /foundry/cache endpoint -async fn get_cache_location(State(state): State) -> impl IntoResponse { - let state = state.lock().unwrap(); - Json(json!({ - "location": state.cache_location - })) -} - -// Handler for /foundry/loaded endpoint -async fn list_loaded_models(State(state): State) -> impl IntoResponse { - let state = state.lock().unwrap(); - Json(json!({ - "models": state.loaded_models - })) -} - -// Handler for /openai/download endpoint -async fn download_model( - State(state): State, - Json(payload): Json, -) -> impl IntoResponse { - let model_id = payload["model"]["Name"].as_str().unwrap_or_default(); - let mut state = state.lock().unwrap(); - if !state.cached_models.contains(&model_id.to_string()) { - state.cached_models.push(model_id.to_string()); - } - - Json(json!({ - "status": "success", - "message": format!("Downloaded model {}", model_id) - })) -} - -// Handler for /openai/load/:model_id endpoint -async fn load_model( - State(state): State, - axum::extract::Path(model_id): axum::extract::Path, -) -> impl IntoResponse { - let mut state = state.lock().unwrap(); - if !state.loaded_models.contains(&model_id) { - state.loaded_models.push(model_id.clone()); - } - - Json(json!({ - "status": "success", - "message": format!("Loaded model {}", model_id) - })) -} - -// Handler for /openai/unload/:model_id endpoint -async fn unload_model( - State(state): State, - axum::extract::Path(model_id): axum::extract::Path, -) -> impl IntoResponse { - let mut state = state.lock().unwrap(); - state.loaded_models.retain(|id| *id != model_id); - - Json(json!({ - "status": "success", - "message": format!("Unloaded model {}", model_id) - })) -} - -// Create and start the mock server -pub async fn start_mock_server() -> (String, oneshot::Sender<()>) { - let state = Arc::new(Mutex::new(MockState::default())); - - let app = Router::new() - .route("/foundry/list", get(list_catalog)) - .route("/openai/models", get(list_cached_models)) - .route("/foundry/cache", get(get_cache_location)) - .route("/openai/loadedmodels", get(list_loaded_models)) - .route("/openai/download", post(download_model)) - .route("/openai/load/:model_id", get(load_model)) - .route("/openai/unload/:model_id", get(unload_model)) - .with_state(state); - - let addr = SocketAddr::from(([127, 0, 0, 1], 0)); - let listener = TcpListener::bind(addr).await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - let server_uri = format!("http://{}", server_addr); - - let (tx, rx) = oneshot::channel(); - - tokio::spawn(async move { - axum::serve(listener, app) - .with_graceful_shutdown(async { - rx.await.ok(); - }) - .await - .unwrap(); - }); - - (server_uri, tx) -} diff --git a/sdk_legacy/rust/tests/test_api.rs b/sdk_legacy/rust/tests/test_api.rs deleted file mode 100644 index 843d14df5..000000000 --- a/sdk_legacy/rust/tests/test_api.rs +++ /dev/null @@ -1,242 +0,0 @@ -mod mock_service; - -use foundry_local::{models::ExecutionProvider, FoundryLocalManager}; -use mock_service::start_mock_server; -use std::env; - -// Helper trait to inject mock service URI into FoundryLocalManager for testing -trait TestableFoundryLocalManager { - async fn with_test_uri(uri: &str) -> Self; -} - -impl TestableFoundryLocalManager for FoundryLocalManager { - async fn with_test_uri(uri: &str) -> Self { - let mut manager = FoundryLocalManager::builder().build().await.unwrap(); - manager.set_test_service_uri(uri); - manager - } -} - -#[tokio::test] -async fn test_list_catalog_models() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Test listing catalog models - let catalog_models = manager.list_catalog_models().await.unwrap(); - - // Verify the result - assert_eq!(catalog_models.len(), 3); - assert_eq!(catalog_models[0].id, "Phi-4-mini-instruct-generic-cpu:1"); - assert_eq!(catalog_models[0].alias, "phi-4-mini"); - assert_eq!(catalog_models[0].runtime, ExecutionProvider::CPU); - assert_eq!(catalog_models[1].id, "qwen2.5-0.5b-instruct-cuda-gpu:1"); - assert_eq!(catalog_models[1].alias, "qwen2.5-0.5b"); - assert_eq!(catalog_models[1].runtime, ExecutionProvider::CUDA); - assert_eq!(catalog_models[2].id, "qwen2.5-0.5b-instruct-cuda-gpu:2"); - assert_eq!(catalog_models[2].alias, "qwen2.5-0.5b"); - assert_eq!(catalog_models[2].runtime, ExecutionProvider::CUDA); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_get_model_info() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Test getting model info by ID - let model_info = manager - .get_model_info("Phi-4-mini-instruct-generic-cpu:1", false) - .await - .unwrap(); - assert_eq!(model_info.id, "Phi-4-mini-instruct-generic-cpu:1"); - assert_eq!(model_info.alias, "phi-4-mini"); - - // Test getting model info by alias - let model_info = manager.get_model_info("qwen2.5-0.5b", false).await.unwrap(); - assert_eq!(model_info.id, "qwen2.5-0.5b-instruct-cuda-gpu:2"); - assert_eq!(model_info.alias, "qwen2.5-0.5b"); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_get_cache_location() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Test getting cache location - let cache_location = manager.get_cache_location().await.unwrap(); - assert_eq!(cache_location, "/tmp/mock-cache"); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_list_cached_models() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Test listing cached models - let cached_models = manager.list_cached_models().await.unwrap(); - assert_eq!(cached_models.len(), 1); - assert_eq!(cached_models[0].id, "qwen2.5-0.5b-instruct-cuda-gpu:1"); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_download_model() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Test downloading a model - let model_info = manager - .download_model("qwen2.5-0.5b", None, false) - .await - .unwrap(); - assert_eq!(model_info.id, "qwen2.5-0.5b-instruct-cuda-gpu:2"); - - // Verify latest version of the model is now also cached - let cached_models = manager.list_cached_models().await.unwrap(); - assert_eq!(cached_models.len(), 2); - assert!(cached_models - .iter() - .any(|m| m.id == "qwen2.5-0.5b-instruct-cuda-gpu:1")); - assert!(cached_models - .iter() - .any(|m| m.id == "qwen2.5-0.5b-instruct-cuda-gpu:2")); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_is_model_upgradeable() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // When no version is in the cache - let is_upgradeable = manager.is_model_upgradeable("phi-4-mini").await.unwrap(); - assert!( - is_upgradeable, - "Expected upgradeable because latest version is not cached" - ); - - // When the latest version is not in the cache - let is_upgradeable = manager.is_model_upgradeable("qwen2.5-0.5b").await.unwrap(); - assert!( - is_upgradeable, - "Expected upgradeable because latest version is not cached" - ); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_upgrade_model_success() { - let (server_uri, shutdown_tx) = start_mock_server().await; - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // This should trigger download of latest model "qwen2.5-0.5b-instruct-cuda-gpu:2" - let model_info = manager.upgrade_model("qwen2.5-0.5b", None).await.unwrap(); - - // Assert returned model info is correct (example) - assert_eq!(model_info.id, "qwen2.5-0.5b-instruct-cuda-gpu:2"); - - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_upgrade_model_not_found() { - let (server_uri, shutdown_tx) = start_mock_server().await; - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Call upgrade_model without unwrap to get Result - let result = manager.upgrade_model("non-existent-model", None).await; - - // Assert it returned an error - assert!(result.is_err()); - - let err_msg = format!("{}", result.unwrap_err()); - assert!(err_msg.contains("not found")); - - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_load_and_unload_model() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let mut manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Test loading a model - let model_info = manager.load_model("phi-4-mini", Some(300)).await.unwrap(); - assert_eq!(model_info.id, "Phi-4-mini-instruct-generic-cpu:1"); - - // Verify the model is loaded - let loaded_models = manager.list_loaded_models().await.unwrap(); - assert_eq!(loaded_models.len(), 1); - assert_eq!(loaded_models[0].id, "Phi-4-mini-instruct-generic-cpu:1"); - - // Test unloading the model - manager.unload_model("phi-4-mini", false).await.unwrap(); - - // Verify the model is unloaded - let loaded_models = manager.list_loaded_models().await.unwrap(); - assert_eq!(loaded_models.len(), 0); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -} - -#[tokio::test] -async fn test_endpoint_and_api_key() { - // Start the mock server - let (server_uri, shutdown_tx) = start_mock_server().await; - - // Create a manager with the mock server URI - let manager = FoundryLocalManager::with_test_uri(&server_uri).await; - - // Test getting endpoint - let endpoint = manager.endpoint().unwrap(); - assert_eq!(endpoint, format!("{}/v1", server_uri)); - - // Test getting API key (default) - let api_key = manager.api_key(); - assert_eq!(api_key, "OPENAI_API_KEY"); - - // Test getting API key (with env var) - env::set_var("OPENAI_API_KEY", "test-api-key"); - let api_key = manager.api_key(); - assert_eq!(api_key, "test-api-key"); - - // Shutdown the mock server - shutdown_tx.send(()).unwrap(); -}