@arinova-ai/spaces-sdk 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -80
- package/dist/index.d.ts +48 -76
- package/dist/index.js +138 -269
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +7 -12
- package/.turbo/turbo-build.log +0 -4
- package/src/index.ts +0 -336
- package/src/types.ts +0 -107
- package/tsconfig.json +0 -19
package/README.md
CHANGED
|
@@ -1,109 +1,91 @@
|
|
|
1
|
-
# @arinova
|
|
1
|
+
# @arinova/spaces-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
OAuth PKCE SDK for Arinova Spaces — authenticate users without exposing secrets.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @arinova
|
|
8
|
+
npm install @arinova/spaces-sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
```js
|
|
14
|
+
import { Arinova } from "@arinova/spaces-sdk";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
import { Arinova } from "@arinova-ai/spaces-sdk";
|
|
16
|
+
const arinova = new Arinova({ appId: "your-client-id" });
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
// Trigger login (opens popup)
|
|
19
|
+
const token = await arinova.login();
|
|
20
|
+
console.log(token.user.name); // "Alice"
|
|
21
|
+
console.log(token.access_token); // Bearer token for API calls
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## Setup
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
1. Register your app: `arinova-cli app create --name "My App" --redirect-uri "https://myapp.com"`
|
|
27
|
+
2. Copy the `Client ID` from the output
|
|
28
|
+
3. No `client_secret` needed — all apps use PKCE
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
- **Standalone (outside iframe)**: falls back to the OAuth login flow.
|
|
30
|
+
## API
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
const { user, accessToken, agents } = await Arinova.connect({ timeout: 5000 });
|
|
33
|
-
// user: { id, name, email, image }
|
|
34
|
-
// accessToken: session token for API calls
|
|
35
|
-
// agents: user's connected agents (may be empty)
|
|
36
|
-
```
|
|
32
|
+
### `new Arinova(config)`
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
| Option | Type | Default | Description |
|
|
35
|
+
|--------|------|---------|-------------|
|
|
36
|
+
| `appId` | `string` | *required* | Your OAuth app client_id |
|
|
37
|
+
| `endpoint` | `string` | `https://chat.arinova.ai` | Arinova server URL |
|
|
38
|
+
| `redirectUri` | `string` | `{origin}/callback` | OAuth callback URL |
|
|
39
|
+
| `scope` | `string` | `"profile"` | OAuth scope |
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
// Redirect to Arinova login
|
|
42
|
-
Arinova.login({ scope: ["profile", "agents"] });
|
|
41
|
+
### `arinova.login(): Promise<TokenResponse>`
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
Opens a popup for user authorization (PKCE flow). Returns:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
{
|
|
47
|
+
access_token: string;
|
|
48
|
+
token_type: "Bearer";
|
|
49
|
+
expires_in: 604800; // 7 days
|
|
50
|
+
scope: "profile";
|
|
51
|
+
user: { id, name, email, image };
|
|
52
|
+
}
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
###
|
|
55
|
+
### `arinova.handleCallback(): Promise<TokenResponse>`
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
// Get user's agents
|
|
57
|
-
const agents = await Arinova.user.agents(accessToken);
|
|
57
|
+
Call on your redirect_uri page to complete the flow (for redirect mode instead of popup).
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
const { response } = await Arinova.agent.chat({
|
|
61
|
-
agentId: agents[0].id,
|
|
62
|
-
prompt: "Your board state...",
|
|
63
|
-
accessToken,
|
|
64
|
-
});
|
|
59
|
+
## PKCE Flow
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
1. SDK generates `code_verifier` (random) and `code_challenge = BASE64URL(SHA256(code_verifier))`
|
|
62
|
+
2. User is redirected to Arinova with `code_challenge`
|
|
63
|
+
3. After authorization, Arinova redirects back with `code`
|
|
64
|
+
4. SDK exchanges `code` + `code_verifier` for `access_token` (no secret needed)
|
|
65
|
+
|
|
66
|
+
## redirect_uri Rules
|
|
67
|
+
|
|
68
|
+
- Origin match: scheme + host + port must match your registered URI
|
|
69
|
+
- Path can differ (SDK uses `window.location.origin + /callback` by default)
|
|
70
|
+
- Must use HTTPS in production
|
|
71
|
+
- `http://localhost` is allowed for development
|
|
72
|
+
|
|
73
|
+
## Example: Redirect Mode
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
If popups are blocked, use redirect mode:
|
|
76
76
|
|
|
77
|
-
```
|
|
78
|
-
//
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
description: "Game entry fee",
|
|
83
|
-
clientId: "your-client-id",
|
|
84
|
-
clientSecret: "your-client-secret",
|
|
77
|
+
```js
|
|
78
|
+
// On login page:
|
|
79
|
+
const arinova = new Arinova({
|
|
80
|
+
appId: "your-client-id",
|
|
81
|
+
redirectUri: "https://myapp.com/auth/callback",
|
|
85
82
|
});
|
|
83
|
+
arinova.login(); // Will redirect if popup is blocked
|
|
86
84
|
|
|
87
|
-
//
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
description: "Game prize",
|
|
92
|
-
clientId: "your-client-id",
|
|
93
|
-
clientSecret: "your-client-secret",
|
|
85
|
+
// On callback page (/auth/callback):
|
|
86
|
+
const arinova = new Arinova({
|
|
87
|
+
appId: "your-client-id",
|
|
88
|
+
redirectUri: "https://myapp.com/auth/callback",
|
|
94
89
|
});
|
|
90
|
+
const token = await arinova.handleCallback();
|
|
95
91
|
```
|
|
96
|
-
|
|
97
|
-
## API Reference
|
|
98
|
-
|
|
99
|
-
### `Arinova.init(config)`
|
|
100
|
-
### `Arinova.connect(options?)`
|
|
101
|
-
### `Arinova.login(options?)`
|
|
102
|
-
### `Arinova.handleCallback(params)`
|
|
103
|
-
### `Arinova.user.profile(accessToken)`
|
|
104
|
-
### `Arinova.user.agents(accessToken)`
|
|
105
|
-
### `Arinova.agent.chat(options)`
|
|
106
|
-
### `Arinova.agent.chatStream(options)`
|
|
107
|
-
### `Arinova.economy.charge(options)`
|
|
108
|
-
### `Arinova.economy.award(options)`
|
|
109
|
-
### `Arinova.economy.balance(accessToken)`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,80 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Arinova Spaces SDK
|
|
3
|
+
*
|
|
4
|
+
* Public client OAuth with PKCE — no client_secret needed.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const arinova = new Arinova({ appId: "my-app-abc123" });
|
|
8
|
+
* const token = await arinova.login();
|
|
9
|
+
* // token.access_token is ready to use
|
|
10
|
+
*/
|
|
11
|
+
export interface ArinovaConfig {
|
|
12
|
+
/** Your OAuth app's client_id (from Developer Console) */
|
|
13
|
+
appId: string;
|
|
14
|
+
/** Arinova server URL (default: https://chat.arinova.ai) */
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
/** OAuth redirect URI (default: current page origin + /callback) */
|
|
17
|
+
redirectUri?: string;
|
|
18
|
+
/** OAuth scope (default: "profile") */
|
|
19
|
+
scope?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface TokenResponse {
|
|
22
|
+
access_token: string;
|
|
23
|
+
token_type: string;
|
|
24
|
+
expires_in: number;
|
|
25
|
+
scope: string;
|
|
26
|
+
user: {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
email: string;
|
|
30
|
+
image: string | null;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare class Arinova {
|
|
34
|
+
private appId;
|
|
35
|
+
private endpoint;
|
|
36
|
+
private redirectUri;
|
|
37
|
+
private scope;
|
|
38
|
+
constructor(config: ArinovaConfig);
|
|
21
39
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
40
|
+
* Start the OAuth PKCE login flow.
|
|
41
|
+
* Opens a popup window for authorization.
|
|
42
|
+
* Returns the token response on success.
|
|
24
43
|
*/
|
|
25
|
-
login(
|
|
44
|
+
login(): Promise<TokenResponse>;
|
|
26
45
|
/**
|
|
27
|
-
* Handle the OAuth callback
|
|
28
|
-
*
|
|
46
|
+
* Handle the OAuth callback (call this on your redirect_uri page).
|
|
47
|
+
* Reads code and state from URL, exchanges for token.
|
|
29
48
|
*/
|
|
30
|
-
handleCallback(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
redirectUri: string;
|
|
35
|
-
}): Promise<LoginResult>;
|
|
36
|
-
user: {
|
|
37
|
-
/**
|
|
38
|
-
* Get the authenticated user's profile.
|
|
39
|
-
*/
|
|
40
|
-
profile(accessToken: string): Promise<ArinovaUser>;
|
|
41
|
-
/**
|
|
42
|
-
* Get the authenticated user's agents.
|
|
43
|
-
* Requires "agents" scope.
|
|
44
|
-
*/
|
|
45
|
-
agents(accessToken: string): Promise<AgentInfo[]>;
|
|
46
|
-
};
|
|
47
|
-
agent: {
|
|
48
|
-
/**
|
|
49
|
-
* Send a prompt to a user's agent and get a complete response.
|
|
50
|
-
*/
|
|
51
|
-
chat(options: AgentChatOptions): Promise<AgentChatResponse>;
|
|
52
|
-
/**
|
|
53
|
-
* Send a prompt to a user's agent and receive a streaming response via SSE.
|
|
54
|
-
*/
|
|
55
|
-
chatStream(options: AgentChatStreamOptions): Promise<AgentChatStreamResponse>;
|
|
56
|
-
};
|
|
57
|
-
economy: {
|
|
58
|
-
/**
|
|
59
|
-
* Charge coins from a user's balance (server-to-server).
|
|
60
|
-
* Requires clientId and clientSecret.
|
|
61
|
-
*/
|
|
62
|
-
charge(options: ChargeOptions & {
|
|
63
|
-
clientId: string;
|
|
64
|
-
clientSecret: string;
|
|
65
|
-
}): Promise<ChargeResponse>;
|
|
66
|
-
/**
|
|
67
|
-
* Award coins to a user (server-to-server).
|
|
68
|
-
* Requires clientId and clientSecret.
|
|
69
|
-
*/
|
|
70
|
-
award(options: AwardOptions & {
|
|
71
|
-
clientId: string;
|
|
72
|
-
clientSecret: string;
|
|
73
|
-
}): Promise<AwardResponse>;
|
|
74
|
-
/**
|
|
75
|
-
* Get a user's coin balance (uses OAuth access token).
|
|
76
|
-
*/
|
|
77
|
-
balance(accessToken: string): Promise<BalanceResponse>;
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
//# sourceMappingURL=index.d.ts.map
|
|
49
|
+
handleCallback(): Promise<TokenResponse>;
|
|
50
|
+
private exchangeCode;
|
|
51
|
+
}
|
|
52
|
+
export default Arinova;
|
package/dist/index.js
CHANGED
|
@@ -1,284 +1,153 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (config.clientId && config.clientSecret) {
|
|
20
|
-
_oauthClientInfo = { clientId: config.clientId, clientSecret: config.clientSecret };
|
|
21
|
-
}
|
|
22
|
-
},
|
|
1
|
+
/**
|
|
2
|
+
* Arinova Spaces SDK
|
|
3
|
+
*
|
|
4
|
+
* Public client OAuth with PKCE — no client_secret needed.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const arinova = new Arinova({ appId: "my-app-abc123" });
|
|
8
|
+
* const token = await arinova.login();
|
|
9
|
+
* // token.access_token is ready to use
|
|
10
|
+
*/
|
|
11
|
+
export class Arinova {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.appId = config.appId;
|
|
14
|
+
this.endpoint = (config.endpoint ?? "https://chat.arinova.ai").replace(/\/+$/, "");
|
|
15
|
+
this.redirectUri =
|
|
16
|
+
config.redirectUri ?? `${window.location.origin}/callback`;
|
|
17
|
+
this.scope = config.scope ?? "profile";
|
|
18
|
+
}
|
|
23
19
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* - **Outside an iframe** (standalone): falls back to the OAuth login() flow.
|
|
28
|
-
*
|
|
29
|
-
* @param options.timeout - How long to wait for postMessage in iframe mode (default: 5000ms)
|
|
30
|
-
* @returns Promise resolving with user, accessToken, and agents
|
|
20
|
+
* Start the OAuth PKCE login flow.
|
|
21
|
+
* Opens a popup window for authorization.
|
|
22
|
+
* Returns the token response on success.
|
|
31
23
|
*/
|
|
32
|
-
async
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
async login() {
|
|
25
|
+
const { verifier, challenge } = await generatePKCE();
|
|
26
|
+
const state = generateRandom(32);
|
|
27
|
+
// Store state + verifier for callback validation
|
|
28
|
+
sessionStorage.setItem("arinova_pkce_verifier", verifier);
|
|
29
|
+
sessionStorage.setItem("arinova_pkce_state", state);
|
|
30
|
+
const params = new URLSearchParams({
|
|
31
|
+
client_id: this.appId,
|
|
32
|
+
redirect_uri: this.redirectUri,
|
|
33
|
+
scope: this.scope,
|
|
34
|
+
state,
|
|
35
|
+
code_challenge: challenge,
|
|
36
|
+
code_challenge_method: "S256",
|
|
37
|
+
response_type: "code",
|
|
38
|
+
});
|
|
39
|
+
const authUrl = `${this.endpoint}/oauth/authorize?${params}`;
|
|
42
40
|
return new Promise((resolve, reject) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
}, timeout);
|
|
51
|
-
function handler(event) {
|
|
52
|
-
if (event.data?.type !== "arinova:auth")
|
|
53
|
-
return;
|
|
54
|
-
if (settled)
|
|
55
|
-
return;
|
|
56
|
-
settled = true;
|
|
57
|
-
clearTimeout(timer);
|
|
58
|
-
window.removeEventListener("message", handler);
|
|
59
|
-
const { user, accessToken, agents } = event.data.payload;
|
|
60
|
-
resolve({ user, accessToken, agents: agents ?? [] });
|
|
41
|
+
const popup = window.open(authUrl, "arinova_auth", "width=500,height=600");
|
|
42
|
+
if (!popup) {
|
|
43
|
+
// Fallback: redirect instead of popup
|
|
44
|
+
window.location.href = authUrl;
|
|
45
|
+
reject(new Error("Popup blocked — redirecting instead"));
|
|
46
|
+
return;
|
|
61
47
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
48
|
+
const interval = setInterval(() => {
|
|
49
|
+
try {
|
|
50
|
+
if (popup.closed) {
|
|
51
|
+
clearInterval(interval);
|
|
52
|
+
reject(new Error("Login cancelled"));
|
|
53
|
+
}
|
|
54
|
+
const popupUrl = popup.location.href;
|
|
55
|
+
if (popupUrl.startsWith(this.redirectUri)) {
|
|
56
|
+
clearInterval(interval);
|
|
57
|
+
popup.close();
|
|
58
|
+
const url = new URL(popupUrl);
|
|
59
|
+
const code = url.searchParams.get("code");
|
|
60
|
+
const returnedState = url.searchParams.get("state");
|
|
61
|
+
if (returnedState !== state) {
|
|
62
|
+
reject(new Error("State mismatch — possible CSRF attack"));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!code) {
|
|
66
|
+
reject(new Error(url.searchParams.get("error_description") ?? "No code received"));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.exchangeCode(code, verifier).then(resolve).catch(reject);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Cross-origin — popup is on a different domain, ignore
|
|
74
|
+
}
|
|
75
|
+
}, 200);
|
|
76
|
+
// Timeout after 5 minutes
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
clearInterval(interval);
|
|
79
|
+
try {
|
|
80
|
+
popup.close();
|
|
81
|
+
}
|
|
82
|
+
catch { /* ignore */ }
|
|
83
|
+
reject(new Error("Login timed out"));
|
|
84
|
+
}, 300000);
|
|
80
85
|
});
|
|
81
|
-
|
|
82
|
-
},
|
|
86
|
+
}
|
|
83
87
|
/**
|
|
84
|
-
* Handle the OAuth callback
|
|
85
|
-
*
|
|
88
|
+
* Handle the OAuth callback (call this on your redirect_uri page).
|
|
89
|
+
* Reads code and state from URL, exchanges for token.
|
|
86
90
|
*/
|
|
87
|
-
async handleCallback(
|
|
88
|
-
const
|
|
89
|
-
const
|
|
91
|
+
async handleCallback() {
|
|
92
|
+
const url = new URL(window.location.href);
|
|
93
|
+
const code = url.searchParams.get("code");
|
|
94
|
+
const state = url.searchParams.get("state");
|
|
95
|
+
const verifier = sessionStorage.getItem("arinova_pkce_verifier");
|
|
96
|
+
const expectedState = sessionStorage.getItem("arinova_pkce_state");
|
|
97
|
+
sessionStorage.removeItem("arinova_pkce_verifier");
|
|
98
|
+
sessionStorage.removeItem("arinova_pkce_state");
|
|
99
|
+
if (!code) {
|
|
100
|
+
throw new Error(url.searchParams.get("error_description") ?? "No authorization code");
|
|
101
|
+
}
|
|
102
|
+
if (state !== expectedState) {
|
|
103
|
+
throw new Error("State mismatch");
|
|
104
|
+
}
|
|
105
|
+
if (!verifier) {
|
|
106
|
+
throw new Error("No PKCE verifier found — did you start login()?");
|
|
107
|
+
}
|
|
108
|
+
return this.exchangeCode(code, verifier);
|
|
109
|
+
}
|
|
110
|
+
async exchangeCode(code, codeVerifier) {
|
|
111
|
+
const res = await fetch(`${this.endpoint}/oauth/token`, {
|
|
90
112
|
method: "POST",
|
|
91
113
|
headers: { "Content-Type": "application/json" },
|
|
92
114
|
body: JSON.stringify({
|
|
93
115
|
grant_type: "authorization_code",
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
client_id: this.appId,
|
|
117
|
+
code,
|
|
118
|
+
redirect_uri: this.redirectUri,
|
|
119
|
+
code_verifier: codeVerifier,
|
|
98
120
|
}),
|
|
99
121
|
});
|
|
100
122
|
if (!res.ok) {
|
|
101
|
-
const
|
|
102
|
-
throw new Error(
|
|
123
|
+
const body = await res.json().catch(() => ({}));
|
|
124
|
+
throw new Error(body.error_description ??
|
|
125
|
+
body.error ??
|
|
126
|
+
`Token exchange failed (${res.status})`);
|
|
103
127
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
131
|
-
});
|
|
132
|
-
if (!res.ok)
|
|
133
|
-
throw new Error("Failed to get user agents");
|
|
134
|
-
const data = await res.json();
|
|
135
|
-
return data.agents;
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
agent: {
|
|
139
|
-
/**
|
|
140
|
-
* Send a prompt to a user's agent and get a complete response.
|
|
141
|
-
*/
|
|
142
|
-
async chat(options) {
|
|
143
|
-
const baseUrl = getBaseUrl();
|
|
144
|
-
const res = await fetch(`${baseUrl}/api/v1/agent/chat`, {
|
|
145
|
-
method: "POST",
|
|
146
|
-
headers: {
|
|
147
|
-
"Content-Type": "application/json",
|
|
148
|
-
Authorization: `Bearer ${options.accessToken}`,
|
|
149
|
-
},
|
|
150
|
-
body: JSON.stringify({
|
|
151
|
-
agentId: options.agentId,
|
|
152
|
-
prompt: options.prompt,
|
|
153
|
-
systemPrompt: options.systemPrompt,
|
|
154
|
-
}),
|
|
155
|
-
});
|
|
156
|
-
if (!res.ok) {
|
|
157
|
-
const error = await res.json().catch(() => ({ error: "agent_chat_failed" }));
|
|
158
|
-
throw new Error(error.error || "Agent chat failed");
|
|
159
|
-
}
|
|
160
|
-
return res.json();
|
|
161
|
-
},
|
|
162
|
-
/**
|
|
163
|
-
* Send a prompt to a user's agent and receive a streaming response via SSE.
|
|
164
|
-
*/
|
|
165
|
-
async chatStream(options) {
|
|
166
|
-
const baseUrl = getBaseUrl();
|
|
167
|
-
const res = await fetch(`${baseUrl}/api/v1/agent/chat/stream`, {
|
|
168
|
-
method: "POST",
|
|
169
|
-
headers: {
|
|
170
|
-
"Content-Type": "application/json",
|
|
171
|
-
Authorization: `Bearer ${options.accessToken}`,
|
|
172
|
-
},
|
|
173
|
-
body: JSON.stringify({
|
|
174
|
-
agentId: options.agentId,
|
|
175
|
-
prompt: options.prompt,
|
|
176
|
-
systemPrompt: options.systemPrompt,
|
|
177
|
-
}),
|
|
178
|
-
});
|
|
179
|
-
if (!res.ok) {
|
|
180
|
-
const error = await res.json().catch(() => ({ error: "agent_stream_failed" }));
|
|
181
|
-
throw new Error(error.error || "Agent stream failed");
|
|
182
|
-
}
|
|
183
|
-
const reader = res.body.getReader();
|
|
184
|
-
const decoder = new TextDecoder();
|
|
185
|
-
let fullContent = "";
|
|
186
|
-
let buffer = "";
|
|
187
|
-
while (true) {
|
|
188
|
-
const { done, value } = await reader.read();
|
|
189
|
-
if (done)
|
|
190
|
-
break;
|
|
191
|
-
buffer += decoder.decode(value, { stream: true });
|
|
192
|
-
const lines = buffer.split("\n");
|
|
193
|
-
buffer = lines.pop() || "";
|
|
194
|
-
for (const line of lines) {
|
|
195
|
-
if (!line.startsWith("data: "))
|
|
196
|
-
continue;
|
|
197
|
-
try {
|
|
198
|
-
const event = JSON.parse(line.slice(6));
|
|
199
|
-
if (event.type === "chunk") {
|
|
200
|
-
options.onChunk(event.content);
|
|
201
|
-
fullContent = event.content;
|
|
202
|
-
}
|
|
203
|
-
else if (event.type === "done") {
|
|
204
|
-
fullContent = event.content;
|
|
205
|
-
}
|
|
206
|
-
else if (event.type === "error") {
|
|
207
|
-
throw new Error(event.error);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
catch (e) {
|
|
211
|
-
if (e instanceof Error && e.message !== "Unexpected end of JSON input")
|
|
212
|
-
throw e;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return { content: fullContent, agentId: options.agentId };
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
economy: {
|
|
220
|
-
/**
|
|
221
|
-
* Charge coins from a user's balance (server-to-server).
|
|
222
|
-
* Requires clientId and clientSecret.
|
|
223
|
-
*/
|
|
224
|
-
async charge(options) {
|
|
225
|
-
const baseUrl = getBaseUrl();
|
|
226
|
-
const res = await fetch(`${baseUrl}/api/v1/economy/charge`, {
|
|
227
|
-
method: "POST",
|
|
228
|
-
headers: {
|
|
229
|
-
"Content-Type": "application/json",
|
|
230
|
-
"X-Client-Id": options.clientId,
|
|
231
|
-
"X-App-Secret": options.clientSecret,
|
|
232
|
-
},
|
|
233
|
-
body: JSON.stringify({
|
|
234
|
-
userId: options.userId,
|
|
235
|
-
amount: options.amount,
|
|
236
|
-
description: options.description,
|
|
237
|
-
}),
|
|
238
|
-
});
|
|
239
|
-
if (!res.ok) {
|
|
240
|
-
const error = await res.json().catch(() => ({ error: "charge_failed" }));
|
|
241
|
-
throw new Error(error.error || "Charge failed");
|
|
242
|
-
}
|
|
243
|
-
return res.json();
|
|
244
|
-
},
|
|
245
|
-
/**
|
|
246
|
-
* Award coins to a user (server-to-server).
|
|
247
|
-
* Requires clientId and clientSecret.
|
|
248
|
-
*/
|
|
249
|
-
async award(options) {
|
|
250
|
-
const baseUrl = getBaseUrl();
|
|
251
|
-
const res = await fetch(`${baseUrl}/api/v1/economy/award`, {
|
|
252
|
-
method: "POST",
|
|
253
|
-
headers: {
|
|
254
|
-
"Content-Type": "application/json",
|
|
255
|
-
"X-Client-Id": options.clientId,
|
|
256
|
-
"X-App-Secret": options.clientSecret,
|
|
257
|
-
},
|
|
258
|
-
body: JSON.stringify({
|
|
259
|
-
userId: options.userId,
|
|
260
|
-
amount: options.amount,
|
|
261
|
-
description: options.description,
|
|
262
|
-
}),
|
|
263
|
-
});
|
|
264
|
-
if (!res.ok) {
|
|
265
|
-
const error = await res.json().catch(() => ({ error: "award_failed" }));
|
|
266
|
-
throw new Error(error.error || "Award failed");
|
|
267
|
-
}
|
|
268
|
-
return res.json();
|
|
269
|
-
},
|
|
270
|
-
/**
|
|
271
|
-
* Get a user's coin balance (uses OAuth access token).
|
|
272
|
-
*/
|
|
273
|
-
async balance(accessToken) {
|
|
274
|
-
const baseUrl = getBaseUrl();
|
|
275
|
-
const res = await fetch(`${baseUrl}/api/v1/economy/balance`, {
|
|
276
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
277
|
-
});
|
|
278
|
-
if (!res.ok)
|
|
279
|
-
throw new Error("Failed to get balance");
|
|
280
|
-
return res.json();
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
//# sourceMappingURL=index.js.map
|
|
128
|
+
return res.json();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ── PKCE Helpers ──────────────────────────────────────────────
|
|
132
|
+
function generateRandom(length) {
|
|
133
|
+
const array = new Uint8Array(length);
|
|
134
|
+
crypto.getRandomValues(array);
|
|
135
|
+
return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
136
|
+
}
|
|
137
|
+
async function generatePKCE() {
|
|
138
|
+
const verifier = generateRandom(32); // 64 hex chars
|
|
139
|
+
const encoder = new TextEncoder();
|
|
140
|
+
const data = encoder.encode(verifier);
|
|
141
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
142
|
+
const challenge = base64UrlEncode(new Uint8Array(hash));
|
|
143
|
+
return { verifier, challenge };
|
|
144
|
+
}
|
|
145
|
+
function base64UrlEncode(buffer) {
|
|
146
|
+
let binary = "";
|
|
147
|
+
for (const byte of buffer) {
|
|
148
|
+
binary += String.fromCharCode(byte);
|
|
149
|
+
}
|
|
150
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
151
|
+
}
|
|
152
|
+
// Default export for convenience
|
|
153
|
+
export default Arinova;
|
package/dist/types.d.ts
CHANGED
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arinova-ai/spaces-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Arinova Spaces SDK — OAuth PKCE login for third-party apps",
|
|
4
5
|
"type": "module",
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
7
|
-
"
|
|
8
|
-
".": {
|
|
9
|
-
"import": "./dist/index.js",
|
|
10
|
-
"types": "./dist/index.d.ts"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
13
9
|
"scripts": {
|
|
14
|
-
"build": "tsc"
|
|
15
|
-
"dev": "tsc --watch"
|
|
10
|
+
"build": "tsc"
|
|
16
11
|
},
|
|
17
12
|
"devDependencies": {
|
|
18
|
-
"typescript": "^5.
|
|
13
|
+
"typescript": "^5.4.0"
|
|
19
14
|
}
|
|
20
15
|
}
|
package/.turbo/turbo-build.log
DELETED
package/src/index.ts
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ArinovaConfig,
|
|
3
|
-
LoginOptions,
|
|
4
|
-
LoginResult,
|
|
5
|
-
ConnectOptions,
|
|
6
|
-
ConnectResult,
|
|
7
|
-
ArinovaUser,
|
|
8
|
-
AgentInfo,
|
|
9
|
-
AgentChatOptions,
|
|
10
|
-
AgentChatResponse,
|
|
11
|
-
AgentChatStreamOptions,
|
|
12
|
-
AgentChatStreamResponse,
|
|
13
|
-
ChargeOptions,
|
|
14
|
-
ChargeResponse,
|
|
15
|
-
AwardOptions,
|
|
16
|
-
AwardResponse,
|
|
17
|
-
BalanceResponse,
|
|
18
|
-
SSEEvent,
|
|
19
|
-
} from "./types.js";
|
|
20
|
-
|
|
21
|
-
export type * from "./types.js";
|
|
22
|
-
|
|
23
|
-
let _config: ArinovaConfig | null = null;
|
|
24
|
-
let _oauthClientInfo: { clientId: string; clientSecret: string } | null = null;
|
|
25
|
-
|
|
26
|
-
function getBaseUrl(): string {
|
|
27
|
-
if (!_config) throw new Error("Arinova SDK not initialized. Call Arinova.init() first.");
|
|
28
|
-
return _config.baseUrl || "https://api.arinova.ai";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getConfig(): ArinovaConfig {
|
|
32
|
-
if (!_config) throw new Error("Arinova SDK not initialized. Call Arinova.init() first.");
|
|
33
|
-
return _config;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const Arinova = {
|
|
37
|
-
/**
|
|
38
|
-
* Initialize the SDK with your app configuration.
|
|
39
|
-
*/
|
|
40
|
-
init(config: ArinovaConfig & { clientId?: string; clientSecret?: string }) {
|
|
41
|
-
_config = config;
|
|
42
|
-
if (config.clientId && config.clientSecret) {
|
|
43
|
-
_oauthClientInfo = { clientId: config.clientId, clientSecret: config.clientSecret };
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Connect to Arinova — works seamlessly in both iframe and standalone contexts.
|
|
49
|
-
*
|
|
50
|
-
* - **Inside an iframe** (embedded in Arinova Chat): receives auth via postMessage from the parent window.
|
|
51
|
-
* - **Outside an iframe** (standalone): falls back to the OAuth login() flow.
|
|
52
|
-
*
|
|
53
|
-
* @param options.timeout - How long to wait for postMessage in iframe mode (default: 5000ms)
|
|
54
|
-
* @returns Promise resolving with user, accessToken, and agents
|
|
55
|
-
*/
|
|
56
|
-
async connect(options?: ConnectOptions): Promise<ConnectResult> {
|
|
57
|
-
const timeout = options?.timeout ?? 5000;
|
|
58
|
-
|
|
59
|
-
const inIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
60
|
-
|
|
61
|
-
if (!inIframe) {
|
|
62
|
-
// Not in iframe — fall back to OAuth login flow
|
|
63
|
-
this.login();
|
|
64
|
-
// login() redirects, so this promise never resolves in practice
|
|
65
|
-
return new Promise(() => {});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// In iframe — listen for postMessage auth from parent
|
|
69
|
-
return new Promise<ConnectResult>((resolve, reject) => {
|
|
70
|
-
let settled = false;
|
|
71
|
-
|
|
72
|
-
const timer = setTimeout(() => {
|
|
73
|
-
if (!settled) {
|
|
74
|
-
settled = true;
|
|
75
|
-
window.removeEventListener("message", handler);
|
|
76
|
-
reject(new Error("Arinova connect timeout: no auth message received from parent window within " + timeout + "ms"));
|
|
77
|
-
}
|
|
78
|
-
}, timeout);
|
|
79
|
-
|
|
80
|
-
function handler(event: MessageEvent) {
|
|
81
|
-
if (event.data?.type !== "arinova:auth") return;
|
|
82
|
-
if (settled) return;
|
|
83
|
-
settled = true;
|
|
84
|
-
clearTimeout(timer);
|
|
85
|
-
window.removeEventListener("message", handler);
|
|
86
|
-
|
|
87
|
-
const { user, accessToken, agents } = event.data.payload as ConnectResult;
|
|
88
|
-
resolve({ user, accessToken, agents: agents ?? [] });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
window.addEventListener("message", handler);
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Redirect to Arinova OAuth login page.
|
|
97
|
-
* Call this from your game's frontend.
|
|
98
|
-
*/
|
|
99
|
-
login(options?: LoginOptions): void {
|
|
100
|
-
const config = getConfig();
|
|
101
|
-
const baseUrl = getBaseUrl();
|
|
102
|
-
const scope = options?.scope?.join(" ") || "profile";
|
|
103
|
-
|
|
104
|
-
// Store current URL for callback
|
|
105
|
-
const currentUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
106
|
-
|
|
107
|
-
const params = new URLSearchParams({
|
|
108
|
-
client_id: config.appId,
|
|
109
|
-
redirect_uri: currentUrl,
|
|
110
|
-
scope,
|
|
111
|
-
state: crypto.randomUUID(),
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
window.location.href = `${baseUrl}/oauth/authorize?${params}`;
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Handle the OAuth callback. Call this on your redirect page.
|
|
119
|
-
* Exchanges the authorization code for an access token.
|
|
120
|
-
*/
|
|
121
|
-
async handleCallback(params: {
|
|
122
|
-
code: string;
|
|
123
|
-
clientId: string;
|
|
124
|
-
clientSecret: string;
|
|
125
|
-
redirectUri: string;
|
|
126
|
-
}): Promise<LoginResult> {
|
|
127
|
-
const baseUrl = getBaseUrl();
|
|
128
|
-
|
|
129
|
-
const res = await fetch(`${baseUrl}/oauth/token`, {
|
|
130
|
-
method: "POST",
|
|
131
|
-
headers: { "Content-Type": "application/json" },
|
|
132
|
-
body: JSON.stringify({
|
|
133
|
-
grant_type: "authorization_code",
|
|
134
|
-
code: params.code,
|
|
135
|
-
client_id: params.clientId,
|
|
136
|
-
client_secret: params.clientSecret,
|
|
137
|
-
redirect_uri: params.redirectUri,
|
|
138
|
-
}),
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
if (!res.ok) {
|
|
142
|
-
const error = await res.json().catch(() => ({ error: "token_exchange_failed" }));
|
|
143
|
-
throw new Error(error.error || "Token exchange failed");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const data = await res.json();
|
|
147
|
-
return {
|
|
148
|
-
user: data.user,
|
|
149
|
-
accessToken: data.access_token,
|
|
150
|
-
};
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
user: {
|
|
154
|
-
/**
|
|
155
|
-
* Get the authenticated user's profile.
|
|
156
|
-
*/
|
|
157
|
-
async profile(accessToken: string): Promise<ArinovaUser> {
|
|
158
|
-
const baseUrl = getBaseUrl();
|
|
159
|
-
const res = await fetch(`${baseUrl}/api/v1/user/profile`, {
|
|
160
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
161
|
-
});
|
|
162
|
-
if (!res.ok) throw new Error("Failed to get user profile");
|
|
163
|
-
return res.json();
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Get the authenticated user's agents.
|
|
168
|
-
* Requires "agents" scope.
|
|
169
|
-
*/
|
|
170
|
-
async agents(accessToken: string): Promise<AgentInfo[]> {
|
|
171
|
-
const baseUrl = getBaseUrl();
|
|
172
|
-
const res = await fetch(`${baseUrl}/api/v1/user/agents`, {
|
|
173
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
174
|
-
});
|
|
175
|
-
if (!res.ok) throw new Error("Failed to get user agents");
|
|
176
|
-
const data = await res.json();
|
|
177
|
-
return data.agents;
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
agent: {
|
|
182
|
-
/**
|
|
183
|
-
* Send a prompt to a user's agent and get a complete response.
|
|
184
|
-
*/
|
|
185
|
-
async chat(options: AgentChatOptions): Promise<AgentChatResponse> {
|
|
186
|
-
const baseUrl = getBaseUrl();
|
|
187
|
-
const res = await fetch(`${baseUrl}/api/v1/agent/chat`, {
|
|
188
|
-
method: "POST",
|
|
189
|
-
headers: {
|
|
190
|
-
"Content-Type": "application/json",
|
|
191
|
-
Authorization: `Bearer ${options.accessToken}`,
|
|
192
|
-
},
|
|
193
|
-
body: JSON.stringify({
|
|
194
|
-
agentId: options.agentId,
|
|
195
|
-
prompt: options.prompt,
|
|
196
|
-
systemPrompt: options.systemPrompt,
|
|
197
|
-
}),
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
if (!res.ok) {
|
|
201
|
-
const error = await res.json().catch(() => ({ error: "agent_chat_failed" }));
|
|
202
|
-
throw new Error(error.error || "Agent chat failed");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return res.json();
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Send a prompt to a user's agent and receive a streaming response via SSE.
|
|
210
|
-
*/
|
|
211
|
-
async chatStream(options: AgentChatStreamOptions): Promise<AgentChatStreamResponse> {
|
|
212
|
-
const baseUrl = getBaseUrl();
|
|
213
|
-
const res = await fetch(`${baseUrl}/api/v1/agent/chat/stream`, {
|
|
214
|
-
method: "POST",
|
|
215
|
-
headers: {
|
|
216
|
-
"Content-Type": "application/json",
|
|
217
|
-
Authorization: `Bearer ${options.accessToken}`,
|
|
218
|
-
},
|
|
219
|
-
body: JSON.stringify({
|
|
220
|
-
agentId: options.agentId,
|
|
221
|
-
prompt: options.prompt,
|
|
222
|
-
systemPrompt: options.systemPrompt,
|
|
223
|
-
}),
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
if (!res.ok) {
|
|
227
|
-
const error = await res.json().catch(() => ({ error: "agent_stream_failed" }));
|
|
228
|
-
throw new Error(error.error || "Agent stream failed");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const reader = res.body!.getReader();
|
|
232
|
-
const decoder = new TextDecoder();
|
|
233
|
-
let fullContent = "";
|
|
234
|
-
let buffer = "";
|
|
235
|
-
|
|
236
|
-
while (true) {
|
|
237
|
-
const { done, value } = await reader.read();
|
|
238
|
-
if (done) break;
|
|
239
|
-
|
|
240
|
-
buffer += decoder.decode(value, { stream: true });
|
|
241
|
-
const lines = buffer.split("\n");
|
|
242
|
-
buffer = lines.pop() || "";
|
|
243
|
-
|
|
244
|
-
for (const line of lines) {
|
|
245
|
-
if (!line.startsWith("data: ")) continue;
|
|
246
|
-
try {
|
|
247
|
-
const event: SSEEvent = JSON.parse(line.slice(6));
|
|
248
|
-
if (event.type === "chunk") {
|
|
249
|
-
options.onChunk(event.content);
|
|
250
|
-
fullContent = event.content;
|
|
251
|
-
} else if (event.type === "done") {
|
|
252
|
-
fullContent = event.content;
|
|
253
|
-
} else if (event.type === "error") {
|
|
254
|
-
throw new Error(event.error);
|
|
255
|
-
}
|
|
256
|
-
} catch (e) {
|
|
257
|
-
if (e instanceof Error && e.message !== "Unexpected end of JSON input") throw e;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return { content: fullContent, agentId: options.agentId };
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
economy: {
|
|
267
|
-
/**
|
|
268
|
-
* Charge coins from a user's balance (server-to-server).
|
|
269
|
-
* Requires clientId and clientSecret.
|
|
270
|
-
*/
|
|
271
|
-
async charge(options: ChargeOptions & { clientId: string; clientSecret: string }): Promise<ChargeResponse> {
|
|
272
|
-
const baseUrl = getBaseUrl();
|
|
273
|
-
const res = await fetch(`${baseUrl}/api/v1/economy/charge`, {
|
|
274
|
-
method: "POST",
|
|
275
|
-
headers: {
|
|
276
|
-
"Content-Type": "application/json",
|
|
277
|
-
"X-Client-Id": options.clientId,
|
|
278
|
-
"X-App-Secret": options.clientSecret,
|
|
279
|
-
},
|
|
280
|
-
body: JSON.stringify({
|
|
281
|
-
userId: options.userId,
|
|
282
|
-
amount: options.amount,
|
|
283
|
-
description: options.description,
|
|
284
|
-
}),
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
if (!res.ok) {
|
|
288
|
-
const error = await res.json().catch(() => ({ error: "charge_failed" }));
|
|
289
|
-
throw new Error(error.error || "Charge failed");
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return res.json();
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Award coins to a user (server-to-server).
|
|
297
|
-
* Requires clientId and clientSecret.
|
|
298
|
-
*/
|
|
299
|
-
async award(options: AwardOptions & { clientId: string; clientSecret: string }): Promise<AwardResponse> {
|
|
300
|
-
const baseUrl = getBaseUrl();
|
|
301
|
-
const res = await fetch(`${baseUrl}/api/v1/economy/award`, {
|
|
302
|
-
method: "POST",
|
|
303
|
-
headers: {
|
|
304
|
-
"Content-Type": "application/json",
|
|
305
|
-
"X-Client-Id": options.clientId,
|
|
306
|
-
"X-App-Secret": options.clientSecret,
|
|
307
|
-
},
|
|
308
|
-
body: JSON.stringify({
|
|
309
|
-
userId: options.userId,
|
|
310
|
-
amount: options.amount,
|
|
311
|
-
description: options.description,
|
|
312
|
-
}),
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
if (!res.ok) {
|
|
316
|
-
const error = await res.json().catch(() => ({ error: "award_failed" }));
|
|
317
|
-
throw new Error(error.error || "Award failed");
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return res.json();
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Get a user's coin balance (uses OAuth access token).
|
|
325
|
-
*/
|
|
326
|
-
async balance(accessToken: string): Promise<BalanceResponse> {
|
|
327
|
-
const baseUrl = getBaseUrl();
|
|
328
|
-
const res = await fetch(`${baseUrl}/api/v1/economy/balance`, {
|
|
329
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
if (!res.ok) throw new Error("Failed to get balance");
|
|
333
|
-
return res.json();
|
|
334
|
-
},
|
|
335
|
-
},
|
|
336
|
-
};
|
package/src/types.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// ===== Config =====
|
|
2
|
-
export interface ArinovaConfig {
|
|
3
|
-
appId: string;
|
|
4
|
-
baseUrl?: string; // defaults to "https://api.arinova.ai"
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// ===== Auth =====
|
|
8
|
-
export interface LoginOptions {
|
|
9
|
-
scope?: string[]; // defaults to ["profile"]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface LoginResult {
|
|
13
|
-
user: ArinovaUser;
|
|
14
|
-
accessToken: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ConnectOptions {
|
|
18
|
-
timeout?: number; // milliseconds, defaults to 5000
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ConnectResult {
|
|
22
|
-
user: ArinovaUser;
|
|
23
|
-
accessToken: string;
|
|
24
|
-
agents: AgentInfo[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ArinovaUser {
|
|
28
|
-
id: string;
|
|
29
|
-
name: string;
|
|
30
|
-
email: string;
|
|
31
|
-
image: string | null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ===== Agent =====
|
|
35
|
-
export interface AgentInfo {
|
|
36
|
-
id: string;
|
|
37
|
-
name: string;
|
|
38
|
-
description: string | null;
|
|
39
|
-
avatarUrl: string | null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface AgentChatOptions {
|
|
43
|
-
agentId: string;
|
|
44
|
-
prompt: string;
|
|
45
|
-
systemPrompt?: string;
|
|
46
|
-
accessToken: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface AgentChatResponse {
|
|
50
|
-
response: string;
|
|
51
|
-
agentId: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface AgentChatStreamOptions extends AgentChatOptions {
|
|
55
|
-
onChunk: (chunk: string) => void;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface AgentChatStreamResponse {
|
|
59
|
-
content: string;
|
|
60
|
-
agentId: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ===== Economy =====
|
|
64
|
-
export interface ChargeOptions {
|
|
65
|
-
userId: string;
|
|
66
|
-
amount: number;
|
|
67
|
-
description?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface ChargeResponse {
|
|
71
|
-
transactionId: string;
|
|
72
|
-
newBalance: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface AwardOptions {
|
|
76
|
-
userId: string;
|
|
77
|
-
amount: number;
|
|
78
|
-
description?: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface AwardResponse {
|
|
82
|
-
transactionId: string;
|
|
83
|
-
newBalance: number;
|
|
84
|
-
platformFee: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface BalanceResponse {
|
|
88
|
-
balance: number;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ===== SSE Event =====
|
|
92
|
-
export interface SSEChunkEvent {
|
|
93
|
-
type: "chunk";
|
|
94
|
-
content: string;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface SSEDoneEvent {
|
|
98
|
-
type: "done";
|
|
99
|
-
content: string;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface SSEErrorEvent {
|
|
103
|
-
type: "error";
|
|
104
|
-
error: string;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export type SSEEvent = SSEChunkEvent | SSEDoneEvent | SSEErrorEvent;
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2022", "DOM"],
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"declaration": true,
|
|
10
|
-
"declarationMap": true,
|
|
11
|
-
"sourceMap": true,
|
|
12
|
-
"strict": true,
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"skipLibCheck": true,
|
|
15
|
-
"forceConsistentCasingInFileNames": true
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|