stagent 0.3.5 → 0.3.6
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/package.json +1 -1
- package/src/app/api/settings/runtime/route.ts +25 -0
- package/src/app/settings/page.tsx +2 -0
- package/src/components/settings/runtime-timeout-section.tsx +90 -0
- package/src/lib/agents/runtime/claude.ts +29 -3
- package/src/lib/chat/tools/settings-tools.ts +1 -0
- package/src/lib/constants/settings.ts +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getSetting, setSetting } from "@/lib/settings/helpers";
|
|
3
|
+
import { SETTINGS_KEYS } from "@/lib/constants/settings";
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const sdkTimeoutSeconds = await getSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS);
|
|
7
|
+
return NextResponse.json({
|
|
8
|
+
sdkTimeoutSeconds: sdkTimeoutSeconds ?? "60",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function POST(req: NextRequest) {
|
|
13
|
+
const body = await req.json();
|
|
14
|
+
const seconds = parseInt(body.sdkTimeoutSeconds, 10);
|
|
15
|
+
|
|
16
|
+
if (isNaN(seconds) || seconds < 10 || seconds > 300) {
|
|
17
|
+
return NextResponse.json(
|
|
18
|
+
{ error: "sdkTimeoutSeconds must be between 10 and 300" },
|
|
19
|
+
{ status: 400 }
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await setSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS, String(seconds));
|
|
24
|
+
return NextResponse.json({ sdkTimeoutSeconds: String(seconds) });
|
|
25
|
+
}
|
|
@@ -4,6 +4,7 @@ import { PermissionsSections } from "@/components/settings/permissions-sections"
|
|
|
4
4
|
import { DataManagementSection } from "@/components/settings/data-management-section";
|
|
5
5
|
import { BudgetGuardrailsSection } from "@/components/settings/budget-guardrails-section";
|
|
6
6
|
import { ChatSettingsSection } from "@/components/settings/chat-settings-section";
|
|
7
|
+
import { RuntimeTimeoutSection } from "@/components/settings/runtime-timeout-section";
|
|
7
8
|
import { PageShell } from "@/components/shared/page-shell";
|
|
8
9
|
|
|
9
10
|
export const dynamic = "force-dynamic";
|
|
@@ -15,6 +16,7 @@ export default function SettingsPage() {
|
|
|
15
16
|
<AuthConfigSection />
|
|
16
17
|
<OpenAIRuntimeSection />
|
|
17
18
|
<ChatSettingsSection />
|
|
19
|
+
<RuntimeTimeoutSection />
|
|
18
20
|
<BudgetGuardrailsSection />
|
|
19
21
|
<PermissionsSections />
|
|
20
22
|
<DataManagementSection />
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import { Timer } from "lucide-react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@/components/ui/card";
|
|
13
|
+
import { Input } from "@/components/ui/input";
|
|
14
|
+
import { FormSectionCard } from "@/components/shared/form-section-card";
|
|
15
|
+
|
|
16
|
+
const SETTING_KEY = "runtime.sdkTimeoutSeconds";
|
|
17
|
+
const DEFAULT_TIMEOUT = 60;
|
|
18
|
+
|
|
19
|
+
export function RuntimeTimeoutSection() {
|
|
20
|
+
const [timeout, setTimeout_] = useState(String(DEFAULT_TIMEOUT));
|
|
21
|
+
const [saving, setSaving] = useState(false);
|
|
22
|
+
|
|
23
|
+
const fetchTimeout = useCallback(async () => {
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch("/api/settings/runtime");
|
|
26
|
+
if (res.ok) {
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
if (data.sdkTimeoutSeconds) setTimeout_(data.sdkTimeoutSeconds);
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Use default
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
fetchTimeout();
|
|
37
|
+
}, [fetchTimeout]);
|
|
38
|
+
|
|
39
|
+
const handleSave = async (value: string) => {
|
|
40
|
+
const num = parseInt(value, 10);
|
|
41
|
+
if (isNaN(num) || num < 10 || num > 300) {
|
|
42
|
+
toast.error("Timeout must be between 10 and 300 seconds");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setSaving(true);
|
|
47
|
+
try {
|
|
48
|
+
await fetch("/api/settings/runtime", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify({ sdkTimeoutSeconds: String(num) }),
|
|
52
|
+
});
|
|
53
|
+
setTimeout_(String(num));
|
|
54
|
+
toast.success("Timeout updated");
|
|
55
|
+
} catch {
|
|
56
|
+
toast.error("Failed to save timeout");
|
|
57
|
+
} finally {
|
|
58
|
+
setSaving(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Card>
|
|
64
|
+
<CardHeader>
|
|
65
|
+
<CardTitle>Runtime</CardTitle>
|
|
66
|
+
<CardDescription>
|
|
67
|
+
Configure runtime behavior for AI operations.
|
|
68
|
+
</CardDescription>
|
|
69
|
+
</CardHeader>
|
|
70
|
+
<CardContent>
|
|
71
|
+
<FormSectionCard
|
|
72
|
+
icon={Timer}
|
|
73
|
+
title="SDK Timeout"
|
|
74
|
+
hint="Maximum seconds to wait for AI responses (task assist, completions). Min 10, max 300."
|
|
75
|
+
>
|
|
76
|
+
<Input
|
|
77
|
+
type="number"
|
|
78
|
+
min={10}
|
|
79
|
+
max={300}
|
|
80
|
+
value={timeout}
|
|
81
|
+
onChange={(e) => setTimeout_(e.target.value)}
|
|
82
|
+
onBlur={(e) => handleSave(e.target.value)}
|
|
83
|
+
disabled={saving}
|
|
84
|
+
className="w-32"
|
|
85
|
+
/>
|
|
86
|
+
</FormSectionCard>
|
|
87
|
+
</CardContent>
|
|
88
|
+
</Card>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -10,6 +10,8 @@ import { executeClaudeTask, resumeClaudeTask } from "@/lib/agents/claude-agent";
|
|
|
10
10
|
import { getRuntimeCapabilities, getRuntimeCatalogEntry } from "./catalog";
|
|
11
11
|
import { buildClaudeSdkEnv } from "./claude-sdk";
|
|
12
12
|
import { getLaunchCwd } from "@/lib/environment/workspace-context";
|
|
13
|
+
import { getSetting } from "@/lib/settings/helpers";
|
|
14
|
+
import { SETTINGS_KEYS } from "@/lib/constants/settings";
|
|
13
15
|
import type {
|
|
14
16
|
AgentRuntimeAdapter,
|
|
15
17
|
RuntimeConnectionResult,
|
|
@@ -89,6 +91,21 @@ async function collectResultText(
|
|
|
89
91
|
return { resultText, usage };
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
/** Read the user-configurable SDK timeout (in ms). Falls back to 60s. */
|
|
95
|
+
async function getSdkTimeout(): Promise<number> {
|
|
96
|
+
const raw = await getSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS);
|
|
97
|
+
const seconds = raw ? parseInt(raw, 10) : 60;
|
|
98
|
+
return (isNaN(seconds) || seconds < 10 ? 60 : seconds) * 1000;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Check if an error is an abort/timeout error from the SDK. */
|
|
102
|
+
function isAbortError(error: unknown): boolean {
|
|
103
|
+
return (
|
|
104
|
+
error instanceof Error &&
|
|
105
|
+
(error.name === "AbortError" || error.message.includes("aborted"))
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
async function runSingleProfileTest(
|
|
93
110
|
profileId: string,
|
|
94
111
|
test: { task: string; expectedKeywords: string[] }
|
|
@@ -106,7 +123,8 @@ async function runSingleProfileTest(
|
|
|
106
123
|
const prompt = `${payload.instructions}\n\n---\n\nTask: ${test.task}\n\nProvide a brief analysis (2-3 paragraphs max). Include specific terminology relevant to your domain.`;
|
|
107
124
|
const authEnv = await getAuthEnv();
|
|
108
125
|
const abortController = new AbortController();
|
|
109
|
-
const
|
|
126
|
+
const sdkTimeoutMs = await getSdkTimeout();
|
|
127
|
+
const timeout = setTimeout(() => abortController.abort(), sdkTimeoutMs);
|
|
110
128
|
const startedAt = new Date();
|
|
111
129
|
let usage: UsageSnapshot = {};
|
|
112
130
|
let ledgerRecorded = false;
|
|
@@ -265,7 +283,8 @@ export async function runMetaCompletion(input: {
|
|
|
265
283
|
const startedAt = new Date();
|
|
266
284
|
let usage: UsageSnapshot = {};
|
|
267
285
|
const abortController = new AbortController();
|
|
268
|
-
const
|
|
286
|
+
const sdkTimeoutMs = await getSdkTimeout();
|
|
287
|
+
const timeout = setTimeout(() => abortController.abort(), sdkTimeoutMs);
|
|
269
288
|
|
|
270
289
|
try {
|
|
271
290
|
const response = query({
|
|
@@ -312,6 +331,9 @@ export async function runMetaCompletion(input: {
|
|
|
312
331
|
startedAt,
|
|
313
332
|
finishedAt: new Date(),
|
|
314
333
|
});
|
|
334
|
+
if (isAbortError(error)) {
|
|
335
|
+
throw new Error("Request timed out. You can increase the timeout in Settings → Runtime.");
|
|
336
|
+
}
|
|
315
337
|
throw error;
|
|
316
338
|
} finally {
|
|
317
339
|
clearTimeout(timeout);
|
|
@@ -336,7 +358,8 @@ async function runClaudeTaskAssist(
|
|
|
336
358
|
let usage: UsageSnapshot = {};
|
|
337
359
|
|
|
338
360
|
const abortController = new AbortController();
|
|
339
|
-
const
|
|
361
|
+
const sdkTimeoutMs = await getSdkTimeout();
|
|
362
|
+
const timeout = setTimeout(() => abortController.abort(), sdkTimeoutMs);
|
|
340
363
|
|
|
341
364
|
try {
|
|
342
365
|
const response = query({
|
|
@@ -394,6 +417,9 @@ async function runClaudeTaskAssist(
|
|
|
394
417
|
startedAt,
|
|
395
418
|
finishedAt: new Date(),
|
|
396
419
|
});
|
|
420
|
+
if (isAbortError(error)) {
|
|
421
|
+
throw new Error("Request timed out. You can increase the timeout in Settings → Runtime.");
|
|
422
|
+
}
|
|
397
423
|
throw error;
|
|
398
424
|
} finally {
|
|
399
425
|
clearTimeout(timeout);
|
|
@@ -8,6 +8,7 @@ export const SETTINGS_KEYS = {
|
|
|
8
8
|
BUDGET_POLICY: "usage.budgetPolicy",
|
|
9
9
|
BUDGET_WARNING_STATE: "usage.budgetWarningState",
|
|
10
10
|
PRICING_REGISTRY: "usage.pricingRegistry",
|
|
11
|
+
SDK_TIMEOUT_SECONDS: "runtime.sdkTimeoutSeconds",
|
|
11
12
|
} as const;
|
|
12
13
|
|
|
13
14
|
export type AuthMethod = "api_key" | "oauth";
|