apteva 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/server.ts CHANGED
@@ -421,7 +421,7 @@ if (hasRestarts) {
421
421
  // Restart in background to not block startup
422
422
  (async () => {
423
423
  // Import startAgentProcess dynamically to avoid circular dependency
424
- const { startAgentProcess } = await import("./routes/api");
424
+ const { startAgentProcess } = await import("./routes/api/agent-utils");
425
425
 
426
426
  // Restart MCP servers first (agents may depend on them)
427
427
  if (mcpServersToRestart.length > 0) {
@@ -51,6 +51,256 @@ const METHOD_COLORS: Record<string, string> = {
51
51
  patch: "#50e3c2",
52
52
  };
53
53
 
54
+ // Try It Out component
55
+ function TryItOut({
56
+ method,
57
+ path,
58
+ parameters,
59
+ requestBody,
60
+ authFetch,
61
+ }: {
62
+ method: string;
63
+ path: string;
64
+ parameters?: Array<{
65
+ name: string;
66
+ in: string;
67
+ required?: boolean;
68
+ schema?: { type: string };
69
+ }>;
70
+ requestBody?: any;
71
+ authFetch: (url: string, options?: RequestInit) => Promise<Response>;
72
+ }) {
73
+ const [paramValues, setParamValues] = useState<Record<string, string>>({});
74
+ const [bodyValue, setBodyValue] = useState("");
75
+ const [response, setResponse] = useState<{ status: number; data: any } | null>(null);
76
+ const [loading, setLoading] = useState(false);
77
+ const [error, setError] = useState<string | null>(null);
78
+
79
+ // Initialize body with example if available
80
+ useEffect(() => {
81
+ if (requestBody?.content?.["application/json"]?.schema) {
82
+ const schema = requestBody.content["application/json"].schema;
83
+ if (schema.example) {
84
+ setBodyValue(JSON.stringify(schema.example, null, 2));
85
+ } else if (schema.properties) {
86
+ // Generate example from properties
87
+ const example: Record<string, any> = {};
88
+ for (const [key, prop] of Object.entries(schema.properties) as [string, any][]) {
89
+ if (prop.example !== undefined) {
90
+ example[key] = prop.example;
91
+ } else if (prop.type === "string") {
92
+ example[key] = "";
93
+ } else if (prop.type === "number" || prop.type === "integer") {
94
+ example[key] = 0;
95
+ } else if (prop.type === "boolean") {
96
+ example[key] = false;
97
+ } else if (prop.type === "array") {
98
+ example[key] = [];
99
+ } else if (prop.type === "object") {
100
+ example[key] = {};
101
+ }
102
+ }
103
+ setBodyValue(JSON.stringify(example, null, 2));
104
+ }
105
+ }
106
+ }, [requestBody]);
107
+
108
+ const execute = async () => {
109
+ setLoading(true);
110
+ setError(null);
111
+ setResponse(null);
112
+
113
+ try {
114
+ // Build URL with path parameters
115
+ let url = path;
116
+ const queryParams: string[] = [];
117
+
118
+ for (const param of parameters || []) {
119
+ const value = paramValues[param.name] || "";
120
+ if (param.in === "path") {
121
+ url = url.replace(`{${param.name}}`, encodeURIComponent(value));
122
+ } else if (param.in === "query" && value) {
123
+ queryParams.push(`${param.name}=${encodeURIComponent(value)}`);
124
+ }
125
+ }
126
+
127
+ if (queryParams.length > 0) {
128
+ url += `?${queryParams.join("&")}`;
129
+ }
130
+
131
+ const options: RequestInit = {
132
+ method: method.toUpperCase(),
133
+ };
134
+
135
+ if (bodyValue && ["post", "put", "patch"].includes(method)) {
136
+ options.headers = { "Content-Type": "application/json" };
137
+ options.body = bodyValue;
138
+ }
139
+
140
+ const res = await authFetch(`/api${url}`, options);
141
+ let data;
142
+ const contentType = res.headers.get("content-type");
143
+ if (contentType?.includes("application/json")) {
144
+ data = await res.json();
145
+ } else {
146
+ data = await res.text();
147
+ }
148
+
149
+ setResponse({ status: res.status, data });
150
+ } catch (err: any) {
151
+ setError(err.message || "Request failed");
152
+ } finally {
153
+ setLoading(false);
154
+ }
155
+ };
156
+
157
+ const pathParams = parameters?.filter((p) => p.in === "path") || [];
158
+ const queryParams = parameters?.filter((p) => p.in === "query") || [];
159
+ const hasBody = ["post", "put", "patch"].includes(method) && requestBody;
160
+
161
+ return (
162
+ <div style={{ marginTop: 16, padding: 16, background: "#0a0a14", borderRadius: 6, border: "1px solid #222" }}>
163
+ <h4 style={{ fontSize: 13, color: "#f97316", marginBottom: 12, fontWeight: 600 }}>Try it out</h4>
164
+
165
+ {/* Path Parameters */}
166
+ {pathParams.length > 0 && (
167
+ <div style={{ marginBottom: 12 }}>
168
+ <div style={{ fontSize: 11, color: "#666", marginBottom: 6 }}>Path Parameters</div>
169
+ {pathParams.map((param) => (
170
+ <div key={param.name} style={{ marginBottom: 8 }}>
171
+ <label style={{ fontSize: 12, color: "#888", display: "block", marginBottom: 4 }}>
172
+ {param.name} {param.required && <span style={{ color: "#f66" }}>*</span>}
173
+ </label>
174
+ <input
175
+ type="text"
176
+ value={paramValues[param.name] || ""}
177
+ onChange={(e) => setParamValues({ ...paramValues, [param.name]: e.target.value })}
178
+ placeholder={param.schema?.type || "string"}
179
+ style={{
180
+ width: "100%",
181
+ padding: "8px 12px",
182
+ background: "#111",
183
+ border: "1px solid #333",
184
+ borderRadius: 4,
185
+ color: "#fff",
186
+ fontSize: 13,
187
+ fontFamily: "monospace",
188
+ }}
189
+ />
190
+ </div>
191
+ ))}
192
+ </div>
193
+ )}
194
+
195
+ {/* Query Parameters */}
196
+ {queryParams.length > 0 && (
197
+ <div style={{ marginBottom: 12 }}>
198
+ <div style={{ fontSize: 11, color: "#666", marginBottom: 6 }}>Query Parameters</div>
199
+ {queryParams.map((param) => (
200
+ <div key={param.name} style={{ marginBottom: 8 }}>
201
+ <label style={{ fontSize: 12, color: "#888", display: "block", marginBottom: 4 }}>
202
+ {param.name} {param.required && <span style={{ color: "#f66" }}>*</span>}
203
+ </label>
204
+ <input
205
+ type="text"
206
+ value={paramValues[param.name] || ""}
207
+ onChange={(e) => setParamValues({ ...paramValues, [param.name]: e.target.value })}
208
+ placeholder={param.schema?.type || "string"}
209
+ style={{
210
+ width: "100%",
211
+ padding: "8px 12px",
212
+ background: "#111",
213
+ border: "1px solid #333",
214
+ borderRadius: 4,
215
+ color: "#fff",
216
+ fontSize: 13,
217
+ fontFamily: "monospace",
218
+ }}
219
+ />
220
+ </div>
221
+ ))}
222
+ </div>
223
+ )}
224
+
225
+ {/* Request Body */}
226
+ {hasBody && (
227
+ <div style={{ marginBottom: 12 }}>
228
+ <div style={{ fontSize: 11, color: "#666", marginBottom: 6 }}>Request Body (JSON)</div>
229
+ <textarea
230
+ value={bodyValue}
231
+ onChange={(e) => setBodyValue(e.target.value)}
232
+ rows={6}
233
+ style={{
234
+ width: "100%",
235
+ padding: "8px 12px",
236
+ background: "#111",
237
+ border: "1px solid #333",
238
+ borderRadius: 4,
239
+ color: "#fff",
240
+ fontSize: 12,
241
+ fontFamily: "monospace",
242
+ resize: "vertical",
243
+ }}
244
+ />
245
+ </div>
246
+ )}
247
+
248
+ {/* Execute Button */}
249
+ <button
250
+ onClick={execute}
251
+ disabled={loading}
252
+ style={{
253
+ padding: "10px 20px",
254
+ background: loading ? "#333" : "#f97316",
255
+ color: loading ? "#666" : "#000",
256
+ border: "none",
257
+ borderRadius: 4,
258
+ cursor: loading ? "not-allowed" : "pointer",
259
+ fontSize: 13,
260
+ fontWeight: 600,
261
+ }}
262
+ >
263
+ {loading ? "Executing..." : "Execute"}
264
+ </button>
265
+
266
+ {/* Error */}
267
+ {error && (
268
+ <div style={{ marginTop: 12, padding: 12, background: "#2a1515", borderRadius: 4, color: "#f66", fontSize: 12 }}>
269
+ {error}
270
+ </div>
271
+ )}
272
+
273
+ {/* Response */}
274
+ {response && (
275
+ <div style={{ marginTop: 12 }}>
276
+ <div style={{ fontSize: 11, color: "#666", marginBottom: 6 }}>
277
+ Response{" "}
278
+ <span style={{ color: response.status >= 200 && response.status < 300 ? "#49cc90" : "#f66" }}>
279
+ {response.status}
280
+ </span>
281
+ </div>
282
+ <pre
283
+ style={{
284
+ padding: 12,
285
+ background: "#111",
286
+ borderRadius: 4,
287
+ color: "#888",
288
+ fontSize: 11,
289
+ fontFamily: "monospace",
290
+ overflow: "auto",
291
+ maxHeight: 300,
292
+ whiteSpace: "pre-wrap",
293
+ wordBreak: "break-word",
294
+ }}
295
+ >
296
+ {typeof response.data === "string" ? response.data : JSON.stringify(response.data, null, 2)}
297
+ </pre>
298
+ </div>
299
+ )}
300
+ </div>
301
+ );
302
+ }
303
+
54
304
  export function ApiDocsPage() {
55
305
  const { authFetch } = useAuth();
56
306
  const [spec, setSpec] = useState<OpenApiSpec | null>(null);
@@ -516,6 +766,15 @@ export function ApiDocsPage() {
516
766
  </div>
517
767
  </div>
518
768
  )}
769
+
770
+ {/* Try It Out */}
771
+ <TryItOut
772
+ method={method}
773
+ path={path}
774
+ parameters={details.parameters}
775
+ requestBody={details.requestBody}
776
+ authFetch={authFetch}
777
+ />
519
778
  </div>
520
779
  )}
521
780
  </div>
@@ -44,9 +44,11 @@ function hasMultipleAuthMethods(app: IntegrationApp): boolean {
44
44
  // Main component
45
45
  export function IntegrationsPanel({
46
46
  providerId = "composio",
47
+ projectId,
47
48
  onConnectionComplete,
48
49
  }: {
49
50
  providerId?: string;
51
+ projectId?: string | null;
50
52
  onConnectionComplete?: () => void;
51
53
  }) {
52
54
  const { authFetch } = useAuth();
@@ -80,10 +82,11 @@ export function IntegrationsPanel({
80
82
  const fetchData = useCallback(async () => {
81
83
  setLoading(true);
82
84
  setError(null);
85
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
83
86
  try {
84
87
  const [appsRes, connectedRes] = await Promise.all([
85
- authFetch(`/api/integrations/${providerId}/apps`),
86
- authFetch(`/api/integrations/${providerId}/connected`),
88
+ authFetch(`/api/integrations/${providerId}/apps${projectParam}`),
89
+ authFetch(`/api/integrations/${providerId}/connected${projectParam}`),
87
90
  ]);
88
91
 
89
92
  const appsData = await appsRes.json();
@@ -96,7 +99,7 @@ export function IntegrationsPanel({
96
99
  setError("Failed to load integrations");
97
100
  }
98
101
  setLoading(false);
99
- }, [authFetch, providerId]);
102
+ }, [authFetch, providerId, projectId]);
100
103
 
101
104
  useEffect(() => {
102
105
  fetchData();
@@ -118,11 +121,12 @@ export function IntegrationsPanel({
118
121
  // Poll for pending connection status
119
122
  useEffect(() => {
120
123
  if (!pendingConnection?.connectionId) return;
124
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
121
125
 
122
126
  const pollInterval = setInterval(async () => {
123
127
  try {
124
128
  const res = await authFetch(
125
- `/api/integrations/${providerId}/connection/${pendingConnection.connectionId}`
129
+ `/api/integrations/${providerId}/connection/${pendingConnection.connectionId}${projectParam}`
126
130
  );
127
131
  const data = await res.json();
128
132
 
@@ -142,7 +146,7 @@ export function IntegrationsPanel({
142
146
  }, 2000);
143
147
 
144
148
  return () => clearInterval(pollInterval);
145
- }, [pendingConnection, authFetch, providerId, fetchData, onConnectionComplete]);
149
+ }, [pendingConnection, authFetch, providerId, projectId, fetchData, onConnectionComplete]);
146
150
 
147
151
  // Initiate connection
148
152
  const connectApp = async (app: IntegrationApp, apiKey?: string, forceOAuth?: boolean) => {
@@ -172,7 +176,8 @@ export function IntegrationsPanel({
172
176
  };
173
177
  }
174
178
 
175
- const res = await authFetch(`/api/integrations/${providerId}/connect`, {
179
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
180
+ const res = await authFetch(`/api/integrations/${providerId}/connect${projectParam}`, {
176
181
  method: "POST",
177
182
  headers: { "Content-Type": "application/json" },
178
183
  body: JSON.stringify(body),
@@ -231,9 +236,10 @@ export function IntegrationsPanel({
231
236
 
232
237
  // Disconnect (called after confirmation)
233
238
  const disconnectApp = async (account: ConnectedAccount) => {
239
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
234
240
  try {
235
241
  const res = await authFetch(
236
- `/api/integrations/${providerId}/connection/${account.id}`,
242
+ `/api/integrations/${providerId}/connection/${account.id}${projectParam}`,
237
243
  { method: "DELETE" }
238
244
  );
239
245
 
@@ -263,7 +269,8 @@ export function IntegrationsPanel({
263
269
  setError(null);
264
270
 
265
271
  try {
266
- const res = await authFetch(`/api/integrations/${providerId}/configs`, {
272
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
273
+ const res = await authFetch(`/api/integrations/${providerId}/configs${projectParam}`, {
267
274
  method: "POST",
268
275
  headers: { "Content-Type": "application/json" },
269
276
  body: JSON.stringify({