@inferencesh/sdk 0.6.10 → 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.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/actions.js +3 -3
  3. package/dist/agent/actions.test.js +6 -0
  4. package/dist/api/agents.d.ts +10 -0
  5. package/dist/api/agents.js +17 -3
  6. package/dist/api/agents.test.js +215 -0
  7. package/dist/api/api-keys.d.ts +24 -0
  8. package/dist/api/api-keys.js +26 -0
  9. package/dist/api/api-keys.test.d.ts +1 -0
  10. package/dist/api/api-keys.test.js +44 -0
  11. package/dist/api/apps.js +1 -1
  12. package/dist/api/apps.test.js +71 -3
  13. package/dist/api/chats.d.ts +14 -0
  14. package/dist/api/chats.js +18 -0
  15. package/dist/api/chats.test.js +52 -0
  16. package/dist/api/engines.d.ts +12 -0
  17. package/dist/api/engines.js +18 -0
  18. package/dist/api/engines.test.js +78 -0
  19. package/dist/api/files.test.js +183 -0
  20. package/dist/api/flow-runs.test.js +42 -0
  21. package/dist/api/flows.d.ts +4 -0
  22. package/dist/api/flows.js +6 -0
  23. package/dist/api/flows.test.js +75 -0
  24. package/dist/api/integrations.d.ts +41 -0
  25. package/dist/api/integrations.js +56 -0
  26. package/dist/api/integrations.test.d.ts +1 -0
  27. package/dist/api/integrations.test.js +109 -0
  28. package/dist/api/knowledge.d.ts +112 -0
  29. package/dist/api/knowledge.js +163 -0
  30. package/dist/api/knowledge.test.d.ts +1 -0
  31. package/dist/api/knowledge.test.js +94 -0
  32. package/dist/api/mcp-servers.d.ts +45 -0
  33. package/dist/api/mcp-servers.js +62 -0
  34. package/dist/api/mcp-servers.test.d.ts +1 -0
  35. package/dist/api/mcp-servers.test.js +64 -0
  36. package/dist/api/projects.d.ts +29 -0
  37. package/dist/api/projects.js +38 -0
  38. package/dist/api/projects.test.d.ts +1 -0
  39. package/dist/api/projects.test.js +52 -0
  40. package/dist/api/search.d.ts +21 -0
  41. package/dist/api/search.js +20 -0
  42. package/dist/api/search.test.d.ts +1 -0
  43. package/dist/api/search.test.js +39 -0
  44. package/dist/api/secrets.d.ts +29 -0
  45. package/dist/api/secrets.js +38 -0
  46. package/dist/api/secrets.test.d.ts +1 -0
  47. package/dist/api/secrets.test.js +61 -0
  48. package/dist/api/tasks.d.ts +13 -1
  49. package/dist/api/tasks.js +18 -0
  50. package/dist/api/tasks.test.js +134 -0
  51. package/dist/api/teams.d.ts +71 -0
  52. package/dist/api/teams.js +92 -0
  53. package/dist/api/teams.test.d.ts +1 -0
  54. package/dist/api/teams.test.js +79 -0
  55. package/dist/http/client.d.ts +5 -0
  56. package/dist/http/client.js +42 -23
  57. package/dist/http/client.test.js +245 -0
  58. package/dist/http/errors.test.js +13 -0
  59. package/dist/index.d.ts +25 -0
  60. package/dist/index.js +25 -0
  61. package/dist/tool-builder.test.js +11 -1
  62. package/dist/types.d.ts +110 -5
  63. package/dist/types.js +58 -2
  64. package/package.json +4 -4
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 250+ models including flux, stable diffusion, llms (claude, gpt, gemini), video generation (veo, seedance), and more.
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
 
@@ -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
- // Check for client tool invocations that need execution
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;
@@ -391,6 +391,12 @@ describe('createActions', () => {
391
391
  publicActions.stopGeneration();
392
392
  expect(mockAgentApi.stopChat).toHaveBeenCalledWith(ctx.client, 'chat-short');
393
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
+ });
394
400
  it('submitToolResult should set error state when API fails', async () => {
395
401
  mockAgentApi.submitToolResult.mockRejectedValueOnce(new Error('submit failed'));
396
402
  const onError = jest.fn();
@@ -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
  */
@@ -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
  // ==========================================================================
@@ -1,4 +1,5 @@
1
1
  import { HttpClient } from '../http/client';
2
+ import { StreamableManager } from '../http/streamable';
2
3
  import { ChatStatusBusy, ChatStatusIdle, ToolInvocationStatusAwaitingInput, ToolTypeClient, } from '../types';
3
4
  import { FilesAPI } from './files';
4
5
  import { AgentsAPI } from './agents';
@@ -281,6 +282,44 @@ describe('Agent.sendMessage (file attachments)', () => {
281
282
  expect(mockFetch.mock.calls.filter(([url]) => String(url).includes('/files')).length).toBe(0);
282
283
  });
283
284
  });
285
+ describe('Agent.sendMessage (ad-hoc config)', () => {
286
+ beforeEach(() => {
287
+ jest.clearAllMocks();
288
+ });
289
+ const adHocAgent = () => {
290
+ const http = new HttpClient({
291
+ apiKey: 'test-key',
292
+ stream: false,
293
+ pollIntervalMs: 20,
294
+ });
295
+ return new AgentsAPI(http, new FilesAPI(http)).create({
296
+ core_app: { ref: 'openrouter/claude@latest' },
297
+ system_prompt: 'You are helpful',
298
+ name: 'adhoc-bot',
299
+ });
300
+ };
301
+ it('should POST agent_config and agent_name instead of agent template ref', async () => {
302
+ mockJsonResponse({
303
+ user_message: makeMessage({ id: 'user-1', role: 'user' }),
304
+ assistant_message: makeMessage(),
305
+ });
306
+ mockJsonResponse({ status: ChatStatusBusy });
307
+ mockJsonResponse({ id: 'chat-1', status: ChatStatusBusy, chat_messages: [] });
308
+ mockJsonResponse({ status: ChatStatusIdle });
309
+ mockJsonResponse({ id: 'chat-1', status: ChatStatusIdle, chat_messages: [] });
310
+ await adHocAgent().sendMessage('hello', { stream: false });
311
+ const runCall = mockFetch.mock.calls.find(([url]) => String(url).includes('/agents/run'));
312
+ const body = JSON.parse(String(runCall[1].body));
313
+ expect(body.agent).toBeUndefined();
314
+ expect(body.agent_config).toEqual({
315
+ core_app: { ref: 'openrouter/claude@latest' },
316
+ system_prompt: 'You are helpful',
317
+ name: 'adhoc-bot',
318
+ });
319
+ expect(body.agent_name).toBe('adhoc-bot');
320
+ expect(body.input.text).toBe('hello');
321
+ });
322
+ });
284
323
  describe('Agent lifecycle', () => {
285
324
  beforeEach(() => {
286
325
  jest.clearAllMocks();
@@ -317,6 +356,66 @@ describe('Agent lifecycle', () => {
317
356
  await agentInstance.stopChat();
318
357
  expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/chats/chat-1/stop'), expect.anything());
319
358
  });
359
+ it('disconnect should stop active stream managers', async () => {
360
+ const http = new HttpClient({
361
+ apiKey: 'test-key',
362
+ stream: true,
363
+ pollIntervalMs: 20,
364
+ });
365
+ const agentInstance = new AgentsAPI(http, new FilesAPI(http)).create('my-agent');
366
+ const stopSpy = jest.spyOn(StreamableManager.prototype, 'stop');
367
+ mockFetch.mockImplementation((url) => {
368
+ if (url.includes('/agents/run')) {
369
+ return Promise.resolve({
370
+ ok: true,
371
+ status: 200,
372
+ text: () => Promise.resolve(JSON.stringify({
373
+ user_message: makeMessage({ id: 'user-1', role: 'user' }),
374
+ assistant_message: makeMessage(),
375
+ })),
376
+ });
377
+ }
378
+ return Promise.resolve(mockNdjsonStream([
379
+ `${JSON.stringify({ event: 'chats', data: { id: 'chat-1', status: ChatStatusIdle } })}\n`,
380
+ ]));
381
+ });
382
+ await agentInstance.sendMessage('hello', { onChat: jest.fn() });
383
+ agentInstance.disconnect();
384
+ expect(stopSpy).toHaveBeenCalled();
385
+ stopSpy.mockRestore();
386
+ });
387
+ it('startStreaming should no-op when there is no active chat', () => {
388
+ const agentInstance = agent();
389
+ agentInstance.startStreaming();
390
+ expect(mockFetch).not.toHaveBeenCalled();
391
+ });
392
+ it('startStreaming should open the chat stream when chatId exists', async () => {
393
+ const http = new HttpClient({
394
+ apiKey: 'test-key',
395
+ stream: true,
396
+ pollIntervalMs: 20,
397
+ });
398
+ const agentInstance = new AgentsAPI(http, new FilesAPI(http)).create('my-agent');
399
+ mockJsonResponse({
400
+ user_message: makeMessage({ id: 'user-1', role: 'user' }),
401
+ assistant_message: makeMessage(),
402
+ });
403
+ mockJsonResponse({ status: ChatStatusBusy });
404
+ mockJsonResponse({
405
+ id: 'chat-1', status: ChatStatusBusy, chat_messages: [],
406
+ });
407
+ mockJsonResponse({ status: ChatStatusIdle });
408
+ mockJsonResponse({
409
+ id: 'chat-1', status: ChatStatusIdle, chat_messages: [],
410
+ });
411
+ await agentInstance.sendMessage('hello', { stream: false });
412
+ jest.clearAllMocks();
413
+ mockFetch.mockResolvedValue(mockNdjsonStream([
414
+ `${JSON.stringify({ event: 'chats', data: { id: 'chat-1', status: ChatStatusIdle } })}\n`,
415
+ ]));
416
+ agentInstance.startStreaming({ onChat: jest.fn() });
417
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/chats/chat-1/stream'), expect.anything());
418
+ });
320
419
  it('reset should clear chat state so stopChat is a no-op', async () => {
321
420
  const agentInstance = agent();
322
421
  mockJsonResponse({
@@ -338,6 +437,29 @@ describe('Agent lifecycle', () => {
338
437
  expect(mockFetch).not.toHaveBeenCalled();
339
438
  });
340
439
  });
440
+ describe('Agent.getChat', () => {
441
+ beforeEach(() => {
442
+ jest.clearAllMocks();
443
+ });
444
+ const agent = () => {
445
+ const http = new HttpClient({ apiKey: 'test-key' });
446
+ return new AgentsAPI(http, new FilesAPI(http)).create('my-agent');
447
+ };
448
+ it('should return null without a chat id and no active chat', async () => {
449
+ const result = await agent().getChat();
450
+ expect(result).toBeNull();
451
+ expect(mockFetch).not.toHaveBeenCalled();
452
+ });
453
+ it('should GET /chats/{id} when chatId is provided', async () => {
454
+ const chat = { id: 'chat-42', status: 'idle', chat_messages: [] };
455
+ mockJsonResponse(chat);
456
+ const result = await agent().getChat('chat-42');
457
+ expect(result).toEqual(chat);
458
+ const [url, init] = mockFetch.mock.calls[0];
459
+ expect(url).toContain('/chats/chat-42');
460
+ expect(init.method).toBe('GET');
461
+ });
462
+ });
341
463
  describe('Agent.submitToolResult', () => {
342
464
  beforeEach(() => {
343
465
  jest.clearAllMocks();
@@ -356,6 +478,23 @@ describe('Agent.submitToolResult', () => {
356
478
  expect(body.result).toBe(JSON.stringify(payload));
357
479
  });
358
480
  });
481
+ describe('AgentsAPI.submitToolResult', () => {
482
+ beforeEach(() => {
483
+ jest.clearAllMocks();
484
+ });
485
+ const api = () => {
486
+ const http = new HttpClient({ apiKey: 'test-key' });
487
+ return new AgentsAPI(http, new FilesAPI(http));
488
+ };
489
+ it('should POST plain string results to /tools/{invocationId}', async () => {
490
+ mockJsonResponse(null);
491
+ await api().submitToolResult('inv-plain', 'done');
492
+ const [url, init] = mockFetch.mock.calls[0];
493
+ expect(url).toContain('/tools/inv-plain');
494
+ expect(init.method).toBe('POST');
495
+ expect(JSON.parse(String(init.body))).toEqual({ result: 'done' });
496
+ });
497
+ });
359
498
  describe('AgentsAPI (template CRUD)', () => {
360
499
  beforeEach(() => {
361
500
  jest.clearAllMocks();
@@ -392,4 +531,80 @@ describe('AgentsAPI (template CRUD)', () => {
392
531
  expect(init.method).toBe('POST');
393
532
  expect(JSON.parse(init.body)).toEqual(payload);
394
533
  });
534
+ it('should GET /agents/{namespace}/{name} for getByName()', async () => {
535
+ const agent = { id: 'agent-1', name: 'my-agent' };
536
+ mockJsonResponse(agent);
537
+ const result = await api().getByName('inference', 'my-agent');
538
+ expect(result).toEqual(agent);
539
+ const [url, init] = mockFetch.mock.calls[0];
540
+ expect(url).toContain('/agents/inference/my-agent');
541
+ expect(init.method).toBe('GET');
542
+ });
543
+ it('should POST /agents/list for list()', async () => {
544
+ const page = { items: [{ id: 'agent-1' }], next_cursor: null };
545
+ mockJsonResponse(page);
546
+ const result = await api().list({ limit: 10 });
547
+ expect(result).toEqual(page);
548
+ const [url, init] = mockFetch.mock.calls[0];
549
+ expect(url).toContain('/agents/list');
550
+ expect(init.method).toBe('POST');
551
+ });
552
+ it('should GET /agents/{id} for get()', async () => {
553
+ const agent = { id: 'agent-1', name: 'support-bot' };
554
+ mockJsonResponse(agent);
555
+ const result = await api().get('agent-1');
556
+ expect(result).toEqual(agent);
557
+ const [url, init] = mockFetch.mock.calls[0];
558
+ expect(url).toContain('/agents/agent-1');
559
+ expect(init.method).toBe('GET');
560
+ });
561
+ it('should POST /agents/{id} for update()', async () => {
562
+ const agent = { id: 'agent-1', name: 'updated' };
563
+ mockJsonResponse(agent);
564
+ const result = await api().update('agent-1', { name: 'updated' });
565
+ expect(result).toEqual(agent);
566
+ const [, init] = mockFetch.mock.calls[0];
567
+ expect(JSON.parse(init.body)).toEqual({ name: 'updated' });
568
+ });
569
+ it('should DELETE /agents/{id} for delete()', async () => {
570
+ mockJsonResponse(null);
571
+ await api().delete('agent-1');
572
+ const [url, init] = mockFetch.mock.calls[0];
573
+ expect(url).toContain('/agents/agent-1');
574
+ expect(init.method).toBe('DELETE');
575
+ });
576
+ it('should POST /agents/{id}/duplicate for duplicate()', async () => {
577
+ const agent = { id: 'agent-copy' };
578
+ mockJsonResponse(agent);
579
+ const result = await api().duplicate('agent-1');
580
+ expect(result).toEqual(agent);
581
+ const [url, init] = mockFetch.mock.calls[0];
582
+ expect(url).toContain('/agents/agent-1/duplicate');
583
+ expect(init.method).toBe('POST');
584
+ });
585
+ it('should POST /agents/{id}/versions/list for listVersions()', async () => {
586
+ const page = { items: [{ id: 'ver-1' }], next_cursor: null };
587
+ mockJsonResponse(page);
588
+ const result = await api().listVersions('agent-1', { limit: 5 });
589
+ expect(result).toEqual(page);
590
+ const [url, init] = mockFetch.mock.calls[0];
591
+ expect(url).toContain('/agents/agent-1/versions/list');
592
+ expect(JSON.parse(init.body)).toEqual({ limit: 5 });
593
+ });
594
+ it('should GET /agents/{id}/versions/{versionId} for getVersion()', async () => {
595
+ const version = { id: 'ver-1', agent_id: 'agent-1' };
596
+ mockJsonResponse(version);
597
+ const result = await api().getVersion('agent-1', 'ver-1');
598
+ expect(result).toEqual(version);
599
+ const [url, init] = mockFetch.mock.calls[0];
600
+ expect(url).toContain('/agents/agent-1/versions/ver-1');
601
+ expect(init.method).toBe('GET');
602
+ });
603
+ it('should POST visibility for updateVisibility()', async () => {
604
+ const agent = { id: 'agent-1', visibility: 'team' };
605
+ mockJsonResponse(agent);
606
+ await api().updateVisibility('agent-1', 'team');
607
+ const [, init] = mockFetch.mock.calls[0];
608
+ expect(JSON.parse(init.body)).toEqual({ visibility: 'team' });
609
+ });
395
610
  });
@@ -0,0 +1,24 @@
1
+ import { HttpClient } from '../http/client';
2
+ import { ApiKeyDTO, CursorListRequest, CursorListResponse } from '../types';
3
+ /**
4
+ * API Keys API
5
+ */
6
+ export declare class ApiKeysAPI {
7
+ private readonly http;
8
+ constructor(http: HttpClient);
9
+ /**
10
+ * List API keys with cursor-based pagination
11
+ */
12
+ list(params?: Partial<CursorListRequest>): Promise<CursorListResponse<ApiKeyDTO>>;
13
+ /**
14
+ * Create an API key
15
+ */
16
+ create(data: {
17
+ name: string;
18
+ scopes?: string[];
19
+ }): Promise<ApiKeyDTO>;
20
+ /**
21
+ * Delete an API key
22
+ */
23
+ delete(id: string): Promise<void>;
24
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * API Keys API
3
+ */
4
+ export class ApiKeysAPI {
5
+ constructor(http) {
6
+ this.http = http;
7
+ }
8
+ /**
9
+ * List API keys with cursor-based pagination
10
+ */
11
+ async list(params) {
12
+ return this.http.request('post', '/apikeys/list', { data: params });
13
+ }
14
+ /**
15
+ * Create an API key
16
+ */
17
+ async create(data) {
18
+ return this.http.request('post', '/apikeys', { data });
19
+ }
20
+ /**
21
+ * Delete an API key
22
+ */
23
+ async delete(id) {
24
+ return this.http.request('delete', `/apikeys/${id}`);
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import { HttpClient } from '../http/client';
2
+ import { ApiKeysAPI } from './api-keys';
3
+ const mockFetch = jest.fn();
4
+ global.fetch = mockFetch;
5
+ function mockJsonResponse(body) {
6
+ mockFetch.mockResolvedValueOnce({
7
+ ok: true,
8
+ status: 200,
9
+ text: () => Promise.resolve(JSON.stringify(body)),
10
+ });
11
+ }
12
+ describe('ApiKeysAPI', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ });
16
+ const api = () => new ApiKeysAPI(new HttpClient({ apiKey: 'test-key' }));
17
+ it('should POST /apikeys/list for list()', async () => {
18
+ const page = { items: [{ id: 'key-1', name: 'CI' }], next_cursor: null };
19
+ mockJsonResponse(page);
20
+ const result = await api().list();
21
+ expect(result).toEqual(page);
22
+ const [url, init] = mockFetch.mock.calls[0];
23
+ expect(url).toContain('/apikeys/list');
24
+ expect(init.method).toBe('POST');
25
+ });
26
+ it('should POST /apikeys for create()', async () => {
27
+ const payload = { name: 'deploy-bot', scopes: ['tasks:read'] };
28
+ const key = { id: 'key-new', ...payload, key: 'inf_sk_abc' };
29
+ mockJsonResponse(key);
30
+ const result = await api().create(payload);
31
+ expect(result).toEqual(key);
32
+ const [url, init] = mockFetch.mock.calls[0];
33
+ expect(url).toContain('/apikeys');
34
+ expect(init.method).toBe('POST');
35
+ expect(JSON.parse(init.body)).toEqual(payload);
36
+ });
37
+ it('should DELETE /apikeys/{id} for delete()', async () => {
38
+ mockJsonResponse(null);
39
+ await api().delete('key-9');
40
+ const [url, init] = mockFetch.mock.calls[0];
41
+ expect(url).toContain('/apikeys/key-9');
42
+ expect(init.method).toBe('DELETE');
43
+ });
44
+ });
package/dist/api/apps.js CHANGED
@@ -87,7 +87,7 @@ export class AppsAPI {
87
87
  * Set the current (active) version of an app
88
88
  */
89
89
  async setCurrentVersion(appId, versionId) {
90
- return this.http.request('post', `/apps/${appId}/current-version`, { data: { version_id: versionId } });
90
+ return this.http.request('put', `/apps/${appId}/versions/${versionId}/current`);
91
91
  }
92
92
  }
93
93
  export function createAppsAPI(http) {
@@ -48,12 +48,13 @@ describe('AppsAPI', () => {
48
48
  const [, init] = mockFetch.mock.calls[0];
49
49
  expect(JSON.parse(init.body)).toEqual({ license: 'key-abc' });
50
50
  });
51
- it('should POST version_id for setCurrentVersion()', async () => {
51
+ it('should PUT /apps/{id}/versions/{versionId}/current for setCurrentVersion()', async () => {
52
52
  const app = { id: 'app-1', current_version_id: 'ver-2' };
53
53
  mockJsonResponse(app);
54
54
  await api().setCurrentVersion('app-1', 'ver-2');
55
- const [, init] = mockFetch.mock.calls[0];
56
- expect(JSON.parse(init.body)).toEqual({ version_id: 'ver-2' });
55
+ const [url, init] = mockFetch.mock.calls[0];
56
+ expect(url).toContain('/apps/app-1/versions/ver-2/current');
57
+ expect(init.method).toBe('PUT');
57
58
  });
58
59
  it('should POST /apps/{id}/duplicate for duplicate()', async () => {
59
60
  const app = { id: 'app-copy' };
@@ -64,4 +65,71 @@ describe('AppsAPI', () => {
64
65
  expect(url).toContain('/apps/app-1/duplicate');
65
66
  expect(init.method).toBe('POST');
66
67
  });
68
+ it('should GET /apps/{id} for get()', async () => {
69
+ const app = { id: 'app-1', name: 'demo' };
70
+ mockJsonResponse(app);
71
+ const result = await api().get('app-1');
72
+ expect(result).toEqual(app);
73
+ const [url, init] = mockFetch.mock.calls[0];
74
+ expect(url).toContain('/apps/app-1');
75
+ expect(init.method).toBe('GET');
76
+ });
77
+ it('should GET versioned app for getByVersionId()', async () => {
78
+ const app = { id: 'app-1', version_id: 'ver-1' };
79
+ mockJsonResponse(app);
80
+ const result = await api().getByVersionId('app-1', 'ver-1');
81
+ expect(result).toEqual(app);
82
+ const [url] = mockFetch.mock.calls[0];
83
+ expect(url).toContain('/apps/app-1/versions/ver-1');
84
+ });
85
+ it('should POST /apps for create()', async () => {
86
+ const app = { id: 'app-new', name: 'New App' };
87
+ mockJsonResponse(app);
88
+ const result = await api().create({ name: 'New App' });
89
+ expect(result).toEqual(app);
90
+ const [url, init] = mockFetch.mock.calls[0];
91
+ expect(url).toContain('/apps');
92
+ expect(init.method).toBe('POST');
93
+ expect(JSON.parse(init.body)).toEqual({ name: 'New App' });
94
+ });
95
+ it('should POST /apps/{id} for update()', async () => {
96
+ const app = { id: 'app-1', description: 'updated' };
97
+ mockJsonResponse(app);
98
+ const result = await api().update('app-1', { description: 'updated' });
99
+ expect(result).toEqual(app);
100
+ const [url, init] = mockFetch.mock.calls[0];
101
+ expect(url).toContain('/apps/app-1');
102
+ expect(JSON.parse(init.body)).toEqual({ description: 'updated' });
103
+ });
104
+ it('should DELETE /apps/{id} for delete()', async () => {
105
+ mockJsonResponse(null);
106
+ await api().delete('app-1');
107
+ const [url, init] = mockFetch.mock.calls[0];
108
+ expect(url).toContain('/apps/app-1');
109
+ expect(init.method).toBe('DELETE');
110
+ });
111
+ it('should POST /apps/{id}/versions/list for listVersions()', async () => {
112
+ const versions = { items: [{ id: 'ver-1' }], next_cursor: null };
113
+ mockJsonResponse(versions);
114
+ const result = await api().listVersions('app-1', { limit: 20 });
115
+ expect(result).toEqual(versions);
116
+ const [url, init] = mockFetch.mock.calls[0];
117
+ expect(url).toContain('/apps/app-1/versions/list');
118
+ expect(JSON.parse(init.body)).toEqual({ limit: 20 });
119
+ });
120
+ it('should POST visibility for updateVisibility()', async () => {
121
+ const app = { id: 'app-1', visibility: 'private' };
122
+ mockJsonResponse(app);
123
+ await api().updateVisibility('app-1', 'private');
124
+ const [, init] = mockFetch.mock.calls[0];
125
+ expect(JSON.parse(init.body)).toEqual({ visibility: 'private' });
126
+ });
127
+ it('should GET /apps/{id}/license for getLicense()', async () => {
128
+ const license = { app_id: 'app-1', license: 'MIT' };
129
+ mockJsonResponse(license);
130
+ const result = await api().getLicense('app-1');
131
+ expect(result).toEqual(license);
132
+ const [url] = mockFetch.mock.calls[0];
133
+ expect(url).toContain('/apps/app-1/license');
134
+ });
67
135
  });
@@ -26,5 +26,19 @@ export declare class ChatsAPI {
26
26
  * Get chat trace (for debugging/observability)
27
27
  */
28
28
  getTrace(chatId: string): Promise<ChatTraceDTO>;
29
+ /**
30
+ * Get chat status
31
+ */
32
+ getStatus(chatId: string): Promise<{
33
+ status: string;
34
+ }>;
35
+ /**
36
+ * Stop chat generation
37
+ */
38
+ stop(chatId: string): Promise<void>;
39
+ /**
40
+ * Stream chat updates
41
+ */
42
+ stream(chatId: string): Promise<import("eventsource").EventSource | null>;
29
43
  }
30
44
  export declare function createChatsAPI(http: HttpClient): ChatsAPI;
package/dist/api/chats.js CHANGED
@@ -35,6 +35,24 @@ export class ChatsAPI {
35
35
  async getTrace(chatId) {
36
36
  return this.http.request('get', `/chats/${chatId}/trace`);
37
37
  }
38
+ /**
39
+ * Get chat status
40
+ */
41
+ async getStatus(chatId) {
42
+ return this.http.request('get', `/chats/${chatId}/status`);
43
+ }
44
+ /**
45
+ * Stop chat generation
46
+ */
47
+ async stop(chatId) {
48
+ return this.http.request('post', `/chats/${chatId}/stop`);
49
+ }
50
+ /**
51
+ * Stream chat updates
52
+ */
53
+ stream(chatId) {
54
+ return this.http.createEventSource(`/chats/${chatId}/stream`);
55
+ }
38
56
  }
39
57
  export function createChatsAPI(http) {
40
58
  return new ChatsAPI(http);