@inferencesh/sdk 0.10.1 → 0.12.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.
@@ -4,13 +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 { AgentRunStateWorking, AgentRunStateSubmitted, AgentRunStateInputRequired, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, ChatMessageStatusReady, ChatMessageStatusFailed, ChatMessageStatusCancelled, } from '../types';
8
- import { isChatBusy } from '../utils';
7
+ import { ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, } from '../types';
8
+ import { isChatBusy, isMessageTerminal, isRunInterrupted, isRunWorking } from '../utils';
9
9
  import { StreamableManager } from '../http/streamable';
10
10
  import { PollManager } from '../http/poll';
11
11
  import { createLLMDeltaAccumulator } from '../delta';
12
12
  import { isAdHocConfig, extractClientToolHandlers } from './types';
13
13
  import * as api from './api';
14
+ import { buildMCPInputResult } from './mcp-input';
15
+ import { isInferenceError } from '../http/errors';
14
16
  // =============================================================================
15
17
  // Action Creators
16
18
  // =============================================================================
@@ -148,9 +150,7 @@ export function createActions(ctx) {
148
150
  // A message that has reached a terminal state will receive no further
149
151
  // deltas, so its accumulator is done. This bounds the map by the number
150
152
  // of messages streaming at once rather than by chat length.
151
- if (message.status === ChatMessageStatusReady
152
- || message.status === ChatMessageStatusFailed
153
- || message.status === ChatMessageStatusCancelled) {
153
+ if (isMessageTerminal(message.status)) {
154
154
  deltaAccums.delete(message.id);
155
155
  }
156
156
  updateMessage(message, fields);
@@ -158,8 +158,7 @@ export function createActions(ctx) {
158
158
  // Listen for AgentRun updates (state transitions, output)
159
159
  manager.addEventListener('agent_runs', (run) => {
160
160
  dispatch({ type: 'UPDATE_ACTIVE_RUN', payload: run });
161
- const isRunActive = run.state === AgentRunStateWorking || run.state === AgentRunStateSubmitted || run.state === AgentRunStateInputRequired;
162
- callbacks.onStatusChange?.(isRunActive ? 'streaming' : 'idle');
161
+ callbacks.onStatusChange?.(isRunWorking(run.state) || isRunInterrupted(run.state) ? 'streaming' : 'idle');
163
162
  const currentChat = getState().chat;
164
163
  if (currentChat)
165
164
  checkTurnEnd({ ...currentChat, active_run: run });
@@ -326,6 +325,23 @@ export function createActions(ctx) {
326
325
  throw error;
327
326
  }
328
327
  },
328
+ submitMCPInput: async (toolInvocationId, responses) => {
329
+ try {
330
+ await api.submitToolResult(client, toolInvocationId, buildMCPInputResult(responses));
331
+ }
332
+ catch (error) {
333
+ // A 400 means the answers were rejected and the call is still waiting:
334
+ // the caller shows it next to the form, the connection is fine.
335
+ if (isInferenceError(error) && error.statusCode === 400)
336
+ throw error;
337
+ console.error('[AgentSDK] Failed to submit MCP input:', error);
338
+ const err = error instanceof Error ? error : new Error('Failed to submit MCP input');
339
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'error' });
340
+ dispatch({ type: 'SET_ERROR', payload: err.message });
341
+ callbacks.onError?.(err);
342
+ throw error;
343
+ }
344
+ },
329
345
  approveTool: async (toolInvocationId) => {
330
346
  try {
331
347
  await api.approveTool(client, toolInvocationId);
@@ -48,3 +48,4 @@ export { useAgentChat, useAgentActions, useAgentClient, useAgentChatContext, use
48
48
  export { AgentChatContext, type AgentChatContextValue } from './context';
49
49
  export type { AgentClient, FileRef, AdHocAgentConfig, TemplateAgentConfig, AgentOptions, AgentChatState, AgentChatActions, ChatStatus, AgentChatProviderProps, ClientTool, ClientToolHandlerFn, } from './types';
50
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';
@@ -51,3 +51,5 @@ export { useAgentChat, useAgentActions, useAgentClient, useAgentChatContext, use
51
51
  export { AgentChatContext } from './context';
52
52
  // Type guards and helpers
53
53
  export { isAdHocConfig, isTemplateConfig, isClientTool, extractToolSchemas, extractClientToolHandlers, } from './types';
54
+ // MCP input requests
55
+ export { MCPMethodElicitationCreate, parseMCPInputState, elicitParams, isURLElicitation, buildMCPInputResult, } from './mcp-input';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * MCP input requests on agent tool invocations.
3
+ *
4
+ * When a remote MCP server answers a tool call with `input_required`, the agent
5
+ * runtime parks the invocation in `awaiting_input` and stores an MCPInputState in
6
+ * its `data`. The UI renders each input request, collects an ElicitResult per
7
+ * key, and submits them with `submitMCPInput`; the runtime then re-sends the
8
+ * tool call with those responses.
9
+ */
10
+ import type { ElicitResult, InputRequest } from '../types';
11
+ export declare const MCPMethodElicitationCreate = "elicitation/create";
12
+ /** Stored in ToolInvocationDTO.data while an MCP tool call waits for the user. */
13
+ export interface MCPInputState {
14
+ input_required: true;
15
+ input_requests: Record<string, InputRequest>;
16
+ request_state?: string;
17
+ /** 1 for the first request; goes up each time the server asks again. */
18
+ round: number;
19
+ }
20
+ /** One property of a form-mode requestedSchema (MCP restricts these to flat primitives). */
21
+ export interface ElicitPropertySchema {
22
+ type?: 'string' | 'number' | 'integer' | 'boolean';
23
+ title?: string;
24
+ description?: string;
25
+ default?: unknown;
26
+ format?: 'email' | 'uri' | 'date' | 'date-time' | string;
27
+ minLength?: number;
28
+ maxLength?: number;
29
+ minimum?: number;
30
+ maximum?: number;
31
+ enum?: string[];
32
+ enumNames?: string[];
33
+ oneOf?: {
34
+ const: string;
35
+ title?: string;
36
+ }[];
37
+ }
38
+ export interface ElicitRequestedSchema {
39
+ type?: 'object';
40
+ properties?: Record<string, ElicitPropertySchema>;
41
+ required?: string[];
42
+ }
43
+ /** params of an elicitation/create request. */
44
+ export interface ElicitRequestParams {
45
+ mode?: 'form' | 'url';
46
+ message: string;
47
+ requestedSchema?: ElicitRequestedSchema;
48
+ url?: string;
49
+ }
50
+ /** Parses an invocation's data into MCPInputState, or null when it isn't one. */
51
+ export declare function parseMCPInputState(data: unknown): MCPInputState | null;
52
+ /** Returns the elicitation params of an input request, or null for other methods. */
53
+ export declare function elicitParams(request: InputRequest): ElicitRequestParams | null;
54
+ /** URL mode sends the user to a page; form mode asks for fields. Mode defaults to form. */
55
+ export declare function isURLElicitation(params: ElicitRequestParams): boolean;
56
+ /** Builds the tool result string the runtime expects: {<key>: {action, content?}}. */
57
+ export declare function buildMCPInputResult(responses: Record<string, ElicitResult>): string;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * MCP input requests on agent tool invocations.
3
+ *
4
+ * When a remote MCP server answers a tool call with `input_required`, the agent
5
+ * runtime parks the invocation in `awaiting_input` and stores an MCPInputState in
6
+ * its `data`. The UI renders each input request, collects an ElicitResult per
7
+ * key, and submits them with `submitMCPInput`; the runtime then re-sends the
8
+ * tool call with those responses.
9
+ */
10
+ export const MCPMethodElicitationCreate = 'elicitation/create';
11
+ /** Parses an invocation's data into MCPInputState, or null when it isn't one. */
12
+ export function parseMCPInputState(data) {
13
+ let value = data;
14
+ if (typeof value === 'string') {
15
+ try {
16
+ value = JSON.parse(value);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ if (!value || typeof value !== 'object')
23
+ return null;
24
+ const state = value;
25
+ if (state.input_required !== true)
26
+ return null;
27
+ if (!state.input_requests || typeof state.input_requests !== 'object')
28
+ return null;
29
+ if (Object.keys(state.input_requests).length === 0)
30
+ return null;
31
+ return {
32
+ input_required: true,
33
+ input_requests: state.input_requests,
34
+ request_state: state.request_state,
35
+ round: typeof state.round === 'number' ? state.round : 1,
36
+ };
37
+ }
38
+ /** Returns the elicitation params of an input request, or null for other methods. */
39
+ export function elicitParams(request) {
40
+ if (request.method !== MCPMethodElicitationCreate || !request.params)
41
+ return null;
42
+ const params = request.params;
43
+ return { ...params, message: params.message ?? '' };
44
+ }
45
+ /** URL mode sends the user to a page; form mode asks for fields. Mode defaults to form. */
46
+ export function isURLElicitation(params) {
47
+ return params.mode === 'url' || (!params.mode && !!params.url);
48
+ }
49
+ /** Builds the tool result string the runtime expects: {<key>: {action, content?}}. */
50
+ export function buildMCPInputResult(responses) {
51
+ const out = {};
52
+ for (const [key, response] of Object.entries(responses)) {
53
+ out[key] = response.action === 'accept' && response.content
54
+ ? { action: response.action, content: response.content }
55
+ : { action: response.action };
56
+ }
57
+ return JSON.stringify(out);
58
+ }
@@ -3,13 +3,14 @@
3
3
  *
4
4
  * Pure reducer for managing agent chat state.
5
5
  */
6
- import { ChatStatusBusy, ChatStatusAwaitingInput, ChatStatusIdle, AgentRunStateWorking, AgentRunStateSubmitted } from '../types';
6
+ import { ChatStatusBusy, ChatStatusAwaitingInput, ChatStatusIdle } from '../types';
7
+ import { isRunInterrupted, isRunWorking } from '../utils';
7
8
  function deriveChatStatus(run) {
8
9
  if (!run)
9
10
  return ChatStatusIdle;
10
- if (run.state === AgentRunStateWorking || run.state === AgentRunStateSubmitted)
11
+ if (isRunWorking(run.state))
11
12
  return ChatStatusBusy;
12
- if (run.state === 'input_required' || run.state === 'auth_required')
13
+ if (isRunInterrupted(run.state))
13
14
  return ChatStatusAwaitingInput;
14
15
  return ChatStatusIdle;
15
16
  }
@@ -4,7 +4,7 @@
4
4
  * Public types for the Agent chat module.
5
5
  */
6
6
  import type { Dispatch } from 'react';
7
- import type { AgentRunDTO, AgentVersionDTO, ChatDTO, ChatMessageDTO, AgentTool, AgentConfigInput as GeneratedAgentConfig, CoreAppConfigInput as CoreAppConfig, FileRef } from '../types';
7
+ import type { AgentRunDTO, AgentVersionDTO, ChatDTO, ChatMessageDTO, AgentTool, AgentConfigInput as GeneratedAgentConfig, CoreAppConfigInput as CoreAppConfig, FileRef, ElicitResult } from '../types';
8
8
  import type { HttpClient } from '../http/client';
9
9
  import type { StreamableManager } from '../http/streamable';
10
10
  import type { PollManager } from '../http/poll';
@@ -126,6 +126,8 @@ export interface AgentChatActions {
126
126
  clearError: () => void;
127
127
  /** Submit a tool result (for widgets/awaiting input) - usually called automatically by SDK */
128
128
  submitToolResult: (toolInvocationId: string, result: string) => Promise<void>;
129
+ /** Answer an MCP tool call's input requests (AwaitingInput status with MCPInputState data) */
130
+ submitMCPInput: (toolInvocationId: string, responses: Record<string, ElicitResult>) => Promise<void>;
129
131
  /** Approve a tool (for HIL approval - AwaitingApproval status) */
130
132
  approveTool: (toolInvocationId: string) => Promise<void>;
131
133
  /** Reject a tool (for HIL approval - AwaitingApproval status) */
@@ -1,9 +1,8 @@
1
1
  import { StreamableManager } from '../http/streamable';
2
2
  import { PollManager } from '../http/poll';
3
3
  import { createLLMDeltaAccumulator } from '../delta';
4
- import { ChatMessageStatusCancelled, ChatMessageStatusFailed, ChatMessageStatusReady, ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, } from '../types';
5
- import { isChatBusy } from '../utils';
6
- const terminalMessageStatuses = new Set([ChatMessageStatusReady, ChatMessageStatusFailed, ChatMessageStatusCancelled]);
4
+ import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, } from '../types';
5
+ import { isChatBusy, isMessageTerminal } from '../utils';
7
6
  /**
8
7
  * Decides when one sendMessage() turn is over.
9
8
  *
@@ -25,7 +24,7 @@ class TurnGate {
25
24
  this.sawBusy = true;
26
25
  }
27
26
  observeMessage(message) {
28
- if (message.id === this.assistantMessageId && terminalMessageStatuses.has(message.status)) {
27
+ if (message.id === this.assistantMessageId && isMessageTerminal(message.status)) {
29
28
  this.assistantDone = true;
30
29
  }
31
30
  }
@@ -86,23 +85,19 @@ export class Agent {
86
85
  if (others.length > 0)
87
86
  fileUris = others.map((f) => f.uri);
88
87
  }
89
- const body = isTemplate
90
- ? {
91
- chat_id: this.chatId,
92
- agent: this.config,
93
- context: this.context,
94
- input: { text, images: imageUris, files: fileUris, role: 'user', context: [], system_prompt: '', context_size: 0 },
95
- }
96
- : {
97
- chat_id: this.chatId,
98
- agent_config: this.config,
99
- agent_name: this.agentName ?? this.config.name,
100
- context: this.context,
101
- input: { text, images: imageUris, files: fileUris, role: 'user', context: [], system_prompt: '', context_size: 0 },
102
- };
103
- if (options.channel_context !== undefined) {
104
- body.channel_context = options.channel_context;
105
- }
88
+ const input = { text, images: imageUris, files: fileUris, role: 'user', context: [], system_prompt: '', context_size: 0 };
89
+ const body = {
90
+ chat_id: this.chatId ?? undefined,
91
+ context: this.context,
92
+ input,
93
+ channel_context: options.channel_context,
94
+ ...(isTemplate
95
+ ? { agent: this.config }
96
+ : {
97
+ agent_config: this.config,
98
+ agent_name: this.agentName ?? this.config.name,
99
+ }),
100
+ };
106
101
  const useStream = options.stream ?? this.http.getStreamDefault();
107
102
  const shouldWait = useStream === false || hasCallbacks;
108
103
  const gate = new TurnGate(!!this.chatId);
@@ -270,7 +265,7 @@ export class Agent {
270
265
  });
271
266
  this.stream.addEventListener('chat_messages', (message) => {
272
267
  // A terminal message receives no further deltas.
273
- if (terminalMessageStatuses.has(message.status))
268
+ if (isMessageTerminal(message.status))
274
269
  deltaAccums.delete(message.id);
275
270
  gate.observeMessage(message);
276
271
  options.onMessage?.(message);
package/dist/index.d.ts CHANGED
@@ -26,14 +26,15 @@ export { ProjectsAPI } from './api/projects';
26
26
  export { MCPServersAPI } from './api/mcp-servers';
27
27
  export { LiveSession, accessUrl } from './live/session';
28
28
  export type { LiveState, LiveEnd, LiveHandlers, LiveSessionOptions, LiveUpdate, WebSocketLike, WebSocketConstructor } from './live/session';
29
- export { STREAM_FORMAT, CLEAR_KEY, ERROR_KEY, isLiveField, parseMediaType, pcmFormat, splitLiveSchema, binaryLiveField, alternativeLabel, alternativeTag, } from './live/schema';
29
+ export { STREAM_FORMAT, CLEAR_KEY, ERROR_KEY, REDIAL_CODES } from './live/protocol';
30
+ export { isLiveField, parseMediaType, pcmFormat, splitLiveSchema, binaryLiveField, alternativeLabel, alternativeTag, } from './live/schema';
30
31
  export type { JsonSchema, MediaType, PCMFormat, LiveField } from './live/schema';
31
32
  export { tool, appTool, agentTool, webhookTool, httpTool, callTool, mcpTool, internalTools, string, number, integer, boolean, enumOf, object, array, optional, } from './tool-builder';
32
33
  export type { ClientTool, ClientToolHandler } from './tool-builder';
33
34
  export { lifecycleHook } from './hook-builder';
34
35
  export type { LifecycleHookBuilder } from './hook-builder';
35
36
  export { DeltaAccumulator, createLLMDeltaAccumulator, type DeltaEvent, type FieldTags, type FieldTagsRegistry } from './delta';
36
- export { parseStatus, isTerminalStatus, isChatBusy, pendingApprovals } from './utils';
37
+ export { parseStatus, isTerminalStatus, isChatBusy, isAwaitingHuman, isRunTerminal, isRunInterrupted, isRunSettled, isRunWorking, isMessageTerminal, pendingApprovals, } from './utils';
37
38
  export type { PendingApproval } from './utils';
38
39
  export * from './types';
39
40
  export type { TaskDTO as Task } from './types';
package/dist/index.js CHANGED
@@ -28,7 +28,8 @@ export { ProjectsAPI } from './api/projects';
28
28
  export { MCPServersAPI } from './api/mcp-servers';
29
29
  // Live: the socket of a stream task and the live fields of its schemas
30
30
  export { LiveSession, accessUrl } from './live/session';
31
- export { STREAM_FORMAT, CLEAR_KEY, ERROR_KEY, isLiveField, parseMediaType, pcmFormat, splitLiveSchema, binaryLiveField, alternativeLabel, alternativeTag, } from './live/schema';
31
+ export { STREAM_FORMAT, CLEAR_KEY, ERROR_KEY, REDIAL_CODES } from './live/protocol';
32
+ export { isLiveField, parseMediaType, pcmFormat, splitLiveSchema, binaryLiveField, alternativeLabel, alternativeTag, } from './live/schema';
32
33
  // Tool Builder (fluent API)
33
34
  export { tool, appTool, agentTool, webhookTool, httpTool, callTool, mcpTool, internalTools, string, number, integer, boolean, enumOf, object, array, optional, } from './tool-builder';
34
35
  // Hook Builder (fluent API)
@@ -36,7 +37,7 @@ export { lifecycleHook } from './hook-builder';
36
37
  // Delta accumulator for streaming responses
37
38
  export { DeltaAccumulator, createLLMDeltaAccumulator } from './delta';
38
39
  // Status utilities (handle both int and string status values)
39
- export { parseStatus, isTerminalStatus, isChatBusy, pendingApprovals } from './utils';
40
+ export { parseStatus, isTerminalStatus, isChatBusy, isAwaitingHuman, isRunTerminal, isRunInterrupted, isRunSettled, isRunWorking, isMessageTerminal, pendingApprovals, } from './utils';
40
41
  // Types - includes TaskStatus constants and all DTOs
41
42
  export * from './types';
42
43
  // =============================================================================
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The live socket's wire protocol. sdk-py (inferencesh/models/stream.py)
3
+ * mirrors all of it and sdk-js-app (src/stream.ts) mirrors CLEAR_KEY and
4
+ * ERROR_KEY; change the copies together.
5
+ */
6
+ export declare const STREAM_FORMAT = "stream";
7
+ /** `{"$clear": "audio"}`: drop what has been buffered of a live output field. */
8
+ export declare const CLEAR_KEY = "$clear";
9
+ /**
10
+ * `{"$error": {"field": ..., "message": ...}}`: a refused frame, or anything
11
+ * else the caller should be told went wrong. The stream goes on.
12
+ */
13
+ export declare const ERROR_KEY = "$error";
14
+ /**
15
+ * Relay close codes that mean "dial again". 1012: the relay is restarting and
16
+ * closed an end that still waited for its peer. 1013: the peer did not come in
17
+ * time. Neither means anything once frames have flowed.
18
+ */
19
+ export declare const REDIAL_CODES: ReadonlySet<number>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The live socket's wire protocol. sdk-py (inferencesh/models/stream.py)
3
+ * mirrors all of it and sdk-js-app (src/stream.ts) mirrors CLEAR_KEY and
4
+ * ERROR_KEY; change the copies together.
5
+ */
6
+ export const STREAM_FORMAT = 'stream';
7
+ // Control frames. Reserved keys start with `$`, which no field name can, so a
8
+ // control frame is never mistaken for an output field.
9
+ /** `{"$clear": "audio"}`: drop what has been buffered of a live output field. */
10
+ export const CLEAR_KEY = '$clear';
11
+ /**
12
+ * `{"$error": {"field": ..., "message": ...}}`: a refused frame, or anything
13
+ * else the caller should be told went wrong. The stream goes on.
14
+ */
15
+ export const ERROR_KEY = '$error';
16
+ /**
17
+ * Relay close codes that mean "dial again". 1012: the relay is restarting and
18
+ * closed an end that still waited for its peer. 1013: the peer did not come in
19
+ * time. Neither means anything once frames have flowed.
20
+ */
21
+ export const REDIAL_CODES = new Set([1012, 1013]);
@@ -16,14 +16,6 @@
16
16
  * The helpers are generic over the schema type so a caller with a richer
17
17
  * JSON Schema type keeps it.
18
18
  */
19
- export declare const STREAM_FORMAT = "stream";
20
- /** `{"$clear": "audio"}`: drop what has been buffered of a live output field. */
21
- export declare const CLEAR_KEY = "$clear";
22
- /**
23
- * `{"$error": {"field": ..., "message": ...}}`: a refused frame, or anything
24
- * else the caller should be told went wrong. The stream goes on.
25
- */
26
- export declare const ERROR_KEY = "$error";
27
19
  /** The part of JSON Schema these helpers read. */
28
20
  export interface JsonSchema {
29
21
  $ref?: string;
@@ -16,16 +16,7 @@
16
16
  * The helpers are generic over the schema type so a caller with a richer
17
17
  * JSON Schema type keeps it.
18
18
  */
19
- export const STREAM_FORMAT = 'stream';
20
- // Control frames. Reserved keys start with `$`, which no field name can, so a
21
- // control frame is never mistaken for an output field.
22
- /** `{"$clear": "audio"}`: drop what has been buffered of a live output field. */
23
- export const CLEAR_KEY = '$clear';
24
- /**
25
- * `{"$error": {"field": ..., "message": ...}}`: a refused frame, or anything
26
- * else the caller should be told went wrong. The stream goes on.
27
- */
28
- export const ERROR_KEY = '$error';
19
+ import { STREAM_FORMAT } from './protocol';
29
20
  export function isLiveField(schema) {
30
21
  return !!schema && schema.format === STREAM_FORMAT;
31
22
  }
@@ -1,9 +1,6 @@
1
- import { binaryLiveField, CLEAR_KEY, ERROR_KEY, splitLiveSchema } from './schema';
1
+ import { CLEAR_KEY, ERROR_KEY, REDIAL_CODES } from './protocol';
2
+ import { binaryLiveField, splitLiveSchema } from './schema';
2
3
  const WS_OPEN = 1;
3
- // 1012: the relay is restarting and closed an end that still waited for its
4
- // peer. 1013: the peer did not come in time. Both mean "dial again" while the
5
- // task is alive, and neither means anything once frames have flowed.
6
- const REDIAL_CODES = new Set([1012, 1013]);
7
4
  const MAX_REDIALS = 5;
8
5
  function globalWebSocket() {
9
6
  const ctor = globalThis.WebSocket;