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.
Files changed (87) hide show
  1. package/README.md +16 -0
  2. package/dist/browser/lemma-client.js +13661 -4410
  3. package/dist/browser.d.ts +14 -6
  4. package/dist/browser.js +35 -6
  5. package/dist/client.d.ts +4 -0
  6. package/dist/client.js +15 -3
  7. package/dist/config.d.ts +4 -0
  8. package/dist/config.js +7 -1
  9. package/dist/generated.d.ts +14 -1
  10. package/dist/generated.js +62 -11
  11. package/dist/http.d.ts +47 -1
  12. package/dist/http.js +145 -26
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/namespaces/agents.d.ts +20 -1
  16. package/dist/namespaces/agents.js +29 -1
  17. package/dist/namespaces/conversations.d.ts +3 -0
  18. package/dist/namespaces/conversations.js +2 -0
  19. package/dist/namespaces/desks.d.ts +9 -0
  20. package/dist/namespaces/desks.js +6 -0
  21. package/dist/namespaces/files.d.ts +18 -0
  22. package/dist/namespaces/files.js +21 -0
  23. package/dist/namespaces/functions.d.ts +3 -0
  24. package/dist/namespaces/functions.js +5 -0
  25. package/dist/namespaces/widgets.d.ts +17 -0
  26. package/dist/namespaces/widgets.js +16 -0
  27. package/dist/openapi_client/core/OpenAPI.js +1 -1
  28. package/dist/openapi_client/index.d.ts +4 -0
  29. package/dist/openapi_client/index.js +1 -0
  30. package/dist/openapi_client/models/AgentRunStatus.d.ts +10 -0
  31. package/dist/openapi_client/models/AgentRunStatus.js +15 -0
  32. package/dist/openapi_client/models/AgentToolset.d.ts +2 -2
  33. package/dist/openapi_client/models/AgentToolset.js +1 -1
  34. package/dist/openapi_client/models/ConversationResponse.d.ts +4 -0
  35. package/dist/openapi_client/models/ConversationType.d.ts +2 -1
  36. package/dist/openapi_client/models/ConversationType.js +1 -0
  37. package/dist/openapi_client/models/DatastoreQueryRequest.d.ts +1 -1
  38. package/dist/openapi_client/models/FileSearchResultSchema.d.ts +2 -0
  39. package/dist/openapi_client/models/FileSignedUrlRequest.d.ts +4 -0
  40. package/dist/openapi_client/models/FileSignedUrlRequest.js +1 -0
  41. package/dist/openapi_client/models/FileSignedUrlResponse.d.ts +6 -0
  42. package/dist/openapi_client/models/FileSignedUrlResponse.js +1 -0
  43. package/dist/openapi_client/models/FileUrlResponse.d.ts +6 -0
  44. package/dist/openapi_client/models/FileUrlResponse.js +1 -0
  45. package/dist/openapi_client/services/AgentConversationsService.d.ts +3 -2
  46. package/dist/openapi_client/services/AgentConversationsService.js +4 -2
  47. package/dist/openapi_client/services/FilesService.d.ts +20 -0
  48. package/dist/openapi_client/services/FilesService.js +47 -0
  49. package/dist/openapi_client/services/QueryService.d.ts +1 -1
  50. package/dist/openapi_client/services/QueryService.js +1 -1
  51. package/dist/run-utils.d.ts +24 -0
  52. package/dist/run-utils.js +54 -0
  53. package/dist/version.d.ts +5 -0
  54. package/dist/version.js +7 -0
  55. package/package.json +10 -5
  56. package/dist/hey_client/client/client.gen.d.ts +0 -2
  57. package/dist/hey_client/client/client.gen.js +0 -216
  58. package/dist/hey_client/client/index.d.ts +0 -10
  59. package/dist/hey_client/client/index.js +0 -6
  60. package/dist/hey_client/client/types.gen.d.ts +0 -120
  61. package/dist/hey_client/client/types.gen.js +0 -2
  62. package/dist/hey_client/client/utils.gen.d.ts +0 -37
  63. package/dist/hey_client/client/utils.gen.js +0 -228
  64. package/dist/hey_client/client.gen.d.ts +0 -12
  65. package/dist/hey_client/client.gen.js +0 -3
  66. package/dist/hey_client/core/auth.gen.d.ts +0 -25
  67. package/dist/hey_client/core/auth.gen.js +0 -14
  68. package/dist/hey_client/core/bodySerializer.gen.d.ts +0 -25
  69. package/dist/hey_client/core/bodySerializer.gen.js +0 -57
  70. package/dist/hey_client/core/params.gen.d.ts +0 -43
  71. package/dist/hey_client/core/params.gen.js +0 -100
  72. package/dist/hey_client/core/pathSerializer.gen.d.ts +0 -33
  73. package/dist/hey_client/core/pathSerializer.gen.js +0 -106
  74. package/dist/hey_client/core/queryKeySerializer.gen.d.ts +0 -18
  75. package/dist/hey_client/core/queryKeySerializer.gen.js +0 -92
  76. package/dist/hey_client/core/serverSentEvents.gen.d.ts +0 -71
  77. package/dist/hey_client/core/serverSentEvents.gen.js +0 -132
  78. package/dist/hey_client/core/types.gen.d.ts +0 -83
  79. package/dist/hey_client/core/types.gen.js +0 -2
  80. package/dist/hey_client/core/utils.gen.d.ts +0 -19
  81. package/dist/hey_client/core/utils.gen.js +0 -87
  82. package/dist/hey_client/index.d.ts +0 -2
  83. package/dist/hey_client/index.js +0 -2
  84. package/dist/hey_client/sdk.gen.d.ts +0 -1005
  85. package/dist/hey_client/sdk.gen.js +0 -1438
  86. package/dist/hey_client/types.gen.d.ts +0 -12992
  87. 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
- * <script src="/lemma-client.js"></script>
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({ podId: "...", apiUrl: "..." });
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
- export { LemmaClient } from "./client.js";
12
- export { AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken, } from "./auth.js";
13
- export { ApiError } from "./http.js";
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
- * <script src="/lemma-client.js"></script>
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({ podId: "...", apiUrl: "..." });
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
- export { LemmaClient } from "./client.js";
12
- export { AuthManager, buildAuthUrl, buildFederatedLogoutUrl, clearTestingToken, getTestingToken, resolveSafeRedirectUri, setTestingToken, } from "./auth.js";
13
- export { ApiError } from "./http.js";
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
- this._generated = new GeneratedClientAdapter(this._config.apiUrl, this.auth);
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 { apiUrl: apiUrl.replace(/\/$/, ""), authUrl: authUrl.replace(/\/$/, ""), podId };
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
  }
@@ -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
- constructor(apiUrl: string, auth: AuthManager);
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 { ApiError } from "./http.js";
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
- constructor(apiUrl, auth) {
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 = undefined;
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 operation();
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
- catch (error) {
42
- if (error instanceof GeneratedApiError) {
43
- if (error.status === 401) {
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
- constructor(apiUrl: string, auth: AuthManager);
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
- constructor(apiUrl, auth) {
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
- return new ApiError(response.status, message, code, details, raw);
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
- return this.mergeHeaders(withAuth, options.headers);
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
- const response = await fetch(url, init);
107
- // Only 401 means the session is gone — 403 is a permission/RLS error, not an auth failure
108
- if (response.status === 401) {
109
- this.auth.markUnauthenticated();
110
- }
111
- if (!response.ok) {
112
- throw await this.parseError(response);
113
- }
114
- if (response.status === 204) {
115
- return undefined;
116
- }
117
- const contentType = response.headers.get("content-type") ?? "";
118
- if (contentType.includes("application/json")) {
119
- return response.json();
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
- const response = await fetch(this.buildUrl(path, options.params), this.buildRequestInit(options.method ?? "GET", {
125
- ...options,
126
- headers: {
127
- Accept: "text/event-stream",
128
- ...options.headers,
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 fetch(url, this.auth.getRequestInit({ method }));
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
- constructor(client: GeneratedClientAdapter, podId: () => string);
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>;