Skip to content

Commit d4bd532

Browse files
authored
[FEATURE] Allow additional metadata and request-based privacy level override
2 parents d886e41 + d7984d5 commit d4bd532

16 files changed

Lines changed: 675 additions & 214 deletions

File tree

Classes/Middleware/RequestLoggingMiddleware.php

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private function logRequest(
7171
RequestContext $context,
7272
): void {
7373
// Determine effective privacy level
74-
$privacyLevel = $this->resolvePrivacyLevel($configuration);
74+
$privacyLevel = $this->resolvePrivacyLevel($configuration, $request);
7575
if ($privacyLevel === PrivacyLevel::None) {
7676
return;
7777
}
@@ -165,23 +165,28 @@ private function logRequest(
165165
}
166166

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

175176
$user = $this->getBackendUser();
176-
if ($user === null || !method_exists($user, 'getTSConfig')) {
177-
return $configLevel;
177+
if ($user !== null && method_exists($user, 'getTSConfig')) {
178+
$userLevel = PrivacyLevel::fromString(
179+
(string)($user->getTSConfig()['aim.']['privacyLevel'] ?? 'standard')
180+
);
181+
$level = $level->strictest($userLevel);
178182
}
179183

180-
$userLevel = PrivacyLevel::fromString(
181-
(string)($user->getTSConfig()['aim.']['privacyLevel'] ?? 'standard')
182-
);
184+
$requestOverride = $request->getPrivacyLevelOverride();
185+
if ($requestOverride !== null) {
186+
$level = $level->strictest($requestOverride);
187+
}
183188

184-
return $configLevel->strictest($userLevel);
189+
return $level;
185190
}
186191

187192
private function extractMetadata(AiRequestInterface $request): array

Classes/Request/AiRequestInterface.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace B13\Aim\Request;
1414

1515
use B13\Aim\Domain\Model\ProviderConfiguration;
16+
use B13\Aim\Governance\PrivacyLevel;
1617

1718
/**
1819
* Contract for all AI request objects passed through the middleware pipeline.
@@ -30,4 +31,31 @@ public function getConfiguration(): ProviderConfiguration;
3031
* Used during fallback to swap the API key/model without reflection.
3132
*/
3233
public function withConfiguration(ProviderConfiguration $configuration): static;
34+
35+
/**
36+
* Return a copy of this request with additional metadata merged in.
37+
*
38+
* Example:
39+
* $request = $request->withMetadata(['my_extension.context' => $value]);
40+
* return $next->handle($request, $provider, $configuration);
41+
*/
42+
public function withMetadata(array $additional): static;
43+
44+
/**
45+
* Return a copy of this request with a per-request privacy level override.
46+
*
47+
* Folds into the existing strictness ladder (provider config → user
48+
* TSconfig → per-request), strictest wins. The override can only
49+
* escalate — it cannot relax a stricter level set upstream.
50+
*
51+
* Example (suppress logging for a health-check request):
52+
* $request = $request->withPrivacyLevel(PrivacyLevel::None);
53+
*/
54+
public function withPrivacyLevel(PrivacyLevel $level): static;
55+
56+
/**
57+
* The privacy-level override carried on the request, or null when none
58+
* has been set. Returned for the logging middleware's strictness ladder.
59+
*/
60+
public function getPrivacyLevelOverride(): ?PrivacyLevel;
3361
}

Classes/Request/ConversationRequest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace B13\Aim\Request;
1414

1515
use B13\Aim\Domain\Model\ProviderConfiguration;
16+
use B13\Aim\Governance\PrivacyLevel;
1617
use B13\Aim\Request\Message\AbstractMessage;
1718

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

3537
public function getConfiguration(): ProviderConfiguration
@@ -44,4 +46,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
4446
['configuration' => $configuration],
4547
));
4648
}
49+
50+
public function withMetadata(array $additional): static
51+
{
52+
return new static(...array_merge(
53+
get_object_vars($this),
54+
['metadata' => [...$this->metadata, ...$additional]],
55+
));
56+
}
57+
58+
public function withPrivacyLevel(PrivacyLevel $level): static
59+
{
60+
return new static(...array_merge(
61+
get_object_vars($this),
62+
['privacyLevelOverride' => $level],
63+
));
64+
}
65+
66+
public function getPrivacyLevelOverride(): ?PrivacyLevel
67+
{
68+
return $this->privacyLevelOverride;
69+
}
4770
}

Classes/Request/EmbeddingRequest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace B13\Aim\Request;
1414

1515
use B13\Aim\Domain\Model\ProviderConfiguration;
16+
use B13\Aim\Governance\PrivacyLevel;
1617

1718
final class EmbeddingRequest implements AiRequestInterface
1819
{
@@ -26,6 +27,7 @@ public function __construct(
2627
public readonly int $dimensions = 0,
2728
public readonly string $user = '',
2829
public readonly array $metadata = [],
30+
public readonly ?PrivacyLevel $privacyLevelOverride = null,
2931
) {}
3032

3133
public function getConfiguration(): ProviderConfiguration
@@ -40,4 +42,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
4042
['configuration' => $configuration],
4143
));
4244
}
45+
46+
public function withMetadata(array $additional): static
47+
{
48+
return new static(...array_merge(
49+
get_object_vars($this),
50+
['metadata' => [...$this->metadata, ...$additional]],
51+
));
52+
}
53+
54+
public function withPrivacyLevel(PrivacyLevel $level): static
55+
{
56+
return new static(...array_merge(
57+
get_object_vars($this),
58+
['privacyLevelOverride' => $level],
59+
));
60+
}
61+
62+
public function getPrivacyLevelOverride(): ?PrivacyLevel
63+
{
64+
return $this->privacyLevelOverride;
65+
}
4366
}

Classes/Request/TextGenerationRequest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace B13\Aim\Request;
1414

1515
use B13\Aim\Domain\Model\ProviderConfiguration;
16+
use B13\Aim\Governance\PrivacyLevel;
1617

1718
final class TextGenerationRequest implements AiRequestInterface
1819
{
@@ -25,6 +26,7 @@ public function __construct(
2526
public readonly float $temperature = 0.2,
2627
public readonly string $user = '',
2728
public readonly array $metadata = [],
29+
public readonly ?PrivacyLevel $privacyLevelOverride = null,
2830
) {}
2931

3032
public function getConfiguration(): ProviderConfiguration
@@ -39,4 +41,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
3941
['configuration' => $configuration],
4042
));
4143
}
44+
45+
public function withMetadata(array $additional): static
46+
{
47+
return new static(...array_merge(
48+
get_object_vars($this),
49+
['metadata' => [...$this->metadata, ...$additional]],
50+
));
51+
}
52+
53+
public function withPrivacyLevel(PrivacyLevel $level): static
54+
{
55+
return new static(...array_merge(
56+
get_object_vars($this),
57+
['privacyLevelOverride' => $level],
58+
));
59+
}
60+
61+
public function getPrivacyLevelOverride(): ?PrivacyLevel
62+
{
63+
return $this->privacyLevelOverride;
64+
}
4265
}

Classes/Request/ToolCallingRequest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace B13\Aim\Request;
1414

1515
use B13\Aim\Domain\Model\ProviderConfiguration;
16+
use B13\Aim\Governance\PrivacyLevel;
1617
use B13\Aim\Request\Message\AbstractMessage;
1718

1819
/**
@@ -40,6 +41,7 @@ public function __construct(
4041
public readonly float $temperature = 0.7,
4142
public readonly string $user = '',
4243
public readonly array $metadata = [],
44+
public readonly ?PrivacyLevel $privacyLevelOverride = null,
4345
) {}
4446

4547
public function getConfiguration(): ProviderConfiguration
@@ -54,4 +56,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
5456
['configuration' => $configuration],
5557
));
5658
}
59+
60+
public function withMetadata(array $additional): static
61+
{
62+
return new static(...array_merge(
63+
get_object_vars($this),
64+
['metadata' => [...$this->metadata, ...$additional]],
65+
));
66+
}
67+
68+
public function withPrivacyLevel(PrivacyLevel $level): static
69+
{
70+
return new static(...array_merge(
71+
get_object_vars($this),
72+
['privacyLevelOverride' => $level],
73+
));
74+
}
75+
76+
public function getPrivacyLevelOverride(): ?PrivacyLevel
77+
{
78+
return $this->privacyLevelOverride;
79+
}
5780
}

Classes/Request/TranslationRequest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace B13\Aim\Request;
1414

1515
use B13\Aim\Domain\Model\ProviderConfiguration;
16+
use B13\Aim\Governance\PrivacyLevel;
1617

1718
final class TranslationRequest implements AiRequestInterface
1819
{
@@ -26,6 +27,7 @@ public function __construct(
2627
public readonly float $temperature = 0.2,
2728
public readonly string $user = '',
2829
public readonly array $metadata = [],
30+
public readonly ?PrivacyLevel $privacyLevelOverride = null,
2931
) {}
3032

3133
public function getConfiguration(): ProviderConfiguration
@@ -40,4 +42,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
4042
['configuration' => $configuration],
4143
));
4244
}
45+
46+
public function withMetadata(array $additional): static
47+
{
48+
return new static(...array_merge(
49+
get_object_vars($this),
50+
['metadata' => [...$this->metadata, ...$additional]],
51+
));
52+
}
53+
54+
public function withPrivacyLevel(PrivacyLevel $level): static
55+
{
56+
return new static(...array_merge(
57+
get_object_vars($this),
58+
['privacyLevelOverride' => $level],
59+
));
60+
}
61+
62+
public function getPrivacyLevelOverride(): ?PrivacyLevel
63+
{
64+
return $this->privacyLevelOverride;
65+
}
4366
}

Classes/Request/VisionRequest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace B13\Aim\Request;
1414

1515
use B13\Aim\Domain\Model\ProviderConfiguration;
16+
use B13\Aim\Governance\PrivacyLevel;
1617

1718
final class VisionRequest implements AiRequestInterface
1819
{
@@ -26,6 +27,7 @@ public function __construct(
2627
public readonly float $temperature = 0.2,
2728
public readonly string $user = '',
2829
public readonly array $metadata = [],
30+
public readonly ?PrivacyLevel $privacyLevelOverride = null,
2931
) {}
3032

3133
public function getConfiguration(): ProviderConfiguration
@@ -40,4 +42,25 @@ public function withConfiguration(ProviderConfiguration $configuration): static
4042
['configuration' => $configuration],
4143
));
4244
}
45+
46+
public function withMetadata(array $additional): static
47+
{
48+
return new static(...array_merge(
49+
get_object_vars($this),
50+
['metadata' => [...$this->metadata, ...$additional]],
51+
));
52+
}
53+
54+
public function withPrivacyLevel(PrivacyLevel $level): static
55+
{
56+
return new static(...array_merge(
57+
get_object_vars($this),
58+
['privacyLevelOverride' => $level],
59+
));
60+
}
61+
62+
public function getPrivacyLevelOverride(): ?PrivacyLevel
63+
{
64+
return $this->privacyLevelOverride;
65+
}
4366
}

0 commit comments

Comments
 (0)