@to11ai/sdk 1.1.0 → 2.0.0-rc.10

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.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Format-at-load shaping: turn a neutral `RenderedPrompt` into a provider-shaped
3
+ * `{ messages, config, metadata }` result the caller spreads into their own
4
+ * provider `create()` call. Header-only — this only reshapes data, it never
5
+ * sends a request. See docs/product-specs/2026-07-14-to11-2691-sdk-simpler-setup.md.
6
+ */
7
+
8
+ import {
9
+ type AnthropicMessage,
10
+ type AnthropicTool,
11
+ type AnthropicToolChoice,
12
+ type ConvertMessagesOptions,
13
+ type OpenAIChatMessage,
14
+ type OpenAITool,
15
+ type OpenAIToolChoice,
16
+ toAnthropicMessages,
17
+ toAnthropicSystem,
18
+ toAnthropicToolChoice,
19
+ toAnthropicTools,
20
+ toOpenAIMessages,
21
+ toOpenAIToolChoice,
22
+ toOpenAITools,
23
+ } from "../gateway";
24
+ import type { RenderedPrompt, RenderFormat } from "./types";
25
+
26
+ /** Model params passed through verbatim from the resolved version's modelConfig. */
27
+ export interface PromptConfig {
28
+ model?: string;
29
+ temperature?: number;
30
+ // Snake_case to match the stored modelConfig and the provider wire key
31
+ // (spread verbatim into e.g. OpenAI's `create()`), per TO11-2691.
32
+ max_tokens?: number;
33
+ /** Any additional stored model-config keys are passed through unchanged. */
34
+ [key: string]: unknown;
35
+ }
36
+
37
+ /** Prompt provenance for `turn.headers()` — kept apart from the request body. */
38
+ export interface PromptMetadata {
39
+ promptId: string;
40
+ versionId: string;
41
+ version: number;
42
+ releaseId: string | null;
43
+ /** The single label the prompt resolved through (env slug today). */
44
+ label: string;
45
+ /** The prompt's human-readable slug — surfaced as `x-to11-prompt-slug`. */
46
+ slug: string;
47
+ variantName?: string;
48
+ }
49
+
50
+ /** OpenAI-shaped render result: spread `{ ...config, messages }` into create(). */
51
+ export interface OpenAiRenderedPrompt {
52
+ messages: OpenAIChatMessage[];
53
+ config: PromptConfig;
54
+ metadata: PromptMetadata;
55
+ /** Authored tools, provider-shaped. Present only when the prompt declares tools. */
56
+ tools?: OpenAITool[];
57
+ /** Authored tool-choice directive. Present only when the prompt sets one. */
58
+ tool_choice?: OpenAIToolChoice;
59
+ }
60
+
61
+ /** Anthropic-shaped render result: system/developer folded into `system`. */
62
+ export interface AnthropicRenderedPrompt {
63
+ /** Folded system/developer instruction text (Anthropic's top-level `system`). */
64
+ system?: string;
65
+ messages: AnthropicMessage[];
66
+ config: PromptConfig;
67
+ metadata: PromptMetadata;
68
+ /** Authored tools, provider-shaped. Present only when the prompt declares tools. */
69
+ tools?: AnthropicTool[];
70
+ /** Authored tool-choice directive. Present only when the prompt sets one. */
71
+ tool_choice?: AnthropicToolChoice;
72
+ }
73
+
74
+ /** The render return type for a given client/call format. */
75
+ export type RenderResult<F extends RenderFormat | undefined> =
76
+ F extends "openai"
77
+ ? OpenAiRenderedPrompt
78
+ : F extends "anthropic"
79
+ ? AnthropicRenderedPrompt
80
+ : RenderedPrompt;
81
+
82
+ function toPromptConfig(modelConfig: unknown): PromptConfig {
83
+ return modelConfig !== null && typeof modelConfig === "object"
84
+ ? { ...(modelConfig as Record<string, unknown>) }
85
+ : {};
86
+ }
87
+
88
+ function toMetadata(prompt: RenderedPrompt): PromptMetadata {
89
+ const metadata: PromptMetadata = {
90
+ promptId: prompt.promptId,
91
+ versionId: prompt.versionId,
92
+ version: prompt.version,
93
+ releaseId: prompt.releaseId,
94
+ label: prompt.label,
95
+ slug: prompt.slug,
96
+ };
97
+ if (prompt.variantName !== undefined)
98
+ metadata.variantName = prompt.variantName;
99
+ return metadata;
100
+ }
101
+
102
+ export function shapeOpenAI(
103
+ prompt: RenderedPrompt,
104
+ options: ConvertMessagesOptions = {},
105
+ ): OpenAiRenderedPrompt {
106
+ const result: OpenAiRenderedPrompt = {
107
+ messages: toOpenAIMessages(prompt.messages, options),
108
+ config: toPromptConfig(prompt.modelConfig),
109
+ metadata: toMetadata(prompt),
110
+ };
111
+ // Carry authored tools so the spread-ready result is complete for
112
+ // tool-using prompts. Omit when empty/absent so we never send `tools: []`.
113
+ const tools = toOpenAITools(prompt.tools);
114
+ if (tools.length > 0) result.tools = tools;
115
+ const toolChoice = toOpenAIToolChoice(prompt.toolChoice);
116
+ if (toolChoice !== undefined) result.tool_choice = toolChoice;
117
+ return result;
118
+ }
119
+
120
+ export function shapeAnthropic(
121
+ prompt: RenderedPrompt,
122
+ options: ConvertMessagesOptions = {},
123
+ ): AnthropicRenderedPrompt {
124
+ const result: AnthropicRenderedPrompt = {
125
+ messages: toAnthropicMessages(prompt.messages, options),
126
+ config: toPromptConfig(prompt.modelConfig),
127
+ metadata: toMetadata(prompt),
128
+ };
129
+ const system = toAnthropicSystem(prompt.messages);
130
+ if (system !== undefined) result.system = system;
131
+ const tools = toAnthropicTools(prompt.tools);
132
+ if (tools.length > 0) result.tools = tools;
133
+ const toolChoice = toAnthropicToolChoice(prompt.toolChoice);
134
+ if (toolChoice !== undefined) result.tool_choice = toolChoice;
135
+ return result;
136
+ }
137
+
138
+ /** Shape a rendered prompt for `format`, or return it neutral when unset. */
139
+ export function shapePrompt(
140
+ prompt: RenderedPrompt,
141
+ format: RenderFormat | undefined,
142
+ options: ConvertMessagesOptions = {},
143
+ ): RenderedPrompt | OpenAiRenderedPrompt | AnthropicRenderedPrompt {
144
+ switch (format) {
145
+ case "openai":
146
+ return shapeOpenAI(prompt, options);
147
+ case "anthropic":
148
+ return shapeAnthropic(prompt, options);
149
+ default:
150
+ return prompt;
151
+ }
152
+ }
@@ -6,7 +6,6 @@ import type {
6
6
  PromptLabelPointer as GenPromptLabelPointer,
7
7
  PromptVersion as GenPromptVersion,
8
8
  RenderedChatPrompt as GenRenderedChatPrompt,
9
- RenderedPrompt as GenRenderedPrompt,
10
9
  ResolvedPrompt as GenResolvedPrompt,
11
10
  ResolvedToolCall as GenResolvedToolCall,
12
11
  ResolvedToolDefinition as GenResolvedToolDefinition,
@@ -24,19 +23,27 @@ export type PromptVersion = GenPromptVersion;
24
23
  export type PromptFormat = PromptVersion["format"];
25
24
  export type PromptLabelPointer = GenPromptLabelPointer;
26
25
 
27
- export type TextChatRole = "system" | "user" | "assistant" | "developer";
26
+ export type ChatRole = "system" | "user" | "assistant" | "developer";
28
27
 
29
- export interface TextChatMessage {
30
- role: TextChatRole;
28
+ export interface ChatMessage {
29
+ role: ChatRole;
31
30
  content: string;
32
31
  disabled?: boolean;
33
32
  }
34
33
 
35
- export type ChatMessage = TextChatMessage;
36
-
37
34
  export type RenderedChatMessage = GenRenderedChatPrompt["messages"][number];
38
35
 
39
- export type RenderedPrompt = GenRenderedPrompt;
36
+ /** Provider shape for `prompts.render()` output. Omit for the neutral result. */
37
+ export type RenderFormat = "openai" | "anthropic";
38
+
39
+ /**
40
+ * Policy for a message whose role is outside the known set
41
+ * (`system | user | assistant | developer | tool`) when converting to a
42
+ * provider shape. `"drop"` (default) omits it silently; `"warn"` omits it and
43
+ * emits a `console.warn`; `"error"` throws `UnknownRoleError`. Applies to the
44
+ * standalone converters and to `render({ format })`.
45
+ */
46
+ export type UnknownRoleMode = "drop" | "warn" | "error";
40
47
 
41
48
  /**
42
49
  * A provider-neutral tool definition (`{ name, description?, parameters }`),
@@ -56,11 +63,12 @@ export type ResolvedPrompt = Pick<
56
63
  | "promptId"
57
64
  | "versionId"
58
65
  | "version"
59
- | "labels"
66
+ | "label"
60
67
  | "contentHash"
61
68
  | "rendered"
62
69
  | "tools"
63
70
  | "toolChoice"
71
+ | "modelConfig"
64
72
  >;
65
73
 
66
74
  export interface PaginationInfo {
@@ -76,7 +84,7 @@ export interface PaginatedResult<T> {
76
84
  }
77
85
 
78
86
  export interface CreatePromptInput {
79
- projectId: string;
87
+ projectId?: string;
80
88
  name: string;
81
89
  slug: string;
82
90
  description?: string;
@@ -86,7 +94,7 @@ export interface CreatePromptInput {
86
94
  export type CreatePromptResult = Prompt;
87
95
 
88
96
  export interface ListPromptsInput {
89
- projectId: string;
97
+ projectId?: string;
90
98
  status?: PromptStatus;
91
99
  sortBy?: "createdAt" | "updatedAt" | "name";
92
100
  tags?: string;
@@ -97,14 +105,14 @@ export interface ListPromptsInput {
97
105
  export type ListPromptsResult = Prompt;
98
106
 
99
107
  export interface GetPromptInput {
100
- projectId: string;
108
+ projectId?: string;
101
109
  promptId: string;
102
110
  }
103
111
 
104
112
  export type GetPromptResult = Prompt;
105
113
 
106
114
  export interface UpdatePromptInput {
107
- projectId: string;
115
+ projectId?: string;
108
116
  promptId: string;
109
117
  name?: string;
110
118
  description?: string | null;
@@ -114,14 +122,14 @@ export interface UpdatePromptInput {
114
122
  export type UpdatePromptResult = Prompt;
115
123
 
116
124
  export interface ArchivePromptInput {
117
- projectId: string;
125
+ projectId?: string;
118
126
  promptId: string;
119
127
  }
120
128
 
121
129
  export type ArchivePromptResult = Prompt;
122
130
 
123
131
  export interface CreatePromptVersionInput {
124
- projectId: string;
132
+ projectId?: string;
125
133
  promptId: string;
126
134
  format: PromptFormat;
127
135
  templateJson: unknown;
@@ -133,14 +141,14 @@ export interface CreatePromptVersionInput {
133
141
  export type CreatePromptVersionResult = PromptVersion;
134
142
 
135
143
  export interface ListPromptVersionsInput {
136
- projectId: string;
144
+ projectId?: string;
137
145
  promptId: string;
138
146
  }
139
147
 
140
148
  export type ListPromptVersionsResult = PromptVersion[];
141
149
 
142
150
  export interface GetPromptVersionInput {
143
- projectId: string;
151
+ projectId?: string;
144
152
  promptId: string;
145
153
  versionNumber: number;
146
154
  }
@@ -148,7 +156,7 @@ export interface GetPromptVersionInput {
148
156
  export type GetPromptVersionResult = PromptVersion;
149
157
 
150
158
  export interface MovePromptLabelInput {
151
- projectId: string;
159
+ projectId?: string;
152
160
  promptId: string;
153
161
  label: string;
154
162
  versionId: string;
@@ -158,30 +166,24 @@ export interface MovePromptLabelInput {
158
166
  export type MovePromptLabelResult = PromptLabelPointer;
159
167
 
160
168
  export interface ListPromptLabelsInput {
161
- projectId: string;
169
+ projectId?: string;
162
170
  promptId: string;
163
171
  }
164
172
 
165
173
  export type ListPromptLabelsResult = PromptLabelPointer[];
166
174
 
167
- export interface ResolvePromptInput {
168
- projectId: string;
169
- slug: string;
170
- labels: string[];
171
- variables?: Record<string, unknown>;
172
- /** Set false to bypass the LRU cache for this call. Default: true */
173
- cache?: boolean;
174
- /** Fallback prompt served if the API is unreachable and cache is empty. */
175
- fallback?: ResolvedPrompt;
176
- }
177
-
178
- export type ResolvePromptResult = ResolvedPrompt;
179
-
175
+ /**
176
+ * Slug rule for a `render()` label — lowercase-kebab, 1-63 chars. Env slugs are
177
+ * a special case of a label value, so labels are held to the same strict rule
178
+ * (no uppercase, no underscores): a label is a routing key, and case-insensitive
179
+ * routing keys invite silent "production" vs "Production" mismatches. Intentionally
180
+ * strict — do not loosen.
181
+ */
180
182
  export const ENV_SLUG_REGEX = /^[a-z0-9-]{1,63}$/;
181
183
 
182
184
  export type BlockRenderRecord = Record<string, "rendered" | "dimmed">;
183
185
 
184
- export interface FetchedPrompt {
186
+ export interface RenderedPrompt {
185
187
  messages: RenderedChatMessage[];
186
188
  version: number;
187
189
  releaseId: string | null;
@@ -190,8 +192,24 @@ export interface FetchedPrompt {
190
192
  promptId: string;
191
193
  versionId: string;
192
194
  contentHash: string;
193
- labels: string[];
194
- rendered: RenderedPrompt;
195
+ /**
196
+ * The single label the prompt resolved through — the env slug today, a real
197
+ * label once labels ship. Surfaced as `x-to11-prompt-label` by
198
+ * `gatewayPromptHeaders` / `turn.headers()` (TO11-2725).
199
+ */
200
+ label: string;
201
+ /**
202
+ * The prompt's human-readable slug (the `render()` input). Surfaced as
203
+ * `x-to11-prompt-slug` by `gatewayPromptHeaders` / `turn.headers()` so traces
204
+ * carry a readable name alongside the opaque `promptId` (TO11-2727).
205
+ */
206
+ slug: string;
207
+ /**
208
+ * The served version's model config (model, temperature, max_tokens, …),
209
+ * passed through verbatim. Surfaced as `config` by `render({ format })`.
210
+ * Optional: absent when a pre-`modelConfig` API omits it.
211
+ */
212
+ modelConfig?: unknown;
195
213
  /**
196
214
  * Provider-neutral tool definitions lifted from authored tool-definition
197
215
  * blocks. Absent (rather than `[]`) when the resolved version declares no
@@ -204,37 +222,31 @@ export interface FetchedPrompt {
204
222
  * into `toOpenAIToolChoice` / `toAnthropicToolChoice`.
205
223
  *
206
224
  * `NonNullable` because `normalizeResolvedEnvelope` maps wire-`null` to absent,
207
- * so a present `FetchedPrompt.toolChoice` is never `null` — callers get the
225
+ * so a present `RenderedPrompt.toolChoice` is never `null` — callers get the
208
226
  * key or nothing, and never have to branch on `null`.
209
227
  */
210
228
  toolChoice?: NonNullable<ResolvedPrompt["toolChoice"]>;
211
229
  }
212
230
 
213
- export type ResolvedPromptEnvelope = GenResolvedPrompt & {
214
- /** Active release strategy. Used to decide weighted-no-subject cache bypass (D8). */
215
- strategy?: "single" | "weighted";
216
- };
217
-
218
- /**
219
- * How an authored `developer` block is rendered. The caller controls this
220
- * per-call because it knows the upstream it targets. Omitted ⇒ `"user"`
221
- * (back-compatible; safe for any upstream). `"developer"` preserves the role
222
- * (OpenAI); `"system"` folds it into a system message.
223
- */
224
- export type DeveloperRole = "user" | "developer" | "system";
231
+ export type ResolvedPromptEnvelope = GenResolvedPrompt;
225
232
 
226
- export interface FetchPromptOptions {
227
- env?: string;
228
- subject?: string;
233
+ export interface RenderOptions {
234
+ /**
235
+ * Label to resolve this call's prompt version; overrides the client default.
236
+ * When omitted, falls back to the client's `env` (env is a label value).
237
+ */
238
+ label?: string;
229
239
  variables?: Record<string, unknown>;
230
- cache?: boolean;
231
- fallback?: ResolvedPrompt | FetchedPrompt;
240
+ /** Prompt served if the API is unreachable on a network error. */
241
+ fallback?: RenderedPrompt;
242
+ /** Provider shape for this call's output; overrides the client default. */
243
+ format?: RenderFormat;
232
244
  /**
233
- * How authored `developer` blocks render. Default `"user"`. Forwarded on the
234
- * resolve request and folded into the cache key so two calls differing only
235
- * by `developerRole` never share a cached result.
245
+ * How to handle a message with an unrecognized role while shaping for a
246
+ * provider (`format` set). Overrides the client default; defaults to `"drop"`.
247
+ * Only meaningful with a `format` — the neutral result runs no converter.
236
248
  */
237
- developerRole?: DeveloperRole;
249
+ onUnknownRole?: UnknownRoleMode;
238
250
  }
239
251
 
240
252
  export type PaletteToken = ProjectPromptLabel["displayColor"];
@@ -246,7 +258,7 @@ export type ProjectPromptLabelUsage = GenProjectPromptLabelUsage;
246
258
  export type PromptLabelEvent = GenPromptLabelEvent;
247
259
 
248
260
  export interface ListProjectPromptLabelsInput {
249
- projectId: string;
261
+ projectId?: string;
250
262
  /** Set true to include archived (soft-deleted) entries. Default: false. */
251
263
  includeArchived?: boolean;
252
264
  }
@@ -254,7 +266,7 @@ export interface ListProjectPromptLabelsInput {
254
266
  export type ListProjectPromptLabelsResult = ProjectPromptLabel[];
255
267
 
256
268
  export interface CreateProjectPromptLabelInput {
257
- projectId: string;
269
+ projectId?: string;
258
270
  label: string;
259
271
  isDefault?: boolean;
260
272
  description?: string;
@@ -264,14 +276,14 @@ export interface CreateProjectPromptLabelInput {
264
276
  export type CreateProjectPromptLabelResult = ProjectPromptLabel;
265
277
 
266
278
  export interface GetProjectPromptLabelInput {
267
- projectId: string;
279
+ projectId?: string;
268
280
  label: string;
269
281
  }
270
282
 
271
283
  export type GetProjectPromptLabelResult = ProjectPromptLabel;
272
284
 
273
285
  export interface UpdateProjectPromptLabelInput {
274
- projectId: string;
286
+ projectId?: string;
275
287
  label: string;
276
288
  isDefault?: boolean;
277
289
  description?: string | null;
@@ -281,14 +293,14 @@ export interface UpdateProjectPromptLabelInput {
281
293
  export type UpdateProjectPromptLabelResult = ProjectPromptLabel;
282
294
 
283
295
  export interface DeleteProjectPromptLabelInput {
284
- projectId: string;
296
+ projectId?: string;
285
297
  label: string;
286
298
  }
287
299
 
288
300
  export type DeleteProjectPromptLabelResult = ProjectPromptLabel;
289
301
 
290
302
  export interface ListProjectPromptLabelUsagesInput {
291
- projectId: string;
303
+ projectId?: string;
292
304
  label: string;
293
305
  }
294
306
 
@@ -297,7 +309,7 @@ export type ListProjectPromptLabelUsagesResult = ProjectPromptLabelUsage[];
297
309
  export type ListLabelEventsInput =
298
310
  | {
299
311
  scope: "label";
300
- projectId: string;
312
+ projectId?: string;
301
313
  promptId: string;
302
314
  label: string;
303
315
  page?: number;
@@ -307,7 +319,7 @@ export type ListLabelEventsInput =
307
319
  }
308
320
  | {
309
321
  scope: "prompt";
310
- projectId: string;
322
+ projectId?: string;
311
323
  promptId: string;
312
324
  label?: string;
313
325
  page?: number;
@@ -317,7 +329,7 @@ export type ListLabelEventsInput =
317
329
  }
318
330
  | {
319
331
  scope: "project";
320
- projectId: string;
332
+ projectId?: string;
321
333
  promptId?: string;
322
334
  label?: string;
323
335
  page?: number;
package/src/transport.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import type { BehaviorStore } from "./behaviors/store";
2
2
  import {
3
- AmbiguousEnvLabelsError,
4
3
  EnvNotEnabledForProviderError,
5
- EnvNotFoundError,
4
+ LabelNotFoundError,
6
5
  NoActiveReleaseError,
7
6
  NoApplicableRouteError,
8
7
  PromptLabelNotRegisteredError,
@@ -108,14 +107,11 @@ function mapPromptResolveError(
108
107
  );
109
108
  }
110
109
  if (detailsCode === "ENV_NOT_FOUND") {
111
- // Server-side unknown-env. (The client-eager "missing" variant is
112
- // raised in resource.ts before any request — never here.)
113
- return new EnvNotFoundError(
114
- message,
115
- { env, source: "unknown" },
116
- details,
117
- status,
118
- );
110
+ // Server-side unknown-label. (The client-side MissingLabelError — no
111
+ // label supplied at all — is raised in resource.ts before any request.)
112
+ // Prefer `details.label`, falling back to `details.env`.
113
+ const label = typeof d.label === "string" ? d.label : env;
114
+ return new LabelNotFoundError(message, label, details, status);
119
115
  }
120
116
  if (detailsCode === "NO_ACTIVE_RELEASE") {
121
117
  return new NoActiveReleaseError(
@@ -125,9 +121,6 @@ function mapPromptResolveError(
125
121
  status,
126
122
  );
127
123
  }
128
- if (detailsCode === "AMBIGUOUS_ENV_LABELS") {
129
- return new AmbiguousEnvLabelsError(message, { env, slug }, details, status);
130
- }
131
124
  if (PROMPT_POLICY_VIOLATION_DETAILS_CODES.has(detailsCode)) {
132
125
  const policy = typeof d.policy === "string" ? d.policy : undefined;
133
126
  return new PromptPolicyViolationError(
@@ -140,9 +133,9 @@ function mapPromptResolveError(
140
133
  return undefined;
141
134
  }
142
135
 
143
- export type AuthConfig =
144
- | { readonly apiKey: string; readonly getAccessToken?: never }
145
- | { readonly getAccessToken: () => Promise<string>; readonly apiKey?: never };
136
+ export interface AuthConfig {
137
+ readonly apiKey: string;
138
+ }
146
139
 
147
140
  export interface TransportConfig {
148
141
  readonly baseUrl: string;
@@ -248,12 +241,25 @@ function mapHttpError(
248
241
  }
249
242
  if (status === 429) {
250
243
  const retryAfterHeader = headers?.get("retry-after");
251
- const retryAfterSeconds = retryAfterHeader
252
- ? Number(retryAfterHeader)
253
- : undefined;
244
+ let retryAfterSeconds: number | undefined;
245
+ if (retryAfterHeader) {
246
+ const numeric = Number(retryAfterHeader);
247
+ if (Number.isFinite(numeric)) {
248
+ retryAfterSeconds = Math.max(0, numeric);
249
+ } else {
250
+ // RFC 9110 HTTP-date form → seconds until that instant.
251
+ const when = Date.parse(retryAfterHeader);
252
+ if (!Number.isNaN(when)) {
253
+ retryAfterSeconds = Math.max(
254
+ 0,
255
+ Math.round((when - Date.now()) / 1000),
256
+ );
257
+ }
258
+ }
259
+ }
254
260
  return new To11aiRateLimitError(
255
261
  message,
256
- Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined,
262
+ retryAfterSeconds,
257
263
  errorCode,
258
264
  details,
259
265
  );
@@ -395,25 +401,20 @@ export class Transport {
395
401
  private readonly behaviorStore: BehaviorStore | undefined;
396
402
 
397
403
  constructor(config: TransportConfig) {
398
- this.baseUrl = config.baseUrl.replace(/\/$/, "");
404
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
399
405
  this.auth = config.auth;
400
406
  this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
401
407
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
402
408
  this.behaviorStore = config.behaviorStore;
403
409
  }
404
410
 
405
- private async buildHeaders(): Promise<Record<string, string>> {
411
+ private buildHeaders(): Record<string, string> {
406
412
  const headers: Record<string, string> = {
407
413
  "content-type": "application/json",
408
414
  accept: "application/json",
409
415
  };
410
416
 
411
- if (this.auth.apiKey !== undefined) {
412
- headers.authorization = `Bearer ${this.auth.apiKey}`;
413
- } else if (this.auth.getAccessToken) {
414
- const token = await this.auth.getAccessToken();
415
- headers.authorization = `Bearer ${token}`;
416
- }
417
+ headers.authorization = `Bearer ${this.auth.apiKey}`;
417
418
 
418
419
  const tp = activeTraceContext();
419
420
  if (tp) {
@@ -452,7 +453,7 @@ export class Transport {
452
453
  let attempt = 0;
453
454
 
454
455
  while (true) {
455
- const headers = await this.buildHeaders();
456
+ const headers = this.buildHeaders();
456
457
  const controller = new AbortController();
457
458
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
458
459
 
@@ -495,16 +496,28 @@ export class Transport {
495
496
  let json: ApiEnvelope<T> | undefined;
496
497
  const text = await response.text();
497
498
 
498
- try {
499
- json = JSON.parse(text) as ApiEnvelope<T>;
500
- } catch {
501
- // Not a JSON body — treat as generic API error
502
- throw mapHttpError(
503
- response.status,
504
- null,
505
- text || response.statusText,
506
- response.headers,
507
- );
499
+ if (text === "" && response.ok) {
500
+ // A successful empty body (e.g. 204): synthesize an empty-data
501
+ // envelope rather than mis-mapping it to an API error.
502
+ json = {
503
+ success: true,
504
+ code: response.status,
505
+ data: null,
506
+ error: null,
507
+ pagination: null,
508
+ } as ApiEnvelope<T>;
509
+ } else {
510
+ try {
511
+ json = JSON.parse(text) as ApiEnvelope<T>;
512
+ } catch {
513
+ // Not a JSON body — treat as generic API error
514
+ throw mapHttpError(
515
+ response.status,
516
+ null,
517
+ text || response.statusText,
518
+ response.headers,
519
+ );
520
+ }
508
521
  }
509
522
 
510
523
  if (!response.ok) {
@@ -554,7 +567,7 @@ export class Transport {
554
567
  const isTimeout =
555
568
  err instanceof DOMException && err.name === "AbortError";
556
569
  // Only retry on genuine network failures (fetch throws TypeError) or timeouts.
557
- // Unexpected errors (e.g. getAccessToken rejections) propagate immediately.
570
+ // Other unexpected errors propagate immediately.
558
571
  const isNetworkError = err instanceof TypeError;
559
572
 
560
573
  if (