Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions Classes/Middleware/RequestLoggingMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private function logRequest(
RequestContext $context,
): void {
// Determine effective privacy level
$privacyLevel = $this->resolvePrivacyLevel($configuration);
$privacyLevel = $this->resolvePrivacyLevel($configuration, $request);
if ($privacyLevel === PrivacyLevel::None) {
return;
}
Expand Down Expand Up @@ -165,23 +165,28 @@ private function logRequest(
}

/**
* Resolve the effective privacy level from the provider config and user TSconfig.
* The stricter of the two wins.
* Resolve the effective privacy level from provider config, user TSconfig,
* and any per-request override carried on the request. The strictest of
* the three wins — an override can only escalate, never relax.
*/
private function resolvePrivacyLevel(ProviderConfiguration $configuration): PrivacyLevel
private function resolvePrivacyLevel(ProviderConfiguration $configuration, AiRequestInterface $request): PrivacyLevel
{
$configLevel = PrivacyLevel::fromString($configuration->privacyLevel);
$level = PrivacyLevel::fromString($configuration->privacyLevel);

$user = $this->getBackendUser();
if ($user === null || !method_exists($user, 'getTSConfig')) {
return $configLevel;
if ($user !== null && method_exists($user, 'getTSConfig')) {
$userLevel = PrivacyLevel::fromString(
(string)($user->getTSConfig()['aim.']['privacyLevel'] ?? 'standard')
);
$level = $level->strictest($userLevel);
}

$userLevel = PrivacyLevel::fromString(
(string)($user->getTSConfig()['aim.']['privacyLevel'] ?? 'standard')
);
$requestOverride = $request->getPrivacyLevelOverride();
if ($requestOverride !== null) {
$level = $level->strictest($requestOverride);
}

return $configLevel->strictest($userLevel);
return $level;
}

private function extractMetadata(AiRequestInterface $request): array
Expand Down
28 changes: 28 additions & 0 deletions Classes/Request/AiRequestInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace B13\Aim\Request;

use B13\Aim\Domain\Model\ProviderConfiguration;
use B13\Aim\Governance\PrivacyLevel;

/**
* Contract for all AI request objects passed through the middleware pipeline.
Expand All @@ -30,4 +31,31 @@ public function getConfiguration(): ProviderConfiguration;
* Used during fallback to swap the API key/model without reflection.
*/
public function withConfiguration(ProviderConfiguration $configuration): static;

/**
* Return a copy of this request with additional metadata merged in.
*
* Example:
* $request = $request->withMetadata(['my_extension.context' => $value]);
* return $next->handle($request, $provider, $configuration);
*/
public function withMetadata(array $additional): static;

/**
* Return a copy of this request with a per-request privacy level override.
*
* Folds into the existing strictness ladder (provider config → user
* TSconfig → per-request), strictest wins. The override can only
* escalate — it cannot relax a stricter level set upstream.
*
* Example (suppress logging for a health-check request):
* $request = $request->withPrivacyLevel(PrivacyLevel::None);
*/
public function withPrivacyLevel(PrivacyLevel $level): static;

/**
* The privacy-level override carried on the request, or null when none
* has been set. Returned for the logging middleware's strictness ladder.
*/
public function getPrivacyLevelOverride(): ?PrivacyLevel;
}
23 changes: 23 additions & 0 deletions Classes/Request/ConversationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace B13\Aim\Request;

use B13\Aim\Domain\Model\ProviderConfiguration;
use B13\Aim\Governance\PrivacyLevel;
use B13\Aim\Request\Message\AbstractMessage;

final class ConversationRequest implements AiRequestInterface
Expand All @@ -30,6 +31,7 @@ public function __construct(
public readonly string $user = '',
public readonly array $metadata = [],
public readonly bool $stream = false,
public readonly ?PrivacyLevel $privacyLevelOverride = null,
) {}

public function getConfiguration(): ProviderConfiguration
Expand All @@ -44,4 +46,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
['configuration' => $configuration],
));
}

public function withMetadata(array $additional): static
{
return new static(...array_merge(
get_object_vars($this),
['metadata' => [...$this->metadata, ...$additional]],
));
}

public function withPrivacyLevel(PrivacyLevel $level): static
{
return new static(...array_merge(
get_object_vars($this),
['privacyLevelOverride' => $level],
));
}

public function getPrivacyLevelOverride(): ?PrivacyLevel
{
return $this->privacyLevelOverride;
}
}
23 changes: 23 additions & 0 deletions Classes/Request/EmbeddingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace B13\Aim\Request;

use B13\Aim\Domain\Model\ProviderConfiguration;
use B13\Aim\Governance\PrivacyLevel;

final class EmbeddingRequest implements AiRequestInterface
{
Expand All @@ -26,6 +27,7 @@ public function __construct(
public readonly int $dimensions = 0,
public readonly string $user = '',
public readonly array $metadata = [],
public readonly ?PrivacyLevel $privacyLevelOverride = null,
) {}

public function getConfiguration(): ProviderConfiguration
Expand All @@ -40,4 +42,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
['configuration' => $configuration],
));
}

public function withMetadata(array $additional): static
{
return new static(...array_merge(
get_object_vars($this),
['metadata' => [...$this->metadata, ...$additional]],
));
}

public function withPrivacyLevel(PrivacyLevel $level): static
{
return new static(...array_merge(
get_object_vars($this),
['privacyLevelOverride' => $level],
));
}

public function getPrivacyLevelOverride(): ?PrivacyLevel
{
return $this->privacyLevelOverride;
}
}
23 changes: 23 additions & 0 deletions Classes/Request/TextGenerationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace B13\Aim\Request;

use B13\Aim\Domain\Model\ProviderConfiguration;
use B13\Aim\Governance\PrivacyLevel;

final class TextGenerationRequest implements AiRequestInterface
{
Expand All @@ -25,6 +26,7 @@ public function __construct(
public readonly float $temperature = 0.2,
public readonly string $user = '',
public readonly array $metadata = [],
public readonly ?PrivacyLevel $privacyLevelOverride = null,
) {}

public function getConfiguration(): ProviderConfiguration
Expand All @@ -39,4 +41,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
['configuration' => $configuration],
));
}

public function withMetadata(array $additional): static
{
return new static(...array_merge(
get_object_vars($this),
['metadata' => [...$this->metadata, ...$additional]],
));
}

public function withPrivacyLevel(PrivacyLevel $level): static
{
return new static(...array_merge(
get_object_vars($this),
['privacyLevelOverride' => $level],
));
}

public function getPrivacyLevelOverride(): ?PrivacyLevel
{
return $this->privacyLevelOverride;
}
}
23 changes: 23 additions & 0 deletions Classes/Request/ToolCallingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace B13\Aim\Request;

use B13\Aim\Domain\Model\ProviderConfiguration;
use B13\Aim\Governance\PrivacyLevel;
use B13\Aim\Request\Message\AbstractMessage;

/**
Expand Down Expand Up @@ -40,6 +41,7 @@ public function __construct(
public readonly float $temperature = 0.7,
public readonly string $user = '',
public readonly array $metadata = [],
public readonly ?PrivacyLevel $privacyLevelOverride = null,
) {}

public function getConfiguration(): ProviderConfiguration
Expand All @@ -54,4 +56,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
['configuration' => $configuration],
));
}

public function withMetadata(array $additional): static
{
return new static(...array_merge(
get_object_vars($this),
['metadata' => [...$this->metadata, ...$additional]],
));
}

public function withPrivacyLevel(PrivacyLevel $level): static
{
return new static(...array_merge(
get_object_vars($this),
['privacyLevelOverride' => $level],
));
}

public function getPrivacyLevelOverride(): ?PrivacyLevel
{
return $this->privacyLevelOverride;
}
}
23 changes: 23 additions & 0 deletions Classes/Request/TranslationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace B13\Aim\Request;

use B13\Aim\Domain\Model\ProviderConfiguration;
use B13\Aim\Governance\PrivacyLevel;

final class TranslationRequest implements AiRequestInterface
{
Expand All @@ -26,6 +27,7 @@ public function __construct(
public readonly float $temperature = 0.2,
public readonly string $user = '',
public readonly array $metadata = [],
public readonly ?PrivacyLevel $privacyLevelOverride = null,
) {}

public function getConfiguration(): ProviderConfiguration
Expand All @@ -40,4 +42,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
['configuration' => $configuration],
));
}

public function withMetadata(array $additional): static
{
return new static(...array_merge(
get_object_vars($this),
['metadata' => [...$this->metadata, ...$additional]],
));
}

public function withPrivacyLevel(PrivacyLevel $level): static
{
return new static(...array_merge(
get_object_vars($this),
['privacyLevelOverride' => $level],
));
}

public function getPrivacyLevelOverride(): ?PrivacyLevel
{
return $this->privacyLevelOverride;
}
}
23 changes: 23 additions & 0 deletions Classes/Request/VisionRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace B13\Aim\Request;

use B13\Aim\Domain\Model\ProviderConfiguration;
use B13\Aim\Governance\PrivacyLevel;

final class VisionRequest implements AiRequestInterface
{
Expand All @@ -26,6 +27,7 @@ public function __construct(
public readonly float $temperature = 0.2,
public readonly string $user = '',
public readonly array $metadata = [],
public readonly ?PrivacyLevel $privacyLevelOverride = null,
) {}

public function getConfiguration(): ProviderConfiguration
Expand All @@ -40,4 +42,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
['configuration' => $configuration],
));
}

public function withMetadata(array $additional): static
{
return new static(...array_merge(
get_object_vars($this),
['metadata' => [...$this->metadata, ...$additional]],
));
}

public function withPrivacyLevel(PrivacyLevel $level): static
{
return new static(...array_merge(
get_object_vars($this),
['privacyLevelOverride' => $level],
));
}

public function getPrivacyLevelOverride(): ?PrivacyLevel
{
return $this->privacyLevelOverride;
}
}
Loading