Skip to content

Commit 6a147ac

Browse files
authored
Merge pull request #3 from wp-cli/copilot/scaffold-initial-implementation
2 parents 02562c3 + f5bb17a commit 6a147ac

7 files changed

Lines changed: 982 additions & 15 deletions

File tree

ai-command.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
}
1616

1717
WP_CLI::add_command( 'ai', AI_Command::class );
18+
WP_CLI::add_command( 'ai credentials', Credentials_Command::class );

composer.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@
2727
},
2828
"bundled": false,
2929
"commands": [
30-
"ai"
30+
"ai",
31+
"ai credentials",
32+
"ai credentials get",
33+
"ai credentials set",
34+
"ai credentials delete",
35+
"ai credentials list",
36+
"ai check",
37+
"ai generate",
38+
"ai status"
3139
]
3240
},
3341
"autoload": {

features/credentials.feature

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
Feature: Manage AI provider credentials
2+
3+
Background:
4+
Given a WP install
5+
6+
Scenario: List credentials when none exist
7+
When I run `wp ai credentials list`
8+
Then STDOUT should contain:
9+
"""
10+
No credentials found.
11+
"""
12+
13+
Scenario: Set and list credentials
14+
When I run `wp ai credentials set openai --api-key=sk-test123456789`
15+
Then STDOUT should contain:
16+
"""
17+
Success: Credentials for provider "openai" have been saved.
18+
"""
19+
20+
When I run `wp ai credentials list --format=json`
21+
Then STDOUT should be JSON containing:
22+
"""
23+
[{"provider":"openai","api_key":"sk-*********6789"}]
24+
"""
25+
26+
Scenario: Get specific provider credentials
27+
When I run `wp ai credentials set anthropic --api-key=sk-ant-api-key-123`
28+
Then STDOUT should contain:
29+
"""
30+
Success: Credentials for provider "anthropic" have been saved.
31+
"""
32+
33+
When I run `wp ai credentials get anthropic --format=json`
34+
Then STDOUT should contain:
35+
"""
36+
"provider":"anthropic"
37+
"""
38+
And STDOUT should contain:
39+
"""
40+
"api_key":"sk-**********-123"
41+
"""
42+
43+
Scenario: Delete provider credentials
44+
When I run `wp ai credentials set google --api-key=test-google-key`
45+
Then STDOUT should contain:
46+
"""
47+
Success: Credentials for provider "google" have been saved.
48+
"""
49+
50+
When I run `wp ai credentials delete google`
51+
Then STDOUT should contain:
52+
"""
53+
Success: Credentials for provider "google" have been deleted.
54+
"""
55+
56+
When I try `wp ai credentials get google`
57+
Then STDERR should contain:
58+
"""
59+
Error: Credentials for provider "google" not found.
60+
"""
61+
And the return code should be 1
62+
63+
Scenario: Error when getting non-existent credentials
64+
When I try `wp ai credentials get nonexistent`
65+
Then STDERR should contain:
66+
"""
67+
Error: Credentials for provider "nonexistent" not found.
68+
"""
69+
And the return code should be 1
70+
71+
Scenario: Error when setting credentials without api-key
72+
When I try `wp ai credentials set openai`
73+
Then STDERR should contain:
74+
"""
75+
missing --api-key parameter
76+
"""
77+
And the return code should be 1
78+
79+
Scenario: List multiple credentials in table format
80+
When I run `wp ai credentials set openai --api-key=sk-openai123`
81+
And I run `wp ai credentials set anthropic --api-key=sk-ant-api-456`
82+
And I run `wp ai credentials list`
83+
Then STDOUT should be a table containing rows:
84+
| provider | api_key |
85+
| openai | sk-*****i123 |
86+
| anthropic | sk-*******-456 |
87+
88+
Scenario: Update existing credentials
89+
When I run `wp ai credentials set openai --api-key=old-key-123`
90+
Then STDOUT should contain:
91+
"""
92+
Success: Credentials for provider "openai" have been saved.
93+
"""
94+
95+
When I run `wp ai credentials set openai --api-key=new-key-456`
96+
Then STDOUT should contain:
97+
"""
98+
Success: Credentials for provider "openai" have been saved.
99+
"""
100+
101+
When I run `wp ai credentials get openai --format=json`
102+
Then STDOUT should contain:
103+
"""
104+
"api_key":"new****-456"
105+
"""

features/generate.feature

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
Feature: Generate AI content
2+
3+
Background:
4+
Given a WP install
5+
And a wp-content/mu-plugins/mock-provider.php file:
6+
"""
7+
<?php
8+
9+
use WordPress\AiClient\AiClient;
10+
use WordPress\AiClient\Messages\DTO\MessagePart;
11+
use WordPress\AiClient\Messages\DTO\ModelMessage;
12+
use WordPress\AiClient\Messages\Enums\ModalityEnum;
13+
use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface;
14+
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
15+
use WordPress\AiClient\Providers\Contracts\ProviderInterface;
16+
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
17+
use WordPress\AiClient\Providers\Enums\ProviderTypeEnum;
18+
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
19+
use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface;
20+
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
21+
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
22+
use WordPress\AiClient\Providers\Models\DTO\SupportedOption;
23+
use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum;
24+
use WordPress\AiClient\Providers\Models\Enums\OptionEnum;
25+
use WordPress\AiClient\Results\DTO\Candidate;
26+
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
27+
use WordPress\AiClient\Results\DTO\TokenUsage;
28+
use WordPress\AiClient\Results\Enums\FinishReasonEnum;
29+
use WordPress\AI_Client\API_Credentials\API_Credentials_Manager;
30+
31+
class WP_CLI_Mock_Model implements ModelInterface, TextGenerationModelInterface {
32+
private $id;
33+
private $config;
34+
35+
public function __construct( $id ) {
36+
$this->id = $id;
37+
$this->config = new ModelConfig();
38+
}
39+
40+
public function metadata(): ModelMetadata {
41+
return new ModelMetadata(
42+
$this->id,
43+
'WP-CLI Mock Model',
44+
// Supported capabilities.
45+
[
46+
CapabilityEnum::textGeneration(),
47+
CapabilityEnum::imageGeneration(),
48+
],
49+
// Supported options.
50+
[
51+
new SupportedOption(
52+
OptionEnum::inputModalities(),
53+
[
54+
[ModalityEnum::text()]
55+
]
56+
),
57+
new SupportedOption(
58+
OptionEnum::outputModalities(),
59+
[
60+
[ModalityEnum::text()],
61+
[ModalityEnum::text(), ModalityEnum::image()],
62+
]
63+
),
64+
]
65+
);
66+
}
67+
68+
public function providerMetadata(): ProviderMetadata {
69+
return WP_CLI_Mock_Provider::metadata();
70+
}
71+
72+
public function setConfig( ModelConfig $config ): void {
73+
$this->config = $config;
74+
}
75+
76+
public function getConfig(): ModelConfig {
77+
return $this->config;
78+
}
79+
80+
public function generateTextResult(array $prompt): GenerativeAiResult {
81+
// throw new RuntimeException('No candidates were generated');
82+
83+
$modelMessage = new ModelMessage([
84+
new MessagePart('Generated content')
85+
]);
86+
$candidate = new Candidate(
87+
$modelMessage,
88+
FinishReasonEnum::stop(),
89+
42
90+
);
91+
$tokenUsage = new TokenUsage(10, 42, 52);
92+
return new GenerativeAiResult(
93+
'result_123',
94+
[ $candidate ],
95+
$tokenUsage,
96+
$this->providerMetadata(),
97+
$this->metadata(),
98+
[ 'provider' => 'wp-cli-mock-provider' ]
99+
);
100+
}
101+
102+
public function streamGenerateTextResult(array $prompt): Generator {
103+
yield from [];
104+
}
105+
}
106+
107+
class WP_CLI_Mock_Provider implements ProviderInterface {
108+
public static function metadata(): ProviderMetadata {
109+
return new ProviderMetadata( 'wp-cli-mock-provider', 'WP-CLI Mock Provider', ProviderTypeEnum::cloud() );
110+
}
111+
112+
public static function model( string $modelId, ?ModelConfig $modelConfig = null ): ModelInterface {
113+
return new WP_CLI_Mock_Model( $modelId );
114+
}
115+
116+
public static function availability(): ProviderAvailabilityInterface {
117+
return new class() implements ProviderAvailabilityInterface {
118+
public function isConfigured(): bool {
119+
return true;
120+
}
121+
};
122+
}
123+
124+
public static function modelMetadataDirectory(): ModelMetadataDirectoryInterface {
125+
return new class() implements ModelMetadataDirectoryInterface {
126+
public function listModelMetadata(): array {
127+
return [
128+
( new WP_CLI_Mock_Model( 'wp-cli-mock-model' ) )->metadata()
129+
];
130+
}
131+
132+
public function hasModelMetadata( string $modelId ): bool {
133+
return true;
134+
}
135+
136+
public function getModelMetadata( string $modelId ): ModelMetadata {
137+
return self::model()->metadata();
138+
}
139+
};
140+
}
141+
}
142+
143+
WP_CLI::add_hook(
144+
'ai_client_init',
145+
static function () {
146+
AiClient::defaultRegistry()->registerProvider( WP_CLI_Mock_Provider::class );
147+
148+
( new API_Credentials_Manager() )->initialize();
149+
}
150+
);
151+
"""
152+
153+
Scenario: Generate command validates model format
154+
When I try `wp ai generate text "Test prompt" --model=invalidformat`
155+
Then the return code should be 1
156+
157+
Scenario: Generate command validates max-tokens
158+
When I try `wp ai generate text "Test prompt" --max-tokens=-5`
159+
Then the return code should be 1
160+
And STDERR should contain:
161+
"""
162+
Max tokens must be a positive integer
163+
"""
164+
165+
Scenario: Generate command validates top-p range
166+
When I try `wp ai generate text "Test prompt" --top-p=1.5`
167+
Then the return code should be 1
168+
And STDERR should contain:
169+
"""
170+
Top-p must be between 0.0 and 1.0
171+
"""
172+
173+
Scenario: Generate command validates top-k positive
174+
When I try `wp ai generate text "Test prompt" --top-k=-10`
175+
Then the return code should be 1
176+
And STDERR should contain:
177+
"""
178+
Top-k must be a positive integer
179+
"""

phpstan.neon.dist

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,3 @@ parameters:
88
scanFiles:
99
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
1010
treatPhpDocTypesAsCertain: false
11-
ignoreErrors:
12-
# WP-CLI command method signatures use untyped arrays
13-
-
14-
message: '#no value type specified in iterable type array#'
15-
reportUnmatched: false

0 commit comments

Comments
 (0)