@inferencesh/sdk 0.22.1 → 0.23.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/agent/actions.d.ts +2 -2
  3. package/dist/agent/actions.js +93 -21
  4. package/dist/agent/api.d.ts +3 -3
  5. package/dist/agent/api.js +1 -1
  6. package/dist/agent/context.d.ts +1 -1
  7. package/dist/agent/hooks.d.ts +2 -2
  8. package/dist/agent/hooks.js +1 -1
  9. package/dist/agent/index.d.ts +6 -6
  10. package/dist/agent/index.js +5 -5
  11. package/dist/agent/mcp-input.d.ts +1 -1
  12. package/dist/agent/provider.d.ts +1 -1
  13. package/dist/agent/provider.js +5 -5
  14. package/dist/agent/reducer.d.ts +1 -1
  15. package/dist/agent/reducer.js +2 -2
  16. package/dist/agent/types.d.ts +5 -5
  17. package/dist/api/agents.d.ts +8 -10
  18. package/dist/api/agents.js +45 -34
  19. package/dist/api/api-keys.d.ts +3 -3
  20. package/dist/api/apps.d.ts +4 -4
  21. package/dist/api/artifacts.d.ts +3 -3
  22. package/dist/api/chats.d.ts +4 -4
  23. package/dist/api/credentials.d.ts +3 -3
  24. package/dist/api/engines.d.ts +4 -4
  25. package/dist/api/files.d.ts +3 -3
  26. package/dist/api/flow-runs.d.ts +3 -3
  27. package/dist/api/flows.d.ts +4 -4
  28. package/dist/api/index.d.ts +4 -4
  29. package/dist/api/index.js +4 -4
  30. package/dist/api/knowledge.d.ts +4 -4
  31. package/dist/api/mcp-servers.d.ts +4 -4
  32. package/dist/api/projects.d.ts +4 -4
  33. package/dist/api/search.d.ts +3 -3
  34. package/dist/api/secrets.d.ts +3 -3
  35. package/dist/api/sessions.d.ts +3 -3
  36. package/dist/api/sockets.d.ts +6 -6
  37. package/dist/api/sockets.js +2 -2
  38. package/dist/api/tasks.d.ts +3 -3
  39. package/dist/api/tasks.js +4 -4
  40. package/dist/api/teams.d.ts +4 -4
  41. package/dist/api/write-bodies.d.ts +1 -1
  42. package/dist/delta.d.ts +1 -1
  43. package/dist/delta.js +1 -1
  44. package/dist/hook-builder.d.ts +1 -1
  45. package/dist/hook-builder.js +1 -1
  46. package/dist/http/client.d.ts +2 -2
  47. package/dist/http/client.js +2 -2
  48. package/dist/http/errors.d.ts +1 -1
  49. package/dist/http/index.d.ts +4 -4
  50. package/dist/http/index.js +4 -4
  51. package/dist/http/response.d.ts +1 -1
  52. package/dist/http/stream-until-terminal.d.ts +22 -8
  53. package/dist/http/stream-until-terminal.js +17 -9
  54. package/dist/http/streamable.d.ts +63 -7
  55. package/dist/http/streamable.js +277 -119
  56. package/dist/index.d.ts +65 -65
  57. package/dist/index.js +53 -53
  58. package/dist/live/schema.js +1 -1
  59. package/dist/live/session.d.ts +3 -3
  60. package/dist/live/session.js +2 -2
  61. package/dist/proxy/express.d.ts +1 -1
  62. package/dist/proxy/express.js +1 -1
  63. package/dist/proxy/hono.d.ts +1 -1
  64. package/dist/proxy/hono.js +1 -1
  65. package/dist/proxy/index.js +0 -2
  66. package/dist/proxy/nextjs.d.ts +3 -3
  67. package/dist/proxy/nextjs.js +2 -2
  68. package/dist/proxy/remix.d.ts +1 -1
  69. package/dist/proxy/remix.js +1 -1
  70. package/dist/proxy/svelte.d.ts +1 -1
  71. package/dist/proxy/svelte.js +1 -1
  72. package/dist/tool-builder.d.ts +1 -1
  73. package/dist/tool-builder.js +1 -1
  74. package/dist/types.d.ts +164 -40
  75. package/dist/types.js +33 -9
  76. package/dist/utils.d.ts +2 -2
  77. package/dist/utils.js +2 -2
  78. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.23.0] - 2026-10-11
11
+
12
+ ### Changed
13
+
14
+ - Generated types follow the API: `MessageInput` (what an agent run takes), `ContextField.in_prompt`, `ApiAgentRunRequest` takes `agent_id`/`agent_version_id` and no `channel_context`; `CreateAgentMessageRequest` is gone. Per-function `credentials` and annotations on `AppFunction`, and `DeployWarning` on `AppVersionDTO` (INF-1053). `ScopesResponse.account_groups`. Agent events for subagent ids, context, limits and account.
15
+ - Dev dependency lockfile refreshed for security advisories (handlebars, devalue, sharp). Nothing shipped in the package changes.
16
+
17
+ ### Deprecated
18
+
19
+ - `SendMessageOptions.channel_context`. The API no longer reads a channel context from an agent run request; a chat's routing comes from the server's own channel receivers. The option is still accepted and is not sent. Chats still return `channel_context`.
20
+
21
+ ### Added
22
+
23
+ - `StreamableManager` reconnects on opt-in: `reconnect: true | { maxAttempts, baseDelayMs, maxDelayMs, idleTimeoutMs }` (defaults 10, 1000, 30000, 30000). A failed stream is retried with jittered exponential backoff; any line received, heartbeats included, resets the failure count; a stream silent for `idleTimeoutMs` is reconnected; in a browser, `online` or the tab becoming visible retries at once. A 4xx other than 408/429 is not retried. `onRetry(error, attempt, delayMs)` reports each scheduled retry; `onReconnect(attempt)` runs after a retry connects and before the new stream is read, so the caller can fetch what it missed. With `reconnect`, `onError` fires only when the manager gives up. `StreamReconnectOptions` is exported. Without the option nothing changes.
24
+
25
+ ### Fixed
26
+
27
+ - The React chat (`AgentChatProvider`) no longer stays busy after a network blip mid-turn (Chromium `ERR_NETWORK_CHANGED`, "Stream error: TypeError: network error"). The chat stream reconnects; each reconnect refetches the chat and its messages, so a turn that finished while disconnected ends and `onTurnEnd` fires. `connectionStatus` is `'connecting'` while it waits to retry and `'error'` (with `error` set) once it gives up. Whenever the stream ends on its own and the chat is still busy, the chat is polled until idle. A stop, reset or chat switch during a pending retry leaves the new state alone.
28
+ - `Agent.sendMessage`/`startStreaming` and `tasks.run`/`watch` in streaming mode survive a network error: the stream reconnects and refetches the chat (with its messages) or the task, so the wait settles when it finished meanwhile. A 4xx refusal still rejects at once; other errors reject once the stream stops retrying.
29
+
30
+ - The package loads in plain Node (no bundler). 0.22.1's dist used extensionless relative imports, so `import("@inferencesh/sdk")` and every subpath except `proxy` and `internal/upload` failed with ERR_MODULE_NOT_FOUND. The build now uses NodeNext resolution with `.js` import specifiers, and `@inferencesh/sdk/proxy/nextjs` imports `next/server.js`. `require()` of every subpath works on Node >=22.12. CI and publish load each export of the packed tarball (`pnpm run check:exports`).
31
+
10
32
  ## [0.22.1] - 2026-10-10
11
33
 
12
34
  ### Fixed
@@ -4,6 +4,6 @@
4
4
  * Action creators that handle side effects (API calls, streaming).
5
5
  * These are created once per provider instance with access to dispatch.
6
6
  */
7
- import type { ActionsContext, ActionsResult } from './types';
7
+ import type { ActionsContext, ActionsResult } from './types.js';
8
8
  export declare function createActions(ctx: ActionsContext): ActionsResult;
9
- export declare function getClientToolHandlers(config: import('./types').AgentOptions | null): Map<string, import('./types').ClientToolHandlerFn>;
9
+ export declare function getClientToolHandlers(config: import('./types.js').AgentOptions | null): Map<string, import('./types.js').ClientToolHandlerFn>;
@@ -4,15 +4,15 @@
4
4
  * Action creators that handle side effects (API calls, streaming).
5
5
  * These are created once per provider instance with access to dispatch.
6
6
  */
7
- import { ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, } from '../types';
8
- import { isChatBusy, isMessageTerminal, isRunInterrupted, isRunWorking } from '../utils';
9
- import { StreamableManager } from '../http/streamable';
10
- import { PollManager } from '../http/poll';
11
- import { createLLMDeltaAccumulator } from '../delta';
12
- import { isAdHocConfig, extractClientToolHandlers } from './types';
13
- import * as api from './api';
14
- import { buildMCPInputResult } from './mcp-input';
15
- import { isInferenceError } from '../http/errors';
7
+ import { ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, } from '../types.js';
8
+ import { isChatBusy, isMessageTerminal, isRunInterrupted, isRunWorking } from '../utils.js';
9
+ import { StreamableManager } from '../http/streamable.js';
10
+ import { PollManager } from '../http/poll.js';
11
+ import { createLLMDeltaAccumulator } from '../delta.js';
12
+ import { isAdHocConfig, extractClientToolHandlers } from './types.js';
13
+ import * as api from './api.js';
14
+ import { buildMCPInputResult } from './mcp-input.js';
15
+ import { isInferenceError } from '../http/errors.js';
16
16
  // =============================================================================
17
17
  // Action Creators
18
18
  // =============================================================================
@@ -20,6 +20,10 @@ export function createActions(ctx) {
20
20
  const dispatchedToolInvocations = new Set();
21
21
  const { client, dispatch, getState, getConfig, getChatId, getClientToolHandlers, getStreamManager, setStreamManager, getStreamEnabled, getPollIntervalMs, callbacks } = ctx;
22
22
  let prevChatWasBusy = false;
23
+ // Bumped by every streamChat() and stopStream(). A stream whose generation
24
+ // is no longer current (stopped, reset, switched to another chat) leaves
25
+ // state alone, including work it started before that.
26
+ let streamGeneration = 0;
23
27
  const checkTurnEnd = (chat) => {
24
28
  const busy = isChatBusy(chat);
25
29
  if (prevChatWasBusy && !busy) {
@@ -83,6 +87,7 @@ export function createActions(ctx) {
83
87
  }
84
88
  };
85
89
  const streamChat = async (id) => {
90
+ const generation = ++streamGeneration;
86
91
  const existingManager = getStreamManager();
87
92
  if (existingManager) {
88
93
  existingManager.stop();
@@ -93,6 +98,9 @@ export function createActions(ctx) {
93
98
  try {
94
99
  // Fetch initial chat (fetchChat loads messages separately since Chat.Get no longer preloads them)
95
100
  const chat = await api.fetchChat(client, id);
101
+ // Stopped or switched to another chat while fetching.
102
+ if (generation !== streamGeneration)
103
+ return;
96
104
  if (chat) {
97
105
  setChat(chat);
98
106
  }
@@ -108,11 +116,53 @@ export function createActions(ctx) {
108
116
  pollChat(id);
109
117
  return;
110
118
  }
119
+ // Token-by-token streaming state, one accumulator per message being
120
+ // streamed. Deltas name their message (DeltaEvent.resource_id), so state
121
+ // never leaks between messages the way a single shared accumulator allowed.
122
+ const deltaAccums = new Map();
123
+ const isCurrent = () => generation === streamGeneration;
124
+ let failed = false;
125
+ // The API does not replay what the stream missed: the chat as fetched is
126
+ // the source of truth. Messages are merged, so older pages stay loaded.
127
+ const resync = async () => {
128
+ const chat = await api.fetchChat(client, id);
129
+ if (!chat || !isCurrent())
130
+ return null;
131
+ for (const message of chat.chat_messages ?? []) {
132
+ updateMessage(message);
133
+ }
134
+ dispatch({ type: 'UPDATE_CHAT', payload: chat });
135
+ callbacks.onStatusChange?.(isChatBusy(chat) ? 'streaming' : 'idle');
136
+ checkTurnEnd(chat);
137
+ return chat;
138
+ };
111
139
  // Single unified stream with TypedEvents (both Chat and ChatMessage events)
112
140
  const manager = new StreamableManager({
113
141
  request: api.chatStreamRequest(client, id),
142
+ reconnect: true,
143
+ onRetry: (error, attempt, delayMs) => {
144
+ console.warn(`[AgentSDK] Stream error, reconnecting in ${delayMs}ms (attempt ${attempt}):`, error);
145
+ if (!isCurrent())
146
+ return;
147
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'connecting' });
148
+ callbacks.onStatusChange?.('connecting');
149
+ },
150
+ onReconnect: async () => {
151
+ // Partial text from before the drop is replaced by the fetched messages.
152
+ deltaAccums.clear();
153
+ if (!isCurrent())
154
+ return;
155
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'streaming' });
156
+ await resync();
157
+ },
114
158
  onError: (error) => {
159
+ // The manager gave up: a refusal, or every retry failed.
115
160
  console.warn('[AgentSDK] Stream error:', error);
161
+ if (!isCurrent())
162
+ return;
163
+ failed = true;
164
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'error' });
165
+ dispatch({ type: 'SET_ERROR', payload: error.message });
116
166
  callbacks.onError?.(error);
117
167
  },
118
168
  onStart: () => {
@@ -120,14 +170,28 @@ export function createActions(ctx) {
120
170
  callbacks.onStatusChange?.('streaming');
121
171
  },
122
172
  onEnd: () => {
123
- // Only reset if this is an unexpected stop (stream died, max reconnects exhausted).
124
- // If stopStream() was called intentionally, it clears the manager ref first,
125
- // so getStreamManager() will be undefined and we skip the duplicate dispatch.
126
- if (getStreamManager()) {
127
- setStreamManager(undefined);
173
+ // An intentional stop or a newer stream skips all of this.
174
+ if (!isCurrent() || getStreamManager() !== manager)
175
+ return;
176
+ // The stream ended cleanly or gave up. Release the ref now, so a
177
+ // sendMessage meanwhile opens a new stream (and makes this one stale).
178
+ setStreamManager(undefined);
179
+ // A turn still running would leave the UI busy with nothing to end
180
+ // it: read the chat, and poll it until idle if it is still busy.
181
+ resync()
182
+ .catch(() => null)
183
+ .then((chat) => {
184
+ if (!isCurrent() || getStreamManager())
185
+ return;
186
+ if (chat ? isChatBusy(chat) : isChatBusy(getState().chat)) {
187
+ pollChat(id, { untilIdle: true });
188
+ return;
189
+ }
190
+ if (failed)
191
+ return; // keep the 'error' status
128
192
  dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
129
193
  callbacks.onStatusChange?.('idle');
130
- }
194
+ });
131
195
  },
132
196
  });
133
197
  // Listen for Chat object updates (status changes only — don't replace messages)
@@ -138,10 +202,6 @@ export function createActions(ctx) {
138
202
  checkTurnEnd(chatData);
139
203
  }
140
204
  });
141
- // Token-by-token streaming state, one accumulator per message being
142
- // streamed. Deltas name their message (DeltaEvent.resource_id), so state
143
- // never leaks between messages the way a single shared accumulator allowed.
144
- const deltaAccums = new Map();
145
205
  // Listen for ChatMessage updates
146
206
  manager.addEventListener('chat_messages', (message, fields) => {
147
207
  // A message that has reached a terminal state will receive no further
@@ -182,8 +242,12 @@ export function createActions(ctx) {
182
242
  setStreamManager(manager);
183
243
  manager.start();
184
244
  };
185
- /** Poll-based alternative to streaming for restricted environments */
186
- const pollChat = (id) => {
245
+ /**
246
+ * Poll-based alternative to streaming for restricted environments. With
247
+ * `untilIdle` (the fallback when a stream gave up mid-turn) polling stops
248
+ * once the chat is idle; the next sendMessage streams again.
249
+ */
250
+ const pollChat = (id, { untilIdle = false } = {}) => {
187
251
  let prevStatus = null;
188
252
  const manager = new PollManager({
189
253
  pollFunction: async () => {
@@ -205,6 +269,13 @@ export function createActions(ctx) {
205
269
  updateMessage(message);
206
270
  }
207
271
  }
272
+ if (untilIdle && !isChatBusy(chat) && getStreamManager() === manager) {
273
+ // Clear the ref first so onStop is a no-op.
274
+ setStreamManager(undefined);
275
+ manager.stop();
276
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
277
+ callbacks.onStatusChange?.('idle');
278
+ }
208
279
  }
209
280
  }
210
281
  catch (err) {
@@ -232,6 +303,7 @@ export function createActions(ctx) {
232
303
  manager.start();
233
304
  };
234
305
  const stopStream = () => {
306
+ streamGeneration++;
235
307
  const manager = getStreamManager();
236
308
  // Clear ref first so onStop callback (from manager.stop) is a no-op
237
309
  setStreamManager(undefined);
@@ -7,9 +7,9 @@
7
7
  *
8
8
  * /agents/run stays for A2A/programmatic use.
9
9
  */
10
- import type { AlwaysAllowOptionsDTO, AlwaysAllowResultDTO, ChatAgentDTO, ChatDTO, ChatMessageDTO, ChatSettingsDTO, ChatSettingsRequest, InterruptDTO, ToolExplanationDTO } from '../types';
11
- import type { AgentOptions, AgentClient, AgentInfo, FileRef } from './types';
12
- import type { StreamRequest } from '../http/streamable';
10
+ import type { AlwaysAllowOptionsDTO, AlwaysAllowResultDTO, ChatAgentDTO, ChatDTO, ChatMessageDTO, ChatSettingsDTO, ChatSettingsRequest, InterruptDTO, ToolExplanationDTO } from '../types.js';
11
+ import type { AgentOptions, AgentClient, AgentInfo, FileRef } from './types.js';
12
+ import type { StreamRequest } from '../http/streamable.js';
13
13
  export interface SendResult {
14
14
  chatId: string;
15
15
  userMessage: ChatMessageDTO;
package/dist/agent/api.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * /agents/run stays for A2A/programmatic use.
9
9
  */
10
- import { isAdHocConfig } from './types';
10
+ import { isAdHocConfig } from './types.js';
11
11
  function isFileRef(input) {
12
12
  return 'uri' in input && typeof input.uri === 'string';
13
13
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * React Context for sharing chat state and actions.
5
5
  */
6
- import type { AgentChatState, AgentChatActions, AgentClient } from './types';
6
+ import type { AgentChatState, AgentChatActions, AgentClient } from './types.js';
7
7
  /**
8
8
  * Context value combining state, actions, and client
9
9
  */
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * React hooks for accessing chat state and actions.
5
5
  */
6
- import type { AgentChatState, AgentChatActions, AgentClient } from './types';
7
- import type { ChatMessageDTO } from '../types';
6
+ import type { AgentChatState, AgentChatActions, AgentClient } from './types.js';
7
+ import type { ChatMessageDTO } from '../types.js';
8
8
  /**
9
9
  * Hook to access chat state
10
10
  *
@@ -4,7 +4,7 @@
4
4
  * React hooks for accessing chat state and actions.
5
5
  */
6
6
  import { useContext } from 'react';
7
- import { AgentChatContext } from './context';
7
+ import { AgentChatContext } from './context.js';
8
8
  /**
9
9
  * Hook to access chat state
10
10
  *
@@ -43,9 +43,9 @@
43
43
  * }
44
44
  * ```
45
45
  */
46
- export { AgentChatProvider } from './provider';
47
- export { useAgentChat, useAgentActions, useAgentClient, useAgentChatContext, useMessage, } from './hooks';
48
- export { AgentChatContext, type AgentChatContextValue } from './context';
49
- export type { AgentClient, FileRef, AdHocAgentConfig, TemplateAgentConfig, AgentOptions, AgentChatState, AgentChatActions, AlwaysAllowChoice, ChatStatus, AgentChatProviderProps, ClientTool, ClientToolHandlerFn, } from './types';
50
- export { isAdHocConfig, isTemplateConfig, isClientTool, extractToolSchemas, extractClientToolHandlers, } from './types';
51
- export { MCPMethodElicitationCreate, parseMCPInputState, elicitParams, isURLElicitation, buildMCPInputResult, type MCPInputState, type ElicitRequestParams, type ElicitRequestedSchema, type ElicitPropertySchema, } from './mcp-input';
46
+ export { AgentChatProvider } from './provider.js';
47
+ export { useAgentChat, useAgentActions, useAgentClient, useAgentChatContext, useMessage, } from './hooks.js';
48
+ export { AgentChatContext, type AgentChatContextValue } from './context.js';
49
+ export type { AgentClient, FileRef, AdHocAgentConfig, TemplateAgentConfig, AgentOptions, AgentChatState, AgentChatActions, AlwaysAllowChoice, ChatStatus, AgentChatProviderProps, ClientTool, ClientToolHandlerFn, } from './types.js';
50
+ export { isAdHocConfig, isTemplateConfig, isClientTool, extractToolSchemas, extractClientToolHandlers, } from './types.js';
51
+ export { MCPMethodElicitationCreate, parseMCPInputState, elicitParams, isURLElicitation, buildMCPInputResult, type MCPInputState, type ElicitRequestParams, type ElicitRequestedSchema, type ElicitPropertySchema, } from './mcp-input.js';
@@ -44,12 +44,12 @@
44
44
  * ```
45
45
  */
46
46
  // Provider
47
- export { AgentChatProvider } from './provider';
47
+ export { AgentChatProvider } from './provider.js';
48
48
  // Hooks
49
- export { useAgentChat, useAgentActions, useAgentClient, useAgentChatContext, useMessage, } from './hooks';
49
+ export { useAgentChat, useAgentActions, useAgentClient, useAgentChatContext, useMessage, } from './hooks.js';
50
50
  // Context (for advanced use cases)
51
- export { AgentChatContext } from './context';
51
+ export { AgentChatContext } from './context.js';
52
52
  // Type guards and helpers
53
- export { isAdHocConfig, isTemplateConfig, isClientTool, extractToolSchemas, extractClientToolHandlers, } from './types';
53
+ export { isAdHocConfig, isTemplateConfig, isClientTool, extractToolSchemas, extractClientToolHandlers, } from './types.js';
54
54
  // MCP input requests
55
- export { MCPMethodElicitationCreate, parseMCPInputState, elicitParams, isURLElicitation, buildMCPInputResult, } from './mcp-input';
55
+ export { MCPMethodElicitationCreate, parseMCPInputState, elicitParams, isURLElicitation, buildMCPInputResult, } from './mcp-input.js';
@@ -7,7 +7,7 @@
7
7
  * key, and submits them with `submitMCPInput`; the runtime then re-sends the
8
8
  * tool call with those responses.
9
9
  */
10
- import type { ElicitResult, InputRequest } from '../types';
10
+ import type { ElicitResult, InputRequest } from '../types.js';
11
11
  export declare const MCPMethodElicitationCreate = "elicitation/create";
12
12
  /** Stored in ToolInvocationDTO.data while an MCP tool call waits for the user. */
13
13
  export interface MCPInputState {
@@ -5,7 +5,7 @@
5
5
  * Uses React useReducer + Context pattern.
6
6
  */
7
7
  import React from 'react';
8
- import type { AgentChatProviderProps } from './types';
8
+ import type { AgentChatProviderProps } from './types.js';
9
9
  /**
10
10
  * AgentChatProvider - Provides chat state and actions to children
11
11
  *
@@ -6,11 +6,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  * Uses React useReducer + Context pattern.
7
7
  */
8
8
  import { useReducer, useRef, useEffect, useMemo } from 'react';
9
- import { AgentChatContext } from './context';
10
- import { chatReducer, initialState } from './reducer';
11
- import { createActions, getClientToolHandlers } from './actions';
12
- import { fetchAgentInfo } from './api';
13
- import { isTemplateConfig } from './types';
9
+ import { AgentChatContext } from './context.js';
10
+ import { chatReducer, initialState } from './reducer.js';
11
+ import { createActions, getClientToolHandlers } from './actions.js';
12
+ import { fetchAgentInfo } from './api.js';
13
+ import { isTemplateConfig } from './types.js';
14
14
  function mergeHandlers(base, extra) {
15
15
  if (!extra || extra.size === 0)
16
16
  return base;
@@ -3,6 +3,6 @@
3
3
  *
4
4
  * Pure reducer for managing agent chat state.
5
5
  */
6
- import type { AgentChatState, ChatAction } from './types';
6
+ import type { AgentChatState, ChatAction } from './types.js';
7
7
  export declare const initialState: AgentChatState;
8
8
  export declare function chatReducer(state: AgentChatState, action: ChatAction): AgentChatState;
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Pure reducer for managing agent chat state.
5
5
  */
6
- import { ChatStatusBusy, ChatStatusAwaitingInput, ChatStatusIdle } from '../types';
7
- import { isRunInterrupted, isRunWorking } from '../utils';
6
+ import { ChatStatusBusy, ChatStatusAwaitingInput, ChatStatusIdle } from '../types.js';
7
+ import { isRunInterrupted, isRunWorking } from '../utils.js';
8
8
  function deriveChatStatus(run) {
9
9
  if (!run)
10
10
  return ChatStatusIdle;
@@ -4,11 +4,11 @@
4
4
  * Public types for the Agent chat module.
5
5
  */
6
6
  import type { Dispatch } from 'react';
7
- import type { AgentRunDTO, AgentVersionDTO, AlwaysAllowOptionsDTO, AlwaysAllowResultDTO, ToolExplanationDTO, ChatAgentDTO, ChatDTO, ChatMessageDTO, ChatSettingsDTO, ChatSettingsRequest, AgentTool, AgentConfigInput as GeneratedAgentConfig, CoreAppConfigInput as CoreAppConfig, FileRef, ElicitResult } from '../types';
8
- import type { HttpClient } from '../http/client';
9
- import type { StreamableManager } from '../http/streamable';
10
- import type { PollManager } from '../http/poll';
11
- export type { FileRef } from '../types';
7
+ import type { AgentRunDTO, AgentVersionDTO, AlwaysAllowOptionsDTO, AlwaysAllowResultDTO, ToolExplanationDTO, ChatAgentDTO, ChatDTO, ChatMessageDTO, ChatSettingsDTO, ChatSettingsRequest, AgentTool, AgentConfigInput as GeneratedAgentConfig, CoreAppConfigInput as CoreAppConfig, FileRef, ElicitResult } from '../types.js';
8
+ import type { HttpClient } from '../http/client.js';
9
+ import type { StreamableManager } from '../http/streamable.js';
10
+ import type { PollManager } from '../http/poll.js';
11
+ export type { FileRef } from '../types.js';
12
12
  /** The "always allow" option to save: a key from getAlwaysAllowOptions. */
13
13
  export interface AlwaysAllowChoice {
14
14
  option?: string;
@@ -1,8 +1,8 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import type { AgentUpdateBody } from './write-bodies';
4
- import { FilesAPI } from './files';
5
- import { ChatDTO, ChatMessageDTO, ChannelContext, LLMDelta, LLMOutput, AgentConfigInput as AgentConfig, AgentDTO, AgentVersionDTO, CreateAgentRequest, FileDTO as File, InterruptDTO, CursorListRequest, CursorListResponse } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import type { AgentUpdateBody } from './write-bodies.js';
4
+ import { FilesAPI } from './files.js';
5
+ import { ChannelContext, ChatDTO, ChatMessageDTO, LLMDelta, LLMOutput, AgentConfigInput as AgentConfig, AgentDTO, AgentVersionDTO, CreateAgentRequest, FileDTO as File, InterruptDTO, CursorListRequest, CursorListResponse } from '../types.js';
6
6
  /** Internal tool definition returned by getInternalTools */
7
7
  export interface InternalToolDefinition {
8
8
  id: string;
@@ -67,11 +67,9 @@ export interface SendMessageOptions {
67
67
  */
68
68
  signal?: AbortSignal;
69
69
  /**
70
- * The channel this message came through (slack, telegram, a wearable...):
71
- * `channel_type` plus whatever `channel_metadata` the integration needs to
72
- * route the reply (thread ids, chat ids). Sent as `channel_context` on the
73
- * message; the API records it on the chat the first time it sees it, so
74
- * later turns in that chat inherit the routing.
70
+ * @deprecated The API no longer reads a channel context from an agent run;
71
+ * a chat's routing comes from the server's own channel receivers. Accepted
72
+ * for compatibility and not sent.
75
73
  */
76
74
  channel_context?: ChannelContext;
77
75
  }
@@ -1,8 +1,8 @@
1
- import { streamUntilTerminal } from '../http/stream-until-terminal';
2
- import { PollManager } from '../http/poll';
3
- import { createLLMDeltaAccumulator } from '../delta';
4
- import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, } from '../types';
5
- import { isChatBusy, isMessageTerminal } from '../utils';
1
+ import { streamUntilTerminal } from '../http/stream-until-terminal.js';
2
+ import { PollManager } from '../http/poll.js';
3
+ import { createLLMDeltaAccumulator } from '../delta.js';
4
+ import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, } from '../types.js';
5
+ import { isChatBusy, isMessageTerminal } from '../utils.js';
6
6
  /**
7
7
  * Decides when one sendMessage() turn is over.
8
8
  *
@@ -90,7 +90,6 @@ export class Agent {
90
90
  chat_id: this.chatId ?? undefined,
91
91
  context: this.context,
92
92
  input,
93
- channel_context: options.channel_context,
94
93
  ...(isTemplate
95
94
  ? { agent: this.config }
96
95
  : {
@@ -243,6 +242,43 @@ export class Agent {
243
242
  // the delta names, so a tool-call-only turn never shows the previous
244
243
  // message's text. Same attribution rule as the React hooks.
245
244
  const deltaAccums = new Map();
245
+ const observeMessage = (message) => {
246
+ // A terminal message receives no further deltas.
247
+ if (isMessageTerminal(message.status))
248
+ deltaAccums.delete(message.id);
249
+ gate.observeMessage(message);
250
+ options.onMessage?.(message);
251
+ if (idlePending && gate.settled)
252
+ settle.resolve();
253
+ if (message.tool_invocations && options.onToolCall) {
254
+ for (const inv of message.tool_invocations) {
255
+ if (this.dispatchedToolCalls.has(inv.id))
256
+ continue;
257
+ if (inv.type === ToolTypeClient && (inv.status === ToolInvocationStatusInProgress || inv.status === ToolInvocationStatusAwaitingInput)) {
258
+ this.dispatchedToolCalls.add(inv.id);
259
+ options.onToolCall({
260
+ id: inv.id,
261
+ name: inv.function?.name || '',
262
+ args: inv.function?.arguments || {},
263
+ });
264
+ }
265
+ }
266
+ }
267
+ };
268
+ // The chat and its latest messages as the API has them now: what a
269
+ // dropped stream missed, or whether a closed stream's turn is over.
270
+ // Messages first, so this turn's terminal message can gate the idle chat.
271
+ const resync = async () => {
272
+ // Deltas sent while disconnected are gone; the messages replace them.
273
+ deltaAccums.clear();
274
+ const [{ data: chat }, { data: page }] = await Promise.all([
275
+ this.http.request('get', `/chats/${chatId}`),
276
+ this.http.request('get', `/chats/${chatId}/messages`),
277
+ ]);
278
+ for (const message of page?.items ?? [])
279
+ observeMessage(message);
280
+ observeChat(chat);
281
+ };
246
282
  return {
247
283
  request: (init) => this.http.fetch(`/chats/${chatId}/stream`, init),
248
284
  events: {
@@ -264,36 +300,11 @@ export class Agent {
264
300
  seq: evt.seq,
265
301
  });
266
302
  },
267
- chat_messages: (message) => {
268
- // A terminal message receives no further deltas.
269
- if (isMessageTerminal(message.status))
270
- deltaAccums.delete(message.id);
271
- gate.observeMessage(message);
272
- options.onMessage?.(message);
273
- if (idlePending && gate.settled)
274
- settle.resolve();
275
- if (message.tool_invocations && options.onToolCall) {
276
- for (const inv of message.tool_invocations) {
277
- if (this.dispatchedToolCalls.has(inv.id))
278
- continue;
279
- if (inv.type === ToolTypeClient && (inv.status === ToolInvocationStatusInProgress || inv.status === ToolInvocationStatusAwaitingInput)) {
280
- this.dispatchedToolCalls.add(inv.id);
281
- options.onToolCall({
282
- id: inv.id,
283
- name: inv.function?.name || '',
284
- args: inv.function?.arguments || {},
285
- });
286
- }
287
- }
288
- }
289
- },
303
+ chat_messages: (message) => observeMessage(message),
290
304
  },
291
305
  // The stream closed before the chat was seen idle: the chat itself
292
- // says whether the turn is over.
293
- reconcile: async () => {
294
- const { data: chat } = await this.http.request('get', `/chats/${chatId}`);
295
- observeChat(chat);
296
- },
306
+ // says whether the turn is over. A reconnect resyncs the same way.
307
+ reconcile: resync,
297
308
  closedMessage: 'Chat stream closed before the chat became idle',
298
309
  // Stopped by disconnect()/abort or replaced by a newer stream: the
299
310
  // caller stopped waiting on purpose.
@@ -1,6 +1,6 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import { ApiKeyDTO, CreateApiKeyRequest, CursorListRequest, CursorListResponse } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import { ApiKeyDTO, CreateApiKeyRequest, CursorListRequest, CursorListResponse } from '../types.js';
4
4
  /**
5
5
  * API Keys API
6
6
  */
@@ -1,7 +1,7 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import type { AppPatchBody, AppUpdateBody } from './write-bodies';
4
- import { AppDTO as App, AppVersionDTO, CursorListRequest, CursorListResponse, LicenseRecordDTO } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import type { AppPatchBody, AppUpdateBody } from './write-bodies.js';
4
+ import { AppDTO as App, AppVersionDTO, CursorListRequest, CursorListResponse, LicenseRecordDTO } from '../types.js';
5
5
  /**
6
6
  * Apps API
7
7
  */
@@ -1,6 +1,6 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import { ArtifactDTO, ArtifactVersionDTO, ArtifactCreateRequest, ArtifactUpdateRequest, ArtifactPublishRequest, ArtifactContentResponse, CursorListRequest, CursorListResponse } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import { ArtifactDTO, ArtifactVersionDTO, ArtifactCreateRequest, ArtifactUpdateRequest, ArtifactPublishRequest, ArtifactContentResponse, CursorListRequest, CursorListResponse } from '../types.js';
4
4
  /**
5
5
  * Artifacts API — small self-contained HTML/Markdown pages published to a
6
6
  * URL, versioned, permissioned, and shareable.
@@ -1,7 +1,7 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import type { ChatUpdateBody } from './write-bodies';
4
- import { ChatDTO as Chat, ChatMessageDTO, ChatSettingsDTO, ChatSettingsRequest, ChatTraceDTO, CursorListRequest, CursorListResponse } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import type { ChatUpdateBody } from './write-bodies.js';
4
+ import { ChatDTO as Chat, ChatMessageDTO, ChatSettingsDTO, ChatSettingsRequest, ChatTraceDTO, CursorListRequest, CursorListResponse } from '../types.js';
5
5
  /**
6
6
  * Chats API
7
7
  */
@@ -1,6 +1,6 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import { CheckRequirementsRequest, CheckRequirementsResponse, CredentialDTO, CredentialConfigDTO, CredentialConnectRequest, CredentialConnectResponse, CursorListRequest, CursorListResponse } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import { CheckRequirementsRequest, CheckRequirementsResponse, CredentialDTO, CredentialConfigDTO, CredentialConnectRequest, CredentialConnectResponse, CursorListRequest, CursorListResponse } from '../types.js';
4
4
  /**
5
5
  * Credentials API: the accounts and keys a team has connected to external services.
6
6
  */
@@ -1,7 +1,7 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import type { EngineUpdateBody } from './write-bodies';
4
- import { EngineDTO as Engine, CursorListRequest, CursorListResponse } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import type { EngineUpdateBody } from './write-bodies.js';
4
+ import { EngineDTO as Engine, CursorListRequest, CursorListResponse } from '../types.js';
5
5
  /**
6
6
  * Engines API
7
7
  */
@@ -1,6 +1,6 @@
1
- import { HttpClient } from '../http/client';
2
- import type { Response } from '../http/response';
3
- import { FileDTO as File, CursorListRequest, CursorListResponse } from '../types';
1
+ import { HttpClient } from '../http/client.js';
2
+ import type { Response } from '../http/response.js';
3
+ import { FileDTO as File, CursorListRequest, CursorListResponse } from '../types.js';
4
4
  export interface UploadFileOptions {
5
5
  filename?: string;
6
6
  contentType?: string;