@xpert-ai/xpert-sdk 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/client.d.ts +11 -1
  2. package/dist/client.js +35 -2
  3. package/dist/client.js.map +1 -1
  4. package/dist/client.mjs +35 -2
  5. package/dist/client.mjs.map +1 -1
  6. package/dist/content.d.ts +117 -0
  7. package/dist/content.js +26 -0
  8. package/dist/content.js.map +1 -0
  9. package/dist/content.mjs +26 -0
  10. package/dist/content.mjs.map +1 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +2 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.mjs +2 -0
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/schema.d.ts +6 -1
  17. package/dist/schema.js.map +1 -1
  18. package/dist/schema.mjs.map +1 -1
  19. package/dist/tests/content.test.d.ts +1 -0
  20. package/dist/tests/content.test.js +50 -0
  21. package/dist/tests/content.test.js.map +1 -0
  22. package/dist/tests/content.test.mjs +50 -0
  23. package/dist/tests/content.test.mjs.map +1 -0
  24. package/dist/tests/run-status.test.d.ts +1 -0
  25. package/dist/tests/run-status.test.js +13 -0
  26. package/dist/tests/run-status.test.js.map +1 -0
  27. package/dist/tests/run-status.test.mjs +13 -0
  28. package/dist/tests/run-status.test.mjs.map +1 -0
  29. package/dist/tests/thread-control-view.test.d.ts +1 -0
  30. package/dist/tests/thread-control-view.test.js +62 -0
  31. package/dist/tests/thread-control-view.test.js.map +1 -0
  32. package/dist/tests/thread-control-view.test.mjs +62 -0
  33. package/dist/tests/thread-control-view.test.mjs.map +1 -0
  34. package/dist/tests/view-file-preview.test.d.ts +1 -0
  35. package/dist/tests/view-file-preview.test.js +86 -0
  36. package/dist/tests/view-file-preview.test.js.map +1 -0
  37. package/dist/tests/view-file-preview.test.mjs +86 -0
  38. package/dist/tests/view-file-preview.test.mjs.map +1 -0
  39. package/dist/thread-activity.d.ts +2 -0
  40. package/dist/thread-activity.js +3 -0
  41. package/dist/thread-activity.js.map +1 -1
  42. package/dist/thread-activity.mjs +3 -0
  43. package/dist/thread-activity.mjs.map +1 -1
  44. package/dist/thread-control-view.d.ts +23 -0
  45. package/dist/thread-control-view.js +57 -0
  46. package/dist/thread-control-view.js.map +1 -0
  47. package/dist/thread-control-view.mjs +57 -0
  48. package/dist/thread-control-view.mjs.map +1 -0
  49. package/dist/types.stream.d.ts +10 -1
  50. package/dist/view-extension.d.ts +2 -0
  51. package/dist/view-extension.js.map +1 -1
  52. package/dist/view-extension.mjs.map +1 -1
  53. package/dist/view-file-preview.d.ts +11 -0
  54. package/dist/view-file-preview.js +16 -0
  55. package/dist/view-file-preview.js.map +1 -0
  56. package/dist/view-file-preview.mjs +16 -0
  57. package/dist/view-file-preview.mjs.map +1 -0
  58. package/package.json +3 -3
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { Client } from "../index.js";
3
+ describe('ContentClient', () => {
4
+ it('uses the configured SDK transport and scoped credentials for every operation', async () => {
5
+ const fetch = vi
6
+ .fn()
7
+ .mockImplementation(async () => new Response('{}', { headers: { 'content-type': 'application/json' } }));
8
+ const client = new Client({
9
+ apiUrl: 'https://example.test/api/ai',
10
+ callerOptions: { fetch },
11
+ onRequest: (_url, init) => ({
12
+ ...init,
13
+ headers: { ...init.headers, 'x-client-secret': 'test-secret', 'organization-id': 'org' },
14
+ }),
15
+ });
16
+ const scope = { assistantId: 'a/1', projectId: 'p' };
17
+ const abort = new AbortController();
18
+ await client.content.resolve(scope, { resourceId: 'r', providerKey: 'files' }, { signal: abort.signal });
19
+ await client.content.review(scope, 'r');
20
+ await client.content.preview(scope, 'r', 'rev');
21
+ await client.content.mutate(scope, 'r', {
22
+ operation: 'restore',
23
+ revisionId: 'rev',
24
+ epoch: 2,
25
+ expectedSequence: 4,
26
+ });
27
+ expect(fetch.mock.calls.map(([url]) => new URL(String(url)).pathname)).toEqual(['open', 'review', 'preview', 'restore'].map((operation) => `/api/ai/assistants/a%2F1/projects/p/content/r/${operation}`));
28
+ expect(fetch.mock.calls[0][1]?.signal).toBe(abort.signal);
29
+ expect(JSON.parse(String(fetch.mock.calls[0][1]?.body))).toEqual({ providerKey: 'files' });
30
+ expect(JSON.parse(String(fetch.mock.calls[3][1]?.body))).toEqual({
31
+ revisionId: 'rev',
32
+ epoch: 2,
33
+ expectedSequence: 4,
34
+ });
35
+ for (const [, init] of fetch.mock.calls)
36
+ expect(new Headers(init?.headers).get('x-client-secret')).toBe('test-secret');
37
+ });
38
+ it('does not bypass a server authorization rejection', async () => {
39
+ const fetch = vi
40
+ .fn()
41
+ .mockImplementation(async () => new Response('Forbidden', { status: 403 }));
42
+ const client = new Client({
43
+ apiUrl: 'https://example.test/api/ai',
44
+ callerOptions: { fetch, maxRetries: 0 },
45
+ });
46
+ await expect(client.content.resolve({ assistantId: 'a', projectId: 'p' }, { resourceId: 'r' })).rejects.toThrow();
47
+ expect(fetch).toHaveBeenCalledOnce();
48
+ });
49
+ });
50
+ //# sourceMappingURL=content.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.test.js","sourceRoot":"","sources":["../../src/tests/content.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { Client } from "../index.mjs";
3
+ describe('ContentClient', () => {
4
+ it('uses the configured SDK transport and scoped credentials for every operation', async () => {
5
+ const fetch = vi
6
+ .fn()
7
+ .mockImplementation(async () => new Response('{}', { headers: { 'content-type': 'application/json' } }));
8
+ const client = new Client({
9
+ apiUrl: 'https://example.test/api/ai',
10
+ callerOptions: { fetch },
11
+ onRequest: (_url, init) => ({
12
+ ...init,
13
+ headers: { ...init.headers, 'x-client-secret': 'test-secret', 'organization-id': 'org' },
14
+ }),
15
+ });
16
+ const scope = { assistantId: 'a/1', projectId: 'p' };
17
+ const abort = new AbortController();
18
+ await client.content.resolve(scope, { resourceId: 'r', providerKey: 'files' }, { signal: abort.signal });
19
+ await client.content.review(scope, 'r');
20
+ await client.content.preview(scope, 'r', 'rev');
21
+ await client.content.mutate(scope, 'r', {
22
+ operation: 'restore',
23
+ revisionId: 'rev',
24
+ epoch: 2,
25
+ expectedSequence: 4,
26
+ });
27
+ expect(fetch.mock.calls.map(([url]) => new URL(String(url)).pathname)).toEqual(['open', 'review', 'preview', 'restore'].map((operation) => `/api/ai/assistants/a%2F1/projects/p/content/r/${operation}`));
28
+ expect(fetch.mock.calls[0][1]?.signal).toBe(abort.signal);
29
+ expect(JSON.parse(String(fetch.mock.calls[0][1]?.body))).toEqual({ providerKey: 'files' });
30
+ expect(JSON.parse(String(fetch.mock.calls[3][1]?.body))).toEqual({
31
+ revisionId: 'rev',
32
+ epoch: 2,
33
+ expectedSequence: 4,
34
+ });
35
+ for (const [, init] of fetch.mock.calls)
36
+ expect(new Headers(init?.headers).get('x-client-secret')).toBe('test-secret');
37
+ });
38
+ it('does not bypass a server authorization rejection', async () => {
39
+ const fetch = vi
40
+ .fn()
41
+ .mockImplementation(async () => new Response('Forbidden', { status: 403 }));
42
+ const client = new Client({
43
+ apiUrl: 'https://example.test/api/ai',
44
+ callerOptions: { fetch, maxRetries: 0 },
45
+ });
46
+ await expect(client.content.resolve({ assistantId: 'a', projectId: 'p' }, { resourceId: 'r' })).rejects.toThrow();
47
+ expect(fetch).toHaveBeenCalledOnce();
48
+ });
49
+ });
50
+ //# sourceMappingURL=content.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.test.mjs","sourceRoot":"","sources":["../../src/tests/content.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { Client } from "../client.js";
3
+ describe('compact run status', () => {
4
+ it('uses the lifecycle endpoint without changing full get()', async () => {
5
+ const fetch = vi.fn().mockImplementation(async () => new Response(JSON.stringify({ run_id: 'run', status: 'running' }), { headers: { 'Content-Type': 'application/json' } }));
6
+ const client = new Client({ apiUrl: 'https://example.test/api/ai', callerOptions: { fetch } });
7
+ expect((await client.runs.getStatus('thread', 'run')).status).toBe('running');
8
+ await client.runs.get('thread', 'run');
9
+ expect(String(fetch.mock.calls[0][0])).toContain('/threads/thread/runs/run/status');
10
+ expect(String(fetch.mock.calls[1][0])).toMatch(/\/threads\/thread\/runs\/run$/);
11
+ });
12
+ });
13
+ //# sourceMappingURL=run-status.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-status.test.js","sourceRoot":"","sources":["../../src/tests/run-status.test.ts"],"names":[],"mappings":"OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ;OAC1C,EAAE,MAAM,EAAE;AAEjB,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,EAA2B,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAA;QACtM,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,6BAA6B,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;QAC9F,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC7E,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;QACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAA;QACnF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { Client } from "../client.mjs";
3
+ describe('compact run status', () => {
4
+ it('uses the lifecycle endpoint without changing full get()', async () => {
5
+ const fetch = vi.fn().mockImplementation(async () => new Response(JSON.stringify({ run_id: 'run', status: 'running' }), { headers: { 'Content-Type': 'application/json' } }));
6
+ const client = new Client({ apiUrl: 'https://example.test/api/ai', callerOptions: { fetch } });
7
+ expect((await client.runs.getStatus('thread', 'run')).status).toBe('running');
8
+ await client.runs.get('thread', 'run');
9
+ expect(String(fetch.mock.calls[0][0])).toContain('/threads/thread/runs/run/status');
10
+ expect(String(fetch.mock.calls[1][0])).toMatch(/\/threads\/thread\/runs\/run$/);
11
+ });
12
+ });
13
+ //# sourceMappingURL=run-status.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-status.test.mjs","sourceRoot":"","sources":["../../src/tests/run-status.test.ts"],"names":[],"mappings":"OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ;OAC1C,EAAE,MAAM,EAAE;AAEjB,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,EAA2B,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAA;QACtM,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,6BAA6B,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;QAC9F,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC7E,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;QACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAA;QACnF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { Client, isThreadControlView, mergeThreadControlView, } from "../index.js";
3
+ const view = {
4
+ version: 1,
5
+ threadId: 'thread/id',
6
+ runId: 'run',
7
+ revision: '9007199254740993',
8
+ state: 'stopping',
9
+ actions: {
10
+ canPause: false,
11
+ canResume: false,
12
+ canTerminate: false,
13
+ canSend: false,
14
+ canQueue: false,
15
+ canRetry: false,
16
+ canRespond: false,
17
+ },
18
+ };
19
+ describe('public control view', () => {
20
+ it('uses an abortable SDK GET without sending business input', async () => {
21
+ const fetch = vi
22
+ .fn()
23
+ .mockResolvedValue(new Response(JSON.stringify(view)));
24
+ const client = new Client({ apiUrl: 'https://xpert.example/api/ai', callerOptions: { fetch } });
25
+ const signal = new AbortController().signal;
26
+ expect(await client.threads.getControlView(view.threadId, { signal })).toEqual(view);
27
+ expect(fetch).toHaveBeenCalledTimes(1);
28
+ expect(fetch.mock.calls[0][0].pathname).toBe('/api/ai/threads/thread%2Fid/control-view');
29
+ expect(fetch.mock.calls[0][1]).toMatchObject({ signal });
30
+ expect(fetch.mock.calls[0][1]?.body).toBeUndefined();
31
+ });
32
+ it.each([
33
+ null,
34
+ { ...view, version: 2 },
35
+ { ...view, revision: '0' },
36
+ { ...view, revision: '1.1' },
37
+ { ...view, actions: { canResume: true } },
38
+ { ...view, state: 'cancel-accepted' },
39
+ ])('rejects incomplete or invalid response %j', (invalid) => {
40
+ expect(isThreadControlView(invalid)).toBe(false);
41
+ });
42
+ it('rejects a response for another thread', async () => {
43
+ const fetch = vi
44
+ .fn()
45
+ .mockResolvedValue(new Response(JSON.stringify(view)));
46
+ const client = new Client({ apiUrl: 'https://xpert.example/api/ai', callerOptions: { fetch } });
47
+ await expect(client.threads.getControlView('other')).rejects.toThrow();
48
+ });
49
+ it('orders whole-thread revisions above JS integer precision across replacement runs', () => {
50
+ const newer = {
51
+ ...view,
52
+ runId: 'new',
53
+ revision: '9007199254740994',
54
+ state: 'running',
55
+ };
56
+ expect(mergeThreadControlView(newer, view)).toBe(newer);
57
+ expect(mergeThreadControlView(view, newer)).toBe(newer);
58
+ expect(mergeThreadControlView(view, { ...view, actions: { ...view.actions, canTerminate: true } })
59
+ .actions.canTerminate).toBe(true);
60
+ });
61
+ });
62
+ //# sourceMappingURL=thread-control-view.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-control-view.test.js","sourceRoot":"","sources":["../../src/tests/thread-control-view.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { Client, isThreadControlView, mergeThreadControlView, } from "../index.mjs";
3
+ const view = {
4
+ version: 1,
5
+ threadId: 'thread/id',
6
+ runId: 'run',
7
+ revision: '9007199254740993',
8
+ state: 'stopping',
9
+ actions: {
10
+ canPause: false,
11
+ canResume: false,
12
+ canTerminate: false,
13
+ canSend: false,
14
+ canQueue: false,
15
+ canRetry: false,
16
+ canRespond: false,
17
+ },
18
+ };
19
+ describe('public control view', () => {
20
+ it('uses an abortable SDK GET without sending business input', async () => {
21
+ const fetch = vi
22
+ .fn()
23
+ .mockResolvedValue(new Response(JSON.stringify(view)));
24
+ const client = new Client({ apiUrl: 'https://xpert.example/api/ai', callerOptions: { fetch } });
25
+ const signal = new AbortController().signal;
26
+ expect(await client.threads.getControlView(view.threadId, { signal })).toEqual(view);
27
+ expect(fetch).toHaveBeenCalledTimes(1);
28
+ expect(fetch.mock.calls[0][0].pathname).toBe('/api/ai/threads/thread%2Fid/control-view');
29
+ expect(fetch.mock.calls[0][1]).toMatchObject({ signal });
30
+ expect(fetch.mock.calls[0][1]?.body).toBeUndefined();
31
+ });
32
+ it.each([
33
+ null,
34
+ { ...view, version: 2 },
35
+ { ...view, revision: '0' },
36
+ { ...view, revision: '1.1' },
37
+ { ...view, actions: { canResume: true } },
38
+ { ...view, state: 'cancel-accepted' },
39
+ ])('rejects incomplete or invalid response %j', (invalid) => {
40
+ expect(isThreadControlView(invalid)).toBe(false);
41
+ });
42
+ it('rejects a response for another thread', async () => {
43
+ const fetch = vi
44
+ .fn()
45
+ .mockResolvedValue(new Response(JSON.stringify(view)));
46
+ const client = new Client({ apiUrl: 'https://xpert.example/api/ai', callerOptions: { fetch } });
47
+ await expect(client.threads.getControlView('other')).rejects.toThrow();
48
+ });
49
+ it('orders whole-thread revisions above JS integer precision across replacement runs', () => {
50
+ const newer = {
51
+ ...view,
52
+ runId: 'new',
53
+ revision: '9007199254740994',
54
+ state: 'running',
55
+ };
56
+ expect(mergeThreadControlView(newer, view)).toBe(newer);
57
+ expect(mergeThreadControlView(view, newer)).toBe(newer);
58
+ expect(mergeThreadControlView(view, { ...view, actions: { ...view.actions, canTerminate: true } })
59
+ .actions.canTerminate).toBe(true);
60
+ });
61
+ });
62
+ //# sourceMappingURL=thread-control-view.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-control-view.test.mjs","sourceRoot":"","sources":["../../src/tests/thread-control-view.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { Client } from "../client.js";
3
+ describe('authenticated image previews', () => {
4
+ it.each([false, true])('reads one binary response with the full request context (group=%s)', async (group) => {
5
+ const bytes = new Uint8Array([82, 73, 70, 70, 0, 255]);
6
+ const fetch = vi.fn().mockImplementation(async () => new Response(bytes, {
7
+ headers: {
8
+ 'content-type': 'image/webp',
9
+ 'content-disposition': "inline; filename*=UTF-8''%E5%9B%BE.webp",
10
+ },
11
+ }));
12
+ const onRequest = vi.fn((_url, init) => ({
13
+ ...init,
14
+ headers: { ...init.headers, 'x-context': 'current' },
15
+ }));
16
+ const client = new Client({
17
+ apiUrl: 'https://example.test/proxy/api/ai/',
18
+ apiKey: 'test',
19
+ defaultHeaders: { 'organization-id': 'org' },
20
+ callerOptions: { fetch, maxRetries: 0 },
21
+ onRequest,
22
+ });
23
+ const view = group ? client.forGroupWorkbench('group/1').viewHosts : client.viewHosts;
24
+ const signal = new AbortController().signal;
25
+ const runtimeScope = { projectId: 'project', conversationId: 'conversation' };
26
+ const result = await view.readFilePreview('agent', 'assistant', 'view', { fileKey: 'file', targetId: 'version', variant: 'thumbnail' }, { runtimeScope, signal });
27
+ expect(result.fileName).toBe('图.webp');
28
+ expect(new Uint8Array(await result.blob.arrayBuffer())).toEqual(bytes);
29
+ expect(result.blob.type).toBe('image/webp');
30
+ expect(fetch).toHaveBeenCalledTimes(1);
31
+ const [url, init] = fetch.mock.calls[0];
32
+ expect(String(url)).toBe(`https://example.test/proxy/api/ai/${group ? 'groups/group%2F1/workbench/' : ''}workspace-files/view-sessions/preview`);
33
+ expect(init).toMatchObject({
34
+ method: 'POST',
35
+ signal,
36
+ credentials: 'omit',
37
+ redirect: 'error',
38
+ headers: {
39
+ 'x-context': 'current',
40
+ 'organization-id': 'org',
41
+ 'x-xpert-view-project-id': 'project',
42
+ },
43
+ });
44
+ expect(JSON.parse(String(init?.body))).toEqual({
45
+ hostType: 'agent',
46
+ hostId: 'assistant',
47
+ viewKey: 'view',
48
+ fileKey: 'file',
49
+ targetId: 'version',
50
+ variant: 'thumbnail',
51
+ runtimeScope,
52
+ });
53
+ expect(onRequest).toHaveBeenCalledOnce();
54
+ });
55
+ it.each([401, 403, 404, 415])('preserves HTTP %s without returning an error body as an image', async (status) => {
56
+ const fetch = vi
57
+ .fn()
58
+ .mockImplementation(async () => new Response('Denied', { status }));
59
+ const client = new Client({
60
+ apiUrl: 'https://example.test/api/ai',
61
+ callerOptions: { fetch, maxRetries: 0 },
62
+ });
63
+ await expect(client.viewHosts.readFilePreview('agent', 'assistant', 'view', {
64
+ fileKey: 'file',
65
+ variant: 'original',
66
+ })).rejects.toMatchObject({ status });
67
+ expect(fetch).toHaveBeenCalledOnce();
68
+ });
69
+ it.each([
70
+ ['text/html', 'inline; filename="image.png"'],
71
+ ['image/png', ''],
72
+ ['image/png', "inline; filename*=UTF-8''..%2Fimage.png"],
73
+ ])('rejects invalid binary metadata: %s %s', async (type, disposition) => {
74
+ const client = new Client({
75
+ apiUrl: 'https://example.test/api/ai',
76
+ callerOptions: {
77
+ fetch: async () => new Response('data', {
78
+ headers: { 'content-type': type, 'content-disposition': disposition },
79
+ }),
80
+ maxRetries: 0,
81
+ },
82
+ });
83
+ await expect(client.viewHosts.readFilePreview('agent', 'assistant', 'view', { fileKey: 'file' })).rejects.toThrow();
84
+ });
85
+ });
86
+ //# sourceMappingURL=view-file-preview.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-file-preview.test.js","sourceRoot":"","sources":["../../src/tests/view-file-preview.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { Client } from "../client.mjs";
3
+ describe('authenticated image previews', () => {
4
+ it.each([false, true])('reads one binary response with the full request context (group=%s)', async (group) => {
5
+ const bytes = new Uint8Array([82, 73, 70, 70, 0, 255]);
6
+ const fetch = vi.fn().mockImplementation(async () => new Response(bytes, {
7
+ headers: {
8
+ 'content-type': 'image/webp',
9
+ 'content-disposition': "inline; filename*=UTF-8''%E5%9B%BE.webp",
10
+ },
11
+ }));
12
+ const onRequest = vi.fn((_url, init) => ({
13
+ ...init,
14
+ headers: { ...init.headers, 'x-context': 'current' },
15
+ }));
16
+ const client = new Client({
17
+ apiUrl: 'https://example.test/proxy/api/ai/',
18
+ apiKey: 'test',
19
+ defaultHeaders: { 'organization-id': 'org' },
20
+ callerOptions: { fetch, maxRetries: 0 },
21
+ onRequest,
22
+ });
23
+ const view = group ? client.forGroupWorkbench('group/1').viewHosts : client.viewHosts;
24
+ const signal = new AbortController().signal;
25
+ const runtimeScope = { projectId: 'project', conversationId: 'conversation' };
26
+ const result = await view.readFilePreview('agent', 'assistant', 'view', { fileKey: 'file', targetId: 'version', variant: 'thumbnail' }, { runtimeScope, signal });
27
+ expect(result.fileName).toBe('图.webp');
28
+ expect(new Uint8Array(await result.blob.arrayBuffer())).toEqual(bytes);
29
+ expect(result.blob.type).toBe('image/webp');
30
+ expect(fetch).toHaveBeenCalledTimes(1);
31
+ const [url, init] = fetch.mock.calls[0];
32
+ expect(String(url)).toBe(`https://example.test/proxy/api/ai/${group ? 'groups/group%2F1/workbench/' : ''}workspace-files/view-sessions/preview`);
33
+ expect(init).toMatchObject({
34
+ method: 'POST',
35
+ signal,
36
+ credentials: 'omit',
37
+ redirect: 'error',
38
+ headers: {
39
+ 'x-context': 'current',
40
+ 'organization-id': 'org',
41
+ 'x-xpert-view-project-id': 'project',
42
+ },
43
+ });
44
+ expect(JSON.parse(String(init?.body))).toEqual({
45
+ hostType: 'agent',
46
+ hostId: 'assistant',
47
+ viewKey: 'view',
48
+ fileKey: 'file',
49
+ targetId: 'version',
50
+ variant: 'thumbnail',
51
+ runtimeScope,
52
+ });
53
+ expect(onRequest).toHaveBeenCalledOnce();
54
+ });
55
+ it.each([401, 403, 404, 415])('preserves HTTP %s without returning an error body as an image', async (status) => {
56
+ const fetch = vi
57
+ .fn()
58
+ .mockImplementation(async () => new Response('Denied', { status }));
59
+ const client = new Client({
60
+ apiUrl: 'https://example.test/api/ai',
61
+ callerOptions: { fetch, maxRetries: 0 },
62
+ });
63
+ await expect(client.viewHosts.readFilePreview('agent', 'assistant', 'view', {
64
+ fileKey: 'file',
65
+ variant: 'original',
66
+ })).rejects.toMatchObject({ status });
67
+ expect(fetch).toHaveBeenCalledOnce();
68
+ });
69
+ it.each([
70
+ ['text/html', 'inline; filename="image.png"'],
71
+ ['image/png', ''],
72
+ ['image/png', "inline; filename*=UTF-8''..%2Fimage.png"],
73
+ ])('rejects invalid binary metadata: %s %s', async (type, disposition) => {
74
+ const client = new Client({
75
+ apiUrl: 'https://example.test/api/ai',
76
+ callerOptions: {
77
+ fetch: async () => new Response('data', {
78
+ headers: { 'content-type': type, 'content-disposition': disposition },
79
+ }),
80
+ maxRetries: 0,
81
+ },
82
+ });
83
+ await expect(client.viewHosts.readFilePreview('agent', 'assistant', 'view', { fileKey: 'file' })).rejects.toThrow();
84
+ });
85
+ });
86
+ //# sourceMappingURL=view-file-preview.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-file-preview.test.mjs","sourceRoot":"","sources":["../../src/tests/view-file-preview.test.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
+ import { type ThreadControlView } from './thread-control-view.js';
1
2
  import type { TMessageContentResourceCard } from './resource-card.js';
2
3
  /** Complete discovery snapshot. Reconnect replaces discovery state; tokens use runs.joinStream. */
3
4
  export interface ThreadActivitySnapshot {
4
5
  version: 1;
5
6
  threadId: string;
7
+ controlView?: ThreadControlView;
6
8
  runs: Array<{
7
9
  id: string;
8
10
  status: string;
@@ -1,9 +1,12 @@
1
+ import { isThreadControlView } from "./thread-control-view.js";
1
2
  export function isThreadActivitySnapshot(value) {
2
3
  if (!value || typeof value !== 'object' || !('version' in value) || value.version !== 1 ||
3
4
  !('threadId' in value) || typeof value.threadId !== 'string' ||
4
5
  !('runs' in value) || !Array.isArray(value.runs) ||
5
6
  !('cards' in value) || !Array.isArray(value.cards))
6
7
  return false;
8
+ if ('controlView' in value && value.controlView !== undefined && !isThreadControlView(value.controlView))
9
+ return false;
7
10
  // Card payloads use the shared resource-card parser at the rendering boundary.
8
11
  return value.runs.every((run) => !!run && typeof run === 'object' &&
9
12
  'id' in run && typeof run.id === 'string' &&
@@ -1 +1 @@
1
- {"version":3,"file":"thread-activity.js","sourceRoot":"","sources":["../src/thread-activity.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QACnF,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAC5D,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;QAChD,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,+EAA+E;IAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QACxE,IAAI,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QACzC,QAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QACjD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,iBAAiB,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,QAAQ,CAAC,CAAC;AACzE,CAAC"}
1
+ {"version":3,"file":"thread-activity.js","sourceRoot":"","sources":["../src/thread-activity.ts"],"names":[],"mappings":"OAAO,EAAE,mBAAmB,EAA0B;AAYtD,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QACnF,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAC5D,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;QAChD,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,IAAI,aAAa,IAAI,KAAK,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IACvH,+EAA+E;IAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QACxE,IAAI,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QACzC,QAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QACjD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,iBAAiB,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,QAAQ,CAAC,CAAC;AACzE,CAAC"}
@@ -1,9 +1,12 @@
1
+ import { isThreadControlView } from "./thread-control-view.mjs";
1
2
  export function isThreadActivitySnapshot(value) {
2
3
  if (!value || typeof value !== 'object' || !('version' in value) || value.version !== 1 ||
3
4
  !('threadId' in value) || typeof value.threadId !== 'string' ||
4
5
  !('runs' in value) || !Array.isArray(value.runs) ||
5
6
  !('cards' in value) || !Array.isArray(value.cards))
6
7
  return false;
8
+ if ('controlView' in value && value.controlView !== undefined && !isThreadControlView(value.controlView))
9
+ return false;
7
10
  // Card payloads use the shared resource-card parser at the rendering boundary.
8
11
  return value.runs.every((run) => !!run && typeof run === 'object' &&
9
12
  'id' in run && typeof run.id === 'string' &&
@@ -1 +1 @@
1
- {"version":3,"file":"thread-activity.mjs","sourceRoot":"","sources":["../src/thread-activity.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QACnF,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAC5D,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;QAChD,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,+EAA+E;IAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QACxE,IAAI,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QACzC,QAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QACjD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,iBAAiB,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,QAAQ,CAAC,CAAC;AACzE,CAAC"}
1
+ {"version":3,"file":"thread-activity.mjs","sourceRoot":"","sources":["../src/thread-activity.ts"],"names":[],"mappings":"OAAO,EAAE,mBAAmB,EAA0B;AAYtD,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QACnF,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAC5D,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;QAChD,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,IAAI,aAAa,IAAI,KAAK,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IACvH,+EAA+E;IAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QACxE,IAAI,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QACzC,QAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QACjD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,WAAW,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QACvD,iBAAiB,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,QAAQ,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,23 @@
1
+ /** Public hints only. The host revalidates every action against its durable execution state. */
2
+ export type ThreadControlActions = {
3
+ canPause: boolean;
4
+ canResume: boolean;
5
+ canTerminate: boolean;
6
+ canSend: boolean;
7
+ canQueue: boolean;
8
+ canRetry: boolean;
9
+ canRespond: boolean;
10
+ };
11
+ export type ThreadControlView = {
12
+ version: 1;
13
+ threadId: string;
14
+ runId: string | null;
15
+ /** Decimal durable clock; do not compare revisions as strings or timestamps. */
16
+ revision: string;
17
+ state: 'idle' | 'running' | 'pausing' | 'paused' | 'stopping' | 'settling' | 'input_required' | 'confirming' | 'completed' | 'stopped' | 'failed';
18
+ actions: ThreadControlActions;
19
+ pauseId?: string;
20
+ };
21
+ export declare function isThreadControlView(value: unknown): value is ThreadControlView;
22
+ /** Revisions order the whole thread, including replacement runs. Never let an old response restore controls. */
23
+ export declare function mergeThreadControlView(previous: ThreadControlView | undefined, incoming: ThreadControlView): ThreadControlView;