@inferencesh/sdk 0.6.10 → 0.6.13
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/README.md +1 -1
- package/dist/agent/actions.js +3 -3
- package/dist/agent/actions.test.js +231 -1
- package/dist/agent/api.test.js +18 -1
- package/dist/api/agents.d.ts +10 -0
- package/dist/api/agents.js +17 -3
- package/dist/api/agents.test.js +500 -0
- package/dist/api/api-keys.d.ts +24 -0
- package/dist/api/api-keys.js +26 -0
- package/dist/api/api-keys.test.d.ts +1 -0
- package/dist/api/api-keys.test.js +44 -0
- package/dist/api/apps.js +1 -1
- package/dist/api/apps.test.js +71 -3
- package/dist/api/chats.d.ts +14 -0
- package/dist/api/chats.js +18 -0
- package/dist/api/chats.test.js +52 -0
- package/dist/api/engines.d.ts +12 -0
- package/dist/api/engines.js +18 -0
- package/dist/api/engines.test.js +78 -0
- package/dist/api/files.test.js +183 -0
- package/dist/api/flow-runs.test.js +42 -0
- package/dist/api/flows.d.ts +4 -0
- package/dist/api/flows.js +6 -0
- package/dist/api/flows.test.js +75 -0
- package/dist/api/integrations.d.ts +41 -0
- package/dist/api/integrations.js +56 -0
- package/dist/api/integrations.test.d.ts +1 -0
- package/dist/api/integrations.test.js +109 -0
- package/dist/api/knowledge.d.ts +112 -0
- package/dist/api/knowledge.js +163 -0
- package/dist/api/knowledge.test.d.ts +1 -0
- package/dist/api/knowledge.test.js +238 -0
- package/dist/api/mcp-servers.d.ts +45 -0
- package/dist/api/mcp-servers.js +62 -0
- package/dist/api/mcp-servers.test.d.ts +1 -0
- package/dist/api/mcp-servers.test.js +98 -0
- package/dist/api/projects.d.ts +29 -0
- package/dist/api/projects.js +38 -0
- package/dist/api/projects.test.d.ts +1 -0
- package/dist/api/projects.test.js +61 -0
- package/dist/api/search.d.ts +21 -0
- package/dist/api/search.js +20 -0
- package/dist/api/search.test.d.ts +1 -0
- package/dist/api/search.test.js +39 -0
- package/dist/api/secrets.d.ts +29 -0
- package/dist/api/secrets.js +38 -0
- package/dist/api/secrets.test.d.ts +1 -0
- package/dist/api/secrets.test.js +61 -0
- package/dist/api/sessions.test.js +12 -0
- package/dist/api/tasks.d.ts +13 -1
- package/dist/api/tasks.js +18 -0
- package/dist/api/tasks.test.js +171 -0
- package/dist/api/teams.d.ts +71 -0
- package/dist/api/teams.js +92 -0
- package/dist/api/teams.test.d.ts +1 -0
- package/dist/api/teams.test.js +139 -0
- package/dist/client.test.js +112 -1
- package/dist/http/client.d.ts +5 -0
- package/dist/http/client.js +42 -23
- package/dist/http/client.test.js +245 -0
- package/dist/http/errors.test.js +13 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/proxy/hono.test.d.ts +1 -0
- package/dist/proxy/hono.test.js +90 -0
- package/dist/proxy/nextjs.test.d.ts +1 -0
- package/dist/proxy/nextjs.test.js +125 -0
- package/dist/proxy/remix.test.d.ts +1 -0
- package/dist/proxy/remix.test.js +66 -0
- package/dist/proxy/svelte.test.d.ts +1 -0
- package/dist/proxy/svelte.test.js +69 -0
- package/dist/tool-builder.test.js +11 -1
- package/dist/types.d.ts +110 -5
- package/dist/types.js +58 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
official javascript/typescript sdk for [inference.sh](https://inference.sh) — the ai agent runtime for serverless ai inference.
|
|
9
9
|
|
|
10
|
-
run ai models, build ai agents, and deploy generative ai applications with a simple api. access
|
|
10
|
+
run ai models, build ai agents, and deploy generative ai applications with a simple api. access models including flux, stable diffusion, llms (claude, gpt, gemini), video generation (veo, seedance), and more.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
package/dist/agent/actions.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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, ToolTypeClient, ChatStatusBusy, } from '../types';
|
|
7
|
+
import { ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, ChatStatusBusy, } from '../types';
|
|
8
8
|
import { StreamableManager } from '../http/streamable';
|
|
9
9
|
import { PollManager } from '../http/poll';
|
|
10
10
|
import { isAdHocConfig, extractClientToolHandlers } from './types';
|
|
@@ -35,12 +35,12 @@ export function createActions(ctx) {
|
|
|
35
35
|
if (chatId && message.chat_id !== chatId && !message.chat_id.startsWith(chatId))
|
|
36
36
|
return;
|
|
37
37
|
dispatch({ type: 'UPDATE_MESSAGE', payload: message });
|
|
38
|
-
//
|
|
38
|
+
// Dispatch client tool handlers when ready (in_progress or awaiting_input for backwards compat)
|
|
39
39
|
const clientToolHandlers = getClientToolHandlers();
|
|
40
40
|
if (message.tool_invocations && chatId && clientToolHandlers.size > 0) {
|
|
41
41
|
for (const invocation of message.tool_invocations) {
|
|
42
42
|
if (invocation.type === ToolTypeClient &&
|
|
43
|
-
invocation.status === ToolInvocationStatusAwaitingInput) {
|
|
43
|
+
(invocation.status === ToolInvocationStatusInProgress || invocation.status === ToolInvocationStatusAwaitingInput)) {
|
|
44
44
|
// Skip if already dispatched
|
|
45
45
|
if (dispatchedToolInvocations.has(invocation.id)) {
|
|
46
46
|
continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatStatusBusy, ToolInvocationStatusAwaitingInput, ToolTypeClient, } from '../types';
|
|
1
|
+
import { ChatStatusBusy, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, } from '../types';
|
|
2
2
|
import { createActions } from './actions';
|
|
3
3
|
import * as agentApi from './api';
|
|
4
4
|
import { PollManager } from '../http/poll';
|
|
@@ -94,6 +94,12 @@ describe('createActions', () => {
|
|
|
94
94
|
mockAgentApi.approveTool.mockResolvedValue(undefined);
|
|
95
95
|
mockAgentApi.rejectTool.mockResolvedValue(undefined);
|
|
96
96
|
mockAgentApi.alwaysAllowTool.mockResolvedValue(undefined);
|
|
97
|
+
mockAgentApi.uploadFile.mockResolvedValue({
|
|
98
|
+
id: 'file-1',
|
|
99
|
+
uri: 'inf://files/uploaded',
|
|
100
|
+
filename: 'notes.txt',
|
|
101
|
+
content_type: 'text/plain',
|
|
102
|
+
});
|
|
97
103
|
});
|
|
98
104
|
describe('updateMessage (via stream listeners)', () => {
|
|
99
105
|
it('should ignore messages for a different chat when IDs do not prefix-match', async () => {
|
|
@@ -141,6 +147,30 @@ describe('createActions', () => {
|
|
|
141
147
|
expect(handler).toHaveBeenCalledWith({ x: 1 });
|
|
142
148
|
expect(mockAgentApi.submitToolResult).toHaveBeenCalledWith(ctx.client, 'tool-inv-ok', 'tool ok');
|
|
143
149
|
});
|
|
150
|
+
it('should dispatch client tool handlers when status is in_progress', async () => {
|
|
151
|
+
const handler = jest.fn().mockResolvedValue('in progress ok');
|
|
152
|
+
const { ctx } = createTestContext({
|
|
153
|
+
getClientToolHandlers: () => new Map([['my_tool', handler]]),
|
|
154
|
+
});
|
|
155
|
+
const { internalActions } = createActions(ctx);
|
|
156
|
+
internalActions.streamChat('chat-full-id-123');
|
|
157
|
+
await Promise.resolve();
|
|
158
|
+
const onMessage = streamInstances[0].addEventListener.mock.calls.find(([event]) => event === 'chat_messages')?.[1];
|
|
159
|
+
onMessage(makeMessage({
|
|
160
|
+
chat_id: 'chat-short',
|
|
161
|
+
tool_invocations: [
|
|
162
|
+
{
|
|
163
|
+
id: 'tool-inv-progress',
|
|
164
|
+
type: ToolTypeClient,
|
|
165
|
+
status: ToolInvocationStatusInProgress,
|
|
166
|
+
function: { name: 'my_tool', arguments: { y: 2 } },
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}));
|
|
170
|
+
await Promise.resolve();
|
|
171
|
+
expect(handler).toHaveBeenCalledWith({ y: 2 });
|
|
172
|
+
expect(mockAgentApi.submitToolResult).toHaveBeenCalledWith(ctx.client, 'tool-inv-progress', 'in progress ok');
|
|
173
|
+
});
|
|
144
174
|
it('should submit a JSON error when a client tool handler throws', async () => {
|
|
145
175
|
const handler = jest.fn().mockRejectedValue(new Error('handler boom'));
|
|
146
176
|
const { ctx } = createTestContext({
|
|
@@ -216,6 +246,49 @@ describe('createActions', () => {
|
|
|
216
246
|
// Only the explicit stopStream dispatch, not a second from onEnd
|
|
217
247
|
expect(idleDispatches).toHaveLength(1);
|
|
218
248
|
});
|
|
249
|
+
it('should clear the manager ref before poll stop so onStop does not double-dispatch idle', async () => {
|
|
250
|
+
const onStatusChange = jest.fn();
|
|
251
|
+
const { ctx, dispatch, setStreamManager } = createTestContext({
|
|
252
|
+
getStreamEnabled: () => false,
|
|
253
|
+
callbacks: { onStatusChange },
|
|
254
|
+
});
|
|
255
|
+
const { internalActions } = createActions(ctx);
|
|
256
|
+
internalActions.streamChat('chat-full-id-123');
|
|
257
|
+
await Promise.resolve();
|
|
258
|
+
const manager = pollInstances[0];
|
|
259
|
+
internalActions.stopStream();
|
|
260
|
+
expect(setStreamManager).toHaveBeenCalledWith(undefined);
|
|
261
|
+
expect(manager.stop).toHaveBeenCalled();
|
|
262
|
+
manager.options.onStop?.();
|
|
263
|
+
const idleDispatches = dispatch.mock.calls.filter(([action]) => action.type === 'SET_CONNECTION_STATUS' && action.payload === 'idle');
|
|
264
|
+
expect(idleDispatches).toHaveLength(1);
|
|
265
|
+
expect(onStatusChange).toHaveBeenCalledWith('idle');
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
describe('stream and poll error callbacks', () => {
|
|
269
|
+
it('should forward stream errors to onError callback', async () => {
|
|
270
|
+
const onError = jest.fn();
|
|
271
|
+
const { ctx } = createTestContext({ callbacks: { onError } });
|
|
272
|
+
const { internalActions } = createActions(ctx);
|
|
273
|
+
internalActions.streamChat('chat-full-id-123');
|
|
274
|
+
await Promise.resolve();
|
|
275
|
+
const streamError = new Error('stream connection lost');
|
|
276
|
+
streamInstances[0].options.onError?.(streamError);
|
|
277
|
+
expect(onError).toHaveBeenCalledWith(streamError);
|
|
278
|
+
});
|
|
279
|
+
it('should forward poll manager errors to onError callback', async () => {
|
|
280
|
+
const onError = jest.fn();
|
|
281
|
+
const { ctx } = createTestContext({
|
|
282
|
+
getStreamEnabled: () => false,
|
|
283
|
+
callbacks: { onError },
|
|
284
|
+
});
|
|
285
|
+
const { internalActions } = createActions(ctx);
|
|
286
|
+
internalActions.streamChat('chat-full-id-123');
|
|
287
|
+
await Promise.resolve();
|
|
288
|
+
const pollError = new Error('poll transport failed');
|
|
289
|
+
pollInstances[0].options.onError?.(pollError);
|
|
290
|
+
expect(onError).toHaveBeenCalledWith(pollError);
|
|
291
|
+
});
|
|
219
292
|
});
|
|
220
293
|
describe('publicActions.sendMessage', () => {
|
|
221
294
|
it('should call onChatCreated and start streaming for a new chat', async () => {
|
|
@@ -265,6 +338,19 @@ describe('createActions', () => {
|
|
|
265
338
|
await publicActions.sendMessage(' ');
|
|
266
339
|
expect(mockAgentApi.sendMessage).not.toHaveBeenCalled();
|
|
267
340
|
});
|
|
341
|
+
it('should no-op when agent config is missing', async () => {
|
|
342
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
343
|
+
const { ctx, dispatch } = createTestContext({ getConfig: () => null });
|
|
344
|
+
const { publicActions } = createActions(ctx);
|
|
345
|
+
await publicActions.sendMessage('hello');
|
|
346
|
+
expect(mockAgentApi.sendMessage).not.toHaveBeenCalled();
|
|
347
|
+
expect(dispatch).not.toHaveBeenCalledWith({
|
|
348
|
+
type: 'SET_CONNECTION_STATUS',
|
|
349
|
+
payload: 'streaming',
|
|
350
|
+
});
|
|
351
|
+
expect(consoleError).toHaveBeenCalledWith('[AgentSDK] No agent config provided');
|
|
352
|
+
consoleError.mockRestore();
|
|
353
|
+
});
|
|
268
354
|
});
|
|
269
355
|
describe('streamChat error handling', () => {
|
|
270
356
|
it('should reset to idle when initial fetchChat fails', async () => {
|
|
@@ -348,6 +434,77 @@ describe('createActions', () => {
|
|
|
348
434
|
await Promise.resolve();
|
|
349
435
|
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'poll fetch failed' }));
|
|
350
436
|
});
|
|
437
|
+
it('should not refetch chat when poll status is unchanged', async () => {
|
|
438
|
+
const { ctx: baseCtx } = createTestContext({ getStreamEnabled: () => false });
|
|
439
|
+
const { ctx } = createTestContext({
|
|
440
|
+
getStreamEnabled: () => false,
|
|
441
|
+
client: {
|
|
442
|
+
...baseCtx.client,
|
|
443
|
+
http: { ...baseCtx.client.http, request: jest.fn().mockResolvedValue({ status: ChatStatusBusy }) },
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
const { internalActions } = createActions(ctx);
|
|
447
|
+
mockAgentApi.fetchChat.mockResolvedValue({
|
|
448
|
+
id: 'chat-full-id-123',
|
|
449
|
+
status: ChatStatusBusy,
|
|
450
|
+
chat_messages: [],
|
|
451
|
+
});
|
|
452
|
+
internalActions.streamChat('chat-full-id-123');
|
|
453
|
+
await Promise.resolve();
|
|
454
|
+
await pollInstances[0].options.onData?.({ status: ChatStatusBusy });
|
|
455
|
+
await Promise.resolve();
|
|
456
|
+
const fetchCountAfterFirst = mockAgentApi.fetchChat.mock.calls.length;
|
|
457
|
+
await pollInstances[0].options.onData?.({ status: ChatStatusBusy });
|
|
458
|
+
await Promise.resolve();
|
|
459
|
+
expect(mockAgentApi.fetchChat).toHaveBeenCalledTimes(fetchCountAfterFirst);
|
|
460
|
+
});
|
|
461
|
+
it('should dispatch client tools from poll path when status changes', async () => {
|
|
462
|
+
const handler = jest.fn().mockResolvedValue('poll ok');
|
|
463
|
+
const toolMessage = makeMessage({
|
|
464
|
+
chat_id: 'chat-short',
|
|
465
|
+
tool_invocations: [
|
|
466
|
+
{
|
|
467
|
+
id: 'tool-inv-poll',
|
|
468
|
+
type: ToolTypeClient,
|
|
469
|
+
status: ToolInvocationStatusInProgress,
|
|
470
|
+
function: { name: 'my_tool', arguments: { y: 2 } },
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
});
|
|
474
|
+
const { ctx: baseCtx } = createTestContext({
|
|
475
|
+
getStreamEnabled: () => false,
|
|
476
|
+
getClientToolHandlers: () => new Map([['my_tool', handler]]),
|
|
477
|
+
});
|
|
478
|
+
const { ctx } = createTestContext({
|
|
479
|
+
getStreamEnabled: () => false,
|
|
480
|
+
getClientToolHandlers: () => new Map([['my_tool', handler]]),
|
|
481
|
+
client: {
|
|
482
|
+
...baseCtx.client,
|
|
483
|
+
http: {
|
|
484
|
+
...baseCtx.client.http,
|
|
485
|
+
request: jest.fn().mockResolvedValue({ status: ChatStatusBusy }),
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
const { internalActions } = createActions(ctx);
|
|
490
|
+
mockAgentApi.fetchChat
|
|
491
|
+
.mockResolvedValueOnce({
|
|
492
|
+
id: 'chat-full-id-123',
|
|
493
|
+
status: ChatStatusBusy,
|
|
494
|
+
chat_messages: [],
|
|
495
|
+
})
|
|
496
|
+
.mockResolvedValueOnce({
|
|
497
|
+
id: 'chat-full-id-123',
|
|
498
|
+
status: ChatStatusBusy,
|
|
499
|
+
chat_messages: [toolMessage],
|
|
500
|
+
});
|
|
501
|
+
internalActions.streamChat('chat-full-id-123');
|
|
502
|
+
await Promise.resolve();
|
|
503
|
+
await pollInstances[0].options.onData?.({ status: 'idle' });
|
|
504
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
505
|
+
expect(handler).toHaveBeenCalledWith({ y: 2 });
|
|
506
|
+
expect(mockAgentApi.submitToolResult).toHaveBeenCalledWith(ctx.client, 'tool-inv-poll', 'poll ok');
|
|
507
|
+
});
|
|
351
508
|
});
|
|
352
509
|
describe('client tool deduplication', () => {
|
|
353
510
|
it('should not submit the same client tool invocation twice', async () => {
|
|
@@ -378,6 +535,14 @@ describe('createActions', () => {
|
|
|
378
535
|
});
|
|
379
536
|
});
|
|
380
537
|
describe('publicActions lifecycle', () => {
|
|
538
|
+
it('uploadFile should delegate to the API layer', async () => {
|
|
539
|
+
const file = new File(['hello'], 'notes.txt', { type: 'text/plain' });
|
|
540
|
+
const { ctx } = createTestContext();
|
|
541
|
+
const { publicActions } = createActions(ctx);
|
|
542
|
+
const result = await publicActions.uploadFile(file);
|
|
543
|
+
expect(mockAgentApi.uploadFile).toHaveBeenCalledWith(ctx.client, file);
|
|
544
|
+
expect(result).toEqual(expect.objectContaining({ uri: 'inf://files/uploaded', filename: 'notes.txt' }));
|
|
545
|
+
});
|
|
381
546
|
it('reset should stop stream and dispatch RESET', async () => {
|
|
382
547
|
const { ctx, dispatch } = createTestContext();
|
|
383
548
|
const { publicActions } = createActions(ctx);
|
|
@@ -391,6 +556,12 @@ describe('createActions', () => {
|
|
|
391
556
|
publicActions.stopGeneration();
|
|
392
557
|
expect(mockAgentApi.stopChat).toHaveBeenCalledWith(ctx.client, 'chat-short');
|
|
393
558
|
});
|
|
559
|
+
it('stopGeneration should no-op when there is no chatId', () => {
|
|
560
|
+
const { ctx } = createTestContext({ getChatId: () => null });
|
|
561
|
+
const { publicActions } = createActions(ctx);
|
|
562
|
+
publicActions.stopGeneration();
|
|
563
|
+
expect(mockAgentApi.stopChat).not.toHaveBeenCalled();
|
|
564
|
+
});
|
|
394
565
|
it('submitToolResult should set error state when API fails', async () => {
|
|
395
566
|
mockAgentApi.submitToolResult.mockRejectedValueOnce(new Error('submit failed'));
|
|
396
567
|
const onError = jest.fn();
|
|
@@ -416,6 +587,38 @@ describe('createActions', () => {
|
|
|
416
587
|
payload: 'idle',
|
|
417
588
|
});
|
|
418
589
|
});
|
|
590
|
+
it('reset should allow the same client tool invocation to dispatch again', async () => {
|
|
591
|
+
const handler = jest.fn().mockResolvedValue('ok');
|
|
592
|
+
const { ctx } = createTestContext({
|
|
593
|
+
getClientToolHandlers: () => new Map([['my_tool', handler]]),
|
|
594
|
+
});
|
|
595
|
+
const { publicActions, internalActions } = createActions(ctx);
|
|
596
|
+
internalActions.streamChat('chat-full-id-123');
|
|
597
|
+
await Promise.resolve();
|
|
598
|
+
const onMessage = streamInstances[0].addEventListener.mock.calls.find(([event]) => event === 'chat_messages')?.[1];
|
|
599
|
+
const toolMessage = makeMessage({
|
|
600
|
+
chat_id: 'chat-short',
|
|
601
|
+
tool_invocations: [
|
|
602
|
+
{
|
|
603
|
+
id: 'tool-inv-reset',
|
|
604
|
+
type: ToolTypeClient,
|
|
605
|
+
status: ToolInvocationStatusAwaitingInput,
|
|
606
|
+
function: { name: 'my_tool', arguments: {} },
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
});
|
|
610
|
+
onMessage(toolMessage);
|
|
611
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
612
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
613
|
+
publicActions.reset();
|
|
614
|
+
internalActions.streamChat('chat-full-id-123');
|
|
615
|
+
await Promise.resolve();
|
|
616
|
+
const onMessageAfterReset = streamInstances[1].addEventListener.mock.calls.find(([event]) => event === 'chat_messages')?.[1];
|
|
617
|
+
onMessageAfterReset(toolMessage);
|
|
618
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
619
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
620
|
+
expect(mockAgentApi.submitToolResult).toHaveBeenCalledTimes(2);
|
|
621
|
+
});
|
|
419
622
|
});
|
|
420
623
|
describe('HIL tool actions', () => {
|
|
421
624
|
it('approveTool should delegate to the API', async () => {
|
|
@@ -454,6 +657,33 @@ describe('createActions', () => {
|
|
|
454
657
|
});
|
|
455
658
|
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'approve failed' }));
|
|
456
659
|
});
|
|
660
|
+
it('rejectTool should set error state when API fails', async () => {
|
|
661
|
+
mockAgentApi.rejectTool.mockRejectedValueOnce(new Error('reject failed'));
|
|
662
|
+
const onError = jest.fn();
|
|
663
|
+
const { ctx, dispatch } = createTestContext({ callbacks: { onError } });
|
|
664
|
+
const { publicActions } = createActions(ctx);
|
|
665
|
+
await expect(publicActions.rejectTool('inv-1', 'unsafe')).rejects.toThrow('reject failed');
|
|
666
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
667
|
+
type: 'SET_CONNECTION_STATUS',
|
|
668
|
+
payload: 'error',
|
|
669
|
+
});
|
|
670
|
+
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'reject failed' }));
|
|
671
|
+
});
|
|
672
|
+
it('alwaysAllowTool should set error state when API fails', async () => {
|
|
673
|
+
mockAgentApi.alwaysAllowTool.mockRejectedValueOnce(new Error('allow failed'));
|
|
674
|
+
const onError = jest.fn();
|
|
675
|
+
const { ctx, dispatch } = createTestContext({
|
|
676
|
+
getChatId: () => 'chat-short',
|
|
677
|
+
callbacks: { onError },
|
|
678
|
+
});
|
|
679
|
+
const { publicActions } = createActions(ctx);
|
|
680
|
+
await expect(publicActions.alwaysAllowTool('inv-1', 'my_tool')).rejects.toThrow('allow failed');
|
|
681
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
682
|
+
type: 'SET_CONNECTION_STATUS',
|
|
683
|
+
payload: 'error',
|
|
684
|
+
});
|
|
685
|
+
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'allow failed' }));
|
|
686
|
+
});
|
|
457
687
|
});
|
|
458
688
|
describe('setChatId', () => {
|
|
459
689
|
it('should no-op when the chat id is unchanged', async () => {
|
package/dist/agent/api.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpClient } from '../http/client';
|
|
2
2
|
import { FilesAPI } from '../api/files';
|
|
3
|
-
import { sendAdHocMessage, sendTemplateMessage, sendMessage, submitToolResult, approveTool, rejectTool, alwaysAllowTool, fetchChat, stopChat, getChatStreamConfig, } from './api';
|
|
3
|
+
import { sendAdHocMessage, sendTemplateMessage, sendMessage, submitToolResult, approveTool, rejectTool, alwaysAllowTool, fetchChat, stopChat, getChatStreamConfig, uploadFile, } from './api';
|
|
4
4
|
import { ToolTypeClient } from '../types';
|
|
5
5
|
const mockFetch = jest.fn();
|
|
6
6
|
global.fetch = mockFetch;
|
|
@@ -205,4 +205,21 @@ describe('agent/api', () => {
|
|
|
205
205
|
expect(config.headers).toEqual(expect.objectContaining({ Authorization: expect.stringContaining('Bearer') }));
|
|
206
206
|
});
|
|
207
207
|
});
|
|
208
|
+
describe('uploadFile', () => {
|
|
209
|
+
it('should delegate to client.files.upload and return the uploaded file ref', async () => {
|
|
210
|
+
const client = makeClient();
|
|
211
|
+
const fileRecord = {
|
|
212
|
+
id: 'file-1',
|
|
213
|
+
uri: 'inf://files/uploaded',
|
|
214
|
+
filename: 'notes.txt',
|
|
215
|
+
content_type: 'text/plain',
|
|
216
|
+
};
|
|
217
|
+
const uploadSpy = jest.spyOn(client.files, 'upload').mockResolvedValue(fileRecord);
|
|
218
|
+
const file = new File(['hello'], 'notes.txt', { type: 'text/plain' });
|
|
219
|
+
const result = await uploadFile(client, file);
|
|
220
|
+
expect(uploadSpy).toHaveBeenCalledWith(file);
|
|
221
|
+
expect(result).toEqual(fileRecord);
|
|
222
|
+
uploadSpy.mockRestore();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
208
225
|
});
|
package/dist/api/agents.d.ts
CHANGED
|
@@ -144,10 +144,20 @@ export declare class AgentsAPI {
|
|
|
144
144
|
* Get a specific agent version
|
|
145
145
|
*/
|
|
146
146
|
getVersion(agentId: string, versionId: string): Promise<AgentVersionDTO>;
|
|
147
|
+
/**
|
|
148
|
+
* Get an agent by namespace/name (e.g., "inference/my-agent")
|
|
149
|
+
*/
|
|
150
|
+
getByName(namespace: string, name: string): Promise<AgentDTO>;
|
|
147
151
|
/**
|
|
148
152
|
* Get internal tools for the agent
|
|
149
153
|
*/
|
|
150
154
|
getInternalTools(): Promise<InternalToolDefinition[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Get A2A protocol agent card for an agent.
|
|
157
|
+
* Returns raw JSON (not wrapped in {data:...}) for direct upload to GCP Producer Portal.
|
|
158
|
+
* Spec: https://a2a-protocol.org/latest/specification/
|
|
159
|
+
*/
|
|
160
|
+
getA2ACard(agentId: string): Promise<Record<string, unknown>>;
|
|
151
161
|
/**
|
|
152
162
|
* Create an agent instance for chat interactions
|
|
153
163
|
*/
|
package/dist/api/agents.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StreamableManager } from '../http/streamable';
|
|
2
2
|
import { PollManager } from '../http/poll';
|
|
3
|
-
import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ChatStatusBusy, } from '../types';
|
|
3
|
+
import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ChatStatusBusy, } from '../types';
|
|
4
4
|
/**
|
|
5
5
|
* Agent for chat interactions
|
|
6
6
|
*
|
|
@@ -166,7 +166,7 @@ export class Agent {
|
|
|
166
166
|
for (const inv of message.tool_invocations) {
|
|
167
167
|
if (this.dispatchedToolCalls.has(inv.id))
|
|
168
168
|
continue;
|
|
169
|
-
if (inv.type === ToolTypeClient && inv.status === ToolInvocationStatusAwaitingInput) {
|
|
169
|
+
if (inv.type === ToolTypeClient && (inv.status === ToolInvocationStatusInProgress || inv.status === ToolInvocationStatusAwaitingInput)) {
|
|
170
170
|
this.dispatchedToolCalls.add(inv.id);
|
|
171
171
|
options.onToolCall({
|
|
172
172
|
id: inv.id,
|
|
@@ -222,7 +222,7 @@ export class Agent {
|
|
|
222
222
|
for (const inv of message.tool_invocations) {
|
|
223
223
|
if (this.dispatchedToolCalls.has(inv.id))
|
|
224
224
|
continue;
|
|
225
|
-
if (inv.type === ToolTypeClient && inv.status === ToolInvocationStatusAwaitingInput) {
|
|
225
|
+
if (inv.type === ToolTypeClient && (inv.status === ToolInvocationStatusInProgress || inv.status === ToolInvocationStatusAwaitingInput)) {
|
|
226
226
|
this.dispatchedToolCalls.add(inv.id);
|
|
227
227
|
options.onToolCall({
|
|
228
228
|
id: inv.id,
|
|
@@ -319,12 +319,26 @@ export class AgentsAPI {
|
|
|
319
319
|
async getVersion(agentId, versionId) {
|
|
320
320
|
return this.http.request('get', `/agents/${agentId}/versions/${versionId}`);
|
|
321
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Get an agent by namespace/name (e.g., "inference/my-agent")
|
|
324
|
+
*/
|
|
325
|
+
async getByName(namespace, name) {
|
|
326
|
+
return this.http.request('get', `/agents/${namespace}/${name}`);
|
|
327
|
+
}
|
|
322
328
|
/**
|
|
323
329
|
* Get internal tools for the agent
|
|
324
330
|
*/
|
|
325
331
|
async getInternalTools() {
|
|
326
332
|
return this.http.request('get', '/agents/internal-tools');
|
|
327
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Get A2A protocol agent card for an agent.
|
|
336
|
+
* Returns raw JSON (not wrapped in {data:...}) for direct upload to GCP Producer Portal.
|
|
337
|
+
* Spec: https://a2a-protocol.org/latest/specification/
|
|
338
|
+
*/
|
|
339
|
+
async getA2ACard(agentId) {
|
|
340
|
+
return this.http.request('get', `/agents/${agentId}/card`);
|
|
341
|
+
}
|
|
328
342
|
// ==========================================================================
|
|
329
343
|
// Agent Runtime (chat interactions)
|
|
330
344
|
// ==========================================================================
|