Skip to content

Commit 68ef774

Browse files
authored
feat(manage-api): add x-speakeasy-pagination extension to all paginated list endpoints (#1300)
* feat(manage-api): add x-speakeasy-pagination extension to all paginated list endpoints Adds native Speakeasy pagination support to 17 list endpoints by adding the x-speakeasy-pagination OpenAPI extension. This will enable the generated SDK to have built-in pagination methods like .next(). - Created shared.ts with reusable speakeasyOffsetLimitPagination constant - Updated all paginated route files to use the shared constant * biome format and pagination improvements
1 parent 9afba48 commit 68ef774

26 files changed

+317
-128
lines changed

.changeset/olympic-red-leopard.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@inkeep/agents-cli": patch
3+
"@inkeep/agents-core": patch
4+
"@inkeep/agents-manage-api": patch
5+
"@inkeep/agents-manage-ui": patch
6+
"@inkeep/agents-run-api": patch
7+
"@inkeep/agents-sdk": patch
8+
"@inkeep/create-agents": patch
9+
"@inkeep/ai-sdk-provider": patch
10+
---
11+
12+
Add x-speakeasy-pagination extension to all paginated list endpoints for Speakeasy SDK native pagination support

agents-docs/src/app/layout.tsx

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -116,40 +116,40 @@ export default function Layout({ children }: LayoutProps<'/'>) {
116116
<PostHogPageview />
117117
</Suspense>
118118
<PostHogProvider>
119-
<RootProvider search={{ SearchDialog }}>
120-
<DocsLayout
121-
tree={source.pageTree}
122-
nav={{
123-
title: <Logo className="!w-[110px] !h-[32px]" />,
124-
}}
125-
sidebar={{
126-
className: 'bg-background',
127-
banner: (
128-
<div className="flex gap-2">
129-
<Button
130-
variant="outline"
131-
size="sm"
132-
className="grow text-primary border-primary/30 hover:bg-primary/5 dark:bg-primary/5 hover:text-primary dark:text-primary dark:border-primary/30 dark:hover:bg-primary/10"
133-
asChild
134-
>
135-
<a
136-
href="https://inkeep.com/cloud-waitlist?cta_id=docs_nav"
137-
target="_blank"
138-
rel="noreferrer"
119+
<RootProvider search={{ SearchDialog }}>
120+
<DocsLayout
121+
tree={source.pageTree}
122+
nav={{
123+
title: <Logo className="!w-[110px] !h-[32px]" />,
124+
}}
125+
sidebar={{
126+
className: 'bg-background',
127+
banner: (
128+
<div className="flex gap-2">
129+
<Button
130+
variant="outline"
131+
size="sm"
132+
className="grow text-primary border-primary/30 hover:bg-primary/5 dark:bg-primary/5 hover:text-primary dark:text-primary dark:border-primary/30 dark:hover:bg-primary/10"
133+
asChild
139134
>
140-
<Cloud />
141-
Inkeep Cloud
142-
</a>
143-
</Button>
144-
<GithubStars />
145-
</div>
146-
),
147-
}}
148-
links={linkItems}
149-
>
150-
{children}
151-
</DocsLayout>
152-
</RootProvider>
135+
<a
136+
href="https://inkeep.com/cloud-waitlist?cta_id=docs_nav"
137+
target="_blank"
138+
rel="noreferrer"
139+
>
140+
<Cloud />
141+
Inkeep Cloud
142+
</a>
143+
</Button>
144+
<GithubStars />
145+
</div>
146+
),
147+
}}
148+
links={linkItems}
149+
>
150+
{children}
151+
</DocsLayout>
152+
</RootProvider>
153153
</PostHogProvider>
154154
</body>
155155
</html>
Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
"use client";
1+
'use client';
22

3-
import { usePathname, useSearchParams } from "next/navigation";
4-
import { useEffect } from "react";
5-
import posthog from "posthog-js";
3+
import { usePathname, useSearchParams } from 'next/navigation';
4+
import posthog from 'posthog-js';
5+
import { useEffect } from 'react';
66

77
export default function PostHogPageview() {
88
const pathname = usePathname();
@@ -17,17 +17,15 @@ export default function PostHogPageview() {
1717

1818
posthog.register_once({
1919
initial_referrer:
20-
typeof window !== "undefined"
21-
? document.referrer || "(direct)"
22-
: "(direct)",
20+
typeof window !== 'undefined' ? document.referrer || '(direct)' : '(direct)',
2321
initial_landing_page: pathname,
2422
});
2523

26-
const utmSource = searchParams?.get("utm_source");
27-
const utmMedium = searchParams?.get("utm_medium");
28-
const utmCampaign = searchParams?.get("utm_campaign");
29-
const utmContent = searchParams?.get("utm_content");
30-
const utmTerm = searchParams?.get("utm_term");
24+
const utmSource = searchParams?.get('utm_source');
25+
const utmMedium = searchParams?.get('utm_medium');
26+
const utmCampaign = searchParams?.get('utm_campaign');
27+
const utmContent = searchParams?.get('utm_content');
28+
const utmTerm = searchParams?.get('utm_term');
3129

3230
if (utmSource || utmMedium || utmCampaign || utmContent || utmTerm) {
3331
const utmParams: Record<string, string> = {};
@@ -41,11 +39,11 @@ export default function PostHogPageview() {
4139
}
4240

4341
const clickIdParams: Record<string, string> = {};
44-
const gclid = searchParams?.get("gclid");
45-
const fbclid = searchParams?.get("fbclid");
46-
const msclkid = searchParams?.get("msclkid");
47-
const li_fat_id = searchParams?.get("li_fat_id");
48-
const ttclid = searchParams?.get("ttclid");
42+
const gclid = searchParams?.get('gclid');
43+
const fbclid = searchParams?.get('fbclid');
44+
const msclkid = searchParams?.get('msclkid');
45+
const li_fat_id = searchParams?.get('li_fat_id');
46+
const ttclid = searchParams?.get('ttclid');
4947

5048
if (gclid) clickIdParams.gclid = gclid;
5149
if (fbclid) clickIdParams.fbclid = fbclid;
@@ -57,11 +55,11 @@ export default function PostHogPageview() {
5755
posthog.register_once(clickIdParams);
5856
}
5957

60-
posthog.capture("$pageview", {
58+
posthog.capture('$pageview', {
6159
$current_url: url,
6260
});
6361
}
6462
}, [pathname, searchParams]);
6563

6664
return null;
67-
}
65+
}
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,32 @@
1-
"use client";
1+
'use client';
22

3-
import posthog from "posthog-js";
3+
import posthog from 'posthog-js';
44

5-
if (typeof window !== "undefined") {
6-
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY || "", {
7-
api_host:
8-
process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
5+
if (typeof window !== 'undefined') {
6+
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY || '', {
7+
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
98
capture_pageview: false,
10-
person_profiles: "always",
9+
person_profiles: 'always',
1110
disable_surveys: true,
1211
disable_external_dependency_loading: true,
1312
loaded: (posthog) => {
1413
posthog.register({
15-
site: "agents-docs",
14+
site: 'agents-docs',
1615
});
1716

18-
if (process.env.NODE_ENV === "development") {
19-
console.log("[PostHog] Initialized successfully");
17+
if (process.env.NODE_ENV === 'development') {
18+
console.log('[PostHog] Initialized successfully');
2019
}
2120
},
2221
session_recording: {
2322
maskAllInputs: true,
2423
maskTextFn: (text, element) => {
25-
if (element?.dataset.demo === "true") {
24+
if (element?.dataset.demo === 'true') {
2625
return text;
2726
}
28-
return "*".repeat(text.length);
27+
return '*'.repeat(text.length);
2928
},
30-
maskTextSelector: "*",
29+
maskTextSelector: '*',
3130
},
3231
});
33-
}
32+
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
export const DOCS_PAGE_VIEWED = "docs_page_viewed";
2-
export const DOCS_LINK_CLICKED = "docs_link_clicked";
3-
export const DOCS_SEARCH_PERFORMED = "docs_search_performed";
4-
export const DOCS_CODE_COPIED = "docs_code_copied";
5-
export const DOCS_EXTERNAL_LINK_CLICKED = "docs_external_link_clicked";
6-
export const DOCS_GITHUB_LINK_CLICKED = "docs_github_link_clicked";
7-
export const DOCS_SLACK_LINK_CLICKED = "docs_slack_link_clicked";
8-
export const DOCS_CTA_CLICKED = "docs_cta_clicked";
1+
export const DOCS_PAGE_VIEWED = 'docs_page_viewed';
2+
export const DOCS_LINK_CLICKED = 'docs_link_clicked';
3+
export const DOCS_SEARCH_PERFORMED = 'docs_search_performed';
4+
export const DOCS_CODE_COPIED = 'docs_code_copied';
5+
export const DOCS_EXTERNAL_LINK_CLICKED = 'docs_external_link_clicked';
6+
export const DOCS_GITHUB_LINK_CLICKED = 'docs_github_link_clicked';
7+
export const DOCS_SLACK_LINK_CLICKED = 'docs_slack_link_clicked';
8+
export const DOCS_CTA_CLICKED = 'docs_cta_clicked';
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
"use client";
1+
'use client';
22

3-
import { PostHogProvider as PHProvider } from "posthog-js/react";
4-
import posthog from "posthog-js";
5-
import type { ReactNode } from "react";
6-
import "@/instrumentation-client";
3+
import posthog from 'posthog-js';
4+
import { PostHogProvider as PHProvider } from 'posthog-js/react';
5+
import type { ReactNode } from 'react';
6+
import '@/instrumentation-client';
77

88
export function PostHogProvider({ children }: { children: ReactNode }) {
99
return <PHProvider client={posthog}>{children}</PHProvider>;
10-
}
10+
}
Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
1-
import { PostHog } from "posthog-node";
1+
import { PostHog } from 'posthog-node';
22

33
export async function captureServerEvent(
44
distinctId: string,
55
event: string,
66
properties?: Record<string, unknown>
77
): Promise<void> {
8-
if (
9-
process.env.NODE_ENV === "test" ||
10-
!process.env.NEXT_PUBLIC_POSTHOG_KEY
11-
) {
12-
console.log(
13-
`[PostHog Server] Skipping event capture: ${event} (${distinctId})`
14-
);
8+
if (process.env.NODE_ENV === 'test' || !process.env.NEXT_PUBLIC_POSTHOG_KEY) {
9+
console.log(`[PostHog Server] Skipping event capture: ${event} (${distinctId})`);
1510
return;
1611
}
1712

1813
try {
1914
const posthog = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
20-
host:
21-
process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
15+
host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
2216
flushAt: 1,
2317
flushInterval: 0,
2418
});
@@ -28,8 +22,8 @@ export async function captureServerEvent(
2822
event,
2923
properties: {
3024
...properties,
31-
$lib: "posthog-node",
32-
source: "server",
25+
$lib: 'posthog-node',
26+
source: 'server',
3327
},
3428
});
3529

@@ -40,28 +34,22 @@ export async function captureServerEvent(
4034
properties,
4135
});
4236
} catch (error) {
43-
console.error("[PostHog Server] Failed to capture event:", error);
37+
console.error('[PostHog Server] Failed to capture event:', error);
4438
}
4539
}
4640

4741
export async function identifyServerUser(
4842
distinctId: string,
4943
properties?: Record<string, unknown>
5044
): Promise<void> {
51-
if (
52-
process.env.NODE_ENV === "test" ||
53-
!process.env.NEXT_PUBLIC_POSTHOG_KEY
54-
) {
55-
console.log(
56-
`[PostHog Server] Skipping user identify: ${distinctId}`
57-
);
45+
if (process.env.NODE_ENV === 'test' || !process.env.NEXT_PUBLIC_POSTHOG_KEY) {
46+
console.log(`[PostHog Server] Skipping user identify: ${distinctId}`);
5847
return;
5948
}
6049

6150
try {
6251
const posthog = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
63-
host:
64-
process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
52+
host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
6553
flushAt: 1,
6654
flushInterval: 0,
6755
});
@@ -77,6 +65,6 @@ export async function identifyServerUser(
7765
properties,
7866
});
7967
} catch (error) {
80-
console.error("[PostHog Server] Failed to identify user:", error);
68+
console.error('[PostHog Server] Failed to identify user:', error);
8169
}
82-
}
70+
}

agents-manage-api/src/routes/agent.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
getAgentById,
1515
getAgentSubAgentInfos,
1616
getFullAgentDefinition,
17-
listAgents,
17+
listAgentsPaginated,
1818
PaginationQueryParamsSchema,
1919
RelatedAgentInfoListResponse,
2020
TenantProjectAgentParamsSchema,
@@ -26,6 +26,7 @@ import {
2626
import dbClient from '../data/db/dbClient';
2727
import { requirePermission } from '../middleware/require-permission';
2828
import type { BaseAppVariables } from '../types/app';
29+
import { speakeasyOffsetLimitPagination } from './shared';
2930

3031
const app = new OpenAPIHono<{ Variables: BaseAppVariables }>();
3132

@@ -68,22 +69,18 @@ app.openapi(
6869
},
6970
...commonGetErrorResponses,
7071
},
72+
...speakeasyOffsetLimitPagination,
7173
}),
7274
async (c) => {
7375
const { tenantId, projectId } = c.req.valid('param');
7476
const page = Number(c.req.query('page')) || 1;
7577
const limit = Math.min(Number(c.req.query('limit')) || 10, 100);
7678

77-
const agent = await listAgents(dbClient)({ scopes: { tenantId, projectId } });
78-
return c.json({
79-
data: agent,
80-
pagination: {
81-
page,
82-
limit,
83-
total: agent.length,
84-
pages: Math.ceil(agent.length / limit),
85-
},
79+
const result = await listAgentsPaginated(dbClient)({
80+
scopes: { tenantId, projectId },
81+
pagination: { page, limit },
8682
});
83+
return c.json(result);
8784
}
8885
);
8986

agents-manage-api/src/routes/agentToolRelations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import dbClient from '../data/db/dbClient';
2424
import { requirePermission } from '../middleware/require-permission';
2525
import type { BaseAppVariables } from '../types/app';
26+
import { speakeasyOffsetLimitPagination } from './shared';
2627

2728
const app = new OpenAPIHono<{ Variables: BaseAppVariables }>();
2829

@@ -68,6 +69,7 @@ app.openapi(
6869
},
6970
...commonGetErrorResponses,
7071
},
72+
...speakeasyOffsetLimitPagination,
7173
}),
7274
async (c) => {
7375
const { tenantId, projectId, agentId } = c.req.valid('param');

agents-manage-api/src/routes/apiKeys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import dbClient from '../data/db/dbClient';
2222
import { requirePermission } from '../middleware/require-permission';
2323
import type { BaseAppVariables } from '../types/app';
24+
import { speakeasyOffsetLimitPagination } from './shared';
2425

2526
const app = new OpenAPIHono<{ Variables: BaseAppVariables }>();
2627

@@ -67,6 +68,7 @@ app.openapi(
6768
},
6869
...commonGetErrorResponses,
6970
},
71+
...speakeasyOffsetLimitPagination,
7072
}),
7173
async (c) => {
7274
const { tenantId, projectId } = c.req.valid('param');

0 commit comments

Comments
 (0)