Merge origin/master into provider routing connections

This commit is contained in:
Dotta committed 2026-10-05 21:10:55 -05:00
commit b6bc28667e
627 files changed
+97335 -5708

No files matched your search

+3 -2
View File
@@ -226,11 +226,12 @@ export const agentsApi = {
adapterModels: (
companyId: string,
type: string,
options?: { refresh?: boolean; environmentId?: string | null; provider?: string },
options?: { refresh?: boolean; environmentId?: string | null; provider?: string; poolId?: string },
) => {
const params = new URLSearchParams();
if (options?.refresh) params.set("refresh", "1");
if (options?.provider) params.set("provider", options.provider);
if (options?.poolId) params.set("poolId", options.poolId);
if (options?.environmentId) params.set("environmentId", options.environmentId);
const query = params.size > 0 ? `?${params.toString()}` : "";
return api.get<AdapterModel[]>(
@@ -246,7 +247,7 @@ export const agentsApi = {
type: string,
data: {
adapterConfig: Record<string, unknown>;
aiConnection?: import("@paperclipai/shared").AiConnectionBinding;
aiConnection?: import("@paperclipai/shared").AiRuntimeConnectionBinding;
agentId?: string;
testCredentials?: Record<string, string>;
environmentId?: string | null;
+9
View File
@@ -0,0 +1,9 @@
import type { AiConnectionPool, AiConnectionPoolConfig, AiConnectionUsage } from "@paperclipai/shared";
import { api } from "./client";
export type PoolInspection = Record<string, { reason: string; checkedAt: string | null; usage?: AiConnectionUsage }>;
export const aiConnectionPoolsApi = {
list: (companyId: string) => api.get<AiConnectionPool[]>(`/companies/${companyId}/ai-connection-pools`),
save: (companyId: string, body: { pluginKey: string; id?: string; expectedRevision?: number; config: AiConnectionPoolConfig }) => api.post<AiConnectionPool>(`/companies/${companyId}/ai-connection-pools`, body),
inspect: (companyId: string, id: string) => api.get<PoolInspection>(`/companies/${companyId}/ai-connection-pools/${id}/inspection`),
remove: (companyId: string, id: string, expectedRevision: number) => api.delete(`/companies/${companyId}/ai-connection-pools/${id}`, { expectedRevision }),
};
+3
View File
@@ -34,6 +34,8 @@ export type ArtifactKindFilter = Exclude<CompanyArtifactMediaKind, "empty"> | "a
export interface ListArtifactsParams {
kind?: ArtifactKindFilter;
projectId?: string;
/** Only artifacts attributed to this agent. */
agentId?: string;
q?: string;
/** Grouping mode. `none` (default) returns the flat artifact grid. */
groupBy?: CompanyArtifactGroupBy;
@@ -47,6 +49,7 @@ function buildArtifactsQuery(params?: ListArtifactsParams): string {
const search = new URLSearchParams();
if (params?.kind && params.kind !== "all") search.set("kind", params.kind);
if (params?.projectId) search.set("projectId", params.projectId);
if (params?.agentId) search.set("agentId", params.agentId);
if (params?.q) search.set("q", params.q);
if (params?.groupBy && params.groupBy !== "none") search.set("groupBy", params.groupBy);
if (params?.groupIssueId) search.set("groupIssueId", params.groupIssueId);
+18
View File
@@ -4,6 +4,7 @@ import {
tenantSessionRecovery,
} from "@/lib/tenant-session-recovery";
import { __inflightGetCount, api, detachInflightGet } from "./client";
import { toolsApi } from "./tools";
interface Deferred<T> {
promise: Promise<T>;
@@ -199,3 +200,20 @@ describe("per-caller abort semantics", () => {
expect(fetchMock).not.toHaveBeenCalled();
});
});
describe("managed-account request isolation", () => {
it("does not share a previous viewing user's pending account response", async () => {
const previous = deferred<Response>();
const current = deferred<Response>();
fetchMock.mockReturnValueOnce(previous.promise).mockReturnValueOnce(current.promise);
const previousUser = toolsApi.listAggregatorApps("shared-gateway");
const currentUser = toolsApi.listAggregatorApps("shared-gateway");
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(fetchMock.mock.calls.every(([, options]) => options.cache === "no-store")).toBe(true);
current.resolve(jsonResponse({ apps: ["current-user-account"] }));
previous.resolve(jsonResponse({ apps: ["previous-user-account"] }));
expect(await currentUser).toEqual({ apps: ["current-user-account"] });
expect(await previousUser).toEqual({ apps: ["previous-user-account"] });
});
});
+1
View File
@@ -6,6 +6,7 @@ const mockApi = vi.hoisted(() => ({
vi.mock("./client", () => ({
api: mockApi,
detachInflightGet: vi.fn(),
}));
import { toolsApi } from "./tools";
+22 -1
View File
@@ -1,3 +1,5 @@
import type { AggregatorAppsResponse, ArcadeDiscoverySetupInput } from "@paperclipai/shared/aggregator-apps";
import type { ComposioAppAccountInput, ComposioAppSetupInput, ComposioAppSetupResult, ComposioAppSnapshot, ComposioAppsResponse } from "@paperclipai/shared";
import type {
ToolApplication,
ConfigureRailwaySsh,
@@ -64,7 +66,7 @@ import type {
ToolConnectionCreateCapabilities,
ToolAppMetadataPreflightResult,
} from "@paperclipai/shared";
import { api } from "./client";
import { api, detachInflightGet } from "./client";
/**
* Tools & Access API client (Phase 6, PAP-10389).
@@ -156,6 +158,7 @@ export interface CreateToolConnectionInput {
}
export interface UpdateToolConnectionInput {
agentInstructions?: import("@paperclipai/shared").ConnectionAgentInstructions | null;
name?: string;
status?: ToolConnection["status"];
config?: Record<string, unknown>;
@@ -320,6 +323,7 @@ export const toolsApi = {
enabledCatalogEntryIds: string[];
askFirstCatalogEntryIds: string[];
reviewedCatalogEntryIds?: string[];
agentInstructions?: import("@paperclipai/shared").ConnectionAgentInstructions | null;
access: "all_agents" | { agentIds: string[] };
}) =>
api.post<FinishToolAppResult>(
@@ -431,6 +435,23 @@ export const toolsApi = {
api.get<ToolConnectionTestAgentAccessResponse>(
`/tool-connections/${connectionId}/test-agents/${agentId}/access`,
),
setupComposioApp: (connectionId: string, toolkit: string, input: ComposioAppSetupInput) =>
api.post<ComposioAppSetupResult>(`/tool-connections/${connectionId}/composio/apps/${encodeURIComponent(toolkit)}/setup`, input),
listAggregatorApps: (connectionId: string) => {
const path = `/tool-connections/${connectionId}/aggregator/apps`;
// React Query already deduplicates within a viewing-user key. Path-only
// request coalescing could hand a previous user's in-flight response to a new account.
detachInflightGet(path);
return api.get<AggregatorAppsResponse>(path, { cache: "no-store" });
},
syncAggregatorApps: (connectionId: string, force = false) => api.post<AggregatorAppsResponse>(`/tool-connections/${connectionId}/aggregator/apps/sync`, { force }),
refreshAggregatorApps: (connectionId: string, toolkits: string[] = []) => api.post<AggregatorAppsResponse>(`/tool-connections/${connectionId}/aggregator/apps/refresh`, { toolkits }),
configureArcadeDiscovery: (connectionId: string, input: ArcadeDiscoverySetupInput) => api.put<AggregatorAppsResponse>(`/tool-connections/${connectionId}/aggregator/discovery`, input),
listComposioApps: (connectionId: string) => api.get<ComposioAppsResponse>(`/tool-connections/${connectionId}/composio/apps`),
syncComposioApps: (connectionId: string, force = false) => api.post<ComposioAppsResponse>(`/tool-connections/${connectionId}/composio/apps/sync`, { force }),
refreshComposioApps: (connectionId: string, toolkits: string[]) => api.post<ComposioAppsResponse>(`/tool-connections/${connectionId}/composio/apps/refresh`, { toolkits }),
manageComposioAppAccount: (connectionId: string, toolkit: string, input: ComposioAppAccountInput) =>
api.post<ComposioAppSetupResult & { apps: ComposioAppSnapshot[] }>(`/tool-connections/${connectionId}/composio/apps/${encodeURIComponent(toolkit)}/accounts`, input),
runTestCall: (
connectionId: string,
input: { agentId: string; toolName: string; parameters?: Record<string, unknown> },