Skip to content
Draft
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: 27 additions & 0 deletions src/Actions/ManagesModels.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Polyscope\Laravel\Actions;

use Polyscope\Laravel\Resources\ServerModels;

trait ManagesModels
{
/**
* @return array<int, ServerModels>
*/
public function models(?string $serverId = null): array
{
$query = array_filter([
'server_id' => $serverId,
], static fn (mixed $value): bool => $value !== null && $value !== '');

$response = $this->get('v1/models', $query);

return $this->transformCollection(
is_array($response) ? ($response['data'] ?? []) : [],
ServerModels::class,
);
}
}
22 changes: 22 additions & 0 deletions src/Actions/ManagesTeam.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Polyscope\Laravel\Actions;

use Polyscope\Laravel\Resources\GenericResource;

trait ManagesTeam
{
public function inviteTeamMember(string $email): GenericResource
{
$response = $this->post('v1/team/invites', [
'email' => $email,
]);

return new GenericResource(
is_array($response) ? ($response['data'] ?? $response) : [],
$this,
);
}
}
9 changes: 9 additions & 0 deletions src/Actions/ManagesWorkspaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ public function createWorkspaceFromIssue(string $repositoryId, string $issueUrl,
]);
}

public function createWorkspaceFromLinearIssue(string $repositoryId, string $linearIssueUrl, ?string $serverId = null): Workspace
{
return $this->createWorkspace([
'repository_id' => $repositoryId,
'linear_issue_url' => $linearIssueUrl,
'server_id' => $serverId,
]);
}

public function createWorkspaceFromPullRequest(string $repositoryId, string $pullRequestUrl, ?string $serverId = null): Workspace
{
return $this->createWorkspace([
Expand Down
2 changes: 2 additions & 0 deletions src/Polyscope.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

class Polyscope
{
use Actions\ManagesModels;
use Actions\ManagesRepositories;
use Actions\ManagesServers;
use Actions\ManagesTeam;
use Actions\ManagesWorkspaceActions;
use Actions\ManagesWorkspaceMessages;
use Actions\ManagesWorkspaces;
Expand Down
15 changes: 15 additions & 0 deletions src/Resources/AvailableAgent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Polyscope\Laravel\Resources;

class AvailableAgent extends Resource
{
public ?string $id = null;

public ?string $name = null;

/** @var array<int, string> */
public array $models = [];
}
31 changes: 31 additions & 0 deletions src/Resources/ServerModels.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Polyscope\Laravel\Resources;

class ServerModels extends Resource
{
public ?string $serverId = null;

/** @var array<int, AvailableAgent> */
public array $agents = [];

/** @var array<int, string> */
public array $models = [];

/** @var array<int, array<string, mixed>> */
public array $modelCapabilities = [];

protected function fill(): void
{
parent::fill();

$agents = $this->attributes['agents'] ?? [];

$this->agents = $this->transformCollection(
is_array($agents) ? $agents : [],
AvailableAgent::class,
);
}
}
133 changes: 133 additions & 0 deletions tests/PolyscopeSDKTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
use Polyscope\Laravel\Exceptions\ServerOfflineException;
use Polyscope\Laravel\Exceptions\ValidationException;
use Polyscope\Laravel\Polyscope;
use Polyscope\Laravel\Resources\AvailableAgent;
use Polyscope\Laravel\Resources\DiffSnapshot;
use Polyscope\Laravel\Resources\GenericResource;
use Polyscope\Laravel\Resources\ServerModels;
use Polyscope\Laravel\Resources\Message;
use Polyscope\Laravel\Resources\Server;
use Polyscope\Laravel\Resources\Workspace;
Expand Down Expand Up @@ -151,6 +153,137 @@ public function test_repositories_support_server_filter(): void
$this->assertSame('srv-1', $repositories[0]->serverId);
}

public function test_models_are_transformed_to_server_models_resources(): void
{
$sdk = new Polyscope('token', $http = Mockery::mock(Client::class));

$http->shouldReceive('request')
->once()
->with('GET', 'v1/models', [
'timeout' => 30,
])
->andReturn(new Response(200, [], json_encode([
'data' => [[
'server_id' => 'srv-1',
'agents' => [
[
'id' => 'claude',
'name' => 'Claude',
'models' => ['claude-opus-5', 'claude-sonnet-5'],
],
[
'id' => 'codex',
'name' => 'Codex',
'models' => ['gpt-5.3-codex'],
],
],
'models' => ['claude-opus-5', 'claude-sonnet-5', 'gpt-5.3-codex'],
'model_capabilities' => [
[
'value' => 'claude-opus-5',
'displayName' => 'Opus 5',
'supportsFastMode' => true,
],
],
]],
], JSON_THROW_ON_ERROR)));

$serverModels = $sdk->models();

$this->assertCount(1, $serverModels);
$this->assertInstanceOf(ServerModels::class, $serverModels[0]);
$this->assertSame('srv-1', $serverModels[0]->serverId);
$this->assertCount(2, $serverModels[0]->agents);
$this->assertInstanceOf(AvailableAgent::class, $serverModels[0]->agents[0]);
$this->assertSame('claude', $serverModels[0]->agents[0]->id);
$this->assertSame('Claude', $serverModels[0]->agents[0]->name);
$this->assertSame(['claude-opus-5', 'claude-sonnet-5'], $serverModels[0]->agents[0]->models);
$this->assertSame(['claude-opus-5', 'claude-sonnet-5', 'gpt-5.3-codex'], $serverModels[0]->models);
$this->assertSame('claude-opus-5', $serverModels[0]->modelCapabilities[0]['value']);
$this->assertTrue($serverModels[0]->modelCapabilities[0]['supportsFastMode']);
}

public function test_models_support_server_filter(): void
{
$sdk = new Polyscope('token', $http = Mockery::mock(Client::class));

$http->shouldReceive('request')
->once()
->with('GET', 'v1/models', Mockery::on(function (array $options): bool {
return ($options['timeout'] ?? null) === 30
&& ($options['query']['server_id'] ?? null) === 'srv-1';
}))
->andReturn(new Response(200, [], json_encode([
'data' => [[
'server_id' => 'srv-1',
'agents' => [],
'models' => [],
'model_capabilities' => [],
]],
], JSON_THROW_ON_ERROR)));

$serverModels = $sdk->models('srv-1');

$this->assertCount(1, $serverModels);
$this->assertSame('srv-1', $serverModels[0]->serverId);
$this->assertSame([], $serverModels[0]->agents);
$this->assertSame([], $serverModels[0]->modelCapabilities);
}

public function test_creating_workspace_from_linear_issue_sends_linear_issue_url(): void
{
$sdk = new Polyscope('token', $http = Mockery::mock(Client::class));

$http->shouldReceive('request')
->once()
->with('POST', 'v1/workspaces', [
'timeout' => 30,
'json' => [
'repository_id' => 'repo-1',
'linear_issue_url' => 'https://linear.app/team/issue/ABC-1',
],
])
->andReturn(new Response(201, [], json_encode([
'data' => [
'id' => 'wt-1',
'repo_id' => 'repo-1',
'branch' => 'abc-1',
'status' => 'active',
],
], JSON_THROW_ON_ERROR)));

$workspace = $sdk->createWorkspaceFromLinearIssue('repo-1', 'https://linear.app/team/issue/ABC-1');

$this->assertInstanceOf(Workspace::class, $workspace);
$this->assertSame('wt-1', $workspace->id);
}

public function test_inviting_team_member_returns_generic_resource(): void
{
$sdk = new Polyscope('token', $http = Mockery::mock(Client::class));

$http->shouldReceive('request')
->once()
->with('POST', 'v1/team/invites', [
'timeout' => 30,
'json' => [
'email' => 'teammate@example.com',
],
])
->andReturn(new Response(201, [], json_encode([
'data' => [
'email' => 'teammate@example.com',
'invited' => true,
],
], JSON_THROW_ON_ERROR)));

$result = $sdk->inviteTeamMember('teammate@example.com');

$this->assertInstanceOf(GenericResource::class, $result);
$this->assertSame('teammate@example.com', $result->email);
$this->assertTrue($result->invited);
}

public function test_creating_workspace_returns_workspace_resource(): void
{
$sdk = new Polyscope('token', $http = Mockery::mock(Client::class));
Expand Down
Loading