@inferencesh/sdk 0.6.49 → 0.6.51

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.
@@ -139,8 +139,18 @@ export function createActions(ctx) {
139
139
  checkTurnEnd(chatData);
140
140
  }
141
141
  });
142
+ // Token-by-token streaming state, reset at every assistant-message boundary.
143
+ const deltaAccum = createLLMDeltaAccumulator();
142
144
  // Listen for ChatMessage updates
143
145
  manager.addEventListener('chat_messages', (message, fields) => {
146
+ // A new assistant message starts a new accumulation. The accumulator is
147
+ // cumulative and shared across the whole connection, so without this the
148
+ // next message's deltas merge into the previous message's state — and
149
+ // because concat with "" is a no-op, a turn that is only tool calls
150
+ // would render the previous message's text as its own.
151
+ if (message.role === 'assistant' && !getState().messages.some(m => m.id === message.id)) {
152
+ deltaAccum.reset();
153
+ }
144
154
  updateMessage(message, fields);
145
155
  });
146
156
  // Listen for AgentRun updates (state transitions, output)
@@ -152,8 +162,6 @@ export function createActions(ctx) {
152
162
  if (currentChat)
153
163
  checkTurnEnd({ ...currentChat, active_run: run });
154
164
  });
155
- // Listen for LLM delta events (token-by-token streaming)
156
- const deltaAccum = createLLMDeltaAccumulator();
157
165
  manager.addEventListener('delta', (evt) => {
158
166
  if (evt && evt.delta) {
159
167
  deltaAccum.apply(evt.delta);
@@ -1,7 +1,38 @@
1
1
  import { StreamableManager } from '../http/streamable';
2
2
  import { PollManager } from '../http/poll';
3
- import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, } from '../types';
3
+ import { ChatMessageStatusCancelled, ChatMessageStatusFailed, ChatMessageStatusReady, ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, } from '../types';
4
4
  import { isChatBusy } from '../utils';
5
+ const terminalMessageStatuses = new Set([ChatMessageStatusReady, ChatMessageStatusFailed, ChatMessageStatusCancelled]);
6
+ /**
7
+ * Decides when one sendMessage() turn is over.
8
+ *
9
+ * For an existing chat the stream/poll is opened before the POST so nothing is
10
+ * missed, but its first snapshot is the previous turn's idle state. Resolving on
11
+ * that made sendMessage return before the new run had started. An idle snapshot
12
+ * only counts once the chat was seen busy, or once this turn's assistant message
13
+ * reached a terminal status (runs that finish between two observations).
14
+ */
15
+ class TurnGate {
16
+ constructor(existingChat) {
17
+ this.existingChat = existingChat;
18
+ this.assistantMessageId = null;
19
+ this.sawBusy = false;
20
+ this.assistantDone = false;
21
+ }
22
+ observeChat(chat) {
23
+ if (isChatBusy(chat))
24
+ this.sawBusy = true;
25
+ }
26
+ observeMessage(message) {
27
+ if (message.id === this.assistantMessageId && terminalMessageStatuses.has(message.status)) {
28
+ this.assistantDone = true;
29
+ }
30
+ }
31
+ /** Whether an idle observation may end the turn. */
32
+ get settled() {
33
+ return !this.existingChat || this.sawBusy || this.assistantDone;
34
+ }
35
+ }
5
36
  /**
6
37
  * Agent for chat interactions
7
38
  *
@@ -68,17 +99,28 @@ export class Agent {
68
99
  };
69
100
  const useStream = options.stream ?? this.http.getStreamDefault();
70
101
  const shouldWait = useStream === false || hasCallbacks;
102
+ const gate = new TurnGate(!!this.chatId);
71
103
  const waitFn = useStream === false
72
- ? (opts) => this.pollUntilIdle(opts)
73
- : (opts) => this.streamUntilIdle(opts);
104
+ ? (opts) => this.pollUntilIdle(opts, gate)
105
+ : (opts) => this.streamUntilIdle(opts, gate);
74
106
  // For existing chats: Start waiting BEFORE POST so we don't miss updates
75
107
  let waitPromise = null;
76
108
  if (this.chatId && shouldWait) {
77
109
  waitPromise = waitFn(options);
78
110
  }
79
111
  // Make the POST request
80
- const resp = await this.http.request('post', '/agents/run', { data: body });
81
- const response = resp.data;
112
+ let response;
113
+ try {
114
+ const resp = await this.http.request('post', '/agents/run', { data: body });
115
+ response = resp.data;
116
+ }
117
+ catch (err) {
118
+ // Nothing to wait for; don't leave the pre-opened stream/poller running.
119
+ if (waitPromise)
120
+ this.disconnect();
121
+ throw err;
122
+ }
123
+ gate.assistantMessageId = response.assistant_message?.id ?? null;
82
124
  // For new chats: Set chatId and start waiting immediately after POST
83
125
  const isNewChat = !this.chatId && response.assistant_message.chat_id;
84
126
  if (isNewChat) {
@@ -145,10 +187,10 @@ export class Agent {
145
187
  startStreaming(options = {}) {
146
188
  if (!this.chatId)
147
189
  return;
148
- this.streamUntilIdle(options);
190
+ this.streamUntilIdle(options, new TurnGate(false));
149
191
  }
150
192
  /** Stream events until chat becomes idle */
151
- streamUntilIdle(options) {
193
+ streamUntilIdle(options, gate) {
152
194
  if (!this.chatId)
153
195
  return Promise.resolve();
154
196
  const { url, headers, credentials } = this.http.getStreamableConfig(`/chats/${this.chatId}/stream`);
@@ -159,21 +201,37 @@ export class Agent {
159
201
  headers,
160
202
  credentials,
161
203
  });
204
+ // Last chat/run observation was idle but the gate wasn't settled yet;
205
+ // a terminal message for this turn can settle it.
206
+ let idlePending = false;
207
+ const onIdle = () => {
208
+ if (gate.settled)
209
+ resolve();
210
+ else
211
+ idlePending = true;
212
+ };
162
213
  this.stream.addEventListener('chats', (chat) => {
163
214
  options.onChat?.(chat);
164
- if (!isChatBusy(chat)) {
165
- resolve();
166
- }
215
+ gate.observeChat(chat);
216
+ if (!isChatBusy(chat))
217
+ onIdle();
218
+ else
219
+ idlePending = false;
167
220
  });
168
221
  this.stream.addEventListener('agent_runs', (run) => {
169
222
  const asChat = { active_run: run };
170
223
  options.onChat?.(asChat);
171
- if (!isChatBusy(asChat)) {
172
- resolve();
173
- }
224
+ gate.observeChat(asChat);
225
+ if (!isChatBusy(asChat))
226
+ onIdle();
227
+ else
228
+ idlePending = false;
174
229
  });
175
230
  this.stream.addEventListener('chat_messages', (message) => {
231
+ gate.observeMessage(message);
176
232
  options.onMessage?.(message);
233
+ if (idlePending && gate.settled)
234
+ resolve();
177
235
  if (message.tool_invocations && options.onToolCall) {
178
236
  for (const inv of message.tool_invocations) {
179
237
  if (this.dispatchedToolCalls.has(inv.id))
@@ -193,7 +251,7 @@ export class Agent {
193
251
  });
194
252
  }
195
253
  /** Poll until chat becomes idle, dispatching callbacks on changes */
196
- pollUntilIdle(options) {
254
+ pollUntilIdle(options, gate) {
197
255
  if (!this.chatId)
198
256
  return Promise.resolve();
199
257
  const intervalMs = options.pollIntervalMs ?? this.http.getPollIntervalMs();
@@ -206,7 +264,9 @@ export class Agent {
206
264
  // Lightweight status check first
207
265
  const statusResp = await this.http.request('get', `/chats/${this.chatId}/status`);
208
266
  const status = statusResp.data;
209
- if (status.status === prevStatus) {
267
+ // Unchanged status is skipped — unless the turn is still ungated: a run
268
+ // that started and finished between two polls only shows in the messages.
269
+ if (status.status === prevStatus && gate.settled) {
210
270
  // No change — return a stub to skip processing
211
271
  return { status: status.status };
212
272
  }
@@ -220,6 +280,9 @@ export class Agent {
220
280
  return;
221
281
  prevStatus = chat.status;
222
282
  options.onChat?.(chat);
283
+ gate.observeChat(chat);
284
+ for (const message of chat.chat_messages ?? [])
285
+ gate.observeMessage(message);
223
286
  // Dispatch new/updated messages
224
287
  if (chat.chat_messages && options.onMessage) {
225
288
  for (const message of chat.chat_messages) {
@@ -248,7 +311,7 @@ export class Agent {
248
311
  }
249
312
  }
250
313
  }
251
- if (!isChatBusy(chat)) {
314
+ if (!isChatBusy(chat) && gate.settled) {
252
315
  this.poller?.stop();
253
316
  this.poller = null;
254
317
  resolve();
package/dist/delta.d.ts CHANGED
@@ -8,6 +8,14 @@ export declare class DeltaAccumulator {
8
8
  private tags;
9
9
  private registry;
10
10
  constructor(tags: FieldTags, registry?: FieldTagsRegistry);
11
+ /**
12
+ * Discard everything accumulated so far. Required at a message boundary: the
13
+ * accumulator is cumulative, so without a reset the next message's deltas
14
+ * merge into the previous message's state. Concat makes that invisible for
15
+ * text — appending "" leaves the old text in place, so a tool-call-only turn
16
+ * serves the previous message's answer as its own.
17
+ */
18
+ reset(): void;
11
19
  seed(output: Record<string, any>): void;
12
20
  apply(delta: Record<string, any>): void;
13
21
  toOutput(): Record<string, any>;
package/dist/delta.js CHANGED
@@ -57,6 +57,16 @@ export class DeltaAccumulator {
57
57
  this.tags = tags;
58
58
  this.registry = registry;
59
59
  }
60
+ /**
61
+ * Discard everything accumulated so far. Required at a message boundary: the
62
+ * accumulator is cumulative, so without a reset the next message's deltas
63
+ * merge into the previous message's state. Concat makes that invisible for
64
+ * text — appending "" leaves the old text in place, so a tool-call-only turn
65
+ * serves the previous message's answer as its own.
66
+ */
67
+ reset() {
68
+ this.state = {};
69
+ }
60
70
  seed(output) {
61
71
  for (const [key, value] of Object.entries(output)) {
62
72
  if (value != null)
@@ -17,7 +17,11 @@ export class HttpClient {
17
17
  this.proxyUrl = config.proxyUrl;
18
18
  this.getToken = config.getToken;
19
19
  this.customHeaders = { 'X-Client-Source': 'inference-sdk-js/0.5.13', ...config.headers };
20
- this.credentials = config.credentials || 'include';
20
+ // Bearer auth needs no cookies, and 'include' makes browsers reject the
21
+ // response unless the API answers with Allow-Credentials — which it does not
22
+ // for third-party origins (WebKit reports that as "Load failed"). Cookies
23
+ // matter only for the proxy / getToken flows, which keep 'include'.
24
+ this.credentials = config.credentials || (config.apiKey ? 'omit' : 'include');
21
25
  this.onError = config.onError;
22
26
  this.onMessage = config.onMessage;
23
27
  this.streamDefault = config.stream ?? true;
package/dist/index.d.ts CHANGED
@@ -71,6 +71,11 @@ export interface InferenceConfig {
71
71
  * Polling interval in milliseconds when stream is false (default: 2000).
72
72
  */
73
73
  pollIntervalMs?: number;
74
+ /**
75
+ * fetch() credentials mode. Defaults to 'omit' with an apiKey and 'include'
76
+ * for proxyUrl / getToken flows that rely on cookies.
77
+ */
78
+ credentials?: RequestCredentials;
74
79
  }
75
80
  /**
76
81
  * Inference.sh SDK Client
package/dist/types.d.ts CHANGED
@@ -2709,6 +2709,11 @@ export interface MenuItem {
2709
2709
  id: string;
2710
2710
  label: string;
2711
2711
  slug?: string;
2712
+ /**
2713
+ * Path is the linked page's path, filled in when a menu is read so a client
2714
+ * can build the link without fetching each page. Never stored.
2715
+ */
2716
+ path?: string;
2712
2717
  page_id?: string;
2713
2718
  url?: string;
2714
2719
  icon?: string;
@@ -2729,6 +2734,7 @@ export interface PageDTO extends BaseModelDTO, PermissionModelDTO {
2729
2734
  type: PageType;
2730
2735
  metadata: PageMetadata;
2731
2736
  slug: string;
2737
+ path: string;
2732
2738
  /**
2733
2739
  * PublishAt mirrors Metadata.PublishAt, which remains the field clients write.
2734
2740
  * Surfaced here so a reader does not have to reach into the metadata blob.
@@ -4381,6 +4387,11 @@ export type RefRouteType = string;
4381
4387
  export declare const RefRouteTypeApp: RefRouteType;
4382
4388
  export declare const RefRouteTypeAgent: RefRouteType;
4383
4389
  export declare const RefRouteTypeSkill: RefRouteType;
4390
+ /**
4391
+ * RefRouteTypeURL routes a site path to another (/docs/api-files →
4392
+ * /docs/api/sdk/files). Alias and target are literal paths, not refs.
4393
+ */
4394
+ export declare const RefRouteTypeURL: RefRouteType;
4384
4395
  export type RefRouteMode = string;
4385
4396
  export declare const RefRouteModeRewrite: RefRouteMode;
4386
4397
  export declare const RefRouteModeRedirect: RefRouteMode;
package/dist/types.js CHANGED
@@ -678,6 +678,11 @@ export const TeamInviteStatusRevoked = "revoked";
678
678
  export const RefRouteTypeApp = "app";
679
679
  export const RefRouteTypeAgent = "agent";
680
680
  export const RefRouteTypeSkill = "skill";
681
+ /**
682
+ * RefRouteTypeURL routes a site path to another (/docs/api-files →
683
+ * /docs/api/sdk/files). Alias and target are literal paths, not refs.
684
+ */
685
+ export const RefRouteTypeURL = "url";
681
686
  export const RefRouteModeRewrite = "rewrite";
682
687
  export const RefRouteModeRedirect = "redirect";
683
688
  export const KnowledgeTypeConcept = "concept";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inferencesh/sdk",
3
- "version": "0.6.49",
3
+ "version": "0.6.51",
4
4
  "description": "Official JavaScript/TypeScript SDK for inference.sh - Run AI models with a simple API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",