@vertesia/client 1.6.0-dev.20261019.172735Z → 1.6.0-dev.20261022.120426Z

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 (56) hide show
  1. package/lib/ApiKeysApi.d.ts +4 -7
  2. package/lib/ApiKeysApi.d.ts.map +1 -1
  3. package/lib/ApiKeysApi.js +5 -4
  4. package/lib/ApiKeysApi.js.map +1 -1
  5. package/lib/InteractionsApi.d.ts +3 -1
  6. package/lib/InteractionsApi.d.ts.map +1 -1
  7. package/lib/InteractionsApi.js +2 -1
  8. package/lib/InteractionsApi.js.map +1 -1
  9. package/lib/RunsApi.d.ts +3 -2
  10. package/lib/RunsApi.d.ts.map +1 -1
  11. package/lib/RunsApi.js +3 -2
  12. package/lib/RunsApi.js.map +1 -1
  13. package/lib/execute.d.ts +16 -2
  14. package/lib/execute.d.ts.map +1 -1
  15. package/lib/execute.js +13 -2
  16. package/lib/execute.js.map +1 -1
  17. package/lib/index.d.ts +1 -0
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js.map +1 -1
  20. package/lib/nodejs/NodeStreamSource.d.ts +1 -0
  21. package/lib/nodejs/NodeStreamSource.d.ts.map +1 -1
  22. package/lib/nodejs/NodeStreamSource.js +17 -15
  23. package/lib/nodejs/NodeStreamSource.js.map +1 -1
  24. package/lib/store/AgentsApi.d.ts +3 -0
  25. package/lib/store/AgentsApi.d.ts.map +1 -1
  26. package/lib/store/AgentsApi.js +7 -1
  27. package/lib/store/AgentsApi.js.map +1 -1
  28. package/lib/store/FilesApi.d.ts.map +1 -1
  29. package/lib/store/FilesApi.js +8 -2
  30. package/lib/store/FilesApi.js.map +1 -1
  31. package/lib/store/IndexingApi.d.ts +0 -6
  32. package/lib/store/IndexingApi.d.ts.map +1 -1
  33. package/lib/store/IndexingApi.js +3 -17
  34. package/lib/store/IndexingApi.js.map +1 -1
  35. package/lib/store/ObjectsApi.d.ts +0 -6
  36. package/lib/store/ObjectsApi.d.ts.map +1 -1
  37. package/lib/store/ObjectsApi.js +2 -16
  38. package/lib/store/ObjectsApi.js.map +1 -1
  39. package/lib/store/zeno-bulk.d.ts +9 -0
  40. package/lib/store/zeno-bulk.d.ts.map +1 -0
  41. package/lib/store/zeno-bulk.js +26 -0
  42. package/lib/store/zeno-bulk.js.map +1 -0
  43. package/lib/vertesia-client.js +4 -4
  44. package/lib/vertesia-client.js.map +1 -1
  45. package/package.json +13 -8
  46. package/src/ApiKeysApi.ts +4 -12
  47. package/src/InteractionsApi.ts +9 -2
  48. package/src/RunsApi.ts +7 -2
  49. package/src/execute.ts +30 -3
  50. package/src/index.ts +1 -0
  51. package/src/nodejs/NodeStreamSource.ts +28 -24
  52. package/src/store/AgentsApi.ts +10 -1
  53. package/src/store/FilesApi.ts +8 -2
  54. package/src/store/IndexingApi.ts +3 -18
  55. package/src/store/ObjectsApi.ts +2 -17
  56. package/src/store/zeno-bulk.ts +30 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/client",
3
- "version": "1.6.0-dev.20261019.172735Z",
3
+ "version": "1.6.0-dev.20261022.120426Z",
4
4
  "type": "module",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -12,16 +12,16 @@
12
12
  ],
13
13
  "license": "Apache-2.0",
14
14
  "devDependencies": {
15
- "@types/node": "^24.19.1",
15
+ "@types/node": "^24.19.2",
16
16
  "@vertesia/tsconfig": "0.1.0",
17
- "rolldown": "1.2.12",
17
+ "rolldown": "1.2.14",
18
18
  "typescript": "7.0.2",
19
- "vitest": "5.0.3"
19
+ "vitest": "5.0.4"
20
20
  },
21
21
  "dependencies": {
22
- "@llumiverse/common": "1.6.0-dev.20261019.165531Z",
23
- "@vertesia/api-fetch-client": "1.6.0-dev.20261019.172735Z",
24
- "@vertesia/common": "1.6.0-dev.20261019.172735Z",
22
+ "@llumiverse/common": "1.6.0-dev.20261022.120426Z",
23
+ "@vertesia/api-fetch-client": "1.6.0-dev.20261022.120426Z",
24
+ "@vertesia/common": "1.6.0-dev.20261022.120426Z",
25
25
  "eventsource": "^5.1.2"
26
26
  },
27
27
  "exports": {
@@ -49,7 +49,12 @@
49
49
  "ai",
50
50
  "typescript"
51
51
  ],
52
- "gitHead": "85a3010750ab97192d236928b1fca55015a18000",
52
+ "gitHead": "bcaa7879b601ce9ccc394bcd3257822da13869cd",
53
+ "releaseIdentity": {
54
+ "source_sha": "bcaa7879b601ce9ccc394bcd3257822da13869cd",
55
+ "dependency_sha": "93466e47b06db23ac3c800e248d170afe65e5006",
56
+ "lock_sha256": "117e1c8068aa07d59117323a51ceccfc96cab543e50c03eb7efc19709d6f5e6b"
57
+ },
53
58
  "scripts": {
54
59
  "lint": "biome lint src",
55
60
  "clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
package/src/ApiKeysApi.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
2
  import type {
3
3
  ApiKey,
4
- ApiKeyReadResponse,
5
4
  ApiKeyWithValue,
6
5
  AuthTokenResponse,
7
6
  CreateApiKeyPayload,
@@ -42,17 +41,10 @@ export class ApiKeysApi extends ApiTopic {
42
41
  }
43
42
 
44
43
  /**
45
- * Retrieve an ApiKey and its value
46
- * BE VERY CAREFUL USING THIS API AS IT EXPOSE THE API KEY VALUE
47
- * ALL REQUESTS ARE LOGGED IN SECURITY AUDIT LOG
48
- * @returns ApiKeyWithValue
49
- * */
50
- retrieve(id: string, withValue: true): Promise<ApiKeyWithValue>;
51
- retrieve(id: string, withValue?: false): Promise<ApiKey>;
52
- retrieve(id: string, withValue: boolean = false): Promise<ApiKeyReadResponse> {
53
- if (withValue) {
54
- return this.get(`/${id}`, { query: { withValue: true } });
55
- }
44
+ * Retrieve an ApiKey without its value. The raw value is returned only once, by `create`.
45
+ * @returns ApiKey
46
+ */
47
+ retrieve(id: string): Promise<ApiKey> {
56
48
  return this.get(`/${id}`);
57
49
  }
58
50
 
@@ -33,7 +33,13 @@ import type {
33
33
  ResolveInteractionQuery,
34
34
  } from '@vertesia/common';
35
35
  import type { VertesiaClient } from './client.js';
36
- import { checkRateLimit, executeInteraction, executeInteractionAsync, executeInteractionByName } from './execute.js';
36
+ import {
37
+ checkRateLimit,
38
+ type ExecuteInteractionRequestOptions,
39
+ executeInteraction,
40
+ executeInteractionAsync,
41
+ executeInteractionByName,
42
+ } from './execute.js';
37
43
  import { InteractionCatalogApi } from './InteractionCatalogApi.js';
38
44
  import { type EnhancedInteractionExecutionResult, enhanceInteractionExecutionResult } from './InteractionOutput.js';
39
45
 
@@ -222,13 +228,14 @@ export default class InteractionsApi extends ApiTopic {
222
228
  * @param nameWithTag
223
229
  * @param payload
224
230
  * @param onChunk
231
+ * @param requestOptions request options, e.g. `{ query: { include_prompt: false } }` to omit the rendered prompt
225
232
  * @returns
226
233
  */
227
234
  async executeByName<ResultT = unknown, ParamsT = unknown>(
228
235
  nameWithTag: string,
229
236
  payload: InteractionExecutionPayload = {},
230
237
  onChunk?: (chunk: string) => void,
231
- requestOptions?: IRequestParams,
238
+ requestOptions?: ExecuteInteractionRequestOptions,
232
239
  ): Promise<EnhancedInteractionExecutionResult<ResultT, ParamsT>> {
233
240
  const r = await executeInteractionByName<ParamsT>(
234
241
  this.client as VertesiaClient,
package/src/RunsApi.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  ExecutionRunRef,
9
9
  FindPayload,
10
10
  FindRunResult,
11
+ GetRunQuery,
11
12
  InteractionExecutionResult,
12
13
  PopulatedExecutionRun,
13
14
  RunClonePayload,
@@ -67,10 +68,14 @@ export class RunsApi extends ApiTopic {
67
68
  * Get a run by id
68
69
  *
69
70
  * @param id
71
+ * @param query response options, e.g. `{ include_prompt: false }` to omit the rendered prompt
70
72
  * @returns InteractionResult
71
73
  **/
72
- async retrieve<ResultT = unknown, ParamsT = unknown>(id: string): Promise<EnhancedExecutionRun<ResultT, ParamsT>> {
73
- const r = await this.get<ExecutionRun<ParamsT>>(`/${id}`);
74
+ async retrieve<ResultT = unknown, ParamsT = unknown>(
75
+ id: string,
76
+ query?: GetRunQuery,
77
+ ): Promise<EnhancedExecutionRun<ResultT, ParamsT>> {
78
+ const r = await this.get<ExecutionRun<ParamsT>>(`/${id}`, { query });
74
79
  return enhanceExecutionRun<ResultT, ParamsT>(r);
75
80
  }
76
81
 
package/src/execute.ts CHANGED
@@ -2,6 +2,7 @@ import type { IRequestParams } from '@vertesia/api-fetch-client';
2
2
  import {
3
3
  type AsyncExecutionPayload,
4
4
  type AsyncExecutionResult,
5
+ type ExecuteNamedInteractionQuery,
5
6
  ExecutionRunStatus,
6
7
  type InteractionExecutionPayload,
7
8
  type InteractionExecutionResult,
@@ -23,6 +24,20 @@ const INTERACTION_EXECUTION_TIMEOUT_MS = (() => {
23
24
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 30 * 60 * 1000; // 30 minutes
24
25
  })();
25
26
 
27
+ /**
28
+ * Per-request options for a synchronous named-interaction execution: the request options this method
29
+ * can honour, plus the published execute query.
30
+ *
31
+ * `reader` and `jsonPayload` are left out: the method parses the JSON execution result and serializes
32
+ * the payload it builds, so either would break the call. `query` is the typed execute query instead of
33
+ * a free-form record, so a misspelt parameter fails to compile rather than being ignored by the server.
34
+ * With `onChunk`, the options apply to the initial request only; the stream that follows is a separate
35
+ * connection.
36
+ */
37
+ export type ExecuteInteractionRequestOptions = Omit<IRequestParams, 'reader' | 'jsonPayload' | 'query'> & {
38
+ query?: ExecuteNamedInteractionQuery;
39
+ };
40
+
26
41
  export async function EventSourceProvider(): Promise<typeof EventSource> {
27
42
  if (typeof globalThis.EventSource === 'function') {
28
43
  return globalThis.EventSource;
@@ -81,6 +96,7 @@ export async function executeInteraction<P = unknown>(
81
96
  * @param interaction
82
97
  * @param payload
83
98
  * @param onChunk
99
+ * @param requestOptions request options, e.g. `{ query: { include_prompt: false } }` to omit the rendered prompt
84
100
  * @returns
85
101
  */
86
102
  export async function executeInteractionByName<P = unknown>(
@@ -88,7 +104,7 @@ export async function executeInteractionByName<P = unknown>(
88
104
  interaction: string,
89
105
  payload: InteractionExecutionPayload = {},
90
106
  onChunk?: (chunk: string) => void,
91
- requestOptions?: IRequestParams,
107
+ requestOptions?: ExecuteInteractionRequestOptions,
92
108
  ): Promise<InteractionExecutionResult<P>> {
93
109
  const stream = !!onChunk;
94
110
  const response = await client.post<InteractionExecutionResult<P>>('/api/v1/execute', {
@@ -98,8 +114,14 @@ export async function executeInteractionByName<P = unknown>(
98
114
  interaction,
99
115
  stream,
100
116
  } as NamedInteractionExecutionPayload,
101
- // Synchronous interaction execution blocks on the model; keep a long client timeout.
102
- timeoutMs: requestOptions?.timeoutMs ?? (stream ? undefined : INTERACTION_EXECUTION_TIMEOUT_MS),
117
+ // Synchronous interaction execution blocks on the model; keep a long client timeout. An explicit
118
+ // timeout wins, including `null`, `false` or `0`, which disable it.
119
+ timeoutMs:
120
+ requestOptions?.timeoutMs !== undefined
121
+ ? requestOptions.timeoutMs
122
+ : stream
123
+ ? undefined
124
+ : INTERACTION_EXECUTION_TIMEOUT_MS,
103
125
  });
104
126
  if (stream) {
105
127
  if (response.status === ExecutionRunStatus.failed) {
@@ -167,8 +189,13 @@ export async function executeInteractionAsync(
167
189
  client: VertesiaClient,
168
190
  payload: AsyncExecutionPayload,
169
191
  ): Promise<AsyncExecutionResult> {
192
+ // A conversation starts an agent run: without `idempotency_key` a replay could start a second one,
193
+ // so opt out of any client-level retry policy (see `AgentsApi.start`). With a key, a replay
194
+ // returns the run the first request started.
195
+ const unkeyedConversation = payload.type === 'conversation' && !payload.idempotency_key;
170
196
  return await client.post('/api/v1/execute/async', {
171
197
  payload,
198
+ retryPolicy: unkeyedConversation ? false : undefined,
172
199
  });
173
200
  }
174
201
 
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export type { EnableEnvironmentModelPayload, OrphanedAppInstallation } from '@ve
4
4
  export { getOAuthPermissionScopes, Permission } from '@vertesia/common';
5
5
  export * from './client.js';
6
6
  export { default as DelegationGrantsApi } from './DelegationGrantsApi.js';
7
+ export type { ExecuteInteractionRequestOptions } from './execute.js';
7
8
  export type { GroupsQueryOptions } from './GroupsApi.js';
8
9
  export * from './InteractionBase.js';
9
10
  export * from './InteractionOutput.js';
@@ -1,10 +1,11 @@
1
1
  import type { Readable } from 'node:stream';
2
- import { ReadableStream as NodeReadableStream } from 'node:stream/web';
2
+ import { finished } from 'node:stream/promises';
3
3
  import { StreamSource } from '../StreamSource.js';
4
4
 
5
5
  /**
6
6
  * A stream source that wraps a Node.js Readable stream.
7
7
  * This class is only works in Node.js environments.
8
+ * Cancelling the web stream destroys the Node.js stream, releasing its underlying resource (e.g. a file descriptor).
8
9
  */
9
10
  export class NodeStreamSource extends StreamSource {
10
11
  constructor(stream: Readable, name: string, type?: string, id?: string) {
@@ -12,31 +13,34 @@ export class NodeStreamSource extends StreamSource {
12
13
  }
13
14
  }
14
15
 
15
- function readableToWebStream<T = string | Buffer>(stream: Readable): ReadableStream<T> {
16
- if (NodeReadableStream.from) {
17
- return NodeReadableStream.from(stream) as ReadableStream<T>;
18
- } else {
19
- return readableToWebStream_BUN(stream);
20
- }
21
- }
22
-
23
16
  /**
24
- * Bun implementation since bun is not yet supporting NodeReadableStream.from
25
- * @returns
17
+ * Not ReadableStream.from: it cancels by calling return() on the Node.js async iterator, which is a no-op
18
+ * when nothing was read yet, so the Node.js stream would stay open. Also covers Bun, which lacks ReadableStream.from.
26
19
  */
27
- function readableToWebStream_BUN<T = string | Buffer>(stream: Readable): ReadableStream<T> {
20
+ function readableToWebStream<T = string | Buffer>(stream: Readable): ReadableStream<T> {
21
+ // Nothing listens until the first read, so an error before it (e.g. a failed lazy file open) would otherwise
22
+ // crash the process. The iterator still reports the error on the next read.
23
+ stream.on('error', () => undefined);
28
24
  const it = stream[Symbol.asyncIterator]();
29
- return new ReadableStream<T>({
30
- async pull(controller) {
31
- const { value, done } = await it.next();
32
- if (done) {
33
- controller.close();
34
- } else {
35
- controller.enqueue(value);
36
- }
37
- },
38
- cancel() {
39
- it.return?.();
25
+ return new ReadableStream<T>(
26
+ {
27
+ async pull(controller) {
28
+ const { value, done } = await it.next();
29
+ if (done) {
30
+ controller.close();
31
+ } else {
32
+ controller.enqueue(value);
33
+ }
34
+ },
35
+ async cancel() {
36
+ // no error argument: it would emit 'error', which the caller may not listen for
37
+ stream.destroy();
38
+ // resolve once the underlying resource is released, so callers can remove the file afterwards;
39
+ // bounded because some streams never report their close
40
+ await finished(stream, { signal: AbortSignal.timeout(5_000) }).catch(() => undefined);
41
+ },
40
42
  },
41
- });
43
+ // read on demand, like ReadableStream.from
44
+ { highWaterMark: 0 },
45
+ );
42
46
  }
@@ -87,13 +87,22 @@ export class AgentsApi extends ApiTopic {
87
87
  /**
88
88
  * Create and start a new agent run.
89
89
  * Returns the created AgentRun with its stable id.
90
+ *
91
+ * Set `idempotency_key` on an agent run to make the call safe to retry: the client's retry policy
92
+ * then applies, and a replayed request returns the run the first one started.
90
93
  */
91
94
  start<TData = Record<string, unknown>>(payload: CreateAgentRunPayload<TData>): Promise<AgentRun<TData>>;
92
95
  start<TData = Record<string, unknown>>(payload: CreateProcessRunPayload<TData>): Promise<ProcessRun>;
93
96
  start<TData = Record<string, unknown>>(
94
97
  payload: CreateAgentRunPayload<TData> | CreateProcessRunPayload<TData>,
95
98
  ): Promise<AgentRun<TData> | ProcessRun> {
96
- return this.post('/', { payload });
99
+ // Without a key, starting an agent run is not idempotent: a request abandoned client-side can
100
+ // still complete server-side, so a replay would start a second run. Opt out of any client-level
101
+ // retry policy explicitly rather than relying on POST being absent from its method list.
102
+ // Rate-limiter pacing retries are unaffected: those happen before the request reaches the
103
+ // handler. Process starts take no key and keep the client's policy.
104
+ const unkeyedAgentStart = 'interaction' in payload && !payload.idempotency_key;
105
+ return this.post('/', { payload, retryPolicy: unkeyedAgentStart ? false : undefined });
97
106
  }
98
107
 
99
108
  /**
@@ -258,11 +258,17 @@ export class FilesApi extends ApiTopic {
258
258
  'Content-Type': sourceMimeType,
259
259
  },
260
260
  }).catch((err) => {
261
- console.error('Failed to upload file', { err, url, id, path });
261
+ console.error('Failed to upload file', { err, location: fileLocationForError(url), id, path });
262
262
  throw err;
263
263
  });
264
264
  if (!res.ok) {
265
- console.log(res);
265
+ console.error('Failed to upload file', {
266
+ status: res.status,
267
+ statusText: res.statusText,
268
+ location: fileLocationForError(url),
269
+ id,
270
+ path,
271
+ });
266
272
  throw new Error(`Failed to upload file: ${res.statusText}`);
267
273
  }
268
274
 
@@ -27,6 +27,7 @@ import type {
27
27
  SwapAliasResult,
28
28
  TriggerReindexResult,
29
29
  } from '@vertesia/common';
30
+ import { zenoBulkBaseUrl } from './zeno-bulk.js';
30
31
 
31
32
  /**
32
33
  * API for indexing operations on content objects.
@@ -274,30 +275,14 @@ export class IndexingApi extends ApiTopic {
274
275
 
275
276
  // ========================================================================
276
277
  // Zeno Bulk endpoints (Go migration service)
277
- // Routes via LB path rules in prod, or derived URL in dev.
278
+ // Routes via LB path rules in prod, or derived URL in dev (see zenoBulkBaseUrl).
278
279
  // ========================================================================
279
280
 
280
- /**
281
- * Get the zeno-bulk base URL.
282
- * Dev branches: store URL contains "zeno-server" -> replace with "zeno-bulk".
283
- * Production/preview: same domain, LB routes /reindex/* to zeno-bulk.
284
- */
285
- private get zenoBulkBaseUrl(): string {
286
- const storeBaseUrl = this.client.baseUrl;
287
- if (storeBaseUrl.includes('localhost:') || storeBaseUrl.includes('127.0.0.1:')) {
288
- return 'https://zeno-bulk-dev-main.api.dev1.vertesia.io';
289
- }
290
- if (storeBaseUrl.includes('zeno-server')) {
291
- return storeBaseUrl.replace(/zeno-server/, 'zeno-bulk');
292
- }
293
- return storeBaseUrl;
294
- }
295
-
296
281
  /**
297
282
  * POST to a zeno-bulk endpoint. Resolves the path against the zeno-bulk base URL.
298
283
  */
299
284
  private zenoBulkPost<T>(path: string, body: object): Promise<T> {
300
- return this.client.post(this.zenoBulkBaseUrl + path, { payload: body });
285
+ return this.client.post(zenoBulkBaseUrl(this.client.baseUrl) + path, { payload: body });
301
286
  }
302
287
 
303
288
  /**
@@ -63,6 +63,7 @@ import { AnalyzeDocApi } from './AnalyzeDocApi.js';
63
63
  import type { ZenoClient } from './client.js';
64
64
  import { fetchSignedUrl } from './signed-url.js';
65
65
  import { getUploadMimeTypeHint, resolveUploadMimeType } from './uploadMimeType.js';
66
+ import { zenoBulkBaseUrl } from './zeno-bulk.js';
66
67
 
67
68
  /**
68
69
  * The two write payloads, each widened at `content` so callers can hand us a browser `File` or a
@@ -215,24 +216,8 @@ export class ObjectsApi extends ApiTopic {
215
216
  return this.zenoBulkPost('/export/compose', payload, timeoutMs);
216
217
  }
217
218
 
218
- /**
219
- * Get the zeno-bulk base URL.
220
- * Dev branches: store URL contains "zeno-server" -> replace with "zeno-bulk".
221
- * Production/preview: same domain, LB routes /export to zeno-bulk.
222
- */
223
- private get zenoBulkBaseUrl(): string {
224
- const storeBaseUrl = this.client.baseUrl;
225
- if (storeBaseUrl.includes('localhost:') || storeBaseUrl.includes('127.0.0.1:')) {
226
- return 'https://zeno-bulk-dev-main.api.dev1.vertesia.io';
227
- }
228
- if (storeBaseUrl.includes('zeno-server')) {
229
- return storeBaseUrl.replace(/zeno-server/, 'zeno-bulk');
230
- }
231
- return storeBaseUrl;
232
- }
233
-
234
219
  private zenoBulkPost<T>(path: string, body: object, timeoutMs?: number | false | null): Promise<T> {
235
- return this.client.post(this.zenoBulkBaseUrl + path, { payload: body, timeoutMs });
220
+ return this.client.post(zenoBulkBaseUrl(this.client.baseUrl) + path, { payload: body, timeoutMs });
236
221
  }
237
222
 
238
223
  listFolders(_path: string = '/') {
@@ -0,0 +1,30 @@
1
+ type RuntimeProcess = {
2
+ env?: Record<string, string | undefined>;
3
+ };
4
+
5
+ /** The ZENO_BULK_URL environment variable, in a runtime that has one. */
6
+ function getRuntimeZenoBulkUrl() {
7
+ const runtimeProcess = (globalThis as typeof globalThis & { process?: RuntimeProcess }).process;
8
+ return runtimeProcess?.env?.ZENO_BULK_URL || undefined;
9
+ }
10
+
11
+ /**
12
+ * The base URL of zeno-bulk, the service behind the bulk export and reindex endpoints, for a store base URL.
13
+ * - ZENO_BULK_URL, when the runtime sets it: a local stack that runs its own zeno-bulk, or none, points it there.
14
+ * - A store on localhost: the dev1 zeno-bulk, which serves the database a default local stack uses.
15
+ * - A dev-branch store, whose host names zeno-server: the matching zeno-bulk host.
16
+ * - Any other store: its own domain, whose load balancer routes the bulk paths to zeno-bulk.
17
+ */
18
+ export function zenoBulkBaseUrl(storeBaseUrl: string): string {
19
+ const override = getRuntimeZenoBulkUrl();
20
+ if (override) {
21
+ return override.replace(/\/+$/, '');
22
+ }
23
+ if (storeBaseUrl.includes('localhost:') || storeBaseUrl.includes('127.0.0.1:')) {
24
+ return 'https://zeno-bulk-dev-main.api.dev1.vertesia.io';
25
+ }
26
+ if (storeBaseUrl.includes('zeno-server')) {
27
+ return storeBaseUrl.replace(/zeno-server/, 'zeno-bulk');
28
+ }
29
+ return storeBaseUrl;
30
+ }