lemma-sdk 0.2.46 → 0.3.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/README.md +16 -0
- package/dist/browser/lemma-client.js +13661 -4410
- package/dist/browser.d.ts +14 -6
- package/dist/browser.js +35 -6
- package/dist/client.d.ts +4 -0
- package/dist/client.js +15 -3
- package/dist/config.d.ts +4 -0
- package/dist/config.js +7 -1
- package/dist/generated.d.ts +14 -1
- package/dist/generated.js +62 -11
- package/dist/http.d.ts +47 -1
- package/dist/http.js +145 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/namespaces/agents.d.ts +20 -1
- package/dist/namespaces/agents.js +29 -1
- package/dist/namespaces/conversations.d.ts +3 -0
- package/dist/namespaces/conversations.js +2 -0
- package/dist/namespaces/desks.d.ts +9 -0
- package/dist/namespaces/desks.js +6 -0
- package/dist/namespaces/files.d.ts +18 -0
- package/dist/namespaces/files.js +21 -0
- package/dist/namespaces/functions.d.ts +3 -0
- package/dist/namespaces/functions.js +5 -0
- package/dist/namespaces/widgets.d.ts +17 -0
- package/dist/namespaces/widgets.js +16 -0
- package/dist/openapi_client/core/OpenAPI.js +1 -1
- package/dist/openapi_client/index.d.ts +4 -0
- package/dist/openapi_client/index.js +1 -0
- package/dist/openapi_client/models/AgentRunStatus.d.ts +10 -0
- package/dist/openapi_client/models/AgentRunStatus.js +15 -0
- package/dist/openapi_client/models/AgentToolset.d.ts +2 -2
- package/dist/openapi_client/models/AgentToolset.js +1 -1
- package/dist/openapi_client/models/ConversationResponse.d.ts +4 -0
- package/dist/openapi_client/models/ConversationType.d.ts +2 -1
- package/dist/openapi_client/models/ConversationType.js +1 -0
- package/dist/openapi_client/models/DatastoreQueryRequest.d.ts +1 -1
- package/dist/openapi_client/models/FileSearchResultSchema.d.ts +2 -0
- package/dist/openapi_client/models/FileSignedUrlRequest.d.ts +4 -0
- package/dist/openapi_client/models/FileSignedUrlRequest.js +1 -0
- package/dist/openapi_client/models/FileSignedUrlResponse.d.ts +6 -0
- package/dist/openapi_client/models/FileSignedUrlResponse.js +1 -0
- package/dist/openapi_client/models/FileUrlResponse.d.ts +6 -0
- package/dist/openapi_client/models/FileUrlResponse.js +1 -0
- package/dist/openapi_client/services/AgentConversationsService.d.ts +3 -2
- package/dist/openapi_client/services/AgentConversationsService.js +4 -2
- package/dist/openapi_client/services/FilesService.d.ts +20 -0
- package/dist/openapi_client/services/FilesService.js +47 -0
- package/dist/openapi_client/services/QueryService.d.ts +1 -1
- package/dist/openapi_client/services/QueryService.js +1 -1
- package/dist/run-utils.d.ts +24 -0
- package/dist/run-utils.js +54 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +7 -0
- package/package.json +10 -5
- package/dist/hey_client/client/client.gen.d.ts +0 -2
- package/dist/hey_client/client/client.gen.js +0 -216
- package/dist/hey_client/client/index.d.ts +0 -10
- package/dist/hey_client/client/index.js +0 -6
- package/dist/hey_client/client/types.gen.d.ts +0 -120
- package/dist/hey_client/client/types.gen.js +0 -2
- package/dist/hey_client/client/utils.gen.d.ts +0 -37
- package/dist/hey_client/client/utils.gen.js +0 -228
- package/dist/hey_client/client.gen.d.ts +0 -12
- package/dist/hey_client/client.gen.js +0 -3
- package/dist/hey_client/core/auth.gen.d.ts +0 -25
- package/dist/hey_client/core/auth.gen.js +0 -14
- package/dist/hey_client/core/bodySerializer.gen.d.ts +0 -25
- package/dist/hey_client/core/bodySerializer.gen.js +0 -57
- package/dist/hey_client/core/params.gen.d.ts +0 -43
- package/dist/hey_client/core/params.gen.js +0 -100
- package/dist/hey_client/core/pathSerializer.gen.d.ts +0 -33
- package/dist/hey_client/core/pathSerializer.gen.js +0 -106
- package/dist/hey_client/core/queryKeySerializer.gen.d.ts +0 -18
- package/dist/hey_client/core/queryKeySerializer.gen.js +0 -92
- package/dist/hey_client/core/serverSentEvents.gen.d.ts +0 -71
- package/dist/hey_client/core/serverSentEvents.gen.js +0 -132
- package/dist/hey_client/core/types.gen.d.ts +0 -83
- package/dist/hey_client/core/types.gen.js +0 -2
- package/dist/hey_client/core/utils.gen.d.ts +0 -19
- package/dist/hey_client/core/utils.gen.js +0 -87
- package/dist/hey_client/index.d.ts +0 -2
- package/dist/hey_client/index.js +0 -2
- package/dist/hey_client/sdk.gen.d.ts +0 -1005
- package/dist/hey_client/sdk.gen.js +0 -1438
- package/dist/hey_client/types.gen.d.ts +0 -12992
- package/dist/hey_client/types.gen.js +0 -2
package/dist/browser.d.ts
CHANGED
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
* Browser bundle entry point.
|
|
3
3
|
* Exposes LemmaClient as globalThis.LemmaClient.LemmaClient
|
|
4
4
|
*
|
|
5
|
-
* Usage in HTML
|
|
6
|
-
*
|
|
5
|
+
* Usage in HTML (the host injects window.__LEMMA_CONFIG__ — construct with NO
|
|
6
|
+
* args so the desk validator doesn't flag a hardcoded podId):
|
|
7
|
+
* <script src="/public/sdk/lemma-client.js"></script>
|
|
7
8
|
* <script>
|
|
8
|
-
* const client = new window.LemmaClient.LemmaClient(
|
|
9
|
+
* const client = new window.LemmaClient.LemmaClient(); // reads window.__LEMMA_CONFIG__
|
|
9
10
|
* </script>
|
|
11
|
+
*
|
|
12
|
+
* Canonical paths for this one bundle (kept in sync by the CI bundle-freshness
|
|
13
|
+
* gate) — don't let these drift:
|
|
14
|
+
* - served URL: /public/sdk/lemma-client.js (backend public_sdk_controller)
|
|
15
|
+
* - committed on disk: lemma-typescript/public/lemma-client.js
|
|
16
|
+
* - package export: dist/browser/lemma-client.js (unpkg / "./browser-bundle")
|
|
10
17
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
import { LemmaClient } from "./client.js";
|
|
19
|
+
import { AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken } from "./auth.js";
|
|
20
|
+
import { ApiError } from "./http.js";
|
|
21
|
+
export { LemmaClient, AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken, ApiError, };
|
package/dist/browser.js
CHANGED
|
@@ -2,12 +2,41 @@
|
|
|
2
2
|
* Browser bundle entry point.
|
|
3
3
|
* Exposes LemmaClient as globalThis.LemmaClient.LemmaClient
|
|
4
4
|
*
|
|
5
|
-
* Usage in HTML
|
|
6
|
-
*
|
|
5
|
+
* Usage in HTML (the host injects window.__LEMMA_CONFIG__ — construct with NO
|
|
6
|
+
* args so the desk validator doesn't flag a hardcoded podId):
|
|
7
|
+
* <script src="/public/sdk/lemma-client.js"></script>
|
|
7
8
|
* <script>
|
|
8
|
-
* const client = new window.LemmaClient.LemmaClient(
|
|
9
|
+
* const client = new window.LemmaClient.LemmaClient(); // reads window.__LEMMA_CONFIG__
|
|
9
10
|
* </script>
|
|
11
|
+
*
|
|
12
|
+
* Canonical paths for this one bundle (kept in sync by the CI bundle-freshness
|
|
13
|
+
* gate) — don't let these drift:
|
|
14
|
+
* - served URL: /public/sdk/lemma-client.js (backend public_sdk_controller)
|
|
15
|
+
* - committed on disk: lemma-typescript/public/lemma-client.js
|
|
16
|
+
* - package export: dist/browser/lemma-client.js (unpkg / "./browser-bundle")
|
|
10
17
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
import { LemmaClient } from "./client.js";
|
|
19
|
+
import { AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken, } from "./auth.js";
|
|
20
|
+
import { ApiError } from "./http.js";
|
|
21
|
+
export { LemmaClient, AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken, ApiError, };
|
|
22
|
+
// Back-compat alias: conversation widgets historically loaded this bundle as
|
|
23
|
+
// `window.Lemma` (`new Lemma.LemmaClient(...)`). We standardize on
|
|
24
|
+
// `window.LemmaClient`; keep `window.Lemma` pointing at the same surface during
|
|
25
|
+
// migration so existing widget HTML keeps working. See
|
|
26
|
+
// docs/desk-widget-unification.md.
|
|
27
|
+
if (typeof globalThis !== "undefined") {
|
|
28
|
+
const scope = globalThis;
|
|
29
|
+
if (!scope.Lemma) {
|
|
30
|
+
scope.Lemma = {
|
|
31
|
+
LemmaClient,
|
|
32
|
+
AuthManager,
|
|
33
|
+
buildAuthUrl,
|
|
34
|
+
buildFederatedLogoutUrl,
|
|
35
|
+
clearTestingToken,
|
|
36
|
+
getTestingToken,
|
|
37
|
+
resolveSafeRedirectUri,
|
|
38
|
+
setTestingToken,
|
|
39
|
+
ApiError,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { SchedulesNamespace } from "./namespaces/schedules.js";
|
|
|
22
22
|
import { TablesNamespace } from "./namespaces/tables.js";
|
|
23
23
|
import { UsersNamespace } from "./namespaces/users.js";
|
|
24
24
|
import { WorkflowsNamespace } from "./namespaces/workflows.js";
|
|
25
|
+
import { WidgetsNamespace } from "./namespaces/widgets.js";
|
|
25
26
|
import { DatastoreNamespace } from "./namespaces/datastore.js";
|
|
26
27
|
export type { LemmaConfig };
|
|
27
28
|
export { AuthManager };
|
|
@@ -46,11 +47,14 @@ export declare class LemmaClient {
|
|
|
46
47
|
readonly conversations: ConversationsNamespace;
|
|
47
48
|
readonly workflows: WorkflowsNamespace;
|
|
48
49
|
readonly desks: DesksNamespace;
|
|
50
|
+
readonly widgets: WidgetsNamespace;
|
|
49
51
|
readonly integrations: IntegrationsNamespace;
|
|
50
52
|
readonly resources: ResourcesNamespace;
|
|
51
53
|
readonly resourceAccess: ResourceAccessNamespace;
|
|
52
54
|
readonly schedules: SchedulesNamespace;
|
|
53
55
|
readonly datastore: DatastoreNamespace;
|
|
56
|
+
/** Alias of {@link datastore}, matching the Python SDK's `pod.queries`. */
|
|
57
|
+
readonly queries: DatastoreNamespace;
|
|
54
58
|
readonly users: UsersNamespace;
|
|
55
59
|
readonly icons: IconsNamespace;
|
|
56
60
|
readonly pods: PodsNamespace;
|
package/dist/client.js
CHANGED
|
@@ -24,6 +24,7 @@ import { SchedulesNamespace } from "./namespaces/schedules.js";
|
|
|
24
24
|
import { TablesNamespace } from "./namespaces/tables.js";
|
|
25
25
|
import { UsersNamespace } from "./namespaces/users.js";
|
|
26
26
|
import { WorkflowsNamespace } from "./namespaces/workflows.js";
|
|
27
|
+
import { WidgetsNamespace } from "./namespaces/widgets.js";
|
|
27
28
|
import { DatastoreNamespace } from "./namespaces/datastore.js";
|
|
28
29
|
export { AuthManager };
|
|
29
30
|
export class LemmaClient {
|
|
@@ -44,11 +45,14 @@ export class LemmaClient {
|
|
|
44
45
|
conversations;
|
|
45
46
|
workflows;
|
|
46
47
|
desks;
|
|
48
|
+
widgets;
|
|
47
49
|
integrations;
|
|
48
50
|
resources;
|
|
49
51
|
resourceAccess;
|
|
50
52
|
schedules;
|
|
51
53
|
datastore;
|
|
54
|
+
/** Alias of {@link datastore}, matching the Python SDK's `pod.queries`. */
|
|
55
|
+
queries;
|
|
52
56
|
users;
|
|
53
57
|
icons;
|
|
54
58
|
pods;
|
|
@@ -63,8 +67,14 @@ export class LemmaClient {
|
|
|
63
67
|
this._currentPodId = this._config.podId;
|
|
64
68
|
this._podId = this._config.podId;
|
|
65
69
|
this.auth = internalOptions.authManager ?? new AuthManager(this._config.apiUrl, this._config.authUrl);
|
|
66
|
-
this._http = new HttpClient(this._config.apiUrl, this.auth
|
|
67
|
-
|
|
70
|
+
this._http = new HttpClient(this._config.apiUrl, this.auth, {
|
|
71
|
+
timeoutMs: this._config.timeoutMs,
|
|
72
|
+
maxRetries: this._config.maxRetries,
|
|
73
|
+
});
|
|
74
|
+
this._generated = new GeneratedClientAdapter(this._config.apiUrl, this.auth, {
|
|
75
|
+
maxRetries: this._config.maxRetries,
|
|
76
|
+
timeoutMs: this._config.timeoutMs,
|
|
77
|
+
});
|
|
68
78
|
const podIdFn = () => {
|
|
69
79
|
if (!this._currentPodId) {
|
|
70
80
|
throw new Error("pod_id is required. Pass podId in the constructor or call client.setPodId(id).");
|
|
@@ -75,16 +85,18 @@ export class LemmaClient {
|
|
|
75
85
|
this.records = new RecordsNamespace(this._generated, podIdFn);
|
|
76
86
|
this.files = new FilesNamespace(this._generated, this._http, podIdFn);
|
|
77
87
|
this.functions = new FunctionsNamespace(this._generated, podIdFn);
|
|
78
|
-
this.agents = new AgentsNamespace(this._generated, podIdFn);
|
|
88
|
+
this.agents = new AgentsNamespace(this._generated, podIdFn, () => this.conversations);
|
|
79
89
|
this.agentRuntime = new AgentRuntimeNamespace(this._generated);
|
|
80
90
|
this.conversations = new ConversationsNamespace(this._http, podIdFn);
|
|
81
91
|
this.workflows = new WorkflowsNamespace(this._generated, this._http, podIdFn);
|
|
82
92
|
this.desks = new DesksNamespace(this._generated, this._http, podIdFn);
|
|
93
|
+
this.widgets = new WidgetsNamespace(this._http, podIdFn);
|
|
83
94
|
this.integrations = new IntegrationsNamespace(this._generated, this._http);
|
|
84
95
|
this.resources = new ResourcesNamespace(this._http);
|
|
85
96
|
this.resourceAccess = new ResourceAccessNamespace(this._generated, podIdFn);
|
|
86
97
|
this.schedules = new SchedulesNamespace(this._generated, podIdFn);
|
|
87
98
|
this.datastore = new DatastoreNamespace(this._generated, podIdFn);
|
|
99
|
+
this.queries = this.datastore;
|
|
88
100
|
this.users = new UsersNamespace(this._generated);
|
|
89
101
|
this.icons = new IconsNamespace(this._generated);
|
|
90
102
|
this.pods = new PodsNamespace(this._generated, this._http);
|
package/dist/config.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export interface LemmaConfig {
|
|
|
5
5
|
authUrl: string;
|
|
6
6
|
/** Pod ID to scope all pod-level API calls */
|
|
7
7
|
podId?: string;
|
|
8
|
+
/** Per-request timeout in ms (default 30000). */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
/** Max automatic retries on 429/502/503/504 (default 2). */
|
|
11
|
+
maxRetries?: number;
|
|
8
12
|
}
|
|
9
13
|
declare global {
|
|
10
14
|
interface Window {
|
package/dist/config.js
CHANGED
|
@@ -45,5 +45,11 @@ export function resolveConfig(overrides = {}) {
|
|
|
45
45
|
const podId = overrides.podId ??
|
|
46
46
|
win.podId ??
|
|
47
47
|
fromEnv("POD_ID");
|
|
48
|
-
return {
|
|
48
|
+
return {
|
|
49
|
+
apiUrl: apiUrl.replace(/\/$/, ""),
|
|
50
|
+
authUrl: authUrl.replace(/\/$/, ""),
|
|
51
|
+
podId,
|
|
52
|
+
timeoutMs: overrides.timeoutMs ?? win.timeoutMs,
|
|
53
|
+
maxRetries: overrides.maxRetries ?? win.maxRetries,
|
|
54
|
+
};
|
|
49
55
|
}
|
package/dist/generated.d.ts
CHANGED
|
@@ -2,7 +2,20 @@ import type { AuthManager } from "./auth.js";
|
|
|
2
2
|
export declare class GeneratedClientAdapter {
|
|
3
3
|
private readonly apiUrl;
|
|
4
4
|
private readonly auth;
|
|
5
|
-
|
|
5
|
+
private readonly maxRetries;
|
|
6
|
+
private readonly timeoutMs;
|
|
7
|
+
constructor(apiUrl: string, auth: AuthManager, options?: {
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
});
|
|
6
11
|
private configure;
|
|
7
12
|
request<T>(operation: () => PromiseLike<T>): Promise<T>;
|
|
13
|
+
/**
|
|
14
|
+
* Enforce a per-attempt timeout on the generated client (which exposes no
|
|
15
|
+
* timeout of its own). The generated operation returns a CancelablePromise;
|
|
16
|
+
* on timeout we cancel it (aborting the underlying fetch) and surface a
|
|
17
|
+
* NetworkError, matching HttpClient.fetchWithTimeout. Non-cancelable or
|
|
18
|
+
* disabled-timeout cases fall through untouched.
|
|
19
|
+
*/
|
|
20
|
+
private runWithTimeout;
|
|
8
21
|
}
|
package/dist/generated.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NetworkError, apiErrorFromStatus } from "./http.js";
|
|
2
2
|
import { ApiError as GeneratedApiError } from "./openapi_client/core/ApiError.js";
|
|
3
|
+
import { CancelablePromise } from "./openapi_client/core/CancelablePromise.js";
|
|
3
4
|
import { OpenAPI } from "./openapi_client/core/OpenAPI.js";
|
|
5
|
+
import { retryDelayForStatus, sleep } from "./run-utils.js";
|
|
6
|
+
import { CLIENT_HEADER_NAME, CLIENT_HEADER_VALUE } from "./version.js";
|
|
7
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
4
9
|
function extractMessage(body, fallback) {
|
|
5
10
|
if (body && typeof body === "object" && typeof body.message === "string") {
|
|
6
11
|
return body.message;
|
|
@@ -22,30 +27,76 @@ function extractDetails(body) {
|
|
|
22
27
|
export class GeneratedClientAdapter {
|
|
23
28
|
apiUrl;
|
|
24
29
|
auth;
|
|
25
|
-
|
|
30
|
+
maxRetries;
|
|
31
|
+
timeoutMs;
|
|
32
|
+
constructor(apiUrl, auth, options = {}) {
|
|
26
33
|
this.apiUrl = apiUrl;
|
|
27
34
|
this.auth = auth;
|
|
35
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
36
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
28
37
|
}
|
|
29
38
|
configure() {
|
|
30
39
|
OpenAPI.BASE = this.apiUrl;
|
|
31
40
|
OpenAPI.WITH_CREDENTIALS = true;
|
|
32
41
|
OpenAPI.CREDENTIALS = this.auth.isTokenMode ? "omit" : "include";
|
|
33
42
|
OpenAPI.TOKEN = this.auth.getBearerToken() ?? undefined;
|
|
34
|
-
OpenAPI.HEADERS =
|
|
43
|
+
OpenAPI.HEADERS = { [CLIENT_HEADER_NAME]: CLIENT_HEADER_VALUE };
|
|
35
44
|
}
|
|
36
45
|
async request(operation) {
|
|
37
46
|
this.configure();
|
|
47
|
+
for (let attempt = 0;; attempt++) {
|
|
48
|
+
try {
|
|
49
|
+
return await this.runWithTimeout(operation);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof GeneratedApiError) {
|
|
53
|
+
if (error.status === 401) {
|
|
54
|
+
this.auth.markUnauthenticated();
|
|
55
|
+
}
|
|
56
|
+
// Retry transient gateway/rate-limit statuses. The generated client
|
|
57
|
+
// doesn't expose response headers, so there's no Retry-After to honor
|
|
58
|
+
// (retryDelayForStatus falls back to jittered backoff).
|
|
59
|
+
const retryDelay = retryDelayForStatus(error.status, attempt, this.maxRetries, null);
|
|
60
|
+
if (retryDelay !== null) {
|
|
61
|
+
await sleep(retryDelay);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
throw apiErrorFromStatus(error.status, extractMessage(error.body, error.message), extractCode(error.body), extractDetails(error.body), error.body);
|
|
65
|
+
}
|
|
66
|
+
// Not an HTTP error (timeout/cancellation, programming error, raw
|
|
67
|
+
// transport failure) — preserve it untouched.
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Enforce a per-attempt timeout on the generated client (which exposes no
|
|
74
|
+
* timeout of its own). The generated operation returns a CancelablePromise;
|
|
75
|
+
* on timeout we cancel it (aborting the underlying fetch) and surface a
|
|
76
|
+
* NetworkError, matching HttpClient.fetchWithTimeout. Non-cancelable or
|
|
77
|
+
* disabled-timeout cases fall through untouched.
|
|
78
|
+
*/
|
|
79
|
+
async runWithTimeout(operation) {
|
|
80
|
+
const op = operation();
|
|
81
|
+
if (this.timeoutMs <= 0 || !(op instanceof CancelablePromise)) {
|
|
82
|
+
return op;
|
|
83
|
+
}
|
|
84
|
+
let timer;
|
|
38
85
|
try {
|
|
39
|
-
return await
|
|
86
|
+
return await Promise.race([
|
|
87
|
+
op,
|
|
88
|
+
new Promise((_, reject) => {
|
|
89
|
+
timer = setTimeout(() => {
|
|
90
|
+
op.cancel();
|
|
91
|
+
reject(new NetworkError(`Request timed out after ${this.timeoutMs}ms`));
|
|
92
|
+
}, this.timeoutMs);
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
40
95
|
}
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
this.auth.markUnauthenticated();
|
|
45
|
-
}
|
|
46
|
-
throw new ApiError(error.status, extractMessage(error.body, error.message), extractCode(error.body), extractDetails(error.body), error.body);
|
|
96
|
+
finally {
|
|
97
|
+
if (timer) {
|
|
98
|
+
clearTimeout(timer);
|
|
47
99
|
}
|
|
48
|
-
throw error;
|
|
49
100
|
}
|
|
50
101
|
}
|
|
51
102
|
}
|
package/dist/http.d.ts
CHANGED
|
@@ -11,18 +11,64 @@ interface RequestOptions {
|
|
|
11
11
|
headers?: HeadersInit;
|
|
12
12
|
signal?: AbortSignal;
|
|
13
13
|
}
|
|
14
|
+
export interface HttpClientOptions {
|
|
15
|
+
/** Per-request timeout in ms (default 30000). Streaming requests are exempt. */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
/** Max automatic retries on 429/502/503/504 (default 2). */
|
|
18
|
+
maxRetries?: number;
|
|
19
|
+
}
|
|
14
20
|
export declare class ApiError extends Error {
|
|
15
21
|
readonly statusCode: number;
|
|
16
22
|
readonly code?: string | undefined;
|
|
17
23
|
readonly details?: unknown | undefined;
|
|
18
24
|
readonly rawResponse?: unknown | undefined;
|
|
25
|
+
/** Server correlation id (X-Request-Id) when present — quote it in bug reports. */
|
|
26
|
+
requestId?: string;
|
|
19
27
|
constructor(statusCode: number, message: string, code?: string | undefined, details?: unknown | undefined, rawResponse?: unknown | undefined);
|
|
20
28
|
}
|
|
29
|
+
/** 401 — session missing/expired. */
|
|
30
|
+
export declare class UnauthorizedError extends ApiError {
|
|
31
|
+
name: string;
|
|
32
|
+
}
|
|
33
|
+
/** 403 — authenticated but not permitted (often an RLS/grant denial). */
|
|
34
|
+
export declare class ForbiddenError extends ApiError {
|
|
35
|
+
name: string;
|
|
36
|
+
}
|
|
37
|
+
/** 404 — resource not found. */
|
|
38
|
+
export declare class NotFoundError extends ApiError {
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
/** 409 — conflict (e.g. duplicate name). */
|
|
42
|
+
export declare class ConflictError extends ApiError {
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
|
45
|
+
/** 429 — rate limited; `retryAfterMs` is the server-advised wait if provided. */
|
|
46
|
+
export declare class RateLimitError extends ApiError {
|
|
47
|
+
readonly retryAfterMs?: number | undefined;
|
|
48
|
+
name: string;
|
|
49
|
+
constructor(message: string, code?: string, details?: unknown, rawResponse?: unknown, retryAfterMs?: number | undefined);
|
|
50
|
+
}
|
|
51
|
+
/** 5xx — server-side error. */
|
|
52
|
+
export declare class ServerError extends ApiError {
|
|
53
|
+
name: string;
|
|
54
|
+
}
|
|
55
|
+
/** Transport-level failure (DNS, connection refused, timeout) — no HTTP status. */
|
|
56
|
+
export declare class NetworkError extends Error {
|
|
57
|
+
readonly cause?: unknown | undefined;
|
|
58
|
+
name: string;
|
|
59
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
60
|
+
}
|
|
61
|
+
/** Map an HTTP status to the most specific ApiError subclass. */
|
|
62
|
+
export declare function apiErrorFromStatus(status: number, message: string, code?: string, details?: unknown, rawResponse?: unknown, retryAfterMsValue?: number): ApiError;
|
|
21
63
|
export declare class HttpClient {
|
|
22
64
|
private readonly apiUrl;
|
|
23
65
|
private readonly auth;
|
|
24
|
-
|
|
66
|
+
private readonly timeoutMs;
|
|
67
|
+
private readonly maxRetries;
|
|
68
|
+
constructor(apiUrl: string, auth: AuthManager, options?: HttpClientOptions);
|
|
25
69
|
getBaseUrl(): string;
|
|
70
|
+
/** fetch with a default timeout, normalizing transport failures into NetworkError. */
|
|
71
|
+
private fetchWithTimeout;
|
|
26
72
|
private buildUrl;
|
|
27
73
|
private mergeHeaders;
|
|
28
74
|
private parseError;
|
package/dist/http.js
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
* Thin HTTP layer that wraps fetch with auth injection, error handling,
|
|
3
3
|
* and automatic 401→unauthenticated state propagation.
|
|
4
4
|
*/
|
|
5
|
+
import { retryDelayForStatus, serverRetryAfterMs, sleep } from "./run-utils.js";
|
|
6
|
+
import { CLIENT_HEADER_NAME, CLIENT_HEADER_VALUE } from "./version.js";
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
8
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
5
9
|
export class ApiError extends Error {
|
|
6
10
|
statusCode;
|
|
7
11
|
code;
|
|
8
12
|
details;
|
|
9
13
|
rawResponse;
|
|
14
|
+
/** Server correlation id (X-Request-Id) when present — quote it in bug reports. */
|
|
15
|
+
requestId;
|
|
10
16
|
constructor(statusCode, message, code, details, rawResponse) {
|
|
11
17
|
super(message);
|
|
12
18
|
this.statusCode = statusCode;
|
|
@@ -16,16 +22,104 @@ export class ApiError extends Error {
|
|
|
16
22
|
this.name = "ApiError";
|
|
17
23
|
}
|
|
18
24
|
}
|
|
25
|
+
/** 401 — session missing/expired. */
|
|
26
|
+
export class UnauthorizedError extends ApiError {
|
|
27
|
+
name = "UnauthorizedError";
|
|
28
|
+
}
|
|
29
|
+
/** 403 — authenticated but not permitted (often an RLS/grant denial). */
|
|
30
|
+
export class ForbiddenError extends ApiError {
|
|
31
|
+
name = "ForbiddenError";
|
|
32
|
+
}
|
|
33
|
+
/** 404 — resource not found. */
|
|
34
|
+
export class NotFoundError extends ApiError {
|
|
35
|
+
name = "NotFoundError";
|
|
36
|
+
}
|
|
37
|
+
/** 409 — conflict (e.g. duplicate name). */
|
|
38
|
+
export class ConflictError extends ApiError {
|
|
39
|
+
name = "ConflictError";
|
|
40
|
+
}
|
|
41
|
+
/** 429 — rate limited; `retryAfterMs` is the server-advised wait if provided. */
|
|
42
|
+
export class RateLimitError extends ApiError {
|
|
43
|
+
retryAfterMs;
|
|
44
|
+
name = "RateLimitError";
|
|
45
|
+
constructor(message, code, details, rawResponse, retryAfterMs) {
|
|
46
|
+
super(429, message, code, details, rawResponse);
|
|
47
|
+
this.retryAfterMs = retryAfterMs;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** 5xx — server-side error. */
|
|
51
|
+
export class ServerError extends ApiError {
|
|
52
|
+
name = "ServerError";
|
|
53
|
+
}
|
|
54
|
+
/** Transport-level failure (DNS, connection refused, timeout) — no HTTP status. */
|
|
55
|
+
export class NetworkError extends Error {
|
|
56
|
+
cause;
|
|
57
|
+
name = "NetworkError";
|
|
58
|
+
constructor(message, cause) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.cause = cause;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Map an HTTP status to the most specific ApiError subclass. */
|
|
64
|
+
export function apiErrorFromStatus(status, message, code, details, rawResponse, retryAfterMsValue) {
|
|
65
|
+
switch (status) {
|
|
66
|
+
case 401: return new UnauthorizedError(status, message, code, details, rawResponse);
|
|
67
|
+
case 403: return new ForbiddenError(status, message, code, details, rawResponse);
|
|
68
|
+
case 404: return new NotFoundError(status, message, code, details, rawResponse);
|
|
69
|
+
case 409: return new ConflictError(status, message, code, details, rawResponse);
|
|
70
|
+
case 429: return new RateLimitError(message, code, details, rawResponse, retryAfterMsValue);
|
|
71
|
+
default:
|
|
72
|
+
return status >= 500
|
|
73
|
+
? new ServerError(status, message, code, details, rawResponse)
|
|
74
|
+
: new ApiError(status, message, code, details, rawResponse);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
19
77
|
export class HttpClient {
|
|
20
78
|
apiUrl;
|
|
21
79
|
auth;
|
|
22
|
-
|
|
80
|
+
timeoutMs;
|
|
81
|
+
maxRetries;
|
|
82
|
+
constructor(apiUrl, auth, options = {}) {
|
|
23
83
|
this.apiUrl = apiUrl;
|
|
24
84
|
this.auth = auth;
|
|
85
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
86
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
25
87
|
}
|
|
26
88
|
getBaseUrl() {
|
|
27
89
|
return this.apiUrl;
|
|
28
90
|
}
|
|
91
|
+
/** fetch with a default timeout, normalizing transport failures into NetworkError. */
|
|
92
|
+
async fetchWithTimeout(url, init, userSignal) {
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
let timedOut = false;
|
|
95
|
+
const timer = setTimeout(() => {
|
|
96
|
+
timedOut = true;
|
|
97
|
+
controller.abort();
|
|
98
|
+
}, this.timeoutMs);
|
|
99
|
+
const onAbort = () => controller.abort();
|
|
100
|
+
if (userSignal) {
|
|
101
|
+
if (userSignal.aborted)
|
|
102
|
+
controller.abort();
|
|
103
|
+
else
|
|
104
|
+
userSignal.addEventListener("abort", onAbort, { once: true });
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (timedOut) {
|
|
111
|
+
throw new NetworkError(`Request timed out after ${this.timeoutMs}ms`, error);
|
|
112
|
+
}
|
|
113
|
+
if (userSignal?.aborted) {
|
|
114
|
+
throw error; // caller-initiated abort — propagate the original AbortError
|
|
115
|
+
}
|
|
116
|
+
throw new NetworkError(`Network request failed: ${String(error?.message ?? error)}`, error);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
userSignal?.removeEventListener("abort", onAbort);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
29
123
|
buildUrl(path, params) {
|
|
30
124
|
let url = `${this.apiUrl}${path}`;
|
|
31
125
|
if (!params) {
|
|
@@ -74,7 +168,12 @@ export class HttpClient {
|
|
|
74
168
|
catch {
|
|
75
169
|
// non-JSON error body
|
|
76
170
|
}
|
|
77
|
-
|
|
171
|
+
const retryMs = response.status === 429
|
|
172
|
+
? serverRetryAfterMs(response.headers.get("retry-after")) ?? undefined
|
|
173
|
+
: undefined;
|
|
174
|
+
const error = apiErrorFromStatus(response.status, message, code, details, raw, retryMs);
|
|
175
|
+
error.requestId = response.headers.get("x-request-id") ?? undefined;
|
|
176
|
+
return error;
|
|
78
177
|
}
|
|
79
178
|
getRequestBody(options) {
|
|
80
179
|
if (options.body === undefined) {
|
|
@@ -98,36 +197,56 @@ export class HttpClient {
|
|
|
98
197
|
headers: Object.fromEntries(Object.entries(this.auth.getRequestInit(initBase).headers ?? {}).filter(([key]) => key.toLowerCase() !== "content-type")),
|
|
99
198
|
}
|
|
100
199
|
: this.auth.getRequestInit(initBase);
|
|
101
|
-
|
|
200
|
+
const withClient = this.mergeHeaders(withAuth, { [CLIENT_HEADER_NAME]: CLIENT_HEADER_VALUE });
|
|
201
|
+
return this.mergeHeaders(withClient, options.headers);
|
|
102
202
|
}
|
|
103
203
|
async request(method, path, options = {}) {
|
|
104
204
|
const url = this.buildUrl(path, options.params);
|
|
105
205
|
const init = this.buildRequestInit(method, options);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
206
|
+
for (let attempt = 0;; attempt++) {
|
|
207
|
+
const response = await this.fetchWithTimeout(url, init, options.signal);
|
|
208
|
+
// Only 401 means the session is gone — 403 is a permission/RLS error, not an auth failure
|
|
209
|
+
if (response.status === 401) {
|
|
210
|
+
this.auth.markUnauthenticated();
|
|
211
|
+
}
|
|
212
|
+
const retryDelay = retryDelayForStatus(response.status, attempt, this.maxRetries, response.headers.get("retry-after"));
|
|
213
|
+
if (retryDelay !== null) {
|
|
214
|
+
await sleep(retryDelay, options.signal);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
throw await this.parseError(response);
|
|
219
|
+
}
|
|
220
|
+
if (response.status === 204) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
224
|
+
if (contentType.includes("application/json")) {
|
|
225
|
+
return response.json();
|
|
226
|
+
}
|
|
227
|
+
return response.text();
|
|
120
228
|
}
|
|
121
|
-
return response.text();
|
|
122
229
|
}
|
|
123
230
|
async stream(path, options = {}) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
231
|
+
// Streams are deliberately timeout-exempt (they're long-lived), but we still
|
|
232
|
+
// normalize transport failures into NetworkError so the typed-error contract
|
|
233
|
+
// holds on the SSE path too.
|
|
234
|
+
let response;
|
|
235
|
+
try {
|
|
236
|
+
response = await fetch(this.buildUrl(path, options.params), this.buildRequestInit(options.method ?? "GET", {
|
|
237
|
+
...options,
|
|
238
|
+
headers: {
|
|
239
|
+
Accept: "text/event-stream",
|
|
240
|
+
...options.headers,
|
|
241
|
+
},
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
if (options.signal?.aborted) {
|
|
246
|
+
throw error; // caller-initiated abort — propagate the original AbortError
|
|
247
|
+
}
|
|
248
|
+
throw new NetworkError(`Network request failed: ${String(error?.message ?? error)}`, error);
|
|
249
|
+
}
|
|
131
250
|
if (response.status === 401) {
|
|
132
251
|
this.auth.markUnauthenticated();
|
|
133
252
|
}
|
|
@@ -141,7 +260,7 @@ export class HttpClient {
|
|
|
141
260
|
}
|
|
142
261
|
async requestBytes(method, path) {
|
|
143
262
|
const url = `${this.apiUrl}${path}`;
|
|
144
|
-
const response = await
|
|
263
|
+
const response = await this.fetchWithTimeout(url, this.auth.getRequestInit({ method }));
|
|
145
264
|
if (response.status === 401) {
|
|
146
265
|
this.auth.markUnauthenticated();
|
|
147
266
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { LemmaClient } from "./client.js";
|
|
|
2
2
|
export type { LemmaConfig } from "./client.js";
|
|
3
3
|
export { AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken, } from "./auth.js";
|
|
4
4
|
export type { AuthState, AuthListener, AuthStatus, UserInfo, AuthRedirectMode, BuildAuthUrlOptions, BuildFederatedLogoutUrlOptions, RedirectToFederatedLogoutOptions, ResolveSafeRedirectUriOptions, } from "./auth.js";
|
|
5
|
-
export { ApiError } from "./http.js";
|
|
5
|
+
export { ApiError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, RateLimitError, ServerError, NetworkError, apiErrorFromStatus, } from "./http.js";
|
|
6
6
|
export * from "./types.js";
|
|
7
7
|
export { readSSE, parseSSEJson } from "./streams.js";
|
|
8
8
|
export type { SseRawEvent } from "./streams.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { LemmaClient } from "./client.js";
|
|
2
2
|
export { AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken, } from "./auth.js";
|
|
3
|
-
export { ApiError } from "./http.js";
|
|
3
|
+
export { ApiError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, RateLimitError, ServerError, NetworkError, apiErrorFromStatus, } from "./http.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
export { readSSE, parseSSEJson } from "./streams.js";
|
|
6
6
|
export { normalizeRunStatus, isTerminalFunctionStatus, isTerminalFlowStatus, sleep, nextBackoffDelay, } from "./run-utils.js";
|
|
@@ -2,14 +2,33 @@ import type { GeneratedClientAdapter } from "../generated.js";
|
|
|
2
2
|
import type { CreateAgentRequest } from "../openapi_client/models/CreateAgentRequest.js";
|
|
3
3
|
import type { AgentPermissionsReplaceRequest } from "../openapi_client/models/AgentPermissionsReplaceRequest.js";
|
|
4
4
|
import type { UpdateAgentRequest } from "../openapi_client/models/UpdateAgentRequest.js";
|
|
5
|
+
import type { ConversationsNamespace } from "./conversations.js";
|
|
6
|
+
export interface RunAgentOptions {
|
|
7
|
+
title?: string;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
/** Stream tokens instead of awaiting the full reply. */
|
|
10
|
+
stream?: boolean;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
5
13
|
export declare class AgentsNamespace {
|
|
6
14
|
private readonly client;
|
|
7
15
|
private readonly podId;
|
|
8
|
-
|
|
16
|
+
private readonly conversations?;
|
|
17
|
+
constructor(client: GeneratedClientAdapter, podId: () => string, conversations?: (() => ConversationsNamespace) | undefined);
|
|
9
18
|
list(options?: {
|
|
10
19
|
limit?: number;
|
|
11
20
|
pageToken?: string;
|
|
12
21
|
}): Promise<import("../types.js").AgentListResponse>;
|
|
22
|
+
/**
|
|
23
|
+
* Run an agent on a single message (the `.run` verb, alongside
|
|
24
|
+
* `functions.run` / `datastore.query`). Note the return contract differs:
|
|
25
|
+
* those return the result directly, whereas an agent reply is asynchronous —
|
|
26
|
+
* this opens a fresh conversation, sends `message`, and returns the created
|
|
27
|
+
* conversation (read the reply via `client.conversations.messages.list(conv.id)`).
|
|
28
|
+
* With `stream: true` it returns the SSE stream so you can consume tokens as
|
|
29
|
+
* they arrive.
|
|
30
|
+
*/
|
|
31
|
+
run(agentName: string, message: string, options?: RunAgentOptions): Promise<ReadableStream<Uint8Array<ArrayBufferLike>> | import("../types.js").Conversation>;
|
|
13
32
|
create(payload: CreateAgentRequest): Promise<import("../types.js").AgentActionResponse>;
|
|
14
33
|
get(agentName: string): Promise<import("../types.js").AgentDetailResponse>;
|
|
15
34
|
update(agentName: string, payload: UpdateAgentRequest): Promise<import("../types.js").AgentActionResponse>;
|