Skip to content

Commit dadd006

Browse files
committed
fixed more pr comments
1 parent 7f57576 commit dadd006

9 files changed

Lines changed: 151 additions & 80 deletions

File tree

examples/agentic/client/src/app/transport-helpers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,12 @@ const applyModelIdChange = (
148148
},
149149
modelId: string | null,
150150
) => {
151-
input.setModelId(modelId);
151+
const normalized = modelId === "" ? null : modelId;
152+
input.setModelId(normalized);
152153
updateTransportData(input, {
153154
adapterSource: input.adapterSource,
154155
providerId: input.providerId,
155-
modelId,
156+
modelId: normalized,
156157
});
157158
return true;
158159
};

examples/agentic/client/src/components/assistant-ui/tool-fallback.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,22 @@ const ToolFallbackImpl = ({ toolName, argsText, result, status }: ToolFallbackPr
3939
{isCancelled ? "Cancelled tool: " : "Used tool: "}
4040
<b>{toolName}</b>
4141
</p>
42-
<Button onClick={() => setIsCollapsed(!isCollapsed)} size="icon" variant="ghost">
42+
<Button
43+
onClick={() => setIsCollapsed(!isCollapsed)}
44+
size="icon"
45+
variant="ghost"
46+
aria-expanded={!isCollapsed}
47+
aria-controls="tool-fallback-content"
48+
aria-label={isCollapsed ? "Expand tool details" : "Collapse tool details"}
49+
>
4350
{isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
4451
</Button>
4552
</div>
4653
{!isCollapsed && (
47-
<div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
54+
<div
55+
id="tool-fallback-content"
56+
className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2"
57+
>
4858
{cancelledReason && (
4959
<div className="aui-tool-fallback-cancelled-root px-4">
5060
<p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">

examples/agentic/server/index.ts

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -315,36 +315,43 @@ const applyMissingInputError = (input: RunOutcomeInput) => {
315315
};
316316

317317
const runChatRequest = (socket: ServerWebSocket<SocketData>, message: ClientChatRequest) => {
318-
const model = selectModel(toModelSelection({ data: message.data, tokens: socket.data.tokens }));
319-
const coreMessages = toCoreMessages(message.messages);
320-
const text = readUserInputText(coreMessages);
321-
const config = message.data?.agentConfig;
322-
const context = buildAgentContext({ config, context: message.data?.context ?? null });
323-
const threadId = message.data?.threadId ?? message.chatId;
324-
325-
const writer = createWebSocketUiWriter(socket, message.requestId);
326-
const eventStream = createAiSdkInteractionEventStream({ writer });
327-
328318
const outcomeInput: RunOutcomeInput = { socket, requestId: message.requestId };
329-
if (!text) {
330-
return applyMissingInputError(outcomeInput);
319+
try {
320+
const model = selectModel(toModelSelection({ data: message.data, tokens: socket.data.tokens }));
321+
const coreMessages = toCoreMessages(message.messages);
322+
const text = readUserInputText(coreMessages);
323+
const config = message.data?.agentConfig;
324+
const context = buildAgentContext({ config, context: message.data?.context ?? null });
325+
const threadId = message.data?.threadId ?? message.chatId;
326+
327+
const writer = createWebSocketUiWriter(socket, message.requestId);
328+
const eventStream = createAiSdkInteractionEventStream({ writer });
329+
330+
if (!text) {
331+
return applyMissingInputError(outcomeInput);
332+
}
333+
const runtime = createAgentRuntime({
334+
model,
335+
config,
336+
subagents: message.data?.subagents,
337+
});
338+
const runResult = runtime.stream({
339+
text,
340+
context: context ?? undefined,
341+
threadId,
342+
eventStream,
343+
interactionId: message.chatId,
344+
correlationId: message.requestId,
345+
});
346+
const handlerInput = createRunChatHandlerInput(outcomeInput, runResult);
347+
348+
return maybeTry(
349+
bindFirst(applyRunError, outcomeInput),
350+
bindFirst(handleRunResult, handlerInput),
351+
);
352+
} catch (error) {
353+
return applyRunError(outcomeInput, error);
331354
}
332-
const runtime = createAgentRuntime({
333-
model,
334-
config,
335-
subagents: message.data?.subagents,
336-
});
337-
const runResult = runtime.stream({
338-
text,
339-
context: context ?? undefined,
340-
threadId,
341-
eventStream,
342-
interactionId: message.chatId,
343-
correlationId: message.requestId,
344-
});
345-
const handlerInput = createRunChatHandlerInput(outcomeInput, runResult);
346-
347-
return maybeTry(bindFirst(applyRunError, outcomeInput), bindFirst(handleRunResult, handlerInput));
348355
};
349356

350357
const handleAssistantTransport = (req: Request): MaybePromise<Response> => {
@@ -362,30 +369,42 @@ const handleAssistantBodyOrError = (body: unknown) => {
362369
return handleAssistantBody(body);
363370
};
364371

372+
const reportAssistantError = (error: unknown) => {
373+
console.error(error);
374+
return true;
375+
};
376+
365377
const handleAssistantBody = (body: unknown): Response => {
366378
const parsed = parseAssistantTransportRequest(body);
367379
if (!parsed) {
368380
return new Response("Invalid assistant transport payload", { status: 400 });
369381
}
370-
371-
const model = selectModel(toModelSelection({ data: parsed.data }));
372-
const coreMessages = toCoreMessagesFromAssistantCommands(parsed.commands);
373-
const chatId = parsed.data?.chatId ?? crypto.randomUUID();
374-
375-
const streamAdapter = createAssistantUiInteractionStream({ includeReasoning: true });
376-
const eventStream = streamAdapter.eventStream;
377-
378-
const recipeId = resolveInteractionRecipeId(parsed.data?.recipeId);
379-
const runResult = runInteractionRequest({
380-
recipeId,
381-
model,
382-
messages: coreMessages,
383-
eventStream,
384-
interactionId: chatId,
385-
correlationId: chatId,
386-
});
387-
finalizeAssistantRun(runResult, streamAdapter.controller);
388-
return AssistantStream.toResponse(streamAdapter.stream, new DataStreamEncoder());
382+
let streamAdapter: ReturnType<typeof createAssistantUiInteractionStream> | null = null;
383+
384+
try {
385+
const model = selectModel(toModelSelection({ data: parsed.data }));
386+
const coreMessages = toCoreMessagesFromAssistantCommands(parsed.commands);
387+
const chatId = parsed.data?.chatId ?? crypto.randomUUID();
388+
389+
streamAdapter = createAssistantUiInteractionStream({ includeReasoning: true });
390+
const eventStream = streamAdapter.eventStream;
391+
392+
const recipeId = resolveInteractionRecipeId(parsed.data?.recipeId);
393+
const runResult = runInteractionRequest({
394+
recipeId,
395+
model,
396+
messages: coreMessages,
397+
eventStream,
398+
interactionId: chatId,
399+
correlationId: chatId,
400+
});
401+
finalizeAssistantRun(runResult, streamAdapter.controller);
402+
return AssistantStream.toResponse(streamAdapter.stream, new DataStreamEncoder());
403+
} catch (error) {
404+
reportAssistantError(error);
405+
closeAssistantControllerIfPresent(streamAdapter ? streamAdapter.controller : null);
406+
return new Response("Internal server error", { status: 500 });
407+
}
389408
};
390409

391410
const readRequestBody = (req: Request): MaybePromise<unknown> =>
@@ -400,6 +419,13 @@ const closeAssistantController = (controller: { close: () => void }) => {
400419
return true;
401420
};
402421

422+
const closeAssistantControllerIfPresent = (controller: { close: () => void } | null) => {
423+
if (!controller) {
424+
return null;
425+
}
426+
return closeAssistantController(controller);
427+
};
428+
403429
const ignoreAssistantRunError = (_error: unknown) => null;
404430

405431
const finalizeAssistantRun = (run: MaybePromise<unknown>, controller: { close: () => void }) =>

examples/components/model-catalog.ts

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,31 @@ const withAuthHeader = (token: string, headers: HeadersInit) => ({
398398
Authorization: `Bearer ${token}`,
399399
});
400400

401-
const fetchOpenAiModels = async (token: string): Promise<ModelEntry[]> => {
401+
const MODEL_CATALOG_TIMEOUT_MS = 8000;
402+
403+
const abortControllerAbort = (controller: AbortController) => {
404+
controller.abort();
405+
return true;
406+
};
407+
408+
const startAbortTimer = (controller: AbortController, timeoutMs: number) =>
409+
setTimeout(bindFirst(abortControllerAbort, controller), timeoutMs);
410+
411+
const clearAbortTimer = (timerId: ReturnType<typeof setTimeout>) => {
412+
clearTimeout(timerId);
413+
return true;
414+
};
415+
416+
const createTimeoutSignal = (timeoutMs: number) => {
417+
const controller = new AbortController();
418+
const timerId = startAbortTimer(controller, timeoutMs);
419+
return { signal: controller.signal, timerId };
420+
};
421+
422+
const fetchOpenAiModels = async (token: string, signal?: AbortSignal): Promise<ModelEntry[]> => {
402423
const response = await fetch("https://api.openai.com/v1/models", {
403424
headers: withAuthHeader(token, {}),
425+
signal,
404426
});
405427
if (!response.ok) {
406428
throw new Error(`OpenAI models error (${response.status}).`);
@@ -416,12 +438,13 @@ const fetchOpenAiModels = async (token: string): Promise<ModelEntry[]> => {
416438
return entries;
417439
};
418440

419-
const fetchAnthropicModels = async (token: string): Promise<ModelEntry[]> => {
441+
const fetchAnthropicModels = async (token: string, signal?: AbortSignal): Promise<ModelEntry[]> => {
420442
const response = await fetch("https://api.anthropic.com/v1/models", {
421443
headers: {
422444
"x-api-key": token,
423445
"anthropic-version": "2023-06-01",
424446
},
447+
signal,
425448
});
426449
if (!response.ok) {
427450
throw new Error(`Anthropic models error (${response.status}).`);
@@ -439,8 +462,8 @@ const fetchAnthropicModels = async (token: string): Promise<ModelEntry[]> => {
439462
return entries;
440463
};
441464

442-
const fetchOllamaModels = async (): Promise<ModelEntry[]> => {
443-
const response = await fetch("http://127.0.0.1:11434/api/tags");
465+
const fetchOllamaModels = async (signal?: AbortSignal): Promise<ModelEntry[]> => {
466+
const response = await fetch("http://127.0.0.1:11434/api/tags", { signal });
444467
if (!response.ok) {
445468
throw new Error(`Ollama models error (${response.status}).`);
446469
}
@@ -456,19 +479,24 @@ const fetchOllamaModels = async (): Promise<ModelEntry[]> => {
456479
};
457480

458481
const fetchProviderModels = async (input: ModelCatalogRequest): Promise<ModelOption[]> => {
459-
if (input.providerId === "ollama") {
460-
return toModelOptions(await fetchOllamaModels());
461-
}
462-
if (!input.token) {
463-
throw new Error(`Missing API token for ${input.providerId}.`);
464-
}
465-
if (input.providerId === "openai") {
466-
return toModelOptions(await fetchOpenAiModels(input.token));
467-
}
468-
if (input.providerId === "anthropic") {
469-
return toModelOptions(await fetchAnthropicModels(input.token));
482+
const timeout = createTimeoutSignal(MODEL_CATALOG_TIMEOUT_MS);
483+
try {
484+
if (input.providerId === "ollama") {
485+
return toModelOptions(await fetchOllamaModels(timeout.signal));
486+
}
487+
if (!input.token) {
488+
throw new Error(`Missing API token for ${input.providerId}.`);
489+
}
490+
if (input.providerId === "openai") {
491+
return toModelOptions(await fetchOpenAiModels(input.token, timeout.signal));
492+
}
493+
if (input.providerId === "anthropic") {
494+
return toModelOptions(await fetchAnthropicModels(input.token, timeout.signal));
495+
}
496+
throw new Error(`Unsupported provider ${input.providerId}.`);
497+
} finally {
498+
clearAbortTimer(timeout.timerId);
470499
}
471-
throw new Error(`Unsupported provider ${input.providerId}.`);
472500
};
473501

474502
const buildModelResponse = (input: ModelCatalogResponse) =>

examples/components/select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export type SelectProps = {
1010
id: string;
1111
value: string;
1212
options: SelectOption[];
13-
suffix: string;
13+
suffix?: string;
1414
onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
1515
};
1616

examples/components/toggle.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import type { ChangeEvent, FC } from "react";
33
export type ToggleProps = {
44
id: string;
55
checked: boolean;
6-
suffix: string;
6+
suffix?: string;
77
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
88
};
99

1010
export const Toggle: FC<ToggleProps> = ({ id, checked, suffix, onChange }) => {
11+
const hasSuffix = Boolean(suffix && suffix.trim().length > 0);
1112
return (
1213
<div className="agentic-control">
1314
<div className="agentic-field agentic-field--toggle">
@@ -16,9 +17,11 @@ export const Toggle: FC<ToggleProps> = ({ id, checked, suffix, onChange }) => {
1617
<span>{checked ? "On" : "Off"}</span>
1718
</label>
1819

19-
<span className="agentic-suffix" aria-hidden="true">
20-
{suffix}
21-
</span>
20+
{hasSuffix ? (
21+
<span className="agentic-suffix" aria-hidden="true">
22+
{suffix}
23+
</span>
24+
) : null}
2225
</div>
2326
</div>
2427
);

examples/kitchen-sink/client/src/styles.css

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,3 @@ body {
310310
justify-content: flex-start;
311311
}
312312
}
313-
314-
@media (min-width: 1024px) {
315-
.ks-chat-scroll {
316-
/* @apply px-10 py-10; */
317-
}
318-
}

src/interaction/agent-runtime-subagents-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export function applySubagentCompleted(record: SubagentRecord, outcome: Outcome<
221221

222222
export function applySubagentFailed(record: SubagentRecord, error: unknown) {
223223
record.status = "idle";
224-
record.lastOutcome = null;
224+
record.lastOutcome = toSubagentErrorOutcome(error);
225225
return error;
226226
}
227227

tests/interaction/agent-runtime-subagents-helpers.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { EventStreamEvent } from "../../src/adapters/types";
33
import { createAgentEventState } from "../../src/interaction/agent-runtime-events";
44
import type { AgentRuntimeInput } from "../../src/interaction/agent-runtime";
55
import type { Outcome } from "../../src/workflow/types";
6+
import type { MaybePromise } from "../../src/shared/maybe";
67
import {
78
applySubagentOutcome,
89
buildMissingResult,
@@ -74,6 +75,9 @@ const createManager = (stream: RecordingStream): SubagentManager => ({
7475
const createRecord = (manager: SubagentManager, agentId = "agent-1"): SubagentRecord =>
7576
createSubagentRecord(manager, { agentId });
7677

78+
const readLastOutcome = async (outcome: MaybePromise<Outcome<unknown>> | null) =>
79+
outcome ? await outcome : null;
80+
7781
const requireRecord = (records: SubagentRecord[]): SubagentRecord => {
7882
const record = records[0];
7983
if (!record) {
@@ -147,7 +151,12 @@ describe("subagent helpers", () => {
147151
const errorRecord = requireRecord(manager.records);
148152
setRecordStatus(errorRecord, "running");
149153
await applySubagentOutcome({ manager, record: errorRecord }, createErrorOutcome());
150-
expect(errorRecord.lastOutcome).toBeNull();
154+
const lastOutcome = await readLastOutcome(errorRecord.lastOutcome ?? null);
155+
const isError = lastOutcome?.status === "error";
156+
expect(isError).toBe(true);
157+
if (isError && lastOutcome) {
158+
expect(lastOutcome.error).toBeInstanceOf(Error);
159+
}
151160
});
152161

153162
it("wraps last outcomes into wait results", async () => {

0 commit comments

Comments
 (0)