@kmlckj/licos-platform-sdk 0.6.4 → 0.6.5

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 CHANGED
@@ -5,14 +5,15 @@ LICOS platform SDK for server-side project runtime code.
5
5
  ## Runtime Configuration
6
6
 
7
7
  Server-side helpers call platform Studio runtime APIs. Project code does not
8
- pass API base URLs, project IDs, tokens, or environment scopes. The SDK loads
9
- them from runtime environment variables injected by LICOS:
10
-
11
- - `LICOS_PLATFORM_API_BASE_URL`
12
- - `LICOS_RUNTIME_TOKEN` or `LICOS_USER_TOKEN`
13
- - `LICOS_PROJECT_ID` or `AGENT_PROJECT_ID`
14
- - `LICOS_WORKSPACE_ID` or `AGENT_WORKSPACE_ID`
15
- - `LICOS_PROJECT_ENV`, mapped internally to `dev` or `prod`
8
+ pass API base URLs, project IDs, tokens, or environment scopes. The SDK loads
9
+ identity from runtime environment variables injected by LICOS. Authentication
10
+ is resolved automatically by the runtime:
11
+
12
+ - `LICOS_PLATFORM_API_BASE_URL`
13
+ - `LICOS_PROJECT_ID` or `AGENT_PROJECT_ID`
14
+ - `LICOS_WORKSPACE_ID` or `AGENT_WORKSPACE_ID`
15
+ - `LICOS_USER_ID` or `AGENT_USER_ID`
16
+ - `LICOS_PROJECT_ENV`, mapped internally to `dev` or `prod`
16
17
 
17
18
  ## Database
18
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kmlckj/licos-platform-sdk",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "LICOS platform SDK package shell for browser and Node runtimes",
5
5
  "author": "kmlckj",
6
6
  "type": "module",
package/src/database.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ApiError } from './shared.js';
2
2
  import { authHeaders, runtimeConfig } from './runtime.js';
3
3
 
4
- function databaseConfig() {
5
- return runtimeConfig();
6
- }
4
+ async function databaseConfig() {
5
+ return runtimeConfig();
6
+ }
7
7
 
8
8
  function databaseUrl(config, route) {
9
9
  return `${config.baseUrl}/api/v1/studio/runtime/database/projects/${encodeURIComponent(config.projectId)}${route}`;
@@ -45,8 +45,8 @@ async function decodeResponse(response) {
45
45
  return payload.data;
46
46
  }
47
47
 
48
- async function requestJson(route, body) {
49
- const config = databaseConfig();
48
+ async function requestJson(route, body) {
49
+ const config = await databaseConfig();
50
50
  const payload = { environment: config.environment, ...compactObject(body) };
51
51
  const response = await fetch(databaseUrl(config, route), {
52
52
  method: 'POST',
@@ -56,8 +56,8 @@ async function requestJson(route, body) {
56
56
  return decodeResponse(response);
57
57
  }
58
58
 
59
- async function requestControlJson(route, query = {}) {
60
- const config = databaseConfig();
59
+ async function requestControlJson(route, query = {}) {
60
+ const config = await databaseConfig();
61
61
  const url = new URL(controlDatabaseUrl(config, route));
62
62
  for (const [key, value] of Object.entries({ environment: config.environment, ...query })) {
63
63
  if (value !== undefined && value !== null) url.searchParams.set(key, String(value));
@@ -145,8 +145,8 @@ export async function upsert(table, options = {}) {
145
145
  });
146
146
  }
147
147
 
148
- export async function transaction(operations) {
149
- const config = databaseConfig();
148
+ export async function transaction(operations) {
149
+ const config = await databaseConfig();
150
150
  const normalized = operations.map((operation) => ({
151
151
  type: operation.type,
152
152
  request: {
package/src/runtime.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { ConfigurationError } from './shared.js';
2
2
 
3
+ const AI_USER_TOKEN_PATH = '/api/v1/internal/auth/ai-user-token';
4
+ const tokenCache = new Map();
5
+
3
6
  export function env(name) {
4
7
  const value = process.env[name];
5
8
  if (typeof value !== 'string') return undefined;
@@ -40,22 +43,93 @@ export function projectEnvironment(overrideEnvName) {
40
43
  return ['prod', 'production', 'release'].includes(projectEnv) ? 'prod' : 'dev';
41
44
  }
42
45
 
43
- export function runtimeConfig({ environmentOverrideEnv } = {}) {
46
+ function decodeAiTokenPayload(payload) {
47
+ if (!payload || typeof payload !== 'object') {
48
+ throw new ConfigurationError('AI user token exchange response is not an object');
49
+ }
50
+ if ((payload.code !== undefined && payload.code !== 0) || payload.success === false) {
51
+ const error = new ConfigurationError(payload.message || 'AI user token exchange failed');
52
+ error.code = typeof payload.code === 'number' ? payload.code : undefined;
53
+ error.details = payload;
54
+ throw error;
55
+ }
56
+ const data = payload.data;
57
+ const token =
58
+ typeof data === 'string'
59
+ ? data.trim()
60
+ : String(data?.accessToken || data?.access_token || data?.token || '').trim();
61
+ if (!token) {
62
+ const error = new ConfigurationError('AI user token exchange response missing accessToken');
63
+ error.details = payload;
64
+ throw error;
65
+ }
66
+ return token;
67
+ }
68
+
69
+ async function resolveUserToken(baseUrl, userId) {
70
+ const ownerUserId = String(userId || '').trim();
71
+ if (!ownerUserId) {
72
+ throw new ConfigurationError('project owner user ID is not configured');
73
+ }
74
+ const aiAgentToken = env('LICOS_AI_AGENT_TOKEN');
75
+ if (!aiAgentToken) {
76
+ throw new ConfigurationError('platform runtime identity is unavailable');
77
+ }
78
+
79
+ const cacheKey = `${baseUrl}\n${ownerUserId}\n${aiAgentToken}`;
80
+ if (tokenCache.has(cacheKey)) return tokenCache.get(cacheKey);
81
+
82
+ const promise = fetch(`${baseUrl}${AI_USER_TOKEN_PATH}`, {
83
+ method: 'POST',
84
+ headers: {
85
+ Authorization: `Bearer ${aiAgentToken}`,
86
+ 'Content-Type': 'application/json',
87
+ },
88
+ body: JSON.stringify({ userId: ownerUserId }),
89
+ }).then(async (response) => {
90
+ const text = await response.text();
91
+ let payload = null;
92
+ try {
93
+ payload = text ? JSON.parse(text) : null;
94
+ } catch (error) {
95
+ const wrapped = new ConfigurationError('parse AI user token exchange response failed');
96
+ wrapped.status = response.status;
97
+ wrapped.details = text;
98
+ throw wrapped;
99
+ }
100
+ if (!response.ok) {
101
+ const message = payload?.message || text || `AI user token exchange returned ${response.status}`;
102
+ const wrapped = new ConfigurationError(message);
103
+ wrapped.status = response.status;
104
+ wrapped.details = payload;
105
+ throw wrapped;
106
+ }
107
+ return decodeAiTokenPayload(payload);
108
+ });
109
+ tokenCache.set(cacheKey, promise);
110
+ try {
111
+ return await promise;
112
+ } catch (error) {
113
+ tokenCache.delete(cacheKey);
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ export async function runtimeConfig({ environmentOverrideEnv } = {}) {
44
119
  const projectId = env('LICOS_PROJECT_ID') || env('AGENT_PROJECT_ID');
45
120
  if (!projectId) {
46
121
  throw new ConfigurationError('LICOS_PROJECT_ID or AGENT_PROJECT_ID is not configured');
47
122
  }
48
- const token = env('LICOS_RUNTIME_TOKEN') || env('LICOS_USER_TOKEN');
49
- if (!token) {
50
- throw new ConfigurationError('LICOS_RUNTIME_TOKEN or LICOS_USER_TOKEN is not configured');
51
- }
123
+ const baseUrl = platformBaseUrl();
124
+ const userId = env('LICOS_USER_ID') || env('AGENT_USER_ID');
125
+ const token = await resolveUserToken(baseUrl, userId);
52
126
  return {
53
- baseUrl: platformBaseUrl(),
127
+ baseUrl,
54
128
  projectId,
55
129
  environment: projectEnvironment(environmentOverrideEnv),
56
130
  token,
57
131
  workspaceId: env('LICOS_WORKSPACE_ID') || env('AGENT_WORKSPACE_ID'),
58
- userId: env('LICOS_USER_ID') || env('AGENT_USER_ID'),
132
+ userId,
59
133
  };
60
134
  }
61
135
 
package/src/storage.js CHANGED
@@ -6,7 +6,7 @@ import { authHeaders, runtimeConfig } from './runtime.js';
6
6
 
7
7
  export const MAX_UPLOAD_FILE_BYTES = 20 * 1024 * 1024;
8
8
 
9
- function storageConfig() {
9
+ async function storageConfig() {
10
10
  return runtimeConfig({ environmentOverrideEnv: 'LICOS_STORAGE_SCOPE' });
11
11
  }
12
12
 
@@ -45,7 +45,7 @@ async function decodeResponse(response) {
45
45
  }
46
46
 
47
47
  async function requestJson(method, route, { params, body } = {}) {
48
- const config = storageConfig();
48
+ const config = await storageConfig();
49
49
  const response = await fetch(storageUrl(config, route, params), {
50
50
  method,
51
51
  headers: authHeaders(config),
@@ -113,8 +113,8 @@ export async function deleteFolder(folderId) {
113
113
  }
114
114
 
115
115
  export async function uploadFile(input, options = {}) {
116
- const config = storageConfig();
117
116
  const { blob, fileName } = await filePart(input, options);
117
+ const config = await storageConfig();
118
118
  const form = new FormData();
119
119
  form.set('projectId', config.projectId);
120
120
  form.set('scope', config.environment);