@inferencesh/sdk 0.6.8 → 0.6.12
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/CHANGELOG.md +7 -1
- package/README.md +70 -1
- package/dist/agent/actions.js +3 -3
- package/dist/agent/actions.test.js +89 -0
- package/dist/agent/api.test.js +86 -8
- package/dist/api/agents.d.ts +10 -0
- package/dist/api/agents.js +17 -3
- package/dist/api/agents.test.js +303 -92
- 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.d.ts +1 -0
- package/dist/api/apps.test.js +135 -0
- package/dist/api/chats.d.ts +14 -0
- package/dist/api/chats.js +18 -0
- package/dist/api/chats.test.d.ts +1 -0
- package/dist/api/chats.test.js +85 -0
- package/dist/api/engines.d.ts +12 -0
- package/dist/api/engines.js +18 -0
- package/dist/api/engines.test.d.ts +1 -0
- package/dist/api/engines.test.js +133 -0
- package/dist/api/files.test.js +186 -6
- package/dist/api/flow-runs.test.d.ts +1 -0
- package/dist/api/flow-runs.test.js +97 -0
- package/dist/api/flows.d.ts +4 -0
- package/dist/api/flows.js +6 -0
- package/dist/api/flows.test.d.ts +1 -0
- package/dist/api/flows.test.js +118 -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 +94 -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 +64 -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 +52 -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 +4 -4
- package/dist/api/tasks.d.ts +13 -1
- package/dist/api/tasks.js +18 -0
- package/dist/api/tasks.test.js +174 -22
- 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 +79 -0
- package/dist/client.test.js +8 -8
- package/dist/http/client.d.ts +5 -0
- package/dist/http/client.js +46 -48
- package/dist/http/client.test.js +296 -13
- package/dist/http/errors.test.js +13 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/proxy/express.test.d.ts +1 -0
- package/dist/proxy/express.test.js +106 -0
- package/dist/proxy/index.test.js +10 -1
- package/dist/tool-builder.test.js +11 -1
- package/dist/types.d.ts +793 -23
- package/dist/types.js +180 -8
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.8] - 2026-05-20
|
|
11
|
+
|
|
10
12
|
### Added
|
|
11
13
|
|
|
14
|
+
- README tool builder section: `httpTool`/`callTool` auth, `mcpTool`, and builder comparison table
|
|
15
|
+
- `examples/tool-builder.ts` demonstrates HTTP and MCP tool schemas
|
|
16
|
+
|
|
12
17
|
- Typed SDK constants for integrations: `IntegrationProvider*`, `IntegrationAuthType*`, `IntegrationStatus*`
|
|
13
18
|
- `IntegrationDTO` fields (`provider`, `type`, `auth`, `status`) now use those typed aliases
|
|
14
19
|
- Additional `InstanceStatus*` constants (`creating`, `pending_provider`, `error`, `deleting`)
|
|
@@ -65,7 +70,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
65
70
|
- Configurable reconnection behavior
|
|
66
71
|
- Comprehensive error handling
|
|
67
72
|
|
|
68
|
-
[Unreleased]: https://github.com/inference-sh/sdk-js/compare/v0.6.
|
|
73
|
+
[Unreleased]: https://github.com/inference-sh/sdk-js/compare/v0.6.8...HEAD
|
|
74
|
+
[0.6.8]: https://github.com/inference-sh/sdk-js/compare/v0.6.7...v0.6.8
|
|
69
75
|
[0.6.7]: https://github.com/inference-sh/sdk-js/compare/v0.6.6...v0.6.7
|
|
70
76
|
[0.1.1]: https://github.com/inference-sh/sdk-js/compare/v0.1.0...v0.1.1
|
|
71
77
|
[0.1.0]: https://github.com/inference-sh/sdk-js/releases/tag/v0.1.0
|
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
|
|
|
@@ -340,6 +340,75 @@ await agent.sendMessage('What is the weather in Paris?', {
|
|
|
340
340
|
|
|
341
341
|
For multi-turn chats, the SDK opens the chat stream before sending the next message so updates are not missed. Use `stopChat()` to cancel in-flight generation (`POST /chats/{id}/stop`), and `reset()` to clear the current chat and start fresh.
|
|
342
342
|
|
|
343
|
+
### Tool builder
|
|
344
|
+
|
|
345
|
+
Use the fluent builders to define `AgentTool` schemas. Client tools (`tool`) run in your app via `onToolCall`; server-side tools run on inference.sh.
|
|
346
|
+
|
|
347
|
+
| Builder | Runs on | Description |
|
|
348
|
+
|---------|---------|-------------|
|
|
349
|
+
| `tool(name)` | Client | Local handler; only the schema is sent to the API |
|
|
350
|
+
| `appTool(name, appRef)` | Server | Invoke another inference app |
|
|
351
|
+
| `agentTool(name, agentRef)` | Server | Delegate to a sub-agent |
|
|
352
|
+
| `httpTool(name, url)` / `callTool(name, url)` | Server | HTTP request with credential injection (preferred over `webhookTool`) |
|
|
353
|
+
| `webhookTool(name, url)` | Server | Unsigned webhook (legacy; use `httpTool` for new tools) |
|
|
354
|
+
| `mcpTool(name, integrationId, toolName)` | Server | Call a tool on a connected MCP integration |
|
|
355
|
+
| `internalTools()` | Server | Built-in plan, memory, and widget tools |
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import {
|
|
359
|
+
inference,
|
|
360
|
+
tool,
|
|
361
|
+
appTool,
|
|
362
|
+
httpTool,
|
|
363
|
+
mcpTool,
|
|
364
|
+
internalTools,
|
|
365
|
+
string,
|
|
366
|
+
IntegrationProviderGoogle,
|
|
367
|
+
} from '@inferencesh/sdk';
|
|
368
|
+
|
|
369
|
+
const clientTool = tool('get_weather')
|
|
370
|
+
.describe('Get current weather')
|
|
371
|
+
.param('city', string('City name'))
|
|
372
|
+
.build();
|
|
373
|
+
|
|
374
|
+
// HTTP tool with OAuth integration credentials (injected server-side)
|
|
375
|
+
const gmailSend = httpTool('gmail_send', 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send')
|
|
376
|
+
.describe('Send an email via Gmail')
|
|
377
|
+
.method('POST')
|
|
378
|
+
.auth({ integration: IntegrationProviderGoogle, integrationId: 'your-integration-id' })
|
|
379
|
+
.build();
|
|
380
|
+
|
|
381
|
+
// API key or bearer auth
|
|
382
|
+
const fetchData = httpTool('fetch', 'https://api.example.com/data')
|
|
383
|
+
.method('GET')
|
|
384
|
+
.auth({ apiKey: 'YOUR_KEY', header: 'X-API-Key' }) // default header: X-API-Key
|
|
385
|
+
.header('Accept', 'application/json')
|
|
386
|
+
.build();
|
|
387
|
+
|
|
388
|
+
const bearerFetch = httpTool('bearer_fetch', 'https://api.example.com')
|
|
389
|
+
.auth({ bearer: 'YOUR_TOKEN' })
|
|
390
|
+
.build();
|
|
391
|
+
|
|
392
|
+
const imageGen = appTool('generate_image', 'infsh/flux-schnell@abc123')
|
|
393
|
+
.param('prompt', string('Image description'))
|
|
394
|
+
.requireApproval()
|
|
395
|
+
.build();
|
|
396
|
+
|
|
397
|
+
const mcpSearch = mcpTool('notion_search', 'your-mcp-integration-id', 'search')
|
|
398
|
+
.describe('Search Notion pages')
|
|
399
|
+
.param('query', string('Search query'))
|
|
400
|
+
.build();
|
|
401
|
+
|
|
402
|
+
const agent = client.agents.create({
|
|
403
|
+
core_app: { ref: 'infsh/claude-sonnet-4@latest' },
|
|
404
|
+
system_prompt: 'You are helpful.',
|
|
405
|
+
tools: [clientTool, gmailSend, imageGen, mcpSearch],
|
|
406
|
+
internal_tools: internalTools().memory().build(),
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`callTool` is an alias for `httpTool`. Run `npx tsx examples/tool-builder.ts` for more schema examples (no API key required).
|
|
411
|
+
|
|
343
412
|
### File attachments
|
|
344
413
|
|
|
345
414
|
Pass files in `sendMessage` options. `Blob` values are uploaded first; objects with a `uri` (already uploaded via `client.files.upload`) are attached as-is:
|
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;
|
|
@@ -91,6 +91,9 @@ describe('createActions', () => {
|
|
|
91
91
|
assistantMessage: makeMessage(),
|
|
92
92
|
});
|
|
93
93
|
mockAgentApi.submitToolResult.mockResolvedValue(undefined);
|
|
94
|
+
mockAgentApi.approveTool.mockResolvedValue(undefined);
|
|
95
|
+
mockAgentApi.rejectTool.mockResolvedValue(undefined);
|
|
96
|
+
mockAgentApi.alwaysAllowTool.mockResolvedValue(undefined);
|
|
94
97
|
});
|
|
95
98
|
describe('updateMessage (via stream listeners)', () => {
|
|
96
99
|
it('should ignore messages for a different chat when IDs do not prefix-match', async () => {
|
|
@@ -388,6 +391,12 @@ describe('createActions', () => {
|
|
|
388
391
|
publicActions.stopGeneration();
|
|
389
392
|
expect(mockAgentApi.stopChat).toHaveBeenCalledWith(ctx.client, 'chat-short');
|
|
390
393
|
});
|
|
394
|
+
it('stopGeneration should no-op when there is no chatId', () => {
|
|
395
|
+
const { ctx } = createTestContext({ getChatId: () => null });
|
|
396
|
+
const { publicActions } = createActions(ctx);
|
|
397
|
+
publicActions.stopGeneration();
|
|
398
|
+
expect(mockAgentApi.stopChat).not.toHaveBeenCalled();
|
|
399
|
+
});
|
|
391
400
|
it('submitToolResult should set error state when API fails', async () => {
|
|
392
401
|
mockAgentApi.submitToolResult.mockRejectedValueOnce(new Error('submit failed'));
|
|
393
402
|
const onError = jest.fn();
|
|
@@ -400,5 +409,85 @@ describe('createActions', () => {
|
|
|
400
409
|
});
|
|
401
410
|
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'submit failed' }));
|
|
402
411
|
});
|
|
412
|
+
it('clearError should reset error and connection status to idle', () => {
|
|
413
|
+
const { ctx, dispatch } = createTestContext();
|
|
414
|
+
const { publicActions } = createActions(ctx);
|
|
415
|
+
publicActions.clearError();
|
|
416
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
417
|
+
type: 'SET_ERROR',
|
|
418
|
+
payload: undefined,
|
|
419
|
+
});
|
|
420
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
421
|
+
type: 'SET_CONNECTION_STATUS',
|
|
422
|
+
payload: 'idle',
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
describe('HIL tool actions', () => {
|
|
427
|
+
it('approveTool should delegate to the API', async () => {
|
|
428
|
+
const { ctx } = createTestContext();
|
|
429
|
+
const { publicActions } = createActions(ctx);
|
|
430
|
+
await publicActions.approveTool('inv-approve');
|
|
431
|
+
expect(mockAgentApi.approveTool).toHaveBeenCalledWith(ctx.client, 'inv-approve');
|
|
432
|
+
});
|
|
433
|
+
it('rejectTool should pass an optional reason', async () => {
|
|
434
|
+
const { ctx } = createTestContext();
|
|
435
|
+
const { publicActions } = createActions(ctx);
|
|
436
|
+
await publicActions.rejectTool('inv-reject', 'unsafe');
|
|
437
|
+
expect(mockAgentApi.rejectTool).toHaveBeenCalledWith(ctx.client, 'inv-reject', 'unsafe');
|
|
438
|
+
});
|
|
439
|
+
it('alwaysAllowTool should no-op without a chatId', async () => {
|
|
440
|
+
const { ctx } = createTestContext({ getChatId: () => null });
|
|
441
|
+
const { publicActions } = createActions(ctx);
|
|
442
|
+
await publicActions.alwaysAllowTool('inv-allow', 'my_tool');
|
|
443
|
+
expect(mockAgentApi.alwaysAllowTool).not.toHaveBeenCalled();
|
|
444
|
+
});
|
|
445
|
+
it('alwaysAllowTool should call API when chatId exists', async () => {
|
|
446
|
+
const { ctx } = createTestContext({ getChatId: () => 'chat-short' });
|
|
447
|
+
const { publicActions } = createActions(ctx);
|
|
448
|
+
await publicActions.alwaysAllowTool('inv-allow', 'my_tool');
|
|
449
|
+
expect(mockAgentApi.alwaysAllowTool).toHaveBeenCalledWith(ctx.client, 'chat-short', 'inv-allow', 'my_tool');
|
|
450
|
+
});
|
|
451
|
+
it('approveTool should set error state when API fails', async () => {
|
|
452
|
+
mockAgentApi.approveTool.mockRejectedValueOnce(new Error('approve failed'));
|
|
453
|
+
const onError = jest.fn();
|
|
454
|
+
const { ctx, dispatch } = createTestContext({ callbacks: { onError } });
|
|
455
|
+
const { publicActions } = createActions(ctx);
|
|
456
|
+
await expect(publicActions.approveTool('inv-1')).rejects.toThrow('approve failed');
|
|
457
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
458
|
+
type: 'SET_CONNECTION_STATUS',
|
|
459
|
+
payload: 'error',
|
|
460
|
+
});
|
|
461
|
+
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'approve failed' }));
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
describe('setChatId', () => {
|
|
465
|
+
it('should no-op when the chat id is unchanged', async () => {
|
|
466
|
+
const { ctx } = createTestContext({ getChatId: () => 'chat-short' });
|
|
467
|
+
const { internalActions } = createActions(ctx);
|
|
468
|
+
internalActions.setChatId('chat-short');
|
|
469
|
+
await Promise.resolve();
|
|
470
|
+
expect(StreamableManager).not.toHaveBeenCalled();
|
|
471
|
+
});
|
|
472
|
+
it('should reset and stop stream when chat id is cleared', async () => {
|
|
473
|
+
const { ctx, dispatch } = createTestContext();
|
|
474
|
+
const { internalActions } = createActions(ctx);
|
|
475
|
+
internalActions.streamChat('chat-full-id-123');
|
|
476
|
+
await Promise.resolve();
|
|
477
|
+
internalActions.setChatId(null);
|
|
478
|
+
expect(streamInstances[0].stop).toHaveBeenCalled();
|
|
479
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'RESET' });
|
|
480
|
+
});
|
|
481
|
+
it('should start streaming when switching to a new chat id', async () => {
|
|
482
|
+
const { ctx, dispatch } = createTestContext({ getChatId: () => null });
|
|
483
|
+
const { internalActions } = createActions(ctx);
|
|
484
|
+
internalActions.setChatId('chat-new');
|
|
485
|
+
await Promise.resolve();
|
|
486
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
487
|
+
type: 'SET_CHAT_ID',
|
|
488
|
+
payload: 'chat-new',
|
|
489
|
+
});
|
|
490
|
+
expect(StreamableManager).toHaveBeenCalled();
|
|
491
|
+
});
|
|
403
492
|
});
|
|
404
493
|
});
|
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, getChatStreamConfig, } from './api';
|
|
3
|
+
import { sendAdHocMessage, sendTemplateMessage, sendMessage, submitToolResult, approveTool, rejectTool, alwaysAllowTool, fetchChat, stopChat, getChatStreamConfig, } from './api';
|
|
4
4
|
import { ToolTypeClient } from '../types';
|
|
5
5
|
const mockFetch = jest.fn();
|
|
6
6
|
global.fetch = mockFetch;
|
|
@@ -30,8 +30,13 @@ describe('agent/api', () => {
|
|
|
30
30
|
jest.clearAllMocks();
|
|
31
31
|
});
|
|
32
32
|
describe('sendAdHocMessage', () => {
|
|
33
|
+
it('should return null when the API response omits messages', async () => {
|
|
34
|
+
mockJsonResponse({});
|
|
35
|
+
const result = await sendAdHocMessage(makeClient(), adHocConfig, null, 'hello');
|
|
36
|
+
expect(result).toBeNull();
|
|
37
|
+
});
|
|
33
38
|
it('should strip client tool handlers from the agents/run request body', async () => {
|
|
34
|
-
mockJsonResponse(
|
|
39
|
+
mockJsonResponse(runResponse);
|
|
35
40
|
const handler = jest.fn().mockReturnValue('ok');
|
|
36
41
|
await sendAdHocMessage(makeClient(), {
|
|
37
42
|
...adHocConfig,
|
|
@@ -58,8 +63,15 @@ describe('agent/api', () => {
|
|
|
58
63
|
});
|
|
59
64
|
});
|
|
60
65
|
describe('sendTemplateMessage', () => {
|
|
66
|
+
it('should return null when assistant_message is missing', async () => {
|
|
67
|
+
mockJsonResponse({
|
|
68
|
+
user_message: { id: 'u1', chat_id: 'chat-1', role: 'user' },
|
|
69
|
+
});
|
|
70
|
+
const result = await sendTemplateMessage(makeClient(), { agent: 'agent-1' }, null, 'hello');
|
|
71
|
+
expect(result).toBeNull();
|
|
72
|
+
});
|
|
61
73
|
it('should omit empty agent field for existing chats', async () => {
|
|
62
|
-
mockJsonResponse(
|
|
74
|
+
mockJsonResponse(runResponse);
|
|
63
75
|
await sendTemplateMessage(makeClient(), { agent: '' }, 'chat-existing', 'hi');
|
|
64
76
|
const [, init] = mockFetch.mock.calls[0];
|
|
65
77
|
const body = JSON.parse(String(init.body));
|
|
@@ -69,7 +81,7 @@ describe('agent/api', () => {
|
|
|
69
81
|
});
|
|
70
82
|
describe('sendMessage', () => {
|
|
71
83
|
it('should pass FileRef attachments without uploading', async () => {
|
|
72
|
-
mockJsonResponse(
|
|
84
|
+
mockJsonResponse(runResponse);
|
|
73
85
|
const fileRef = {
|
|
74
86
|
id: 'f1',
|
|
75
87
|
uri: 'inf://files/abc',
|
|
@@ -82,6 +94,19 @@ describe('agent/api', () => {
|
|
|
82
94
|
const body = JSON.parse(String(init.body));
|
|
83
95
|
expect(body.input.attachments).toEqual([fileRef]);
|
|
84
96
|
});
|
|
97
|
+
it('should omit attachments when every file upload fails', async () => {
|
|
98
|
+
const client = makeClient();
|
|
99
|
+
const uploadSpy = jest
|
|
100
|
+
.spyOn(client.files, 'upload')
|
|
101
|
+
.mockRejectedValue(new Error('upload failed'));
|
|
102
|
+
mockJsonResponse(runResponse);
|
|
103
|
+
const file = new File(['data'], 'doc.txt', { type: 'text/plain' });
|
|
104
|
+
await sendMessage(client, adHocConfig, null, 'with file', [file]);
|
|
105
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
106
|
+
const body = JSON.parse(String(init.body));
|
|
107
|
+
expect(body.input.attachments).toBeUndefined();
|
|
108
|
+
uploadSpy.mockRestore();
|
|
109
|
+
});
|
|
85
110
|
it('should upload File inputs before sending', async () => {
|
|
86
111
|
const fileRecord = {
|
|
87
112
|
id: 'file-1',
|
|
@@ -90,9 +115,9 @@ describe('agent/api', () => {
|
|
|
90
115
|
upload_url: 'https://upload.example/put',
|
|
91
116
|
content_type: 'text/plain',
|
|
92
117
|
};
|
|
93
|
-
mockJsonResponse(
|
|
118
|
+
mockJsonResponse([fileRecord]);
|
|
94
119
|
mockFetch.mockResolvedValueOnce({ ok: true, status: 200 });
|
|
95
|
-
mockJsonResponse(
|
|
120
|
+
mockJsonResponse(runResponse);
|
|
96
121
|
const file = new File(['hello'], 'hello.txt', { type: 'text/plain' });
|
|
97
122
|
await sendMessage(makeClient(), adHocConfig, null, 'with file', [file]);
|
|
98
123
|
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
@@ -103,14 +128,14 @@ describe('agent/api', () => {
|
|
|
103
128
|
});
|
|
104
129
|
describe('submitToolResult', () => {
|
|
105
130
|
it('should wrap string results in { result }', async () => {
|
|
106
|
-
mockJsonResponse(
|
|
131
|
+
mockJsonResponse(null);
|
|
107
132
|
await submitToolResult(makeClient(), 'inv-1', 'done');
|
|
108
133
|
const [url, init] = mockFetch.mock.calls[0];
|
|
109
134
|
expect(url).toContain('/tools/inv-1');
|
|
110
135
|
expect(JSON.parse(String(init.body))).toEqual({ result: 'done' });
|
|
111
136
|
});
|
|
112
137
|
it('should pass structured action objects through unchanged', async () => {
|
|
113
|
-
mockJsonResponse(
|
|
138
|
+
mockJsonResponse(null);
|
|
114
139
|
const payload = {
|
|
115
140
|
action: { type: 'approve', payload: { ok: true } },
|
|
116
141
|
};
|
|
@@ -119,6 +144,59 @@ describe('agent/api', () => {
|
|
|
119
144
|
expect(JSON.parse(String(init.body))).toEqual(payload);
|
|
120
145
|
});
|
|
121
146
|
});
|
|
147
|
+
describe('fetchChat', () => {
|
|
148
|
+
it('should return chat data on success', async () => {
|
|
149
|
+
const chat = { id: 'chat-1', status: 'idle' };
|
|
150
|
+
mockJsonResponse(chat);
|
|
151
|
+
const result = await fetchChat(makeClient(), 'chat-1');
|
|
152
|
+
expect(result).toEqual(chat);
|
|
153
|
+
});
|
|
154
|
+
it('should return null and not throw when the request fails', async () => {
|
|
155
|
+
mockFetch.mockRejectedValueOnce(new Error('network error'));
|
|
156
|
+
const result = await fetchChat(makeClient(), 'chat-1');
|
|
157
|
+
expect(result).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('stopChat', () => {
|
|
161
|
+
it('should POST to /chats/{id}/stop', async () => {
|
|
162
|
+
mockJsonResponse(null);
|
|
163
|
+
await stopChat(makeClient(), 'chat-1');
|
|
164
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
165
|
+
expect(url).toContain('/chats/chat-1/stop');
|
|
166
|
+
expect(init.method).toBe('POST');
|
|
167
|
+
});
|
|
168
|
+
it('should swallow errors without throwing', async () => {
|
|
169
|
+
mockFetch.mockRejectedValueOnce(new Error('stop failed'));
|
|
170
|
+
await expect(stopChat(makeClient(), 'chat-1')).resolves.toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('HIL tool approval', () => {
|
|
174
|
+
it('approveTool should POST to /tools/{id}/invoke', async () => {
|
|
175
|
+
mockJsonResponse(null);
|
|
176
|
+
await approveTool(makeClient(), 'inv-approve');
|
|
177
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
178
|
+
expect(url).toContain('/tools/inv-approve/invoke');
|
|
179
|
+
expect(init.method).toBe('POST');
|
|
180
|
+
});
|
|
181
|
+
it('rejectTool should POST reason to /tools/{id}/reject', async () => {
|
|
182
|
+
mockJsonResponse(null);
|
|
183
|
+
await rejectTool(makeClient(), 'inv-reject', 'not safe');
|
|
184
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
185
|
+
expect(url).toContain('/tools/inv-reject/reject');
|
|
186
|
+
expect(JSON.parse(String(init.body))).toEqual({ reason: 'not safe' });
|
|
187
|
+
});
|
|
188
|
+
it('alwaysAllowTool should POST tool_name to the chat tools endpoint', async () => {
|
|
189
|
+
mockJsonResponse(null);
|
|
190
|
+
await alwaysAllowTool(makeClient(), 'chat-1', 'inv-allow', 'browser_tool');
|
|
191
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
192
|
+
expect(url).toContain('/chats/chat-1/tools/inv-allow/always-allow');
|
|
193
|
+
expect(JSON.parse(String(init.body))).toEqual({ tool_name: 'browser_tool' });
|
|
194
|
+
});
|
|
195
|
+
it('should rethrow when approveTool request fails', async () => {
|
|
196
|
+
mockFetch.mockRejectedValueOnce(new Error('approve failed'));
|
|
197
|
+
await expect(approveTool(makeClient(), 'inv-1')).rejects.toThrow('approve failed');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
122
200
|
describe('getChatStreamConfig', () => {
|
|
123
201
|
it('should delegate to HttpClient.getStreamableConfig for the chat stream path', () => {
|
|
124
202
|
const client = makeClient();
|
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
|
// ==========================================================================
|