@crouter/api 0.3.382 → 0.3.383

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.
@@ -14,6 +14,7 @@ test('SDK and API expose one fixed code table with contract 2 tuples', () => {
14
14
  for (const code of ['grant_revoked', 'grant_removed', 'grant_suspended', 'refresh_token_expired', 'refresh_token_reused']) {
15
15
  assert.deepEqual(errorCodes[code], { type: 'authentication_error', status: 401, retryable: false }, code);
16
16
  }
17
+ assert.deepEqual(errorCodes.provider_setup_pending, { type: 'permission_error', status: 403, retryable: false });
17
18
  assert.equal('provider_error' in errorCodes, false); // provider's 4xx status varies
18
19
  assert.equal('usage' in errorCodes, false); // legacy socket-only code
19
20
  assert.equal('stream_gap' in errorCodes, false); // stream protocol, not a fixed HTTP row
@@ -7,6 +7,7 @@ import type { NodeEventsQuery } from './dto/node-events.js';
7
7
  import type { BrokerSignalLineDTO } from './dto/broker-signals.js';
8
8
  import type { NodeLogAppendRequest, NodeLogAppendResultDTO, NodeTelemetryRecordRequest, NodeRecapRecordRequest, NodeMessageBodyDTO, NodePushedFinalRequest, NodePushedFinalResultDTO } from './dto/node-records.js';
9
9
  import type { InterruptResultDTO, MessageResultDTO, SendMessageRequest } from './dto/messages.js';
10
+ import type { CreateStructuredRequest, CreateStructuredRequestResponse, StructuredRequestDTO } from './dto/structured-requests.js';
10
11
  import type { PushReportRequest, PushReportResultDTO, ReportDTO, ReportsQuery, SubmitResultDTO, SubmitResultRequest } from './dto/reports.js';
11
12
  import type { CloseRequest, CloseResultDTO, PromoteRequest, RelaunchRootResultDTO, ReviveRequest, ReviveResultDTO, WaitRequest, YieldRequest } from './dto/lifecycle.js';
12
13
  import type { SubscribeRequest, SubscriptionDTO } from './dto/subscriptions.js';
@@ -78,6 +79,10 @@ export interface CrtrRequestOptions {
78
79
  signal?: AbortSignal;
79
80
  timeout?: number;
80
81
  maxRetries?: number;
82
+ /** Limits the full call — every attempt, response body, and cold-socket or
83
+ * interrupted-response recovery — to one wall-clock budget, without changing
84
+ * the client's default inactivity timeout. */
85
+ budgetMs?: number;
81
86
  }
82
87
  export declare class CrtrClient {
83
88
  private readonly baseUrl;
@@ -128,6 +133,12 @@ export declare class CrtrClient {
128
133
  /** Parsed NDJSON hints, consumed eagerly and line by line. The caller must
129
134
  * cancel with `signal` when done; no wall-clock timeout applies to the stream. */
130
135
  getBrokerSignals(id: string, executionId: string, options?: Omit<CrtrRequestOptions, 'timeout'>): AsyncIterable<BrokerSignalLineDTO>;
136
+ createStructuredRequest(req: CreateStructuredRequest, options?: CrtrRequestOptions): Promise<CreateStructuredRequestResponse>;
137
+ getStructuredRequest(requestId: string, { wait, budgetMs }?: {
138
+ wait?: number;
139
+ budgetMs?: number;
140
+ }): Promise<StructuredRequestDTO>;
141
+ cancelStructuredRequest(requestId: string, options?: CrtrRequestOptions): Promise<StructuredRequestDTO>;
131
142
  /** Register or replace an armed target for terminal-outcome delivery. */
132
143
  registerOutcomeDelivery(id: string, req: RegisterOutcomeDeliveryRequest): Promise<OutcomeDeliveryDTO>;
133
144
  getOutcomeDelivery(id: string): Promise<OutcomeDeliveryDTO>;
@@ -417,6 +428,10 @@ export declare class CrtrClient {
417
428
  * only — POST/PATCH are never replayed once a request has actually been
418
429
  * sent). No JSON parse; every wrapper goes through here. */
419
430
  private send;
431
+ private resolveBudget;
432
+ private awaitWithinBudget;
433
+ /** One transport attempt, deliberately without retry, cold-socket, or interrupted-response recovery. */
434
+ private sendOnce;
420
435
  private nodePath;
421
436
  /** Validate a background job id before route construction. Job ids arrive
422
437
  * from the daemon's file-backed roster and must remain one path segment. */
@@ -37,6 +37,7 @@ export async function waitForDaemonAvailability({ windowMs, probe, initialError,
37
37
  await sleep(Math.min(pollIntervalMs, afterProbe));
38
38
  }
39
39
  }
40
+ const budgetExhausted = () => Object.assign(new Error('request budget exhausted'), { code: 'ETIMEDOUT' });
40
41
  export class CrtrClient {
41
42
  baseUrl;
42
43
  fetch;
@@ -193,6 +194,24 @@ export class CrtrClient {
193
194
  await reader.cancel().catch(() => { });
194
195
  }
195
196
  }
197
+ // Structured requests
198
+ createStructuredRequest(req, options) {
199
+ // A synchronous admission is one held exchange. Replaying a POST after a
200
+ // reset can create work the caller has already stopped waiting for, and
201
+ // cold-socket recovery consumes its fixed end-to-end budget.
202
+ if (req.mode === 'sync')
203
+ return this.sendOnce('POST', '/v1/structured-requests', req, options);
204
+ return this.request('POST', '/v1/structured-requests', req, options);
205
+ }
206
+ getStructuredRequest(requestId, { wait = 0, budgetMs } = {}) {
207
+ if (!Number.isInteger(wait) || wait < 0 || wait > 25) {
208
+ throw new RangeError('wait must be an integer between 0 and 25');
209
+ }
210
+ return this.request('GET', withQuery(`/v1/structured-requests/${this.requestPath(requestId)}`, { wait }), undefined, { budgetMs });
211
+ }
212
+ cancelStructuredRequest(requestId, options) {
213
+ return this.request('POST', `/v1/structured-requests/${this.requestPath(requestId)}/cancel`, {}, options);
214
+ }
196
215
  /** Register or replace an armed target for terminal-outcome delivery. */
197
216
  registerOutcomeDelivery(id, req) {
198
217
  return this.request('PUT', routes.nodeOutcomeDelivery(this.nodePath(id)), req);
@@ -808,7 +827,8 @@ export class CrtrClient {
808
827
  * the §7 retry policy (connection errors and 429/5xx on GET/HEAD/DELETE
809
828
  * only — POST/PATCH are never replayed once a request has actually been
810
829
  * sent). No JSON parse; every wrapper goes through here. */
811
- async send(method, path, body, opts = {}) {
830
+ async send(method, path, body, callOpts = {}) {
831
+ const opts = this.resolveBudget(callOpts);
812
832
  const idempotent = method === 'GET' || method === 'HEAD' || method === 'DELETE';
813
833
  const maxRetries = opts.maxRetries ?? this.maxRetries;
814
834
  for (let attempt = 0;; attempt++) {
@@ -819,7 +839,7 @@ export class CrtrClient {
819
839
  catch (err) {
820
840
  if (this.isColdSocketError(err)) {
821
841
  try {
822
- await this.handleColdSocket(err);
842
+ await this.handleColdSocket(err, opts.deadlineAt);
823
843
  response = await this.transport(method, path, body, opts);
824
844
  }
825
845
  catch (recoveryError) {
@@ -846,6 +866,42 @@ export class CrtrClient {
846
866
  }
847
867
  }
848
868
  // internals
869
+ resolveBudget(opts) {
870
+ const { budgetMs, ...rest } = opts;
871
+ if (budgetMs === undefined)
872
+ return rest;
873
+ if (!Number.isFinite(budgetMs) || budgetMs <= 0)
874
+ throw new RangeError('budgetMs must be a positive finite number');
875
+ return { ...rest, deadlineAt: Date.now() + budgetMs };
876
+ }
877
+ async awaitWithinBudget(promise, deadlineAt) {
878
+ if (deadlineAt === undefined)
879
+ return promise;
880
+ const remainingMs = deadlineAt - Date.now();
881
+ if (remainingMs <= 0)
882
+ throw budgetExhausted();
883
+ let timer;
884
+ try {
885
+ return await Promise.race([
886
+ promise,
887
+ new Promise((_resolve, reject) => { timer = setTimeout(() => reject(budgetExhausted()), remainingMs); }),
888
+ ]);
889
+ }
890
+ finally {
891
+ if (timer !== undefined)
892
+ clearTimeout(timer);
893
+ }
894
+ }
895
+ /** One transport attempt, deliberately without retry, cold-socket, or interrupted-response recovery. */
896
+ async sendOnce(method, path, body, callOpts = {}) {
897
+ const opts = this.resolveBudget(callOpts);
898
+ try {
899
+ return await parse(await this.transport(method, path, body, opts));
900
+ }
901
+ catch (error) {
902
+ throw toTransportApiError(error, opts.signal);
903
+ }
904
+ }
849
905
  nodePath(id) {
850
906
  if (!isSafeNodeId(id)) {
851
907
  throw new ApiError(400, 'invalid_node_id', `invalid node id: ${JSON.stringify(id)}`);
@@ -913,11 +969,17 @@ export class CrtrClient {
913
969
  * stays an unread `ReadableStream` on the returned `Response`, so a caller
914
970
  * that wants to stream (SSE, later) never waits on a buffered body. */
915
971
  transport(method, path, body, opts = {}) {
972
+ const remainingBudgetMs = opts.deadlineAt === undefined ? undefined : opts.deadlineAt - Date.now();
973
+ if (remainingBudgetMs !== undefined && remainingBudgetMs <= 0)
974
+ return Promise.reject(budgetExhausted());
916
975
  const payload = body === undefined ? undefined : JSON.stringify(body);
917
976
  const headers = { accept: 'application/json', ...this.headers, ...opts.headers };
918
977
  if (payload !== undefined)
919
978
  headers['content-type'] = 'application/json';
920
- const timeoutMs = opts.timeout ?? this.timeoutMs;
979
+ const baseTimeoutMs = opts.timeout ?? this.timeoutMs;
980
+ const timeoutMs = remainingBudgetMs === undefined
981
+ ? baseTimeoutMs
982
+ : baseTimeoutMs > 0 ? Math.min(baseTimeoutMs, remainingBudgetMs) : remainingBudgetMs;
921
983
  const controller = new AbortController();
922
984
  const timer = timeoutMs > 0
923
985
  ? setTimeout(() => controller.abort(new DOMException('request timed out', 'TimeoutError')), timeoutMs)
@@ -993,7 +1055,7 @@ export class CrtrClient {
993
1055
  * it never replays. */
994
1056
  async rideOutInterruptedRequest(method, path, body, opts = {}) {
995
1057
  try {
996
- await this.awaitAvailability();
1058
+ await this.awaitAvailability(undefined, opts.deadlineAt);
997
1059
  }
998
1060
  catch (error) {
999
1061
  throw toTransportApiError(error, opts.signal);
@@ -1008,9 +1070,12 @@ export class CrtrClient {
1008
1070
  throw toTransportApiError(err, opts.signal);
1009
1071
  }
1010
1072
  }
1011
- async awaitAvailability(initialError) {
1073
+ async awaitAvailability(initialError, deadlineAt) {
1074
+ const remainingBudgetMs = deadlineAt === undefined ? undefined : deadlineAt - Date.now();
1075
+ if (remainingBudgetMs !== undefined && remainingBudgetMs <= 0)
1076
+ throw budgetExhausted();
1012
1077
  await waitForDaemonAvailability({
1013
- windowMs: this.coldStartPollWindowMs,
1078
+ windowMs: Math.min(this.coldStartPollWindowMs, remainingBudgetMs ?? this.coldStartPollWindowMs),
1014
1079
  initialError,
1015
1080
  probe: async (timeoutMs) => {
1016
1081
  const response = await this.transport('GET', routes.healthz(), undefined, { timeout: timeoutMs });
@@ -1034,14 +1099,14 @@ export class CrtrClient {
1034
1099
  /** Optionally start the daemon, then observe availability before retrying the
1035
1100
  * request. Waiting is independent from permission to spawn: externally
1036
1101
  * managed daemons still get the same bounded readiness window. */
1037
- async handleColdSocket(initialError) {
1102
+ async handleColdSocket(initialError, deadlineAt) {
1038
1103
  const startedHere = this.autostart && this.onColdSocket !== undefined && !this.coldStartAttempted;
1039
1104
  if (startedHere) {
1040
1105
  this.coldStartAttempted = true;
1041
- await this.onColdSocket();
1106
+ await this.awaitWithinBudget(this.onColdSocket(), deadlineAt);
1042
1107
  }
1043
1108
  try {
1044
- await this.awaitAvailability(initialError);
1109
+ await this.awaitAvailability(initialError, deadlineAt);
1045
1110
  }
1046
1111
  catch (error) {
1047
1112
  if (startedHere) {
@@ -1,3 +1,5 @@
1
+ /** A provider's setup state for the account (directory spec, "Provider setup state"). */
2
+ export type SetupState = 'not_started' | 'pending' | 'ready' | 'failed';
1
3
  export interface GrantGetDTO {
2
4
  sub: string;
3
5
  grantee: string;
@@ -11,4 +13,6 @@ export interface GrantGetDTO {
11
13
  }[];
12
14
  spend_cap: number | null;
13
15
  payer: 'user';
16
+ /** `{ <provider id>: <setup state> }` as the directory's grant sync carried it; `{}` when the grantee has none. The daemon performs no `requires` check: an app polls this and refuses its own operation with `provider_setup_pending`. */
17
+ dependencies: Record<string, SetupState>;
14
18
  }
@@ -69,6 +69,9 @@ export interface CreateNodeRequest {
69
69
  * validated and duplicate-rejected server-side (`NodeIdConflictError` →
70
70
  * HTTP 409 `node_id_exists`). See `crtr node new --node-id`. */
71
71
  node_id?: string;
72
+ /** Opaque key generated once per create attempt. A repeated key returns the
73
+ * node created by its first served request. */
74
+ request_key?: string;
72
75
  /** Serve this create from the warm pool when a pre-booted spare matches the
73
76
  * request's frozen launch tuple (kind, mode, resolved cwd, profile, model,
74
77
  * situational_context) — answering in milliseconds instead of waiting out a
@@ -40,20 +40,22 @@ export interface DeclinedResultDTO {
40
40
  code: string;
41
41
  retryable: boolean;
42
42
  }
43
- /** `POST /v1/nodes/{id}/result` body ({id} = the submitting node). Either the
44
- * structured result to validate against the node's pending output schema —
45
- * `value` must be present; any JSON value (including null) is legal input to
46
- * validation — or a decline: `decline` is the reason sentence, `code` the
47
- * requester-classified token, `retryable` the node's retry claim. A decline
48
- * records a `failure` outcome with reason `declined` carrying all three and
49
- * clears the request without validating anything. */
43
+ /** `POST /v1/nodes/{id}/result` body ({id} = the submitting node). `request_id`
44
+ * identifies the pending structured request. Either `value` is validated
45
+ * against that request's schema — any JSON value (including null) is legal
46
+ * input — or a decline carries the reason sentence, requester-classified
47
+ * code, and retry claim. A decline records a `failure` outcome with reason
48
+ * `declined` carrying all three and clears the request without validating
49
+ * anything. */
50
50
  export type SubmitResultRequest = {
51
+ request_id: string;
52
+ } & ({
51
53
  value: unknown;
52
54
  } | {
53
55
  decline: string;
54
56
  code: string;
55
57
  retryable: boolean;
56
- };
58
+ });
57
59
  /** Result of a structured-result submission (`crtr push result`). Terminal
58
60
  * mode reuses the final-push machinery, so `transitioned` and the managed-worktree
59
61
  * closure fields carry the same meaning as on PushReportResultDTO. */
@@ -0,0 +1,64 @@
1
+ import type { NodeOutcomeDetailV1 } from './node-outcomes.js';
2
+ import type { CreateNodeRequest } from './nodes.js';
3
+ import type { IsoTime, NodeIdDTO, TerminalReasonDTO } from './common.js';
4
+ import type { DeclinedResultDTO } from './reports.js';
5
+ /** Fixed end-to-end work allowance for synchronous structured requests. */
6
+ export declare const SYNC_WORK_BUDGET_MS = 60000;
7
+ /** Local transport time retained after a synchronous request's work deadline. */
8
+ export declare const TRANSPORT_MARGIN_MS = 5000;
9
+ export type StructuredRequestModeDTO = 'sync' | 'async';
10
+ export type StructuredRequestSubmissionDTO = 'terminal' | 'oneoff';
11
+ export type StructuredRequestStateDTO = 'pending' | 'completed' | 'declined' | 'expired' | 'canceled' | 'failed';
12
+ /** A JSON Schema object. Schema interpretation and validation are daemon-owned. */
13
+ export type JsonSchemaDTO = Record<string, unknown>;
14
+ /** Reasons that can fail a structured request before or after its node starts. */
15
+ export type StructuredFailureReasonDTO = TerminalReasonDTO | 'node_create_failed' | 'bind_failed' | 'prompt_delivery_failed' | 'node_deadline_exceeded';
16
+ export interface StructuredRequestFailureDTO {
17
+ reason: StructuredFailureReasonDTO;
18
+ detail: NodeOutcomeDetailV1 | null;
19
+ }
20
+ /** Durable authoritative state for one structured request. */
21
+ export interface StructuredRequestDTO {
22
+ request_id: string;
23
+ node_id: NodeIdDTO | null;
24
+ mode: StructuredRequestModeDTO;
25
+ submission: StructuredRequestSubmissionDTO;
26
+ state: StructuredRequestStateDTO;
27
+ created_at: IsoTime;
28
+ deadline_at: IsoTime | null;
29
+ settled_at: IsoTime | null;
30
+ result: unknown | null;
31
+ declined: DeclinedResultDTO | null;
32
+ failure: StructuredRequestFailureDTO | null;
33
+ }
34
+ export type CreateStructuredRequestTarget = {
35
+ kind: 'new';
36
+ create: CreateNodeRequest;
37
+ } | {
38
+ kind: 'node';
39
+ node_id: NodeIdDTO;
40
+ };
41
+ /** `POST /v1/structured-requests` body. */
42
+ export interface CreateStructuredRequest {
43
+ request_id: string;
44
+ mode: StructuredRequestModeDTO;
45
+ target: CreateStructuredRequestTarget;
46
+ output_schema: JsonSchemaDTO;
47
+ prompt: string;
48
+ /** An absolute deadline propagated unchanged by callers that already have one. */
49
+ deadline_at?: IsoTime;
50
+ /** Requested duration only when establishing a deadline. */
51
+ work_budget_ms?: number;
52
+ }
53
+ /** An honest admission response: no request record exists when admission runs out of budget. */
54
+ export type CreateStructuredRequestResponse = {
55
+ admitted: true;
56
+ request: StructuredRequestDTO;
57
+ } | {
58
+ admitted: false;
59
+ reason: 'work_budget_exhausted';
60
+ };
61
+ /** `GET /v1/structured-requests/{request_id}` query. */
62
+ export interface ReadStructuredRequestQuery {
63
+ wait?: number;
64
+ }
@@ -0,0 +1,5 @@
1
+ // Structured-request API DTOs.
2
+ /** Fixed end-to-end work allowance for synchronous structured requests. */
3
+ export const SYNC_WORK_BUDGET_MS = 60_000;
4
+ /** Local transport time retained after a synchronous request's work deadline. */
5
+ export const TRANSPORT_MARGIN_MS = 5_000;
@@ -69,6 +69,11 @@ export declare const errorCodes: {
69
69
  readonly status: 403;
70
70
  readonly retryable: false;
71
71
  };
72
+ readonly provider_setup_pending: {
73
+ readonly type: "permission_error";
74
+ readonly status: 403;
75
+ readonly retryable: false;
76
+ };
72
77
  readonly question_pages_off: {
73
78
  readonly type: "permission_error";
74
79
  readonly status: 403;
@@ -18,6 +18,8 @@ export const errorCodes = {
18
18
  user_denied: { type: 'permission_error', status: 403, retryable: false },
19
19
  provider_refused: { type: 'permission_error', status: 403, retryable: false },
20
20
  provider_not_connected: { type: 'permission_error', status: 403, retryable: false },
21
+ // An app refusing its own operation because a provider in its `requires` is not `ready`; details `{ provider, state, next }`, `next` = `<issuer>/connect/setup/<provider>`. The daemon never raises it (it only carries `dependencies`).
22
+ provider_setup_pending: { type: 'permission_error', status: 403, retryable: false },
21
23
  question_pages_off: { type: 'permission_error', status: 403, retryable: false },
22
24
  isolated_run: { type: 'permission_error', status: 403, retryable: false },
23
25
  provider_version_retired: { type: 'permission_error', status: 403, retryable: false },
@@ -17,6 +17,7 @@ export * from './dto/broker-signals.js';
17
17
  export * from './dto/bash-jobs.js';
18
18
  export * from './dto/messages.js';
19
19
  export * from './dto/reports.js';
20
+ export * from './dto/structured-requests.js';
20
21
  export * from './dto/lifecycle.js';
21
22
  export * from './dto/subscriptions.js';
22
23
  export * from './dto/focus.js';
package/dist/api/index.js CHANGED
@@ -17,6 +17,7 @@ export * from './dto/broker-signals.js';
17
17
  export * from './dto/bash-jobs.js';
18
18
  export * from './dto/messages.js';
19
19
  export * from './dto/reports.js';
20
+ export * from './dto/structured-requests.js';
20
21
  export * from './dto/lifecycle.js';
21
22
  export * from './dto/subscriptions.js';
22
23
  export * from './dto/focus.js';
@@ -18,7 +18,7 @@ export declare const CONNECTION_FAULT_RECOVERY_BODY = "The network connection is
18
18
  export declare const PROVIDER_FAULT_RECOVERY_BODY = "Your previous turn stopped on a provider fault. Continue from where you left off and retry the work that failed.";
19
19
  export type ModelFallbackRecoveryReason = 'credential' | 'not-found';
20
20
  /** Format the stop guard's dynamic structured-output mandate. */
21
- export declare function formatStructuredOutputReprompt(schema: string): string;
21
+ export declare function formatStructuredOutputReprompt(schema: string, requestId: string): string;
22
22
  /** Keep model-fallback guidance editable without making it a reader contract. */
23
23
  export declare function formatModelFallbackRecovery(previousModel: string, nextModel: string, reason: ModelFallbackRecoveryReason): string;
24
24
  export interface GeneratedContextMessageLike {
@@ -29,11 +29,11 @@ const REVIEW_APPROVAL_OPEN = '<crtr-review-approval>';
29
29
  const REVIEW_APPROVAL_CLOSE = '</crtr-review-approval>';
30
30
  const MODEL_FALLBACK_RECOVERY_OPEN = '<model-fallback-recovery>';
31
31
  const MODEL_FALLBACK_RECOVERY_CLOSE = '</model-fallback-recovery>';
32
- const STRUCTURED_OUTPUT_REPROMPT_PREFIX = 'You must submit a result matching the required schema with `crtr push result` before you can stop, or decline it with `crtr push result --decline "<reason>" --code <token>` when the schema cannot be honestly satisfied. You cannot finish or go dormant any other way while this request is pending.\n\nRequired schema:\n\n```json\n';
33
32
  const STRUCTURED_OUTPUT_REPROMPT_SUFFIX = '\n```';
34
33
  /** Format the stop guard's dynamic structured-output mandate. */
35
- export function formatStructuredOutputReprompt(schema) {
36
- return `${STRUCTURED_OUTPUT_REPROMPT_PREFIX}${schema}${STRUCTURED_OUTPUT_REPROMPT_SUFFIX}`;
34
+ export function formatStructuredOutputReprompt(schema, requestId) {
35
+ const submit = `crtr push result --request ${requestId}`;
36
+ return `You must submit a result matching the required schema with \`${submit}\` before you can stop, or decline it with \`${submit} --decline "<reason>" --code <token>\` when the schema cannot be honestly satisfied. You cannot finish or go dormant any other way while this request is pending.\n\nRequired schema:\n\n\`\`\`json\n${schema}${STRUCTURED_OUTPUT_REPROMPT_SUFFIX}`;
37
37
  }
38
38
  /** Keep model-fallback guidance editable without making it a reader contract. */
39
39
  export function formatModelFallbackRecovery(previousModel, nextModel, reason) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouter/api",
3
- "version": "0.3.382",
3
+ "version": "0.3.383",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/api/index.js",