@assistant-ui/react 0.4.8 → 0.5.2

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/index.js CHANGED
@@ -53,11 +53,13 @@ __export(src_exports, {
53
53
  ThreadWelcome: () => thread_welcome_default,
54
54
  UserActionBar: () => user_action_bar_default,
55
55
  UserMessage: () => user_message_default,
56
+ fromCoreMessage: () => fromCoreMessage,
56
57
  fromCoreMessages: () => fromCoreMessages,
57
58
  fromLanguageModelMessages: () => fromLanguageModelMessages,
58
59
  fromLanguageModelTools: () => fromLanguageModelTools,
59
60
  makeAssistantTool: () => makeAssistantTool,
60
61
  makeAssistantToolUI: () => makeAssistantToolUI,
62
+ toCoreMessage: () => toCoreMessage,
61
63
  toCoreMessages: () => toCoreMessages,
62
64
  toLanguageModelMessages: () => toLanguageModelMessages,
63
65
  toLanguageModelTools: () => toLanguageModelTools,
@@ -397,13 +399,17 @@ var ThreadProvider = ({
397
399
  (0, import_react4.useCallback)(
398
400
  (thread) => {
399
401
  const onThreadUpdate = () => {
400
- context.useThread.setState(
401
- Object.freeze({
402
- isRunning: context.useThreadRuntime.getState().isRunning
403
- }),
404
- true
405
- );
406
- context.useThreadMessages.setState(thread.messages, true);
402
+ if (thread.isRunning !== context.useThread.getState().isRunning) {
403
+ context.useThread.setState(
404
+ Object.freeze({
405
+ isRunning: thread.isRunning
406
+ }),
407
+ true
408
+ );
409
+ }
410
+ if (thread.messages !== context.useThreadMessages.getState()) {
411
+ context.useThreadMessages.setState(thread.messages, true);
412
+ }
407
413
  };
408
414
  onThreadUpdate();
409
415
  return thread.subscribe(onThreadUpdate);
@@ -1256,12 +1262,35 @@ var import_react38 = require("react");
1256
1262
  var import_react34 = require("react");
1257
1263
  var import_zustand10 = require("zustand");
1258
1264
  var import_jsx_runtime13 = require("react/jsx-runtime");
1259
- var DONE_STATUS = { type: "done" };
1265
+ var COMPLETE_STATUS = {
1266
+ type: "complete"
1267
+ };
1268
+ var toContentPartStatus = (message, partIndex, part) => {
1269
+ if (message.role !== "assistant") return COMPLETE_STATUS;
1270
+ const isLastPart = partIndex === Math.max(0, message.content.length - 1);
1271
+ if (part.type !== "tool-call") {
1272
+ if ("reason" in message.status && message.status.reason === "tool-calls" && isLastPart)
1273
+ throw new Error(
1274
+ "Encountered unexpected requires-action status. This is likely an internal bug in assistant-ui."
1275
+ );
1276
+ return isLastPart ? message.status : COMPLETE_STATUS;
1277
+ }
1278
+ if (!!part.result) {
1279
+ return COMPLETE_STATUS;
1280
+ }
1281
+ return message.status;
1282
+ };
1283
+ var EMPTY_CONTENT = Object.freeze({ type: "text", text: "" });
1260
1284
  var syncContentPart = ({ message }, useContentPart, partIndex) => {
1261
- const part = message.content[partIndex];
1262
- if (!part) return;
1263
- const messageStatus = message.role === "assistant" ? message.status : DONE_STATUS;
1264
- const status = partIndex === message.content.length - 1 ? messageStatus : DONE_STATUS;
1285
+ let part = message.content[partIndex];
1286
+ if (!part) {
1287
+ if (message.content.length === 0 && partIndex === 0) {
1288
+ part = EMPTY_CONTENT;
1289
+ } else {
1290
+ return;
1291
+ }
1292
+ }
1293
+ const status = toContentPartStatus(message, partIndex, part);
1265
1294
  const currentState = useContentPart.getState();
1266
1295
  if (currentState.part === part && currentState.status === status) return;
1267
1296
  useContentPart.setState(
@@ -1386,7 +1415,7 @@ var ContentPartPrimitiveText = (0, import_react36.forwardRef)(({ smooth = true,
1386
1415
  part: { text }
1387
1416
  } = useContentPartText();
1388
1417
  const smoothText = useSmooth(text, smooth);
1389
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_primitive4.Primitive.span, { "data-status": status, ...rest, ref: forwardedRef, children: smoothText });
1418
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_primitive4.Primitive.span, { "data-status": status.type, ...rest, ref: forwardedRef, children: smoothText });
1390
1419
  });
1391
1420
  ContentPartPrimitiveText.displayName = "ContentPartPrimitive.Text";
1392
1421
 
@@ -1414,7 +1443,7 @@ ContentPartPrimitiveDisplay.displayName = "ContentPartPrimitive.Display";
1414
1443
  // src/primitives/contentPart/ContentPartInProgress.tsx
1415
1444
  var ContentPartPrimitiveInProgress = ({ children }) => {
1416
1445
  const { useContentPart } = useContentPartContext();
1417
- const isInProgress = useContentPart((c) => c.status.type === "in_progress");
1446
+ const isInProgress = useContentPart((c) => c.status.type === "running");
1418
1447
  return isInProgress ? children : null;
1419
1448
  };
1420
1449
  ContentPartPrimitiveInProgress.displayName = "ContentPartPrimitive.InProgress";
@@ -1453,10 +1482,16 @@ var MessageContentPartComponent = ({
1453
1482
  const type = part.type;
1454
1483
  switch (type) {
1455
1484
  case "text":
1485
+ if (status.type === "requires-action")
1486
+ throw new Error("Encountered unexpected requires-action status");
1456
1487
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text2, { part, status });
1457
1488
  case "image":
1489
+ if (status.type === "requires-action")
1490
+ throw new Error("Encountered unexpected requires-action status");
1458
1491
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Image2, { part, status });
1459
1492
  case "ui":
1493
+ if (status.type === "requires-action")
1494
+ throw new Error("Encountered unexpected requires-action status");
1460
1495
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(UI, { part, status });
1461
1496
  case "tool-call": {
1462
1497
  const Tool = by_name[part.toolName] || Fallback2;
@@ -1486,7 +1521,7 @@ var MessagePrimitiveContent = ({
1486
1521
  components
1487
1522
  }) => {
1488
1523
  const { useMessage } = useMessageContext();
1489
- const contentLength = useMessage((s) => s.message.content.length);
1524
+ const contentLength = useMessage((s) => s.message.content.length) || 1;
1490
1525
  return new Array(contentLength).fill(null).map((_, idx) => {
1491
1526
  const partIndex = idx;
1492
1527
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
@@ -1783,7 +1818,7 @@ var useThreadViewportAutoScroll = ({
1783
1818
  const div = divRef.current;
1784
1819
  if (!div) return;
1785
1820
  const isAtBottom = useViewport.getState().isAtBottom;
1786
- const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
1821
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight + 1;
1787
1822
  if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
1788
1823
  } else {
1789
1824
  if (newIsAtBottom) {
@@ -2019,6 +2054,40 @@ var ThreadPrimitiveSuggestion = createActionButton(
2019
2054
  // src/runtimes/local/useLocalRuntime.tsx
2020
2055
  var import_react54 = require("react");
2021
2056
 
2057
+ // src/runtimes/core/BaseAssistantRuntime.tsx
2058
+ var BaseAssistantRuntime = class {
2059
+ constructor(_thread) {
2060
+ this._thread = _thread;
2061
+ this._thread = _thread;
2062
+ }
2063
+ get thread() {
2064
+ return this._thread;
2065
+ }
2066
+ set thread(thread) {
2067
+ this._thread = thread;
2068
+ this.subscriptionHandler();
2069
+ }
2070
+ _subscriptions = /* @__PURE__ */ new Set();
2071
+ subscribe(callback) {
2072
+ this._subscriptions.add(callback);
2073
+ return () => this._subscriptions.delete(callback);
2074
+ }
2075
+ subscriptionHandler = () => {
2076
+ for (const callback of this._subscriptions) callback();
2077
+ };
2078
+ };
2079
+
2080
+ // src/internal.ts
2081
+ var internal_exports = {};
2082
+ __export(internal_exports, {
2083
+ BaseAssistantRuntime: () => BaseAssistantRuntime,
2084
+ MessageRepository: () => MessageRepository,
2085
+ ProxyConfigProvider: () => ProxyConfigProvider,
2086
+ TooltipIconButton: () => TooltipIconButton,
2087
+ generateId: () => generateId,
2088
+ useSmooth: () => useSmooth
2089
+ });
2090
+
2022
2091
  // src/utils/idUtils.tsx
2023
2092
  var import_non_secure = require("nanoid/non-secure");
2024
2093
  var generateId = (0, import_non_secure.customAlphabet)(
@@ -2028,6 +2097,54 @@ var generateId = (0, import_non_secure.customAlphabet)(
2028
2097
  var optimisticPrefix = "__optimistic__";
2029
2098
  var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
2030
2099
 
2100
+ // src/runtimes/edge/converters/fromCoreMessage.ts
2101
+ var fromCoreMessages = (message) => {
2102
+ return message.map((message2) => fromCoreMessage(message2));
2103
+ };
2104
+ var fromCoreMessage = (message, {
2105
+ id = generateId(),
2106
+ status = { type: "complete", reason: "unknown" }
2107
+ } = {}) => {
2108
+ const commonProps = {
2109
+ id,
2110
+ createdAt: /* @__PURE__ */ new Date()
2111
+ };
2112
+ const role = message.role;
2113
+ switch (role) {
2114
+ case "assistant":
2115
+ return {
2116
+ ...commonProps,
2117
+ role,
2118
+ content: message.content.map((part) => {
2119
+ if (part.type === "tool-call") {
2120
+ return {
2121
+ ...part,
2122
+ argsText: JSON.stringify(part.args)
2123
+ };
2124
+ }
2125
+ return part;
2126
+ }),
2127
+ status
2128
+ };
2129
+ case "user":
2130
+ return {
2131
+ ...commonProps,
2132
+ role,
2133
+ content: message.content
2134
+ };
2135
+ case "system":
2136
+ return {
2137
+ ...commonProps,
2138
+ role,
2139
+ content: message.content
2140
+ };
2141
+ default: {
2142
+ const unsupportedRole = role;
2143
+ throw new Error(`Unknown message role: ${unsupportedRole}`);
2144
+ }
2145
+ }
2146
+ };
2147
+
2031
2148
  // src/runtimes/utils/MessageRepository.tsx
2032
2149
  var findHead = (message) => {
2033
2150
  if (message.next) return findHead(message.next);
@@ -2118,12 +2235,13 @@ var MessageRepository = class {
2118
2235
  do {
2119
2236
  optimisticId = generateOptimisticId();
2120
2237
  } while (this.messages.has(optimisticId));
2121
- this.addOrUpdateMessage(parentId, {
2122
- ...message,
2123
- id: optimisticId,
2124
- createdAt: /* @__PURE__ */ new Date(),
2125
- ...message.role === "assistant" ? { status: "in_progress" } : void 0
2126
- });
2238
+ this.addOrUpdateMessage(
2239
+ parentId,
2240
+ fromCoreMessage(message, {
2241
+ id: optimisticId,
2242
+ status: { type: "running" }
2243
+ })
2244
+ );
2127
2245
  return optimisticId;
2128
2246
  }
2129
2247
  deleteMessage(messageId, replacementId) {
@@ -2190,40 +2308,6 @@ var MessageRepository = class {
2190
2308
  }
2191
2309
  };
2192
2310
 
2193
- // src/runtimes/core/BaseAssistantRuntime.tsx
2194
- var BaseAssistantRuntime = class {
2195
- constructor(_thread) {
2196
- this._thread = _thread;
2197
- this._thread = _thread;
2198
- }
2199
- get thread() {
2200
- return this._thread;
2201
- }
2202
- set thread(thread) {
2203
- this._thread = thread;
2204
- this.subscriptionHandler();
2205
- }
2206
- _subscriptions = /* @__PURE__ */ new Set();
2207
- subscribe(callback) {
2208
- this._subscriptions.add(callback);
2209
- return () => this._subscriptions.delete(callback);
2210
- }
2211
- subscriptionHandler = () => {
2212
- for (const callback of this._subscriptions) callback();
2213
- };
2214
- };
2215
-
2216
- // src/internal.ts
2217
- var internal_exports = {};
2218
- __export(internal_exports, {
2219
- BaseAssistantRuntime: () => BaseAssistantRuntime,
2220
- MessageRepository: () => MessageRepository,
2221
- ProxyConfigProvider: () => ProxyConfigProvider,
2222
- TooltipIconButton: () => TooltipIconButton,
2223
- generateId: () => generateId,
2224
- useSmooth: () => useSmooth
2225
- });
2226
-
2227
2311
  // src/ui/base/tooltip-icon-button.tsx
2228
2312
  var import_react52 = require("react");
2229
2313
 
@@ -2538,35 +2622,43 @@ var fromLanguageModelMessages = (lm, { mergeRoundtrips }) => {
2538
2622
  return messages;
2539
2623
  };
2540
2624
 
2541
- // src/runtimes/edge/converters/fromCoreMessage.ts
2542
- var fromCoreMessages = (message) => {
2543
- return message.map((message2) => {
2544
- return {
2545
- id: generateId(),
2546
- createdAt: /* @__PURE__ */ new Date(),
2547
- ...message2.role === "assistant" ? {
2548
- status: { type: "done" }
2549
- } : void 0,
2550
- ...message2
2551
- };
2552
- });
2553
- };
2554
-
2555
2625
  // src/runtimes/edge/converters/toCoreMessages.ts
2556
2626
  var toCoreMessages = (message) => {
2557
- return message.map((message2) => {
2558
- return {
2559
- role: message2.role,
2560
- content: message2.content.map((part) => {
2561
- if (part.type === "ui") throw new Error("UI parts are not supported");
2562
- if (part.type === "tool-call") {
2563
- const { argsText, ...rest } = part;
2564
- return rest;
2565
- }
2566
- return part;
2567
- })
2568
- };
2569
- });
2627
+ return message.map(toCoreMessage);
2628
+ };
2629
+ var toCoreMessage = (message) => {
2630
+ const role = message.role;
2631
+ switch (role) {
2632
+ case "assistant":
2633
+ return {
2634
+ role,
2635
+ content: message.content.map((part) => {
2636
+ if (part.type === "ui") throw new Error("UI parts are not supported");
2637
+ if (part.type === "tool-call") {
2638
+ const { argsText, ...rest } = part;
2639
+ return rest;
2640
+ }
2641
+ return part;
2642
+ })
2643
+ };
2644
+ case "user":
2645
+ return {
2646
+ role,
2647
+ content: message.content.map((part) => {
2648
+ if (part.type === "ui") throw new Error("UI parts are not supported");
2649
+ return part;
2650
+ })
2651
+ };
2652
+ case "system":
2653
+ return {
2654
+ role,
2655
+ content: message.content
2656
+ };
2657
+ default: {
2658
+ const unsupportedRole = role;
2659
+ throw new Error(`Unknown message role: ${unsupportedRole}`);
2660
+ }
2661
+ }
2570
2662
  };
2571
2663
 
2572
2664
  // src/runtimes/edge/converters/fromLanguageModelTools.ts
@@ -3036,9 +3128,10 @@ var parsePartialJson = (json) => {
3036
3128
  };
3037
3129
 
3038
3130
  // src/runtimes/edge/streams/runResultStream.ts
3039
- function runResultStream(initialContent) {
3131
+ function runResultStream() {
3040
3132
  let message = {
3041
- content: initialContent
3133
+ content: [],
3134
+ status: { type: "running" }
3042
3135
  };
3043
3136
  const currentToolCall = { toolCallId: "", argsText: "" };
3044
3137
  return new TransformStream({
@@ -3086,7 +3179,13 @@ function runResultStream(initialContent) {
3086
3179
  break;
3087
3180
  }
3088
3181
  case "error": {
3089
- throw chunk.error;
3182
+ if (chunk.error instanceof Error && chunk.error.name === "AbortError") {
3183
+ message = appendOrUpdateCancel(message);
3184
+ controller.enqueue(message);
3185
+ break;
3186
+ } else {
3187
+ throw chunk.error;
3188
+ }
3090
3189
  }
3091
3190
  default: {
3092
3191
  const unhandledType = chunkType;
@@ -3160,11 +3259,42 @@ var appendOrUpdateToolResult = (message, toolCallId, toolName, result) => {
3160
3259
  };
3161
3260
  var appendOrUpdateFinish = (message, chunk) => {
3162
3261
  const { type, ...rest } = chunk;
3262
+ return {
3263
+ ...message,
3264
+ status: getStatus(chunk),
3265
+ roundtrips: [
3266
+ ...message.roundtrips ?? [],
3267
+ {
3268
+ logprobs: rest.logprobs,
3269
+ usage: rest.usage
3270
+ }
3271
+ ]
3272
+ };
3273
+ };
3274
+ var getStatus = (chunk) => {
3275
+ if (chunk.finishReason === "tool-calls") {
3276
+ return {
3277
+ type: "requires-action",
3278
+ reason: "tool-calls"
3279
+ };
3280
+ } else if (chunk.finishReason === "stop" || chunk.finishReason === "unknown") {
3281
+ return {
3282
+ type: "complete",
3283
+ reason: chunk.finishReason
3284
+ };
3285
+ } else {
3286
+ return {
3287
+ type: "incomplete",
3288
+ reason: chunk.finishReason
3289
+ };
3290
+ }
3291
+ };
3292
+ var appendOrUpdateCancel = (message) => {
3163
3293
  return {
3164
3294
  ...message,
3165
3295
  status: {
3166
- type: "done",
3167
- ...rest
3296
+ type: "incomplete",
3297
+ reason: "cancelled"
3168
3298
  }
3169
3299
  };
3170
3300
  };
@@ -3264,7 +3394,7 @@ var EdgeChatAdapter = class {
3264
3394
  constructor(options) {
3265
3395
  this.options = options;
3266
3396
  }
3267
- async roundtrip(initialContent, { messages, abortSignal, config, onUpdate }) {
3397
+ async run({ messages, abortSignal, config, onUpdate }) {
3268
3398
  const result = await fetch(this.options.api, {
3269
3399
  method: "POST",
3270
3400
  headers: {
@@ -3284,46 +3414,14 @@ var EdgeChatAdapter = class {
3284
3414
  `Edge runtime returned status ${result.status}: ${await result.text()}`
3285
3415
  );
3286
3416
  }
3287
- const stream = result.body.pipeThrough(new TextDecoderStream()).pipeThrough(chunkByLineStream()).pipeThrough(assistantDecoderStream()).pipeThrough(toolResultStream(config.tools)).pipeThrough(runResultStream(initialContent));
3288
- let message;
3417
+ const stream = result.body.pipeThrough(new TextDecoderStream()).pipeThrough(chunkByLineStream()).pipeThrough(assistantDecoderStream()).pipeThrough(toolResultStream(config.tools)).pipeThrough(runResultStream());
3289
3418
  let update;
3290
3419
  for await (update of asAsyncIterable(stream)) {
3291
- message = onUpdate(update);
3420
+ onUpdate(update);
3292
3421
  }
3293
3422
  if (update === void 0)
3294
3423
  throw new Error("No data received from Edge Runtime");
3295
- return [message, update];
3296
- }
3297
- async run({ messages, abortSignal, config, onUpdate }) {
3298
- let roundtripAllowance = this.options.maxToolRoundtrips ?? 1;
3299
- let usage = {
3300
- promptTokens: 0,
3301
- completionTokens: 0
3302
- };
3303
- let result;
3304
- let assistantMessage;
3305
- do {
3306
- [assistantMessage, result] = await this.roundtrip(result?.content ?? [], {
3307
- messages: assistantMessage ? [...messages, assistantMessage] : messages,
3308
- abortSignal,
3309
- config,
3310
- onUpdate
3311
- });
3312
- if (result.status?.type === "done") {
3313
- usage.promptTokens += result.status.usage?.promptTokens ?? 0;
3314
- usage.completionTokens += result.status.usage?.completionTokens ?? 0;
3315
- }
3316
- } while (result.status?.type === "done" && result.status.finishReason === "tool-calls" && result.content.every((c) => c.type !== "tool-call" || !!c.result) && roundtripAllowance-- > 0);
3317
- if (result.status?.type === "done" && usage.promptTokens > 0) {
3318
- result = {
3319
- ...result,
3320
- status: {
3321
- ...result.status,
3322
- usage
3323
- }
3324
- };
3325
- }
3326
- return result;
3424
+ return update;
3327
3425
  }
3328
3426
  };
3329
3427
 
@@ -3336,30 +3434,10 @@ var useEdgeRuntime = ({
3336
3434
  return useLocalRuntime(adapter, { initialMessages });
3337
3435
  };
3338
3436
 
3339
- // src/runtimes/local/LocalRuntime.tsx
3340
- var LocalRuntime = class extends BaseAssistantRuntime {
3341
- _proxyConfigProvider;
3342
- constructor(adapter, options) {
3343
- const proxyConfigProvider = new ProxyConfigProvider();
3344
- super(new LocalThreadRuntime(proxyConfigProvider, adapter, options));
3345
- this._proxyConfigProvider = proxyConfigProvider;
3346
- }
3347
- set adapter(adapter) {
3348
- this.thread.adapter = adapter;
3349
- }
3350
- registerModelConfigProvider(provider) {
3351
- return this._proxyConfigProvider.registerModelConfigProvider(provider);
3352
- }
3353
- switchToThread(threadId) {
3354
- if (threadId) {
3355
- throw new Error("LocalRuntime does not yet support switching threads");
3356
- }
3357
- return this.thread = new LocalThreadRuntime(
3358
- this._proxyConfigProvider,
3359
- this.thread.adapter
3360
- );
3361
- }
3362
- };
3437
+ // src/runtimes/local/shouldContinue.tsx
3438
+ var shouldContinue = (result) => result.status?.type === "requires-action" && result.status.reason === "tool-calls" && result.content.every((c) => c.type !== "tool-call" || !!c.result);
3439
+
3440
+ // src/runtimes/local/LocalThreadRuntime.tsx
3363
3441
  var CAPABILITIES = Object.freeze({
3364
3442
  edit: true,
3365
3443
  reload: true,
@@ -3370,6 +3448,7 @@ var LocalThreadRuntime = class {
3370
3448
  constructor(configProvider, adapter, options) {
3371
3449
  this.configProvider = configProvider;
3372
3450
  this.adapter = adapter;
3451
+ this.options = options;
3373
3452
  if (options?.initialMessages) {
3374
3453
  let parentId = null;
3375
3454
  const messages = fromCoreMessages(options.initialMessages);
@@ -3413,27 +3492,51 @@ var LocalThreadRuntime = class {
3413
3492
  }
3414
3493
  async startRun(parentId) {
3415
3494
  this.repository.resetHead(parentId);
3416
- const messages = this.repository.getMessages();
3495
+ const id = generateId();
3417
3496
  let message = {
3418
- id: generateId(),
3497
+ id,
3419
3498
  role: "assistant",
3420
- status: { type: "in_progress" },
3421
- content: [{ type: "text", text: "" }],
3499
+ status: { type: "running" },
3500
+ content: [],
3422
3501
  createdAt: /* @__PURE__ */ new Date()
3423
3502
  };
3503
+ do {
3504
+ message = await this.performRoundtrip(parentId, message);
3505
+ } while (shouldContinue(message));
3506
+ }
3507
+ async performRoundtrip(parentId, message) {
3508
+ const messages = this.repository.getMessages();
3424
3509
  this.abortController?.abort();
3425
3510
  this.abortController = new AbortController();
3426
- this.repository.addOrUpdateMessage(parentId, { ...message });
3427
- this.notifySubscribers();
3511
+ const initialContent = message.content;
3512
+ const initialRoundtrips = message.roundtrips;
3428
3513
  const updateMessage = (m) => {
3429
3514
  message = {
3430
3515
  ...message,
3431
- ...m
3516
+ ...m.content ? { content: [...initialContent, ...m.content ?? []] } : void 0,
3517
+ status: m.status ?? message.status,
3518
+ ...m.roundtrips?.length ? { roundtrips: [...initialRoundtrips ?? [], ...m.roundtrips] } : void 0
3432
3519
  };
3433
3520
  this.repository.addOrUpdateMessage(parentId, message);
3434
3521
  this.notifySubscribers();
3435
- return message;
3436
3522
  };
3523
+ const maxToolRoundtrips = this.options?.maxToolRoundtrips ?? 1;
3524
+ const toolRoundtrips = message.roundtrips?.length ?? 0;
3525
+ if (toolRoundtrips > maxToolRoundtrips) {
3526
+ updateMessage({
3527
+ status: {
3528
+ type: "incomplete",
3529
+ reason: "tool-calls"
3530
+ }
3531
+ });
3532
+ return message;
3533
+ } else {
3534
+ updateMessage({
3535
+ status: {
3536
+ type: "running"
3537
+ }
3538
+ });
3539
+ }
3437
3540
  try {
3438
3541
  const result = await this.adapter.run({
3439
3542
  messages,
@@ -3441,21 +3544,29 @@ var LocalThreadRuntime = class {
3441
3544
  config: this.configProvider.getModelConfig(),
3442
3545
  onUpdate: updateMessage
3443
3546
  });
3444
- if (result.status?.type === "in_progress")
3547
+ if (result.status?.type === "running")
3445
3548
  throw new Error(
3446
- "Unexpected in_progress status returned from ChatModelAdapter"
3549
+ "Unexpected running status returned from ChatModelAdapter"
3447
3550
  );
3448
3551
  this.abortController = null;
3449
- updateMessage({ status: { type: "done" }, ...result });
3450
- this.repository.addOrUpdateMessage(parentId, { ...message });
3451
- } catch (e) {
3452
- const isAbortError = e instanceof Error && e.name === "AbortError";
3453
- this.abortController = null;
3454
3552
  updateMessage({
3455
- status: isAbortError ? { type: "cancelled" } : { type: "error", error: e }
3553
+ status: { type: "complete", reason: "unknown" },
3554
+ ...result
3456
3555
  });
3457
- if (!isAbortError) throw e;
3556
+ } catch (e) {
3557
+ this.abortController = null;
3558
+ if (e instanceof Error && e.name === "AbortError") {
3559
+ updateMessage({
3560
+ status: { type: "incomplete", reason: "cancelled" }
3561
+ });
3562
+ } else {
3563
+ updateMessage({
3564
+ status: { type: "incomplete", reason: "error", error: e }
3565
+ });
3566
+ throw e;
3567
+ }
3458
3568
  }
3569
+ return message;
3459
3570
  }
3460
3571
  cancelRun() {
3461
3572
  if (!this.abortController) return;
@@ -3470,14 +3581,16 @@ var LocalThreadRuntime = class {
3470
3581
  return () => this._subscriptions.delete(callback);
3471
3582
  }
3472
3583
  addToolResult({ messageId, toolCallId, result }) {
3473
- const { parentId, message } = this.repository.getMessage(messageId);
3584
+ let { parentId, message } = this.repository.getMessage(messageId);
3474
3585
  if (message.role !== "assistant")
3475
3586
  throw new Error("Tried to add tool result to non-assistant message");
3587
+ let added = false;
3476
3588
  let found = false;
3477
3589
  const newContent = message.content.map((c) => {
3478
3590
  if (c.type !== "tool-call") return c;
3479
3591
  if (c.toolCallId !== toolCallId) return c;
3480
3592
  found = true;
3593
+ if (!c.result) added = true;
3481
3594
  return {
3482
3595
  ...c,
3483
3596
  result
@@ -3485,10 +3598,39 @@ var LocalThreadRuntime = class {
3485
3598
  });
3486
3599
  if (!found)
3487
3600
  throw new Error("Tried to add tool result to non-existing tool call");
3488
- this.repository.addOrUpdateMessage(parentId, {
3601
+ message = {
3489
3602
  ...message,
3490
3603
  content: newContent
3491
- });
3604
+ };
3605
+ this.repository.addOrUpdateMessage(parentId, message);
3606
+ if (added && shouldContinue(message)) {
3607
+ this.performRoundtrip(parentId, message);
3608
+ }
3609
+ }
3610
+ };
3611
+
3612
+ // src/runtimes/local/LocalRuntime.tsx
3613
+ var LocalRuntime = class extends BaseAssistantRuntime {
3614
+ _proxyConfigProvider;
3615
+ constructor(adapter, options) {
3616
+ const proxyConfigProvider = new ProxyConfigProvider();
3617
+ super(new LocalThreadRuntime(proxyConfigProvider, adapter, options));
3618
+ this._proxyConfigProvider = proxyConfigProvider;
3619
+ }
3620
+ set adapter(adapter) {
3621
+ this.thread.adapter = adapter;
3622
+ }
3623
+ registerModelConfigProvider(provider) {
3624
+ return this._proxyConfigProvider.registerModelConfigProvider(provider);
3625
+ }
3626
+ switchToThread(threadId) {
3627
+ if (threadId) {
3628
+ throw new Error("LocalRuntime does not yet support switching threads");
3629
+ }
3630
+ return this.thread = new LocalThreadRuntime(
3631
+ this._proxyConfigProvider,
3632
+ this.thread.adapter
3633
+ );
3492
3634
  }
3493
3635
  };
3494
3636
 
@@ -3683,7 +3825,7 @@ var Text = ({ status }) => {
3683
3825
  {
3684
3826
  className: (0, import_classnames2.default)(
3685
3827
  "aui-text",
3686
- status.type === "in_progress" && "aui-text-in-progress"
3828
+ status.type === "running" && "aui-text-in-progress"
3687
3829
  ),
3688
3830
  children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(contentPart_exports.Text, {})
3689
3831
  }
@@ -4210,11 +4352,13 @@ var assistant_modal_default = Object.assign(AssistantModal, exports12);
4210
4352
  ThreadWelcome,
4211
4353
  UserActionBar,
4212
4354
  UserMessage,
4355
+ fromCoreMessage,
4213
4356
  fromCoreMessages,
4214
4357
  fromLanguageModelMessages,
4215
4358
  fromLanguageModelTools,
4216
4359
  makeAssistantTool,
4217
4360
  makeAssistantToolUI,
4361
+ toCoreMessage,
4218
4362
  toCoreMessages,
4219
4363
  toLanguageModelMessages,
4220
4364
  toLanguageModelTools,