@xpert-ai/xpert-sdk 0.0.17 → 0.1.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.
- package/dist/client.d.ts +71 -1
- package/dist/client.js +198 -4
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +198 -4
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/schema.d.ts +264 -1
- package/dist/tests/projects-connectors.test.d.ts +1 -0
- package/dist/tests/projects-connectors.test.js +372 -0
- package/dist/tests/projects-connectors.test.js.map +1 -0
- package/dist/tests/projects-connectors.test.mjs +372 -0
- package/dist/tests/projects-connectors.test.mjs.map +1 -0
- package/dist/tests/sse.test.js +17 -0
- package/dist/tests/sse.test.js.map +1 -1
- package/dist/tests/sse.test.mjs +17 -0
- package/dist/tests/sse.test.mjs.map +1 -1
- package/dist/tests/view-hosts.test.js +30 -1
- package/dist/tests/view-hosts.test.js.map +1 -1
- package/dist/tests/view-hosts.test.mjs +30 -1
- package/dist/tests/view-hosts.test.mjs.map +1 -1
- package/dist/utils/sse.js +10 -4
- package/dist/utils/sse.js.map +1 -1
- package/dist/utils/sse.mjs +10 -4
- package/dist/utils/sse.mjs.map +1 -1
- package/dist/view-extension.d.ts +46 -0
- package/dist/view-extension.js +2 -0
- package/dist/view-extension.js.map +1 -1
- package/dist/view-extension.mjs +2 -0
- package/dist/view-extension.mjs.map +1 -1
- package/package.json +1 -1
package/dist/schema.d.ts
CHANGED
|
@@ -463,6 +463,234 @@ export type RuntimeI18nObject = {
|
|
|
463
463
|
[locale: string]: string | undefined;
|
|
464
464
|
};
|
|
465
465
|
export type RuntimeI18nText = string | RuntimeI18nObject;
|
|
466
|
+
export type XpertProjectStatus = "active" | "deprecated" | "archived";
|
|
467
|
+
export type XpertProjectAvailabilityStatus = "active" | "archived" | "all";
|
|
468
|
+
export type XpertProject = {
|
|
469
|
+
id: string;
|
|
470
|
+
name: string;
|
|
471
|
+
description?: string;
|
|
472
|
+
status: XpertProjectStatus;
|
|
473
|
+
/** @deprecated Projects no longer use a workspace as their collaboration boundary. */
|
|
474
|
+
workspaceId?: string;
|
|
475
|
+
avatar?: Record<string, unknown> | string | null;
|
|
476
|
+
createdAt?: string;
|
|
477
|
+
updatedAt?: string;
|
|
478
|
+
};
|
|
479
|
+
export type XpertProjectListOptions = {
|
|
480
|
+
/** Only projects containing this Xpert are returned. */
|
|
481
|
+
xpertId: string;
|
|
482
|
+
status?: XpertProjectAvailabilityStatus;
|
|
483
|
+
skip?: number;
|
|
484
|
+
take?: number;
|
|
485
|
+
signal?: AbortSignal;
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* File or directory returned by an Xpert workspace volume listing.
|
|
489
|
+
* `filePath` is the entry name while `fullPath` is relative to the workspace root.
|
|
490
|
+
*/
|
|
491
|
+
export type XpertWorkspaceFile = {
|
|
492
|
+
filePath: string;
|
|
493
|
+
fullPath?: string;
|
|
494
|
+
directory?: string;
|
|
495
|
+
fileType?: string;
|
|
496
|
+
fileUrl?: string;
|
|
497
|
+
url?: string;
|
|
498
|
+
mimeType?: string;
|
|
499
|
+
size?: number;
|
|
500
|
+
createdAt?: string;
|
|
501
|
+
updatedAt?: string;
|
|
502
|
+
hasChildren?: boolean;
|
|
503
|
+
children?: XpertWorkspaceFile[] | null;
|
|
504
|
+
};
|
|
505
|
+
export type XpertWorkspaceFileListOptions = {
|
|
506
|
+
path?: string;
|
|
507
|
+
depth?: number;
|
|
508
|
+
signal?: AbortSignal;
|
|
509
|
+
};
|
|
510
|
+
export type XpertWorkspaceStatus = "active" | "deprecated" | "archived";
|
|
511
|
+
export type XpertWorkspaceAccessPurpose = "runtime" | "authoring";
|
|
512
|
+
export type XpertWorkspace = {
|
|
513
|
+
id: string;
|
|
514
|
+
name: string;
|
|
515
|
+
description?: string;
|
|
516
|
+
status: XpertWorkspaceStatus;
|
|
517
|
+
ownerId: string;
|
|
518
|
+
capabilities?: {
|
|
519
|
+
canRead: boolean;
|
|
520
|
+
canRun: boolean;
|
|
521
|
+
canWrite: boolean;
|
|
522
|
+
canManage: boolean;
|
|
523
|
+
};
|
|
524
|
+
isTenantShared?: boolean;
|
|
525
|
+
createdAt?: string;
|
|
526
|
+
updatedAt?: string;
|
|
527
|
+
};
|
|
528
|
+
export type XpertWorkspaceDefaultOptions = {
|
|
529
|
+
purpose?: XpertWorkspaceAccessPurpose;
|
|
530
|
+
signal?: AbortSignal;
|
|
531
|
+
};
|
|
532
|
+
export type ConnectorStatus = "pending" | "active" | "expired" | "error" | "disconnected";
|
|
533
|
+
export type ConnectorAuthorizationMode = "personal" | "shared";
|
|
534
|
+
export type ConnectorScope = {
|
|
535
|
+
type: "workspace";
|
|
536
|
+
workspaceId: string;
|
|
537
|
+
} | {
|
|
538
|
+
type: "project";
|
|
539
|
+
projectId: string;
|
|
540
|
+
};
|
|
541
|
+
export type ConnectorAppCredentialField = {
|
|
542
|
+
name: string;
|
|
543
|
+
label: RuntimeI18nText;
|
|
544
|
+
type?: "text" | "password";
|
|
545
|
+
required?: boolean;
|
|
546
|
+
placeholder?: RuntimeI18nText;
|
|
547
|
+
description?: RuntimeI18nText;
|
|
548
|
+
secret?: boolean;
|
|
549
|
+
};
|
|
550
|
+
export type ConnectorCredentialFormDefinition = {
|
|
551
|
+
fields?: ConnectorAppCredentialField[];
|
|
552
|
+
help?: {
|
|
553
|
+
label: RuntimeI18nText;
|
|
554
|
+
url: string;
|
|
555
|
+
};
|
|
556
|
+
defaultValues?: Record<string, string | number | boolean | null>;
|
|
557
|
+
};
|
|
558
|
+
export type ConnectorAuthMethodDefinition = {
|
|
559
|
+
id: string;
|
|
560
|
+
type: "oauth2";
|
|
561
|
+
label: RuntimeI18nText;
|
|
562
|
+
appCredentials?: ConnectorCredentialFormDefinition;
|
|
563
|
+
} | {
|
|
564
|
+
id: string;
|
|
565
|
+
type: "api_key";
|
|
566
|
+
label: RuntimeI18nText;
|
|
567
|
+
credentials: ConnectorCredentialFormDefinition;
|
|
568
|
+
};
|
|
569
|
+
export type ConnectorDefinitionBase = {
|
|
570
|
+
provider: string;
|
|
571
|
+
label: RuntimeI18nText;
|
|
572
|
+
description?: RuntimeI18nText;
|
|
573
|
+
icon?: IconDefinition;
|
|
574
|
+
legacyAuthMethodId?: string;
|
|
575
|
+
/** Missing values are treated as shared-only for legacy providers. */
|
|
576
|
+
authorizationModes?: ConnectorAuthorizationMode[];
|
|
577
|
+
};
|
|
578
|
+
export type ConnectorStrategyDefinition = ConnectorDefinitionBase & ({
|
|
579
|
+
auth: {
|
|
580
|
+
type: "oauth2";
|
|
581
|
+
authorizationUrl?: string;
|
|
582
|
+
tokenUrl?: string;
|
|
583
|
+
userInfoUrl?: string;
|
|
584
|
+
scopes?: string[];
|
|
585
|
+
redirectPath?: string;
|
|
586
|
+
};
|
|
587
|
+
appCredentials?: ConnectorCredentialFormDefinition;
|
|
588
|
+
} | {
|
|
589
|
+
authMethods: ConnectorAuthMethodDefinition[];
|
|
590
|
+
auth?: {
|
|
591
|
+
type: "oauth2";
|
|
592
|
+
authorizationUrl?: string;
|
|
593
|
+
tokenUrl?: string;
|
|
594
|
+
userInfoUrl?: string;
|
|
595
|
+
scopes?: string[];
|
|
596
|
+
redirectPath?: string;
|
|
597
|
+
};
|
|
598
|
+
appCredentials?: ConnectorCredentialFormDefinition;
|
|
599
|
+
});
|
|
600
|
+
export type ConnectorProfile = {
|
|
601
|
+
openId?: string;
|
|
602
|
+
unionId?: string;
|
|
603
|
+
userId?: string;
|
|
604
|
+
name?: string;
|
|
605
|
+
avatarUrl?: string;
|
|
606
|
+
email?: string;
|
|
607
|
+
[key: string]: unknown;
|
|
608
|
+
};
|
|
609
|
+
export type ConnectorInstance = {
|
|
610
|
+
id: string;
|
|
611
|
+
bindingId?: string;
|
|
612
|
+
accountId?: string;
|
|
613
|
+
workspaceId?: string | null;
|
|
614
|
+
projectId?: string | null;
|
|
615
|
+
provider: string;
|
|
616
|
+
authMethodId?: string | null;
|
|
617
|
+
status: ConnectorStatus;
|
|
618
|
+
profile?: ConnectorProfile | null;
|
|
619
|
+
scopes?: string[];
|
|
620
|
+
expiresAt?: string | null;
|
|
621
|
+
refreshExpiresAt?: string | null;
|
|
622
|
+
connectedAt?: string | null;
|
|
623
|
+
disconnectedAt?: string | null;
|
|
624
|
+
lastError?: string | null;
|
|
625
|
+
createdAt?: string;
|
|
626
|
+
updatedAt?: string;
|
|
627
|
+
};
|
|
628
|
+
export type ConnectorBinding = ConnectorInstance & {
|
|
629
|
+
scopeType: ConnectorScope["type"];
|
|
630
|
+
scope: ConnectorScope;
|
|
631
|
+
authorizationMode: ConnectorAuthorizationMode;
|
|
632
|
+
};
|
|
633
|
+
export type ConnectorRuntimeOption = {
|
|
634
|
+
bindingId: string;
|
|
635
|
+
provider: string;
|
|
636
|
+
authorizationMode: ConnectorAuthorizationMode;
|
|
637
|
+
status: ConnectorStatus;
|
|
638
|
+
granted: boolean;
|
|
639
|
+
profile?: ConnectorProfile | null;
|
|
640
|
+
label: RuntimeI18nText;
|
|
641
|
+
description?: RuntimeI18nText;
|
|
642
|
+
icon?: IconDefinition;
|
|
643
|
+
authMethods?: ConnectorAuthMethodDefinition[];
|
|
644
|
+
};
|
|
645
|
+
export type ConnectorRuntimeOptions = {
|
|
646
|
+
scope: ConnectorScope;
|
|
647
|
+
items: ConnectorRuntimeOption[];
|
|
648
|
+
};
|
|
649
|
+
export type ConnectorRuntimeOptionsResponse = ConnectorRuntimeOptions;
|
|
650
|
+
export type ConnectorBindingCreateRequest = {
|
|
651
|
+
scope: ConnectorScope;
|
|
652
|
+
provider: string;
|
|
653
|
+
authorizationMode: ConnectorAuthorizationMode;
|
|
654
|
+
};
|
|
655
|
+
export type ConnectorPersonalAccountInstance = {
|
|
656
|
+
id: string;
|
|
657
|
+
provider: string;
|
|
658
|
+
status: ConnectorStatus;
|
|
659
|
+
authMethodId?: string | null;
|
|
660
|
+
profile?: ConnectorProfile | null;
|
|
661
|
+
scopes?: string[];
|
|
662
|
+
expiresAt?: string | null;
|
|
663
|
+
connectedAt?: string | null;
|
|
664
|
+
disconnectedAt?: string | null;
|
|
665
|
+
lastError?: string | null;
|
|
666
|
+
};
|
|
667
|
+
/** @deprecated Use ConnectorPersonalAccountInstance. */
|
|
668
|
+
export type PersonalConnectorAccount = ConnectorPersonalAccountInstance;
|
|
669
|
+
export type ConnectorConnectRequest = {
|
|
670
|
+
authMethodId?: string;
|
|
671
|
+
values?: Record<string, unknown>;
|
|
672
|
+
xpertId?: string;
|
|
673
|
+
/** @deprecated Use values instead. */
|
|
674
|
+
app?: Record<string, unknown>;
|
|
675
|
+
};
|
|
676
|
+
export type ConnectorConsentRequest = {
|
|
677
|
+
xpertId?: string;
|
|
678
|
+
};
|
|
679
|
+
export type ConnectorConnectResponse = {
|
|
680
|
+
status: "active" | "pending";
|
|
681
|
+
connector: ConnectorInstance;
|
|
682
|
+
authorizationUrl?: string | null;
|
|
683
|
+
stateExpiresAt?: string | null;
|
|
684
|
+
pollIntervalSeconds?: number | null;
|
|
685
|
+
};
|
|
686
|
+
export type ConnectorOAuthStatusResponse = {
|
|
687
|
+
connector: ConnectorInstance;
|
|
688
|
+
granted?: boolean;
|
|
689
|
+
authorizationUrl?: string | null;
|
|
690
|
+
stateExpiresAt?: string | null;
|
|
691
|
+
pollIntervalSeconds?: number | null;
|
|
692
|
+
message?: string | null;
|
|
693
|
+
};
|
|
466
694
|
export type RuntimePromptWorkflow = {
|
|
467
695
|
type: "prompt_workflow";
|
|
468
696
|
name?: string;
|
|
@@ -502,6 +730,9 @@ export type RuntimeCapabilitiesSelectionSet = {
|
|
|
502
730
|
subAgents?: {
|
|
503
731
|
nodeKeys: string[];
|
|
504
732
|
};
|
|
733
|
+
connectors?: {
|
|
734
|
+
bindingIds: string[];
|
|
735
|
+
};
|
|
505
736
|
};
|
|
506
737
|
export type RuntimeCapabilitiesSelection = RuntimeCapabilitiesSelectionSet & {
|
|
507
738
|
mode: "allowlist";
|
|
@@ -700,6 +931,9 @@ export type ChatConversationStatus = "idle" | "busy" | "interrupted" | "error";
|
|
|
700
931
|
export type ThreadGoalStatus = "active" | "paused" | "blocked" | "usage_limited" | "budget_limited" | "complete";
|
|
701
932
|
export type ThreadGoalUserStatus = Extract<ThreadGoalStatus, "active" | "paused">;
|
|
702
933
|
export type ChatConversationFrom = "platform" | "webapp" | "debugger" | "knowledge" | "job" | "api" | "feishu" | "lark" | "dingtalk" | "wecom";
|
|
934
|
+
export type ChatConversationOptions = Record<string, unknown> & {
|
|
935
|
+
runtimeCapabilities?: RuntimeCapabilitiesSelection | null;
|
|
936
|
+
};
|
|
703
937
|
export interface ChatConversation {
|
|
704
938
|
id: string;
|
|
705
939
|
threadId: string;
|
|
@@ -707,7 +941,7 @@ export interface ChatConversation {
|
|
|
707
941
|
status?: ChatConversationStatus;
|
|
708
942
|
from?: ChatConversationFrom;
|
|
709
943
|
fromEndUserId?: string;
|
|
710
|
-
options?:
|
|
944
|
+
options?: ChatConversationOptions;
|
|
711
945
|
error?: string;
|
|
712
946
|
operation?: Record<string, unknown>;
|
|
713
947
|
xpertId?: string;
|
|
@@ -716,6 +950,35 @@ export interface ChatConversation {
|
|
|
716
950
|
createdAt?: string;
|
|
717
951
|
updatedAt?: string;
|
|
718
952
|
}
|
|
953
|
+
export type ChatConversationFileStatus = "uploaded" | "scanning" | "parsing" | "ready" | "partial" | "failed";
|
|
954
|
+
export type ChatConversationFilePurpose = "chat_attachment" | "workspace" | "knowledge";
|
|
955
|
+
export type ChatConversationFileParseMode = "auto" | "fast" | "deep" | "none";
|
|
956
|
+
/**
|
|
957
|
+
* Agent-readable file attached to a conversation.
|
|
958
|
+
*/
|
|
959
|
+
export interface ChatConversationFile {
|
|
960
|
+
id: string;
|
|
961
|
+
storageFileId?: string;
|
|
962
|
+
conversationId?: string;
|
|
963
|
+
threadId?: string;
|
|
964
|
+
projectId?: string;
|
|
965
|
+
xpertId?: string;
|
|
966
|
+
originalName?: string;
|
|
967
|
+
fileName?: string;
|
|
968
|
+
mimeType?: string;
|
|
969
|
+
size?: number;
|
|
970
|
+
purpose: ChatConversationFilePurpose;
|
|
971
|
+
parseMode: ChatConversationFileParseMode;
|
|
972
|
+
status: ChatConversationFileStatus;
|
|
973
|
+
capabilities?: string[];
|
|
974
|
+
workspacePath?: string;
|
|
975
|
+
summary?: string;
|
|
976
|
+
error?: string;
|
|
977
|
+
parsedAt?: string;
|
|
978
|
+
failedAt?: string;
|
|
979
|
+
createdAt?: string;
|
|
980
|
+
updatedAt?: string;
|
|
981
|
+
}
|
|
719
982
|
export interface ThreadGoal {
|
|
720
983
|
id: string;
|
|
721
984
|
conversationId: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Client } from "../index.js";
|
|
3
|
+
function jsonResponse(value, status = 200) {
|
|
4
|
+
return new Response(JSON.stringify(value), {
|
|
5
|
+
status,
|
|
6
|
+
headers: { "content-type": "application/json" },
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
describe("ProjectsClient", () => {
|
|
10
|
+
it("lists only projects available to an Xpert and preserves request hooks", async () => {
|
|
11
|
+
const projects = [
|
|
12
|
+
{
|
|
13
|
+
id: "project-1",
|
|
14
|
+
name: "Launch plan",
|
|
15
|
+
status: "active",
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
const fetchMock = vi
|
|
19
|
+
.fn()
|
|
20
|
+
.mockResolvedValue(jsonResponse({ items: projects, total: 1 }));
|
|
21
|
+
const onRequest = vi.fn((_url, init) => ({
|
|
22
|
+
...init,
|
|
23
|
+
headers: { ...init.headers, "organization-id": "org-1" },
|
|
24
|
+
}));
|
|
25
|
+
const client = new Client({
|
|
26
|
+
apiUrl: "https://xpert.example/api/ai/",
|
|
27
|
+
apiKey: "secret",
|
|
28
|
+
callerOptions: { fetch: fetchMock },
|
|
29
|
+
onRequest,
|
|
30
|
+
});
|
|
31
|
+
await expect(client.projects.list({ xpertId: "xpert/1" })).resolves.toEqual({
|
|
32
|
+
items: projects,
|
|
33
|
+
total: 1,
|
|
34
|
+
});
|
|
35
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
36
|
+
expect(url.pathname).toBe("/api/xpert-project/available");
|
|
37
|
+
expect(url.searchParams.get("xpertId")).toBe("xpert/1");
|
|
38
|
+
expect(url.searchParams.get("status")).toBe("active");
|
|
39
|
+
expect(url.searchParams.get("skip")).toBe("0");
|
|
40
|
+
expect(url.searchParams.get("take")).toBe("100");
|
|
41
|
+
expect(init?.headers).toMatchObject({
|
|
42
|
+
"organization-id": "org-1",
|
|
43
|
+
"x-api-key": "secret",
|
|
44
|
+
});
|
|
45
|
+
expect(onRequest).toHaveBeenCalledOnce();
|
|
46
|
+
});
|
|
47
|
+
it("gets a project by encoded id", async () => {
|
|
48
|
+
const project = {
|
|
49
|
+
id: "project/1",
|
|
50
|
+
name: "Launch plan",
|
|
51
|
+
status: "active",
|
|
52
|
+
};
|
|
53
|
+
const fetchMock = vi
|
|
54
|
+
.fn()
|
|
55
|
+
.mockResolvedValue(jsonResponse(project));
|
|
56
|
+
const client = new Client({
|
|
57
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
58
|
+
callerOptions: { fetch: fetchMock },
|
|
59
|
+
});
|
|
60
|
+
await expect(client.projects.get("project/1")).resolves.toEqual(project);
|
|
61
|
+
expect((fetchMock.mock.calls[0]?.[0]).pathname).toBe("/api/xpert-project/project%2F1");
|
|
62
|
+
});
|
|
63
|
+
it("requests all project statuses explicitly", async () => {
|
|
64
|
+
const fetchMock = vi
|
|
65
|
+
.fn()
|
|
66
|
+
.mockResolvedValue(jsonResponse({ items: [], total: 0 }));
|
|
67
|
+
const client = new Client({
|
|
68
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
69
|
+
callerOptions: { fetch: fetchMock },
|
|
70
|
+
});
|
|
71
|
+
await client.projects.list({
|
|
72
|
+
xpertId: "xpert-1",
|
|
73
|
+
status: "all",
|
|
74
|
+
skip: 10,
|
|
75
|
+
take: 20,
|
|
76
|
+
});
|
|
77
|
+
const url = fetchMock.mock.calls[0]?.[0];
|
|
78
|
+
expect(url.searchParams.get("xpertId")).toBe("xpert-1");
|
|
79
|
+
expect(url.searchParams.get("status")).toBe("all");
|
|
80
|
+
expect(url.searchParams.get("skip")).toBe("10");
|
|
81
|
+
expect(url.searchParams.get("take")).toBe("20");
|
|
82
|
+
});
|
|
83
|
+
it("lists project workspace files with the requested depth", async () => {
|
|
84
|
+
const files = [
|
|
85
|
+
{
|
|
86
|
+
filePath: "briefs",
|
|
87
|
+
hasChildren: true,
|
|
88
|
+
children: [
|
|
89
|
+
{ filePath: "briefs/product.pdf", mimeType: "application/pdf" },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
const fetchMock = vi
|
|
94
|
+
.fn()
|
|
95
|
+
.mockResolvedValue(jsonResponse(files));
|
|
96
|
+
const client = new Client({
|
|
97
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
98
|
+
apiKey: "secret",
|
|
99
|
+
callerOptions: { fetch: fetchMock },
|
|
100
|
+
});
|
|
101
|
+
await expect(client.projects.listFiles("project/1", {
|
|
102
|
+
path: "briefs",
|
|
103
|
+
depth: 8,
|
|
104
|
+
})).resolves.toEqual(files);
|
|
105
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
106
|
+
expect(url.pathname).toBe("/api/xpert-project/project%2F1/files");
|
|
107
|
+
expect(url.searchParams.get("path")).toBe("briefs");
|
|
108
|
+
expect(url.searchParams.get("deepth")).toBe("8");
|
|
109
|
+
expect(init?.headers).toMatchObject({ "x-api-key": "secret" });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe("AssistantsClient runtime capabilities", () => {
|
|
113
|
+
it("scopes runtime capabilities to the selected project", async () => {
|
|
114
|
+
const fetchMock = vi
|
|
115
|
+
.fn()
|
|
116
|
+
.mockResolvedValue(jsonResponse({ skills: [], plugins: [] }));
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
const client = new Client({
|
|
119
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
120
|
+
callerOptions: { fetch: fetchMock },
|
|
121
|
+
});
|
|
122
|
+
await client.assistants.getRuntimeCapabilities("xpert/1", {
|
|
123
|
+
projectId: "project/1",
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
});
|
|
126
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
127
|
+
expect(url.pathname).toBe("/api/ai/assistants/xpert/1/runtime-capabilities");
|
|
128
|
+
expect(url.searchParams.get("projectId")).toBe("project/1");
|
|
129
|
+
expect(init?.signal).toBe(controller.signal);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe("XpertsClient", () => {
|
|
133
|
+
it("lists an assistants workspace files without a conversation", async () => {
|
|
134
|
+
const files = [
|
|
135
|
+
{ filePath: "project.md", mimeType: "text/markdown", size: 42 },
|
|
136
|
+
];
|
|
137
|
+
const fetchMock = vi
|
|
138
|
+
.fn()
|
|
139
|
+
.mockResolvedValue(jsonResponse(files));
|
|
140
|
+
const controller = new AbortController();
|
|
141
|
+
const client = new Client({
|
|
142
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
143
|
+
callerOptions: { fetch: fetchMock },
|
|
144
|
+
});
|
|
145
|
+
await expect(client.xperts.listWorkspaceFiles("assistant/1", {
|
|
146
|
+
depth: 12,
|
|
147
|
+
signal: controller.signal,
|
|
148
|
+
})).resolves.toEqual(files);
|
|
149
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
150
|
+
expect(url.pathname).toBe("/api/xpert/assistant%2F1/workspace/files");
|
|
151
|
+
expect(url.searchParams.get("deepth")).toBe("12");
|
|
152
|
+
expect(init?.signal).toBe(controller.signal);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe("WorkspacesClient", () => {
|
|
156
|
+
it("gets the current users default authoring workspace", async () => {
|
|
157
|
+
const workspace = {
|
|
158
|
+
id: "workspace-1",
|
|
159
|
+
name: "Personal workspace",
|
|
160
|
+
status: "active",
|
|
161
|
+
ownerId: "user-1",
|
|
162
|
+
};
|
|
163
|
+
const fetchMock = vi
|
|
164
|
+
.fn()
|
|
165
|
+
.mockResolvedValue(jsonResponse(workspace));
|
|
166
|
+
const client = new Client({
|
|
167
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
168
|
+
apiKey: "secret",
|
|
169
|
+
callerOptions: { fetch: fetchMock },
|
|
170
|
+
});
|
|
171
|
+
await expect(client.workspaces.getDefault({ purpose: "authoring" })).resolves.toEqual(workspace);
|
|
172
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
173
|
+
expect(url.pathname).toBe("/api/xpert-workspace/my/default");
|
|
174
|
+
expect(url.searchParams.get("purpose")).toBe("authoring");
|
|
175
|
+
expect(init?.headers).toMatchObject({ "x-api-key": "secret" });
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe("ConnectorsClient", () => {
|
|
179
|
+
it("loads runtime options for the Xpert and selected project", async () => {
|
|
180
|
+
const response = {
|
|
181
|
+
scope: { type: "project", projectId: "project-1" },
|
|
182
|
+
items: [
|
|
183
|
+
{
|
|
184
|
+
bindingId: "binding-1",
|
|
185
|
+
provider: "google-drive",
|
|
186
|
+
authorizationMode: "personal",
|
|
187
|
+
status: "active",
|
|
188
|
+
granted: true,
|
|
189
|
+
label: "Google Drive",
|
|
190
|
+
authMethods: [
|
|
191
|
+
{ id: "oauth2", type: "oauth2", label: "OAuth" },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
const fetchMock = vi
|
|
197
|
+
.fn()
|
|
198
|
+
.mockResolvedValue(jsonResponse(response));
|
|
199
|
+
const client = new Client({
|
|
200
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
201
|
+
callerOptions: { fetch: fetchMock },
|
|
202
|
+
});
|
|
203
|
+
await expect(client.connectors.runtimeOptions("xpert/1", {
|
|
204
|
+
projectId: "project/1",
|
|
205
|
+
})).resolves.toEqual(response);
|
|
206
|
+
const url = fetchMock.mock.calls[0]?.[0];
|
|
207
|
+
expect(url.pathname).toBe("/api/connector/runtime-options");
|
|
208
|
+
expect(url.searchParams.get("xpertId")).toBe("xpert/1");
|
|
209
|
+
expect(url.searchParams.get("projectId")).toBe("project/1");
|
|
210
|
+
});
|
|
211
|
+
it("lists definitions and bindings for a typed scope", async () => {
|
|
212
|
+
const definitions = [
|
|
213
|
+
{
|
|
214
|
+
provider: "google-drive",
|
|
215
|
+
label: { en_US: "Google Drive", zh_Hans: "Google Drive" },
|
|
216
|
+
auth: { type: "oauth2" },
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
const bindings = [
|
|
220
|
+
{
|
|
221
|
+
id: "binding-1",
|
|
222
|
+
scopeType: "project",
|
|
223
|
+
scope: { type: "project", projectId: "project-1" },
|
|
224
|
+
projectId: "project-1",
|
|
225
|
+
provider: "google-drive",
|
|
226
|
+
authorizationMode: "shared",
|
|
227
|
+
status: "active",
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
const fetchMock = vi
|
|
231
|
+
.fn()
|
|
232
|
+
.mockResolvedValueOnce(jsonResponse(definitions))
|
|
233
|
+
.mockResolvedValueOnce(jsonResponse(bindings));
|
|
234
|
+
const client = new Client({
|
|
235
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
236
|
+
callerOptions: { fetch: fetchMock },
|
|
237
|
+
});
|
|
238
|
+
const scope = { type: "project", projectId: "project/1" };
|
|
239
|
+
await expect(client.connectors.definitions(scope)).resolves.toEqual(definitions);
|
|
240
|
+
await expect(client.connectors.listBindings(scope)).resolves.toEqual(bindings);
|
|
241
|
+
expect((fetchMock.mock.calls[0]?.[0]).pathname).toBe("/api/connector/definitions");
|
|
242
|
+
expect((fetchMock.mock.calls[0]?.[0]).searchParams.get("scopeType")).toBe("project");
|
|
243
|
+
expect((fetchMock.mock.calls[0]?.[0]).searchParams.get("scopeId")).toBe("project/1");
|
|
244
|
+
expect((fetchMock.mock.calls[1]?.[0]).pathname).toBe("/api/connector/bindings");
|
|
245
|
+
});
|
|
246
|
+
it("starts and polls connector authorization with typed payloads", async () => {
|
|
247
|
+
const connectInput = {
|
|
248
|
+
authMethodId: "oauth2",
|
|
249
|
+
xpertId: "xpert-1",
|
|
250
|
+
};
|
|
251
|
+
const pending = {
|
|
252
|
+
status: "pending",
|
|
253
|
+
connector: {
|
|
254
|
+
id: "connector-1",
|
|
255
|
+
workspaceId: "workspace-1",
|
|
256
|
+
provider: "google-drive",
|
|
257
|
+
authMethodId: "oauth2",
|
|
258
|
+
status: "pending",
|
|
259
|
+
},
|
|
260
|
+
authorizationUrl: "https://accounts.example/authorize",
|
|
261
|
+
pollIntervalSeconds: 5,
|
|
262
|
+
};
|
|
263
|
+
const active = {
|
|
264
|
+
connector: { ...pending.connector, status: "active" },
|
|
265
|
+
granted: true,
|
|
266
|
+
};
|
|
267
|
+
const fetchMock = vi
|
|
268
|
+
.fn()
|
|
269
|
+
.mockResolvedValueOnce(jsonResponse(pending))
|
|
270
|
+
.mockResolvedValueOnce(jsonResponse(active));
|
|
271
|
+
const client = new Client({
|
|
272
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
273
|
+
callerOptions: { fetch: fetchMock },
|
|
274
|
+
});
|
|
275
|
+
await expect(client.connectors.connect("binding/1", connectInput)).resolves.toEqual(pending);
|
|
276
|
+
await expect(client.connectors.authorizationStatus("binding/1", {
|
|
277
|
+
xpertId: "xpert-1",
|
|
278
|
+
})).resolves.toEqual(active);
|
|
279
|
+
const [connectUrl, connectInit] = fetchMock.mock.calls[0] ?? [];
|
|
280
|
+
expect(connectUrl.pathname).toBe("/api/connector/bindings/binding%2F1/connect");
|
|
281
|
+
expect(connectInit?.method).toBe("POST");
|
|
282
|
+
expect(connectInit?.body).toBe(JSON.stringify(connectInput));
|
|
283
|
+
expect((fetchMock.mock.calls[1]?.[0]).pathname).toBe("/api/connector/bindings/binding%2F1/authorization-status");
|
|
284
|
+
expect((fetchMock.mock.calls[1]?.[0]).searchParams.get("xpertId")).toBe("xpert-1");
|
|
285
|
+
});
|
|
286
|
+
it("records personal Connector consent in the current Xpert context", async () => {
|
|
287
|
+
const granted = {
|
|
288
|
+
id: "binding-1",
|
|
289
|
+
provider: "google-drive",
|
|
290
|
+
scopeType: "project",
|
|
291
|
+
scope: { type: "project", projectId: "project-1" },
|
|
292
|
+
projectId: "project-1",
|
|
293
|
+
authorizationMode: "personal",
|
|
294
|
+
status: "active",
|
|
295
|
+
profile: { name: "Alice" },
|
|
296
|
+
};
|
|
297
|
+
const fetchMock = vi
|
|
298
|
+
.fn()
|
|
299
|
+
.mockResolvedValue(jsonResponse(granted));
|
|
300
|
+
const client = new Client({
|
|
301
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
302
|
+
callerOptions: { fetch: fetchMock },
|
|
303
|
+
});
|
|
304
|
+
await expect(client.connectors.consent("binding/1", { xpertId: "xpert-1" })).resolves.toEqual(granted);
|
|
305
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
306
|
+
expect(url.pathname).toBe("/api/connector/bindings/binding%2F1/consent");
|
|
307
|
+
expect(init?.method).toBe("POST");
|
|
308
|
+
expect(init?.body).toBe(JSON.stringify({ xpertId: "xpert-1" }));
|
|
309
|
+
});
|
|
310
|
+
it("lists the current users personal Connector accounts", async () => {
|
|
311
|
+
const accounts = [
|
|
312
|
+
{
|
|
313
|
+
id: "account-1",
|
|
314
|
+
provider: "google-drive",
|
|
315
|
+
status: "active",
|
|
316
|
+
profile: { name: "Alice" },
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
const fetchMock = vi
|
|
320
|
+
.fn()
|
|
321
|
+
.mockResolvedValue(jsonResponse(accounts));
|
|
322
|
+
const controller = new AbortController();
|
|
323
|
+
const client = new Client({
|
|
324
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
325
|
+
callerOptions: { fetch: fetchMock },
|
|
326
|
+
});
|
|
327
|
+
await expect(client.connectors.listPersonalAccounts({ signal: controller.signal })).resolves.toEqual(accounts);
|
|
328
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
329
|
+
expect(url.pathname).toBe("/api/connector/personal-accounts");
|
|
330
|
+
expect(init?.signal).toBe(controller.signal);
|
|
331
|
+
});
|
|
332
|
+
it("surfaces connector API errors through the shared HTTP error contract", async () => {
|
|
333
|
+
const fetchMock = vi.fn().mockResolvedValue(new Response("connector access denied", {
|
|
334
|
+
status: 403,
|
|
335
|
+
statusText: "Forbidden",
|
|
336
|
+
}));
|
|
337
|
+
const client = new Client({
|
|
338
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
339
|
+
callerOptions: { fetch: fetchMock, maxRetries: 0 },
|
|
340
|
+
});
|
|
341
|
+
await expect(client.connectors.runtimeOptions("xpert-1")).rejects.toMatchObject({
|
|
342
|
+
status: 403,
|
|
343
|
+
message: expect.stringContaining("connector access denied"),
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
describe("ConversationsClient workspace files", () => {
|
|
348
|
+
it("lists the workspace files bound to an encoded conversation id", async () => {
|
|
349
|
+
const files = [
|
|
350
|
+
{ filePath: "brief.pdf", mimeType: "application/pdf", size: 2048 },
|
|
351
|
+
];
|
|
352
|
+
const fetchMock = vi
|
|
353
|
+
.fn()
|
|
354
|
+
.mockResolvedValue(jsonResponse(files));
|
|
355
|
+
const controller = new AbortController();
|
|
356
|
+
const client = new Client({
|
|
357
|
+
apiUrl: "https://xpert.example/api/ai",
|
|
358
|
+
apiKey: "secret",
|
|
359
|
+
callerOptions: { fetch: fetchMock },
|
|
360
|
+
});
|
|
361
|
+
await expect(client.conversations.listWorkspaceFiles("conversation/1", {
|
|
362
|
+
depth: 4,
|
|
363
|
+
signal: controller.signal,
|
|
364
|
+
})).resolves.toEqual(files);
|
|
365
|
+
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
|
366
|
+
expect(url.pathname).toBe("/api/ai/conversations/conversation%2F1/files");
|
|
367
|
+
expect(url.searchParams.get("deepth")).toBe("4");
|
|
368
|
+
expect(init?.headers).toMatchObject({ "x-api-key": "secret" });
|
|
369
|
+
expect(init?.signal).toBe(controller.signal);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
//# sourceMappingURL=projects-connectors.test.js.map
|