From fd40c323bc952ab5ad40e20ced394dbb70953f1f Mon Sep 17 00:00:00 2001 From: Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com> Date: Fri, 16 Feb 2024 12:19:18 +0100 Subject: [PATCH] feat: llm responder --- .php-cs-fixer.php | 2 - composer.json | 3 +- composer.lock | 611 +++++++----------- src/AcceptHeaderTest.php | 4 +- src/ArrayFlattenIteratorTest.php | 4 +- src/Attribute/RespondsToPromptSubject.php | 21 + src/BackusNaurFormGrammar.php | 17 + .../SubjectActionGrammar.php | 57 ++ src/Constraint/AnyConstraintTest.php | 4 +- src/Constraint/AnyOfConstraintTest.php | 4 +- src/Constraint/BooleanConstraintTest.php | 4 +- src/Constraint/ConstConstraintTest.php | 4 +- src/Constraint/EnumConstraintTest.php | 4 +- src/Constraint/FilenameConstraintTest.php | 4 +- src/Constraint/IntegerConstraintTest.php | 4 +- src/Constraint/ListConstraintTest.php | 4 +- src/Constraint/MapConstraintTest.php | 4 +- src/Constraint/NumberConstraintTest.php | 4 +- src/Constraint/ObjectConstraintTest.php | 4 +- src/Constraint/StringConstraintTest.php | 4 +- src/Constraint/TupleConstraintTest.php | 4 +- src/LlamaCppCompletionRequest.php | 15 +- src/LlmSystemPrompt.php | 19 + .../SubjectActionSystemPrompt.php | 86 +++ src/OAuth2ScopePatternChunkTest.php | 4 +- src/OAuth2ScopePatternTest.php | 4 +- src/PostfixBounceAnalyzerTest.php | 4 +- src/PromptSubjectRequest.php | 15 + src/PromptSubjectResponderAggregate.php | 80 +++ src/PromptSubjectResponderCollection.php | 57 ++ src/PromptSubjectResponderInterface.php | 13 + src/PromptSubjectResponse.php | 45 ++ src/SingletonCollection.php | 1 + src/SingletonContainerTest.php | 4 +- ...omptSubjectResponderCollectionProvider.php | 50 ++ src/SubjectActionTokenReader.php | 97 +++ src/SubjectActionTokenReaderTest.php | 90 +++ src/WebSocketProtocolIteratorTest.php | 4 +- src/WebSocketRPCConnectionHandle.php | 2 +- src/WebSocketServerController.php | 6 + 40 files changed, 953 insertions(+), 414 deletions(-) create mode 100644 src/Attribute/RespondsToPromptSubject.php create mode 100644 src/BackusNaurFormGrammar.php create mode 100644 src/BackusNaurFormGrammar/SubjectActionGrammar.php create mode 100644 src/LlmSystemPrompt.php create mode 100644 src/LlmSystemPrompt/SubjectActionSystemPrompt.php create mode 100644 src/PromptSubjectRequest.php create mode 100644 src/PromptSubjectResponderAggregate.php create mode 100644 src/PromptSubjectResponderCollection.php create mode 100644 src/PromptSubjectResponderInterface.php create mode 100644 src/PromptSubjectResponse.php create mode 100644 src/SingletonProvider/PromptSubjectResponderCollectionProvider.php create mode 100644 src/SubjectActionTokenReader.php create mode 100644 src/SubjectActionTokenReaderTest.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 13f4d5c7..576e15af 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -56,7 +56,6 @@ return $config->setRules([ 'encoding' => true, 'ereg_to_preg' => true, 'explicit_indirect_variable' => true, - 'final_internal_class' => true, 'fully_qualified_strict_types' => true, 'function_declaration' => true, 'function_to_constant' => true, @@ -181,7 +180,6 @@ return $config->setRules([ 'php_unit_set_up_tear_down_visibility' => true, 'php_unit_test_annotation' => true, 'php_unit_test_case_static_method_calls' => true, - 'php_unit_test_class_requires_covers' => true, 'phpdoc_align' => [ 'align' => 'vertical', ], diff --git a/composer.json b/composer.json index 5c7377b7..e29f77ed 100644 --- a/composer.json +++ b/composer.json @@ -55,8 +55,7 @@ "hyperf/grpc-client": "^3.1" }, "require-dev": { - "mockery/mockery": "^1.6", - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^11.0", "swoole/ide-helper": "^5.1" }, "suggest": { diff --git a/composer.lock b/composer.lock index 001207d8..8eb66b1e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "67a85cdfb2cba2f9447d2cd9278e2dd6", + "content-hash": "4ced37d39429f3fa0e8212c3f62018c9", "packages": [ { "name": "amphp/amp", @@ -957,16 +957,16 @@ }, { "name": "doctrine/dbal", - "version": "3.8.1", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2" + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c9ea252cdce4da324ede3d6c5913dd89f769afd2", - "reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/a19a1d05ca211f41089dffcc387733a6875196cb", + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb", "shasum": "" }, "require": { @@ -1050,7 +1050,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.1" + "source": "https://github.com/doctrine/dbal/tree/3.8.2" }, "funding": [ { @@ -1066,7 +1066,7 @@ "type": "tidelift" } ], - "time": "2024-02-03T17:33:49+00:00" + "time": "2024-02-12T18:36:36+00:00" }, { "name": "doctrine/deprecations", @@ -2027,16 +2027,16 @@ }, { "name": "google/protobuf", - "version": "v3.25.2", + "version": "v3.25.3", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "83ea4c147718666ce6a9b9332ac2aa588c9211eb" + "reference": "983a87f4f8798a90ca3a25b0f300b8fda38df643" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/83ea4c147718666ce6a9b9332ac2aa588c9211eb", - "reference": "83ea4c147718666ce6a9b9332ac2aa588c9211eb", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/983a87f4f8798a90ca3a25b0f300b8fda38df643", + "reference": "983a87f4f8798a90ca3a25b0f300b8fda38df643", "shasum": "" }, "require": { @@ -2065,9 +2065,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v3.25.2" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v3.25.3" }, - "time": "2024-01-09T22:12:32+00:00" + "time": "2024-02-15T21:11:49+00:00" }, { "name": "grpc/grpc", @@ -8332,140 +8332,6 @@ } ], "packages-dev": [ - { - "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "shasum": "" - }, - "require": { - "php": "^5.3|^7.0|^8.0" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" - }, - "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "classmap": [ - "hamcrest" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "This is the PHP port of Hamcrest Matchers", - "keywords": [ - "test" - ], - "support": { - "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" - }, - "time": "2020-07-09T08:09:16+00:00" - }, - { - "name": "mockery/mockery", - "version": "1.6.7", - "source": { - "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06", - "reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06", - "shasum": "" - }, - "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", - "php": ">=7.3" - }, - "conflict": { - "phpunit/phpunit": "<8.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.10", - "symplify/easy-coding-standard": "^12.0.8" - }, - "type": "library", - "autoload": { - "files": [ - "library/helpers.php", - "library/Mockery.php" - ], - "psr-4": { - "Mockery\\": "library/Mockery" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "https://github.com/padraic", - "role": "Author" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "https://davedevelopment.co.uk", - "role": "Developer" - }, - { - "name": "Nathanael Esayeas", - "email": "nathanael.esayeas@protonmail.com", - "homepage": "https://github.com/ghostwriter", - "role": "Lead Developer" - } - ], - "description": "Mockery is a simple yet flexible PHP mock object framework", - "homepage": "https://github.com/mockery/mockery", - "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" - ], - "support": { - "docs": "https://docs.mockery.io/", - "issues": "https://github.com/mockery/mockery/issues", - "rss": "https://github.com/mockery/mockery/releases.atom", - "security": "https://github.com/mockery/mockery/security/advisories", - "source": "https://github.com/mockery/mockery" - }, - "time": "2023-12-10T02:24:34+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.11.1", @@ -8696,35 +8562,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.11", + "version": "11.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "78c3b7625965c2513ee96569a4dbb62601784145" + "reference": "5e238e4b982cb272bf9faeee6f33af83d465d0e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145", - "reference": "78c3b7625965c2513ee96569a4dbb62601784145", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5e238e4b982cb272bf9faeee6f33af83d465d0e2", + "reference": "5e238e4b982cb272bf9faeee6f33af83d465d0e2", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", + "nikic/php-parser": "^5.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-text-template": "^4.0", + "sebastian/code-unit-reverse-lookup": "^4.0", + "sebastian/complexity": "^4.0", + "sebastian/environment": "^7.0", + "sebastian/lines-of-code": "^3.0", + "sebastian/version": "^5.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -8733,7 +8599,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "11.0-dev" } }, "autoload": { @@ -8762,7 +8628,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.0" }, "funding": [ { @@ -8770,32 +8636,32 @@ "type": "github" } ], - "time": "2023-12-21T15:38:30+00:00" + "time": "2024-02-02T06:03:46+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/99e95c94ad9500daca992354fa09d7b99abe2210", + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -8823,7 +8689,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.0" }, "funding": [ { @@ -8831,28 +8697,28 @@ "type": "github" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2024-02-02T06:05:04+00:00" }, { "name": "phpunit/php-invoker", - "version": "4.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5d8d9355a16d8cc5a1305b0a85342cfa420612be", + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -8860,7 +8726,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -8886,7 +8752,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.0" }, "funding": [ { @@ -8894,32 +8761,32 @@ "type": "github" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2024-02-02T06:05:50+00:00" }, { "name": "phpunit/php-text-template", - "version": "3.0.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/d38f6cbff1cdb6f40b03c9811421561668cc133e", + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -8946,7 +8813,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.0" }, "funding": [ { @@ -8954,32 +8821,32 @@ "type": "github" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2024-02-02T06:06:56+00:00" }, { "name": "phpunit/php-timer", - "version": "6.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8a59d9e25720482ee7fcdf296595e08795b84dc5", + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -9005,7 +8872,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.0" }, "funding": [ { @@ -9013,20 +8881,20 @@ "type": "github" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2024-02-02T06:08:01+00:00" }, { "name": "phpunit/phpunit", - "version": "10.5.10", + "version": "11.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c" + "reference": "de24e7e7c67fbf437f7b6cd7bc919f2dc6fd89d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50b8e314b6d0dd06521dc31d1abffa73f25f850c", - "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de24e7e7c67fbf437f7b6cd7bc919f2dc6fd89d4", + "reference": "de24e7e7c67fbf437f7b6cd7bc919f2dc6fd89d4", "shasum": "" }, "require": { @@ -9039,23 +8907,22 @@ "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.5", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-invoker": "^4.0", - "phpunit/php-text-template": "^3.0", - "phpunit/php-timer": "^6.0", - "sebastian/cli-parser": "^2.0", - "sebastian/code-unit": "^2.0", - "sebastian/comparator": "^5.0", - "sebastian/diff": "^5.0", - "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.1", - "sebastian/global-state": "^6.0.1", - "sebastian/object-enumerator": "^5.0", - "sebastian/recursion-context": "^5.0", - "sebastian/type": "^4.0", - "sebastian/version": "^4.0" + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-invoker": "^5.0", + "phpunit/php-text-template": "^4.0", + "phpunit/php-timer": "^7.0", + "sebastian/cli-parser": "^3.0", + "sebastian/code-unit": "^3.0", + "sebastian/comparator": "^6.0", + "sebastian/diff": "^6.0", + "sebastian/environment": "^7.0", + "sebastian/exporter": "^6.0", + "sebastian/global-state": "^7.0", + "sebastian/object-enumerator": "^6.0", + "sebastian/type": "^5.0", + "sebastian/version": "^5.0" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -9066,7 +8933,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-main": "11.0-dev" } }, "autoload": { @@ -9098,7 +8965,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.0.3" }, "funding": [ { @@ -9114,32 +8981,32 @@ "type": "tidelift" } ], - "time": "2024-02-04T09:07:51+00:00" + "time": "2024-02-10T06:31:16+00:00" }, { "name": "sebastian/cli-parser", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae" + "reference": "efd6ce5bb8131fe981e2f879dbd47605fbe0cc6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efd6ce5bb8131fe981e2f879dbd47605fbe0cc6f", + "reference": "efd6ce5bb8131fe981e2f879dbd47605fbe0cc6f", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9162,7 +9029,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.0" }, "funding": [ { @@ -9170,32 +9038,32 @@ "type": "github" } ], - "time": "2023-02-03T06:58:15+00:00" + "time": "2024-02-02T05:48:04+00:00" }, { "name": "sebastian/code-unit", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6634549cb8d702282a04a774e36a7477d2bd9015", + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9218,7 +9086,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.0" }, "funding": [ { @@ -9226,32 +9095,32 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2024-02-02T05:50:41+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/df80c875d3e459b45c6039e4d9b71d4fbccae25d", + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -9273,7 +9142,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.0" }, "funding": [ { @@ -9281,36 +9151,36 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2024-02-02T05:52:17+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bd0f2fa5b9257c69903537b266ccb80fcf940db8", + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -9350,7 +9220,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.0" }, "funding": [ { @@ -9358,33 +9228,33 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-02-02T05:53:45+00:00" }, { "name": "sebastian/complexity", - "version": "3.2.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "reference": "88a434ad86150e11a606ac4866b09130712671f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/88a434ad86150e11a606ac4866b09130712671f0", + "reference": "88a434ad86150e11a606ac4866b09130712671f0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -9408,7 +9278,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.0" }, "funding": [ { @@ -9416,33 +9286,33 @@ "type": "github" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2024-02-02T05:55:19+00:00" }, { "name": "sebastian/diff", - "version": "5.1.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" + "reference": "3e3f502419518897a923aa1c64d51f9def2e0aff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3e3f502419518897a923aa1c64d51f9def2e0aff", + "reference": "3e3f502419518897a923aa1c64d51f9def2e0aff", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -9475,7 +9345,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.0" }, "funding": [ { @@ -9483,27 +9353,27 @@ "type": "github" } ], - "time": "2023-12-22T10:55:06+00:00" + "time": "2024-02-02T05:56:35+00:00" }, { "name": "sebastian/environment", - "version": "6.0.1", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951" + "reference": "100d8b855d7180f79f9a9a5c483f2d960581c3ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951", - "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/100d8b855d7180f79f9a9a5c483f2d960581c3ea", + "reference": "100d8b855d7180f79f9a9a5c483f2d960581c3ea", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-posix": "*" @@ -9511,7 +9381,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -9539,7 +9409,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/environment/tree/7.0.0" }, "funding": [ { @@ -9547,34 +9417,34 @@ "type": "github" } ], - "time": "2023-04-11T05:39:26+00:00" + "time": "2024-02-02T05:57:54+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc" + "reference": "d0c0a93fc746b0c066037f1e7d09104129e868ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64f51654862e0f5e318db7e9dcc2292c63cdbddc", - "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d0c0a93fc746b0c066037f1e7d09104129e868ff", + "reference": "d0c0a93fc746b0c066037f1e7d09104129e868ff", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -9617,7 +9487,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.0.0" }, "funding": [ { @@ -9625,35 +9495,35 @@ "type": "github" } ], - "time": "2023-09-24T13:22:09+00:00" + "time": "2024-02-02T05:58:52+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.1", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4" + "reference": "590e7cbc6565fa2e26c3df4e629a34bb0bc00c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/590e7cbc6565fa2e26c3df4e629a34bb0bc00c17", + "reference": "590e7cbc6565fa2e26c3df4e629a34bb0bc00c17", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -9672,14 +9542,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.0" }, "funding": [ { @@ -9687,33 +9557,33 @@ "type": "github" } ], - "time": "2023-07-19T07:19:23+00:00" + "time": "2024-02-02T05:59:33+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.2", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/376c5b3f6b43c78fdc049740bca76a7c846706c0", + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9737,7 +9607,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.0" }, "funding": [ { @@ -9745,34 +9615,34 @@ "type": "github" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2024-02-02T06:00:36+00:00" }, { "name": "sebastian/object-enumerator", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -9794,7 +9664,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.0" }, "funding": [ { @@ -9802,32 +9673,32 @@ "type": "github" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2024-02-02T06:01:29+00:00" }, { "name": "sebastian/object-reflector", - "version": "3.0.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/bb2a6255d30853425fd38f032eb64ced9f7f132d", + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -9849,7 +9720,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.0" }, "funding": [ { @@ -9857,32 +9729,32 @@ "type": "github" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2024-02-02T06:02:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b75224967b5a466925c6d54e68edd0edf8dd4ed4", + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -9912,7 +9784,8 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.0" }, "funding": [ { @@ -9920,32 +9793,32 @@ "type": "github" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2024-02-02T06:08:48+00:00" }, { "name": "sebastian/type", - "version": "4.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8502785eb3523ca0dd4afe9ca62235590020f3f", + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -9968,7 +9841,8 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.0.0" }, "funding": [ { @@ -9976,29 +9850,29 @@ "type": "github" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2024-02-02T06:09:34+00:00" }, { "name": "sebastian/version", - "version": "4.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/13999475d2cb1ab33cb73403ba356a814fdbb001", + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -10021,7 +9895,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.0" }, "funding": [ { @@ -10029,7 +9904,7 @@ "type": "github" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2024-02-02T06:10:47+00:00" }, { "name": "swoole/ide-helper", diff --git a/src/AcceptHeaderTest.php b/src/AcceptHeaderTest.php index e58b29eb..58c76d1f 100644 --- a/src/AcceptHeaderTest.php +++ b/src/AcceptHeaderTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(AcceptHeader::class)] final class AcceptHeaderTest extends TestCase { public function test_request_header_is_parsed(): void diff --git a/src/ArrayFlattenIteratorTest.php b/src/ArrayFlattenIteratorTest.php index 88429963..f67dd7ac 100644 --- a/src/ArrayFlattenIteratorTest.php +++ b/src/ArrayFlattenIteratorTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(ArrayFlattenIterator::class)] final class ArrayFlattenIteratorTest extends TestCase { public function test_dotpaths_are_generated(): void diff --git a/src/Attribute/RespondsToPromptSubject.php b/src/Attribute/RespondsToPromptSubject.php new file mode 100644 index 00000000..9bb1fe31 --- /dev/null +++ b/src/Attribute/RespondsToPromptSubject.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\Attribute; + +use Attribute; +use Distantmagic\Resonance\Attribute as BaseAttribute; + +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class RespondsToPromptSubject extends BaseAttribute +{ + /** + * @param non-empty-string $action + * @param non-empty-string $subject + */ + public function __construct( + public string $action, + public string $subject, + ) {} +} diff --git a/src/BackusNaurFormGrammar.php b/src/BackusNaurFormGrammar.php new file mode 100644 index 00000000..554403a3 --- /dev/null +++ b/src/BackusNaurFormGrammar.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use JsonSerializable; + +abstract readonly class BackusNaurFormGrammar implements JsonSerializable +{ + abstract public function getGrammarContent(): string; + + public function jsonSerialize(): mixed + { + return $this->getGrammarContent(); + } +} diff --git a/src/BackusNaurFormGrammar/SubjectActionGrammar.php b/src/BackusNaurFormGrammar/SubjectActionGrammar.php new file mode 100644 index 00000000..414fe3af --- /dev/null +++ b/src/BackusNaurFormGrammar/SubjectActionGrammar.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\BackusNaurFormGrammar; + +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\BackusNaurFormGrammar; +use Distantmagic\Resonance\PromptSubjectResponderCollection; + +#[Singleton] +readonly class SubjectActionGrammar extends BackusNaurFormGrammar +{ + /** + * @var non-empty-string + */ + private string $grammar; + + public function __construct( + PromptSubjectResponderCollection $promptSubjectResponderCollection, + ) { + /** + * @var array<non-empty-string> $subjects + */ + $subjects = ['("unknown" " " "unknown")']; + + foreach ($promptSubjectResponderCollection->getPromptableActions() as $subject => $actions) { + $subjects[] = sprintf( + '("%s" " " ("%s"))', + $subject, + implode('" | "', $actions->toArray()) + ); + } + + $subjectsSerialized = sprintf('%s', implode(' | ', $subjects)); + + $stringGrammar = <<<'STRING_GRAMMAR' + "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" + STRING_GRAMMAR; + + $this->grammar = <<<GRAMMAR + root ::= ({$subjectsSerialized}) " " parameters + + parameters ::= string + + string ::= {$stringGrammar} + GRAMMAR; + } + + public function getGrammarContent(): string + { + return $this->grammar; + } +} diff --git a/src/Constraint/AnyConstraintTest.php b/src/Constraint/AnyConstraintTest.php index aad39b4e..3db7380a 100644 --- a/src/Constraint/AnyConstraintTest.php +++ b/src/Constraint/AnyConstraintTest.php @@ -4,14 +4,14 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use stdClass; /** - * @coversNothing - * * @internal */ +#[CoversClass(AnyConstraint::class)] final class AnyConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/AnyOfConstraintTest.php b/src/Constraint/AnyOfConstraintTest.php index 3e08d56e..c5a6e39a 100644 --- a/src/Constraint/AnyOfConstraintTest.php +++ b/src/Constraint/AnyOfConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(AnyOfConstraint::class)] final class AnyOfConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/BooleanConstraintTest.php b/src/Constraint/BooleanConstraintTest.php index ff422df1..56b635bc 100644 --- a/src/Constraint/BooleanConstraintTest.php +++ b/src/Constraint/BooleanConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(BooleanConstraint::class)] final class BooleanConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/ConstConstraintTest.php b/src/Constraint/ConstConstraintTest.php index 658bcb8c..ac815ac2 100644 --- a/src/Constraint/ConstConstraintTest.php +++ b/src/Constraint/ConstConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(ConstConstraint::class)] final class ConstConstraintTest extends TestCase { public function test_is_converted_to_json_schema(): void diff --git a/src/Constraint/EnumConstraintTest.php b/src/Constraint/EnumConstraintTest.php index 7d6a20c1..45ae2155 100644 --- a/src/Constraint/EnumConstraintTest.php +++ b/src/Constraint/EnumConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(EnumConstraint::class)] final class EnumConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/FilenameConstraintTest.php b/src/Constraint/FilenameConstraintTest.php index f168c714..485805fe 100644 --- a/src/Constraint/FilenameConstraintTest.php +++ b/src/Constraint/FilenameConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(FilenameConstraint::class)] final class FilenameConstraintTest extends TestCase { public function test_invalid(): void diff --git a/src/Constraint/IntegerConstraintTest.php b/src/Constraint/IntegerConstraintTest.php index 1fb70bf4..d9043719 100644 --- a/src/Constraint/IntegerConstraintTest.php +++ b/src/Constraint/IntegerConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(IntegerConstraint::class)] final class IntegerConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/ListConstraintTest.php b/src/Constraint/ListConstraintTest.php index fa81178b..76fc28c6 100644 --- a/src/Constraint/ListConstraintTest.php +++ b/src/Constraint/ListConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(ListConstraint::class)] final class ListConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/MapConstraintTest.php b/src/Constraint/MapConstraintTest.php index c63e5744..3cb0a7da 100644 --- a/src/Constraint/MapConstraintTest.php +++ b/src/Constraint/MapConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(MapConstraint::class)] final class MapConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/NumberConstraintTest.php b/src/Constraint/NumberConstraintTest.php index 981bd62b..b804a1c4 100644 --- a/src/Constraint/NumberConstraintTest.php +++ b/src/Constraint/NumberConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(NumberConstraint::class)] final class NumberConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/ObjectConstraintTest.php b/src/Constraint/ObjectConstraintTest.php index bcf082ed..23275470 100644 --- a/src/Constraint/ObjectConstraintTest.php +++ b/src/Constraint/ObjectConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(ObjectConstraint::class)] final class ObjectConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/StringConstraintTest.php b/src/Constraint/StringConstraintTest.php index d5094e14..24fe427f 100644 --- a/src/Constraint/StringConstraintTest.php +++ b/src/Constraint/StringConstraintTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; use Distantmagic\Resonance\ConstraintStringFormat; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(StringConstraint::class)] final class StringConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/Constraint/TupleConstraintTest.php b/src/Constraint/TupleConstraintTest.php index 9949c265..07a87ac0 100644 --- a/src/Constraint/TupleConstraintTest.php +++ b/src/Constraint/TupleConstraintTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(TupleConstraint::class)] final class TupleConstraintTest extends TestCase { public function test_is_converted_optionally_to_json_schema(): void diff --git a/src/LlamaCppCompletionRequest.php b/src/LlamaCppCompletionRequest.php index 3083efc4..7422e3e4 100644 --- a/src/LlamaCppCompletionRequest.php +++ b/src/LlamaCppCompletionRequest.php @@ -10,13 +10,26 @@ readonly class LlamaCppCompletionRequest implements JsonSerializable { public function __construct( public LlmPromptTemplate $promptTemplate, + public ?BackusNaurFormGrammar $backusNaurFormGrammar = null, + public ?LlmSystemPrompt $llmSystemPrompt = null, ) {} public function jsonSerialize(): array { - return [ + $parameters = [ + 'n_predict' => 400, 'prompt' => $this->promptTemplate, 'stream' => true, ]; + + if ($this->backusNaurFormGrammar) { + $parameters['grammar'] = $this->backusNaurFormGrammar; + } + + if ($this->llmSystemPrompt) { + $parameters['system_prompt'] = $this->llmSystemPrompt; + } + + return $parameters; } } diff --git a/src/LlmSystemPrompt.php b/src/LlmSystemPrompt.php new file mode 100644 index 00000000..70dd9289 --- /dev/null +++ b/src/LlmSystemPrompt.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use JsonSerializable; + +abstract readonly class LlmSystemPrompt implements JsonSerializable +{ + abstract public function getPromptContent(): string; + + public function jsonSerialize(): array + { + return [ + 'prompt' => $this->getPromptContent(), + ]; + } +} diff --git a/src/LlmSystemPrompt/SubjectActionSystemPrompt.php b/src/LlmSystemPrompt/SubjectActionSystemPrompt.php new file mode 100644 index 00000000..ea72ad21 --- /dev/null +++ b/src/LlmSystemPrompt/SubjectActionSystemPrompt.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\LlmSystemPrompt; + +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\LlmSystemPrompt; +use Distantmagic\Resonance\PromptSubjectResponderCollection; +use Ds\Set; + +#[Singleton] +readonly class SubjectActionSystemPrompt extends LlmSystemPrompt +{ + /** + * @var non-empty-string $prompt + */ + private string $prompt; + + public function __construct( + PromptSubjectResponderCollection $promptSubjectResponderCollection, + ) { + /** + * @var Set<non-empty-string> + */ + $allActions = new Set(); + + /** + * @var array<non-empty-string> + */ + $subjects = []; + + /** + * @var array<non-empty-string> + */ + $allowedActions = []; + + foreach ($promptSubjectResponderCollection->getPromptableActions() as $subject => $actions) { + $subjects[] = $subject; + $allActions = $allActions->merge($actions); + + $allowedActions[] = sprintf( + 'For "%s" the only allowed actions are: "%s"', + $subject, + $actions->join('", "'), + ); + } + + $allActionsSerialized = implode('", "', $allActions->toArray()); + $subjectsSerialized = implode('", "', $subjects); + $allowedActionsSerialized = '- '.implode("\n -", $allowedActions); + + $this->prompt = <<<PROMPT + You are a natural language intepreter. + Never ask for any clarifications. + Always interpret user intentions to the best of your ability. + Match user intentions the best you can with a set of predefined subjects and actions. + + Always describe everything user says for as one of the + "{$allActionsSerialized}" actions with parameters. + + If you cannot determine the action, use "uknown". + If you cannot determine the subject, use "uknown". + If you are unsure, use "unknown". + If user says something unrelated to the allowed actions, use "unknown". + If user says something unrelated to the allowed subjects, use "unknown". + + When user asks how to do something that means they seek help. + + Summarize and repeat everyting user says using just a few words: + - the first one being the subject ("{$subjectsSerialized}" or "unknown") + - the second one being an action the user mentioned ("{$allActionsSerialized}" or "unknown") + + Valid combinations of subjects and actions you must adhere to: + {$allowedActionsSerialized} + + Respond in the following format always: + subject action parameters + PROMPT; + } + + public function getPromptContent(): string + { + return $this->prompt; + } +} diff --git a/src/OAuth2ScopePatternChunkTest.php b/src/OAuth2ScopePatternChunkTest.php index 5024a379..a294a9c8 100644 --- a/src/OAuth2ScopePatternChunkTest.php +++ b/src/OAuth2ScopePatternChunkTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(OAuth2ScopePatternChunk::class)] final class OAuth2ScopePatternChunkTest extends TestCase { public function test_is_no_variable(): void diff --git a/src/OAuth2ScopePatternTest.php b/src/OAuth2ScopePatternTest.php index 0ea95449..fcebad97 100644 --- a/src/OAuth2ScopePatternTest.php +++ b/src/OAuth2ScopePatternTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance; use Ds\Map; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(OAuth2ScopePattern::class)] final class OAuth2ScopePatternTest extends TestCase { public function test_matches(): void diff --git a/src/PostfixBounceAnalyzerTest.php b/src/PostfixBounceAnalyzerTest.php index 3486c256..f05f057f 100644 --- a/src/PostfixBounceAnalyzerTest.php +++ b/src/PostfixBounceAnalyzerTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(PostfixBounceAnalyzer::class)] final class PostfixBounceAnalyzerTest extends TestCase { use TestsDependencyInectionContainerTrait; diff --git a/src/PromptSubjectRequest.php b/src/PromptSubjectRequest.php new file mode 100644 index 00000000..e0ad6c4d --- /dev/null +++ b/src/PromptSubjectRequest.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +/** + * @psalm-suppress PossiblyUnusedProperty used in apps + */ +readonly class PromptSubjectRequest +{ + public function __construct( + public ?AuthenticatedUser $authenticatedUser, + ) {} +} diff --git a/src/PromptSubjectResponderAggregate.php b/src/PromptSubjectResponderAggregate.php new file mode 100644 index 00000000..6eddc323 --- /dev/null +++ b/src/PromptSubjectResponderAggregate.php @@ -0,0 +1,80 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Distantmagic\Resonance\Attribute\Singleton; +use Generator; +use Psr\Log\LoggerInterface; + +#[Singleton] +readonly class PromptSubjectResponderAggregate +{ + public function __construct( + private LoggerInterface $logger, + private PromptSubjectResponderCollection $promptSubjectResponderCollection, + ) {} + + public function consumeTokens( + ?AuthenticatedUser $authenticatedUser, + LlamaCppCompletionIterator $completion, + ): Generator { + $subjectActionTokenReader = new SubjectActionTokenReader(); + + foreach ($completion as $token) { + $subjectActionTokenReader->write($token); + + if ($subjectActionTokenReader->isUnknown()) { + $completion->stop(); + + break; + } + } + + $action = $subjectActionTokenReader->getAction(); + $subject = $subjectActionTokenReader->getSubject(); + + if ($subjectActionTokenReader->isUnknown() || !isset($action, $subject)) { + yield from $this->respondWithSubjectAction($authenticatedUser, 'unknown', 'unknown'); + } else { + yield from $this->respondWithSubjectAction($authenticatedUser, $subject, $action); + } + } + + /** + * @param non-empty-string $subject + * @param non-empty-string $action + */ + private function respondWithSubjectAction( + ?AuthenticatedUser $authenticatedUser, + string $subject, + string $action, + ): Generator { + $responder = $this + ->promptSubjectResponderCollection + ->promptSubjectResponders + ->get($subject, null) + ?->get($action, null) + ; + + if (!$responder) { + $this->logger->warning(sprintf( + 'No prompt responder matched subject "%s" and action "%s"', + $subject, + $action, + )); + + return; + } + + $request = new PromptSubjectRequest($authenticatedUser); + $response = new PromptSubjectResponse(); + + SwooleCoroutineHelper::mustGo(static function () use ($request, $responder, $response) { + $responder->respondToPromptSubject($request, $response); + }); + + yield from $response; + } +} diff --git a/src/PromptSubjectResponderCollection.php b/src/PromptSubjectResponderCollection.php new file mode 100644 index 00000000..c159b760 --- /dev/null +++ b/src/PromptSubjectResponderCollection.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Distantmagic\Resonance\Attribute\RespondsToPromptSubject; +use Ds\Map; +use Ds\Set; + +readonly class PromptSubjectResponderCollection +{ + /** + * @var Map< + * non-empty-string, + * Map<non-empty-string,PromptSubjectResponderInterface> + * > + */ + public Map $promptSubjectResponders; + + public function __construct() + { + $this->promptSubjectResponders = new Map(); + } + + public function addPromptSubjectResponder( + RespondsToPromptSubject $respondsToLPromptSubject, + PromptSubjectResponderInterface $promptResponder, + ): void { + if (!$this->promptSubjectResponders->hasKey($respondsToLPromptSubject->subject)) { + $this->promptSubjectResponders->put($respondsToLPromptSubject->subject, new Map()); + } + + $this + ->promptSubjectResponders + ->get($respondsToLPromptSubject->subject) + ->put($respondsToLPromptSubject->action, $promptResponder) + ; + } + + /** + * @return Map<non-empty-string,Set<non-empty-string>> + */ + public function getPromptableActions(): Map + { + /** + * @var Map<non-empty-string,Set<non-empty-string>> + */ + $ret = new Map(); + + foreach ($this->promptSubjectResponders as $subject => $actionResponders) { + $ret->put($subject, $actionResponders->keys()); + } + + return $ret; + } +} diff --git a/src/PromptSubjectResponderInterface.php b/src/PromptSubjectResponderInterface.php new file mode 100644 index 00000000..52daef71 --- /dev/null +++ b/src/PromptSubjectResponderInterface.php @@ -0,0 +1,13 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface PromptSubjectResponderInterface +{ + public function respondToPromptSubject( + PromptSubjectRequest $request, + PromptSubjectResponse $response, + ): void; +} diff --git a/src/PromptSubjectResponse.php b/src/PromptSubjectResponse.php new file mode 100644 index 00000000..4b7395e6 --- /dev/null +++ b/src/PromptSubjectResponse.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use IteratorAggregate; +use Swoole\Coroutine\Channel; + +/** + * @template-implements IteratorAggregate<mixed> + */ +readonly class PromptSubjectResponse implements IteratorAggregate +{ + private Channel $channel; + + public function __construct() + { + $this->channel = new Channel(1); + } + + public function __destruct() + { + $this->channel->close(); + } + + /** + * @return SwooleChannelIterator<mixed> + */ + public function getIterator(): SwooleChannelIterator + { + /** + * @var SwooleChannelIterator<mixed> + */ + return new SwooleChannelIterator($this->channel); + } + + /** + * @return mixed because almost every PHP type can be send over websocket + */ + public function write(mixed $payload): bool + { + return $this->channel->push($payload); + } +} diff --git a/src/SingletonCollection.php b/src/SingletonCollection.php index 3ead3d86..64b3f6ba 100644 --- a/src/SingletonCollection.php +++ b/src/SingletonCollection.php @@ -27,6 +27,7 @@ enum SingletonCollection implements SingletonCollectionInterface case OpenAPIRouteRequestBodyContentExtractor; case OpenAPIRouteSecurityRequirementExtractor; case PDOPoolConnectionBuilder; + case PromptSubjectResponder; case ServerPipeMessageHandler; case ServerTaskHandler; case SiteActionGate; diff --git a/src/SingletonContainerTest.php b/src/SingletonContainerTest.php index 5af42426..adee431f 100644 --- a/src/SingletonContainerTest.php +++ b/src/SingletonContainerTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** @@ -12,10 +13,9 @@ use PHPUnit\Framework\TestCase; final class SingletonContainerTestFixtureFoo {} /** - * @coversNothing - * * @internal */ +#[CoversClass(SingletonContainer::class)] final class SingletonContainerTest extends TestCase { public function test_singletons_can_be_set(): void diff --git a/src/SingletonProvider/PromptSubjectResponderCollectionProvider.php b/src/SingletonProvider/PromptSubjectResponderCollectionProvider.php new file mode 100644 index 00000000..f63050eb --- /dev/null +++ b/src/SingletonProvider/PromptSubjectResponderCollectionProvider.php @@ -0,0 +1,50 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\SingletonProvider; + +use Distantmagic\Resonance\Attribute\RequiresSingletonCollection; +use Distantmagic\Resonance\Attribute\RespondsToPromptSubject; +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\PHPProjectFiles; +use Distantmagic\Resonance\PromptSubjectResponderCollection; +use Distantmagic\Resonance\PromptSubjectResponderInterface; +use Distantmagic\Resonance\SingletonAttribute; +use Distantmagic\Resonance\SingletonCollection; +use Distantmagic\Resonance\SingletonContainer; +use Distantmagic\Resonance\SingletonProvider; + +/** + * @template-extends SingletonProvider<PromptSubjectResponderCollection> + */ +#[RequiresSingletonCollection(SingletonCollection::PromptSubjectResponder)] +#[Singleton(provides: PromptSubjectResponderCollection::class)] +final readonly class PromptSubjectResponderCollectionProvider extends SingletonProvider +{ + public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): PromptSubjectResponderCollection + { + $promptableSubjectCollection = new PromptSubjectResponderCollection(); + + foreach ($this->collectPromptSubjectResponders($singletons) as $promptableSubjectAttribute) { + $promptableSubjectCollection->addPromptSubjectResponder( + $promptableSubjectAttribute->attribute, + $promptableSubjectAttribute->singleton, + ); + } + + return $promptableSubjectCollection; + } + + /** + * @return iterable<SingletonAttribute<PromptSubjectResponderInterface,RespondsToPromptSubject>> + */ + private function collectPromptSubjectResponders(SingletonContainer $singletons): iterable + { + return $this->collectAttributes( + $singletons, + PromptSubjectResponderInterface::class, + RespondsToPromptSubject::class, + ); + } +} diff --git a/src/SubjectActionTokenReader.php b/src/SubjectActionTokenReader.php new file mode 100644 index 00000000..062d451c --- /dev/null +++ b/src/SubjectActionTokenReader.php @@ -0,0 +1,97 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use LogicException; +use Stringable; + +class SubjectActionTokenReader +{ + /** + * @var null|non-empty-string + */ + private ?string $action = null; + + /** + * @var null|non-empty-string + */ + private ?string $subject = null; + + private string $unprocessedTokens = ''; + + /** + * @return null|non-empty-string + */ + public function getAction(): ?string + { + return $this->action; + } + + /** + * @return null|non-empty-string + */ + public function getSubject(): ?string + { + return $this->subject; + } + + public function isUnknown(): bool + { + return 'unknown' === $this->action || 'unknown' === $this->subject; + } + + public function write(string|Stringable $token): void + { + $this->unprocessedTokens .= (string) $token; + + if (is_string($this->action) && is_string($this->subject)) { + return; + } + + if (!str_contains($this->unprocessedTokens, ' ')) { + return; + } + + if (is_string($this->subject) && !is_string($this->action)) { + $this->action = $this->readWord(1); + + return; + } + + if (!is_string($this->action)) { + $this->subject = $this->readWord(0); + + if ('unknown' === $this->subject) { + $this->action = 'unknown'; + } + + return; + } + + throw new LogicException('Both subject and action are filled'); + } + + /** + * @return null|non-empty-string + */ + private function readWord(int $offset): ?string + { + $spaceStrpos = mb_strpos($this->unprocessedTokens, ' ', $offset); + + if (false === $spaceStrpos) { + return null; + } + + $chunk = mb_substr($this->unprocessedTokens, $offset, $spaceStrpos - $offset); + + $this->unprocessedTokens = mb_substr($this->unprocessedTokens, $spaceStrpos + $offset); + + if (empty($chunk)) { + return null; + } + + return $chunk; + } +} diff --git a/src/SubjectActionTokenReaderTest.php b/src/SubjectActionTokenReaderTest.php new file mode 100644 index 00000000..97a87faf --- /dev/null +++ b/src/SubjectActionTokenReaderTest.php @@ -0,0 +1,90 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +#[CoversClass(SubjectActionTokenReader::class)] +final class SubjectActionTokenReaderTest extends TestCase +{ + public function test_reads_subject_and_action(): void + { + $reader = new SubjectActionTokenReader(); + + self::assertNull($reader->getAction()); + self::assertNull($reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write('blog_post'); + + self::assertNull($reader->getAction()); + self::assertNull($reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write(' cre'); + + self::assertNull($reader->getAction()); + self::assertSame('blog_post', $reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write('ate'); + + self::assertNull($reader->getAction()); + self::assertSame('blog_post', $reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write(' '); + + self::assertSame('create', $reader->getAction()); + self::assertSame('blog_post', $reader->getSubject()); + self::assertFalse($reader->isUnknown()); + } + + public function test_reads_unknown_action(): void + { + $reader = new SubjectActionTokenReader(); + + self::assertNull($reader->getAction()); + self::assertNull($reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write('blog_post '); + + self::assertNull($reader->getAction()); + self::assertSame('blog_post', $reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write('unknown '); + + self::assertSame('unknown', $reader->getAction()); + self::assertSame('blog_post', $reader->getSubject()); + self::assertTrue($reader->isUnknown()); + } + + public function test_reads_unknown_subject(): void + { + $reader = new SubjectActionTokenReader(); + + self::assertNull($reader->getAction()); + self::assertNull($reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write('unknown'); + + self::assertNull($reader->getAction()); + self::assertNull($reader->getSubject()); + self::assertFalse($reader->isUnknown()); + + $reader->write(' '); + + self::assertSame('unknown', $reader->getAction()); + self::assertSame('unknown', $reader->getSubject()); + self::assertTrue($reader->isUnknown()); + } +} diff --git a/src/WebSocketProtocolIteratorTest.php b/src/WebSocketProtocolIteratorTest.php index c7d28d1c..727baffb 100644 --- a/src/WebSocketProtocolIteratorTest.php +++ b/src/WebSocketProtocolIteratorTest.php @@ -4,13 +4,13 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversNothing - * * @internal */ +#[CoversClass(WebSocketProtocolIterator::class)] final class WebSocketProtocolIteratorTest extends TestCase { public function test_iterates_over_protocol_values(): void diff --git a/src/WebSocketRPCConnectionHandle.php b/src/WebSocketRPCConnectionHandle.php index 0f8918c1..8e37283f 100644 --- a/src/WebSocketRPCConnectionHandle.php +++ b/src/WebSocketRPCConnectionHandle.php @@ -50,7 +50,7 @@ readonly class WebSocketRPCConnectionHandle ->validate($rpcMessage->payload) ; - if ($constraintResult->status->isValid()) { + if (!$constraintResult->status->isValid()) { return $constraintResult; } diff --git a/src/WebSocketServerController.php b/src/WebSocketServerController.php index ee20f6ad..80d45be4 100644 --- a/src/WebSocketServerController.php +++ b/src/WebSocketServerController.php @@ -65,6 +65,8 @@ final readonly class WebSocketServerController implements ServerPipeMessageHandl public function onClose(int $fd): void { + $this->logger->debug(sprintf('websocket_close(%s)', $fd)); + $this->webSocketServerConnectionTable->unregisterConnection($fd); if (!$this->protocolControllers->hasKey($fd)) { @@ -145,10 +147,14 @@ final readonly class WebSocketServerController implements ServerPipeMessageHandl $response->end(); $controllerResolution->controller->onOpen($server, $fd, $authResolution); + + $this->logger->debug(sprintf('websocket_handshake_successful(%s)', $fd)); } public function onMessage(Server $server, Frame $frame): void { + $this->logger->debug(sprintf('websocket_message(%s)', $frame->fd)); + $protocolController = $this->protocolControllers->get($frame->fd, null); if ($protocolController) { -- GitLab