@iloveagents/foundry-agent 0.7.1 → 0.9.0
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/dist/client/agui-runner.d.ts +12 -0
- package/dist/client/agui-runner.js +156 -22
- package/dist/client/runner-events.d.ts +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/store/agent-state-store.d.ts +21 -0
- package/dist/store/agent-state-store.js +18 -0
- package/dist/store/reasoning-effort-store.d.ts +28 -0
- package/dist/store/reasoning-effort-store.js +39 -0
- package/dist/store/streaming-status-store.d.ts +8 -1
- package/package.json +1 -1
|
@@ -28,6 +28,17 @@ export interface AGUIRunnerOptions {
|
|
|
28
28
|
* three missed 15s server heartbeats. Pass `Infinity` to disable.
|
|
29
29
|
*/
|
|
30
30
|
stallAfterMs?: number;
|
|
31
|
+
/**
|
|
32
|
+
* When a run ends with a client-side tool call that never produced a
|
|
33
|
+
* result, synthesize an error result for it instead of leaving the call
|
|
34
|
+
* dangling. Defaults to `true`.
|
|
35
|
+
*
|
|
36
|
+
* Defence-in-depth for the failure this runner already had once: an
|
|
37
|
+
* unanswered tool call makes the model re-issue the tool and then report
|
|
38
|
+
* that it "didn't complete", while the Responses API rejects a replayed
|
|
39
|
+
* history whose `function_call` has no matching output.
|
|
40
|
+
*/
|
|
41
|
+
autoCancelPendingToolCalls?: boolean;
|
|
31
42
|
}
|
|
32
43
|
export interface AGUIRunInput {
|
|
33
44
|
/** AG-UI messages — caller is responsible for runtime-specific message conversion. */
|
|
@@ -50,6 +61,7 @@ export interface AGUIRunInput {
|
|
|
50
61
|
export declare class AGUIRunner {
|
|
51
62
|
private readonly httpAgent;
|
|
52
63
|
private readonly stallAfterMs;
|
|
64
|
+
private readonly autoCancelPendingToolCalls;
|
|
53
65
|
constructor(options?: AGUIRunnerOptions);
|
|
54
66
|
get threadId(): string;
|
|
55
67
|
get state(): unknown;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @see https://docs.ag-ui.com/sdk/js/client/subscriber
|
|
15
15
|
*/
|
|
16
16
|
import { HttpAgent } from "@ag-ui/client";
|
|
17
|
+
import { reasoningEffortStore } from "../store/reasoning-effort-store.js";
|
|
17
18
|
function shouldPreserveAcrossVisibleHistory(message) {
|
|
18
19
|
return message.role === "system" || message.role === "developer" || message.role === "reasoning";
|
|
19
20
|
}
|
|
@@ -107,6 +108,7 @@ export class AGUIRunner {
|
|
|
107
108
|
...(options.fetchFn ? { fetch: options.fetchFn } : {}),
|
|
108
109
|
});
|
|
109
110
|
this.stallAfterMs = options.stallAfterMs ?? 45000;
|
|
111
|
+
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls ?? true;
|
|
110
112
|
}
|
|
111
113
|
get threadId() {
|
|
112
114
|
return this.httpAgent.threadId;
|
|
@@ -147,6 +149,12 @@ export class AGUIRunner {
|
|
|
147
149
|
// Build the request input now, before runAgent fires — dev tooling
|
|
148
150
|
// consumes this snapshot via the request-sent event. `runId` is
|
|
149
151
|
// pre-generated so the snapshot matches what runAgent emits.
|
|
152
|
+
// The user's reasoning-effort choice rides on every turn as an
|
|
153
|
+
// AG-UI forwardedProp — read at request time so changing it
|
|
154
|
+
// mid-conversation takes effect on the next message. "default"
|
|
155
|
+
// sends nothing so the backend keeps its configured level.
|
|
156
|
+
const chosenEffort = reasoningEffortStore.getState().effort;
|
|
157
|
+
const forwardedProps = chosenEffort === "default" ? {} : { reasoningEffort: chosenEffort };
|
|
150
158
|
const runInputSnapshot = {
|
|
151
159
|
threadId: this.httpAgent.threadId,
|
|
152
160
|
runId,
|
|
@@ -154,6 +162,7 @@ export class AGUIRunner {
|
|
|
154
162
|
messages: currentMessages,
|
|
155
163
|
tools,
|
|
156
164
|
context: context ?? [],
|
|
165
|
+
forwardedProps,
|
|
157
166
|
};
|
|
158
167
|
// --- Liveness + protocol-integrity bookkeeping (per turn) ---
|
|
159
168
|
// AG-UI requires a terminal RUN_FINISHED or RUN_ERROR. A stream that
|
|
@@ -211,6 +220,81 @@ export class AGUIRunner {
|
|
|
211
220
|
onTextMessageEndEvent: () => {
|
|
212
221
|
push({ type: "text-message-end" });
|
|
213
222
|
},
|
|
223
|
+
// Reasoning/thinking. `@ag-ui/client` normalizes the deprecated
|
|
224
|
+
// THINKING_* events onto REASONING_* via its backward-compat
|
|
225
|
+
// middleware, so subscribing here covers both wire dialects.
|
|
226
|
+
onReasoningMessageStartEvent: () => {
|
|
227
|
+
push({ type: "streaming-status", status: { status: "reasoning" } });
|
|
228
|
+
},
|
|
229
|
+
onReasoningMessageContentEvent: ({ event }) => {
|
|
230
|
+
push({ type: "reasoning-delta", delta: event.delta });
|
|
231
|
+
},
|
|
232
|
+
onReasoningEndEvent: () => {
|
|
233
|
+
push({ type: "reasoning-end" });
|
|
234
|
+
// Reasoning ended but the answer hasn't started — back to the
|
|
235
|
+
// generic working state rather than leaving "reasoning" stuck.
|
|
236
|
+
push({ type: "streaming-status", status: { status: "thinking" } });
|
|
237
|
+
},
|
|
238
|
+
onStepStartedEvent: ({ event }) => {
|
|
239
|
+
push({ type: "step-started", name: event.stepName });
|
|
240
|
+
push({
|
|
241
|
+
type: "streaming-status",
|
|
242
|
+
status: { status: lastStatus.status, stepName: event.stepName },
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
onStepFinishedEvent: ({ event }) => {
|
|
246
|
+
push({ type: "step-finished", name: event.stepName });
|
|
247
|
+
},
|
|
248
|
+
// Reasoning has two nested boundaries: a BLOCK
|
|
249
|
+
// (REASONING_START…REASONING_END) may contain several MESSAGES
|
|
250
|
+
// (REASONING_MESSAGE_START…REASONING_MESSAGE_END). `reasoning-end`
|
|
251
|
+
// means "the model stopped thinking", so it is emitted only for the
|
|
252
|
+
// block terminal in `onReasoningEndEvent`. Emitting it per message
|
|
253
|
+
// too would fire it repeatedly and let consumers treat reasoning as
|
|
254
|
+
// finished while it is still going.
|
|
255
|
+
onReasoningStartEvent: () => {
|
|
256
|
+
push({ type: "streaming-status", status: { status: "reasoning" } });
|
|
257
|
+
},
|
|
258
|
+
// Server-published agent state. The AG-UI client has already
|
|
259
|
+
// applied the snapshot/patch to `httpAgent.state`; forward the
|
|
260
|
+
// result so consumers don't reach into the client.
|
|
261
|
+
onStateSnapshotEvent: () => {
|
|
262
|
+
push({ type: "agent-state", state: this.httpAgent.state });
|
|
263
|
+
},
|
|
264
|
+
onStateDeltaEvent: ({ event }) => {
|
|
265
|
+
push({
|
|
266
|
+
type: "agent-state",
|
|
267
|
+
state: this.httpAgent.state,
|
|
268
|
+
patch: event.delta,
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
// Server-authoritative history (e.g. after backend compaction).
|
|
272
|
+
// Deliberately does NOT touch the in-flight turn: upstream saw
|
|
273
|
+
// mid-run snapshots truncate a streaming answer.
|
|
274
|
+
onMessagesSnapshotEvent: ({ event }) => {
|
|
275
|
+
push({ type: "messages-snapshot", messages: event.messages });
|
|
276
|
+
},
|
|
277
|
+
// Generative-UI surfaces (MCP apps / A2UI), passed through.
|
|
278
|
+
onActivitySnapshotEvent: ({ event }) => {
|
|
279
|
+
push({
|
|
280
|
+
type: "activity",
|
|
281
|
+
messageId: event.messageId,
|
|
282
|
+
activityType: event.activityType,
|
|
283
|
+
content: event.content,
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
onActivityDeltaEvent: ({ event }) => {
|
|
287
|
+
push({
|
|
288
|
+
type: "activity",
|
|
289
|
+
messageId: event.messageId,
|
|
290
|
+
activityType: event.activityType,
|
|
291
|
+
patch: event.patch,
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
// Provider passthrough — never interpreted here.
|
|
295
|
+
onRawEvent: ({ event }) => {
|
|
296
|
+
push({ type: "raw", event: event.event, source: event.source });
|
|
297
|
+
},
|
|
214
298
|
onCustomEvent: ({ event }) => {
|
|
215
299
|
// Server heartbeat: liveness proof during long tool calls /
|
|
216
300
|
// thinking phases (also keeps intermediary idle-timeouts at bay
|
|
@@ -320,7 +404,7 @@ export class AGUIRunner {
|
|
|
320
404
|
// the protocol violation here.
|
|
321
405
|
// 2. Transport-level rejection (fetch failure, TLS reset).
|
|
322
406
|
const runPromise = this.httpAgent
|
|
323
|
-
.runAgent({ runId, tools, context: context ?? [] }, subscriber)
|
|
407
|
+
.runAgent({ runId, tools, context: context ?? [], forwardedProps }, subscriber)
|
|
324
408
|
.then(() => {
|
|
325
409
|
if (!sawTerminal && !abortSignal?.aborted) {
|
|
326
410
|
push({ type: "streaming-status", status: { status: "idle" } });
|
|
@@ -357,6 +441,26 @@ export class AGUIRunner {
|
|
|
357
441
|
abortSignal?.removeEventListener("abort", onAbort);
|
|
358
442
|
await runPromise; // ensure the run task is settled
|
|
359
443
|
}
|
|
444
|
+
// Any client-side tool that was STARTED but never produced a
|
|
445
|
+
// result leaves a `function_call` with no output. Settle it here so
|
|
446
|
+
// the replayed history stays valid and the model isn't left waiting
|
|
447
|
+
// on an answer that will never come.
|
|
448
|
+
if (this.autoCancelPendingToolCalls) {
|
|
449
|
+
for (const tc of toolCalls.values()) {
|
|
450
|
+
if (!registry.isRegistered(tc.name))
|
|
451
|
+
continue;
|
|
452
|
+
if (tc.result !== undefined || tc.followedUp)
|
|
453
|
+
continue;
|
|
454
|
+
tc.result = { error: "Tool call did not complete before the run ended." };
|
|
455
|
+
tc.isError = true;
|
|
456
|
+
yield {
|
|
457
|
+
type: "tool-call-result",
|
|
458
|
+
id: tc.id,
|
|
459
|
+
result: tc.result,
|
|
460
|
+
isError: true,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
360
464
|
// Decide whether to re-issue: any client-side tool that resolved
|
|
361
465
|
// during this turn and hasn't been replayed yet.
|
|
362
466
|
const pendingClientTools = Array.from(toolCalls.values()).filter((tc) => registry.isRegistered(tc.name) && tc.result !== undefined && !tc.followedUp);
|
|
@@ -366,29 +470,59 @@ export class AGUIRunner {
|
|
|
366
470
|
tc.followedUp = true;
|
|
367
471
|
// Build follow-up messages with the SAME toolCallId the agent emitted.
|
|
368
472
|
// Tool results are appended to the agent's message history (AG-UI
|
|
369
|
-
// convention — append, never replace).
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
473
|
+
// convention — append, never replace).
|
|
474
|
+
//
|
|
475
|
+
// The AG-UI client folds the streamed assistant message — tool calls
|
|
476
|
+
// included — into `httpAgent.messages` itself (AbstractAgent applies
|
|
477
|
+
// the event stream to its own history). Appending an unconditional
|
|
478
|
+
// second copy put the SAME toolCallId in the history twice with only
|
|
479
|
+
// one matching tool result, so the model saw an unanswered tool call:
|
|
480
|
+
// it re-issued the tool (a duplicate card in the UI) and then reported
|
|
481
|
+
// that the call "didn't complete", even though the tool had succeeded.
|
|
482
|
+
// Only synthesize what the client hasn't already recorded.
|
|
483
|
+
const recordedToolCallIds = new Set();
|
|
484
|
+
const recordedToolResultIds = new Set();
|
|
485
|
+
for (const message of this.httpAgent.messages) {
|
|
486
|
+
if (message.role === "assistant" && message.toolCalls) {
|
|
487
|
+
for (const call of message.toolCalls)
|
|
488
|
+
recordedToolCallIds.add(call.id);
|
|
489
|
+
}
|
|
490
|
+
else if (message.role === "tool" && message.toolCallId) {
|
|
491
|
+
recordedToolResultIds.add(message.toolCallId);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const followUpMessages = [];
|
|
495
|
+
// Preserve any pre-tool text the model emitted in this turn so the
|
|
496
|
+
// next-turn context matches what the user actually saw.
|
|
497
|
+
const unrecordedToolCalls = pendingClientTools.filter((tc) => !recordedToolCallIds.has(tc.id));
|
|
498
|
+
if (unrecordedToolCalls.length > 0) {
|
|
499
|
+
followUpMessages.push({
|
|
500
|
+
id: crypto.randomUUID(),
|
|
501
|
+
role: "assistant",
|
|
502
|
+
content: turnAssistantText,
|
|
503
|
+
toolCalls: unrecordedToolCalls.map((tc) => ({
|
|
504
|
+
id: tc.id,
|
|
505
|
+
type: "function",
|
|
506
|
+
function: { name: tc.name, arguments: tc.args },
|
|
507
|
+
})),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
for (const tc of pendingClientTools) {
|
|
511
|
+
if (recordedToolResultIds.has(tc.id))
|
|
512
|
+
continue;
|
|
513
|
+
followUpMessages.push({
|
|
514
|
+
id: crypto.randomUUID(),
|
|
515
|
+
role: "tool",
|
|
516
|
+
toolCallId: tc.id,
|
|
517
|
+
content: typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result),
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
for (const m of followUpMessages) {
|
|
390
521
|
this.httpAgent.addMessage(m);
|
|
391
522
|
}
|
|
523
|
+
// Re-sync from the client's history: it is the authority on what the
|
|
524
|
+
// next request carries (`prepareRunAgentInput` reads it directly).
|
|
525
|
+
currentMessages = [...this.httpAgent.messages];
|
|
392
526
|
}
|
|
393
527
|
}
|
|
394
528
|
finally {
|
|
@@ -46,6 +46,75 @@ export type RunnerEvent = {
|
|
|
46
46
|
} | {
|
|
47
47
|
type: "streaming-status";
|
|
48
48
|
status: StreamingStatus;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Model reasoning/thinking text (AG-UI `REASONING_*`; the deprecated
|
|
52
|
+
* `THINKING_*` events are normalized onto these by `@ag-ui/client`'s
|
|
53
|
+
* backward-compatibility middleware). Kept separate from `text-delta`
|
|
54
|
+
* because reasoning is not the answer — consumers render it as its own
|
|
55
|
+
* collapsed part, never inline with the reply.
|
|
56
|
+
*/
|
|
57
|
+
| {
|
|
58
|
+
type: "reasoning-delta";
|
|
59
|
+
delta: string;
|
|
60
|
+
} | {
|
|
61
|
+
type: "reasoning-end";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Named agent step (AG-UI `STEP_STARTED` / `STEP_FINISHED`). A long
|
|
65
|
+
* agentic run otherwise presents as one opaque "thinking" state for
|
|
66
|
+
* minutes; the step name is what makes the wait legible.
|
|
67
|
+
*/
|
|
68
|
+
| {
|
|
69
|
+
type: "step-started";
|
|
70
|
+
name: string;
|
|
71
|
+
} | {
|
|
72
|
+
type: "step-finished";
|
|
73
|
+
name: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Agent state published by the server (`STATE_SNAPSHOT` / `STATE_DELTA`).
|
|
77
|
+
* The AG-UI client applies deltas (JSON Patch) to its own state; this
|
|
78
|
+
* event carries the RESULT so consumers can react without reaching into
|
|
79
|
+
* the client. `patch` is present only for deltas, for consumers that want
|
|
80
|
+
* to know what changed rather than just the new value.
|
|
81
|
+
*/
|
|
82
|
+
| {
|
|
83
|
+
type: "agent-state";
|
|
84
|
+
state: unknown;
|
|
85
|
+
patch?: unknown[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Server-authoritative history replacement (`MESSAGES_SNAPSHOT`). Emitted
|
|
89
|
+
* when the backend rewrites the conversation — e.g. after compaction. The
|
|
90
|
+
* runner does NOT interrupt an in-flight message for this: upstream saw
|
|
91
|
+
* mid-run snapshots truncate streaming answers, so consumers should
|
|
92
|
+
* reconcile persisted history and leave the streaming turn alone.
|
|
93
|
+
*/
|
|
94
|
+
| {
|
|
95
|
+
type: "messages-snapshot";
|
|
96
|
+
messages: unknown[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generative-UI surface (`ACTIVITY_SNAPSHOT` / `ACTIVITY_DELTA`) — MCP
|
|
100
|
+
* apps and A2UI. Passed through verbatim; hosts that don't render
|
|
101
|
+
* activities can ignore it.
|
|
102
|
+
*/
|
|
103
|
+
| {
|
|
104
|
+
type: "activity";
|
|
105
|
+
messageId: string;
|
|
106
|
+
activityType: string;
|
|
107
|
+
content?: unknown;
|
|
108
|
+
patch?: unknown[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Provider passthrough (`RAW`). Never interpreted here — hosts use it for
|
|
112
|
+
* provider-specific behaviour and debugging.
|
|
113
|
+
*/
|
|
114
|
+
| {
|
|
115
|
+
type: "raw";
|
|
116
|
+
event: unknown;
|
|
117
|
+
source?: string;
|
|
49
118
|
} | {
|
|
50
119
|
type: "run-finished";
|
|
51
120
|
} | {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { AGUIRunner, type AGUIRunnerOptions, type AGUIRunInput } from "./client/
|
|
|
2
2
|
export type { RunnerEvent } from "./client/runner-events.js";
|
|
3
3
|
export { createServiceFetch, type ServiceFetch, type ServiceFetchOptions, } from "./client/service-fetch.js";
|
|
4
4
|
export { clientToolRegistry, type ClientToolEntry, type ToolRegistry } from "./tools/registry.js";
|
|
5
|
+
export { agentStateStore } from "./store/agent-state-store.js";
|
|
6
|
+
export { reasoningEffortStore, REASONING_EFFORT_LABELS, type ReasoningEffort, } from "./store/reasoning-effort-store.js";
|
|
5
7
|
export { streamingStatusStore, type StreamingStatus } from "./store/streaming-status-store.js";
|
|
6
8
|
export { citationStore, type CitationResult, type CitationHandler, } from "./store/citation-store.js";
|
|
7
9
|
export { linkStore, resolveLinkHandler, type LinkHandler, type ResolvedLinkHandler, } from "./store/link-store.js";
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export { createServiceFetch, } from "./client/service-fetch.js";
|
|
|
5
5
|
// --- Tool registry ---
|
|
6
6
|
export { clientToolRegistry } from "./tools/registry.js";
|
|
7
7
|
// --- Stores (vanilla) ---
|
|
8
|
+
export { agentStateStore } from "./store/agent-state-store.js";
|
|
9
|
+
export { reasoningEffortStore, REASONING_EFFORT_LABELS, } from "./store/reasoning-effort-store.js";
|
|
8
10
|
export { streamingStatusStore } from "./store/streaming-status-store.js";
|
|
9
11
|
export { citationStore, } from "./store/citation-store.js";
|
|
10
12
|
export { linkStore, resolveLinkHandler, } from "./store/link-store.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface AgentStateState {
|
|
2
|
+
/** Latest server-published agent state, or `null` before the first snapshot. */
|
|
3
|
+
state: unknown;
|
|
4
|
+
/** JSON Patch from the most recent `STATE_DELTA`, if the last update was one. */
|
|
5
|
+
lastPatch: unknown[] | null;
|
|
6
|
+
setAgentState: (state: unknown, patch?: unknown[]) => void;
|
|
7
|
+
reset: () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Server-published agent state (AG-UI `STATE_SNAPSHOT` / `STATE_DELTA`).
|
|
11
|
+
*
|
|
12
|
+
* The AG-UI client already applies snapshots and JSON-Patch deltas to its
|
|
13
|
+
* own internal state, but nothing surfaced that to the UI — a page could
|
|
14
|
+
* only read it by reaching into the transport. This store is the seam:
|
|
15
|
+
* the runner publishes each update, React consumers bind with
|
|
16
|
+
* `useStore(agentStateStore, selector)`.
|
|
17
|
+
*
|
|
18
|
+
* Vanilla store — this package stays zero-React.
|
|
19
|
+
*/
|
|
20
|
+
export declare const agentStateStore: import("zustand/vanilla").StoreApi<AgentStateState>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createStore } from "zustand/vanilla";
|
|
2
|
+
/**
|
|
3
|
+
* Server-published agent state (AG-UI `STATE_SNAPSHOT` / `STATE_DELTA`).
|
|
4
|
+
*
|
|
5
|
+
* The AG-UI client already applies snapshots and JSON-Patch deltas to its
|
|
6
|
+
* own internal state, but nothing surfaced that to the UI — a page could
|
|
7
|
+
* only read it by reaching into the transport. This store is the seam:
|
|
8
|
+
* the runner publishes each update, React consumers bind with
|
|
9
|
+
* `useStore(agentStateStore, selector)`.
|
|
10
|
+
*
|
|
11
|
+
* Vanilla store — this package stays zero-React.
|
|
12
|
+
*/
|
|
13
|
+
export const agentStateStore = createStore((set) => ({
|
|
14
|
+
state: null,
|
|
15
|
+
lastPatch: null,
|
|
16
|
+
setAgentState: (state, patch) => set({ state, lastPatch: patch ?? null }),
|
|
17
|
+
reset: () => set({ state: null, lastPatch: null }),
|
|
18
|
+
}));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How hard the model should think before answering.
|
|
3
|
+
*
|
|
4
|
+
* `"default"` sends nothing and leaves the backend's configured level
|
|
5
|
+
* alone — the user hasn't expressed a preference, so we don't override
|
|
6
|
+
* one. The remaining levels map to the AG-UI/Responses reasoning effort.
|
|
7
|
+
*/
|
|
8
|
+
export type ReasoningEffort = "default" | "low" | "medium" | "high" | "xhigh";
|
|
9
|
+
/**
|
|
10
|
+
* Level names follow the convention users already know from other assistants
|
|
11
|
+
* (Instant / Medium / High / Extra High) rather than inventing a private
|
|
12
|
+
* vocabulary. `Auto` is ours: it means "no preference — use whatever the app
|
|
13
|
+
* is configured for", which the protocol enum has no member for.
|
|
14
|
+
*/
|
|
15
|
+
export declare const REASONING_EFFORT_LABELS: Record<ReasoningEffort, string>;
|
|
16
|
+
interface ReasoningEffortState {
|
|
17
|
+
effort: ReasoningEffort;
|
|
18
|
+
setEffort: (effort: ReasoningEffort) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The user's chosen reasoning effort, persisted across reloads.
|
|
22
|
+
*
|
|
23
|
+
* Vanilla store (this package is zero-React); the runner reads it when
|
|
24
|
+
* building each request so the choice applies per turn — change it
|
|
25
|
+
* mid-conversation and the next message uses the new level.
|
|
26
|
+
*/
|
|
27
|
+
export declare const reasoningEffortStore: import("zustand/vanilla").StoreApi<ReasoningEffortState>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createStore } from "zustand/vanilla";
|
|
2
|
+
/**
|
|
3
|
+
* Level names follow the convention users already know from other assistants
|
|
4
|
+
* (Instant / Medium / High / Extra High) rather than inventing a private
|
|
5
|
+
* vocabulary. `Auto` is ours: it means "no preference — use whatever the app
|
|
6
|
+
* is configured for", which the protocol enum has no member for.
|
|
7
|
+
*/
|
|
8
|
+
export const REASONING_EFFORT_LABELS = {
|
|
9
|
+
default: "Auto",
|
|
10
|
+
low: "Instant",
|
|
11
|
+
medium: "Medium",
|
|
12
|
+
high: "High",
|
|
13
|
+
xhigh: "Extra High",
|
|
14
|
+
};
|
|
15
|
+
const STORAGE_KEY = "foundry:reasoning-effort";
|
|
16
|
+
function readPersisted() {
|
|
17
|
+
if (typeof localStorage === "undefined")
|
|
18
|
+
return "default";
|
|
19
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
20
|
+
return raw === "low" || raw === "medium" || raw === "high" || raw === "xhigh" || raw === "default"
|
|
21
|
+
? raw
|
|
22
|
+
: "default";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The user's chosen reasoning effort, persisted across reloads.
|
|
26
|
+
*
|
|
27
|
+
* Vanilla store (this package is zero-React); the runner reads it when
|
|
28
|
+
* building each request so the choice applies per turn — change it
|
|
29
|
+
* mid-conversation and the next message uses the new level.
|
|
30
|
+
*/
|
|
31
|
+
export const reasoningEffortStore = createStore((set) => ({
|
|
32
|
+
effort: readPersisted(),
|
|
33
|
+
setEffort: (effort) => {
|
|
34
|
+
if (typeof localStorage !== "undefined") {
|
|
35
|
+
localStorage.setItem(STORAGE_KEY, effort);
|
|
36
|
+
}
|
|
37
|
+
set({ effort });
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/** Streaming status emitted by the AG-UI runner. */
|
|
2
2
|
export interface StreamingStatus {
|
|
3
|
-
status: "thinking" | "calling" | "streaming" | "stalled" | "idle";
|
|
3
|
+
status: "thinking" | "reasoning" | "calling" | "streaming" | "stalled" | "idle";
|
|
4
4
|
toolName?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Name of the agent step currently running, from AG-UI `STEP_STARTED`.
|
|
7
|
+
* Long agentic runs otherwise show one opaque "thinking" state for
|
|
8
|
+
* minutes; the step name is what makes that wait legible. Undefined when
|
|
9
|
+
* the agent doesn't emit step events.
|
|
10
|
+
*/
|
|
11
|
+
stepName?: string;
|
|
5
12
|
}
|
|
6
13
|
interface StreamingStatusState {
|
|
7
14
|
streamingStatus: StreamingStatus;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iloveagents/foundry-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cross-runtime AG-UI transport for Foundry UI — AGUIRunner protocol engine, vanilla zustand stores, optional MSAL auth subpath, service-fetch factory. Zero React, zero DOM.",
|
|
6
6
|
"keywords": [
|