@caitlyn-ai/sdk-core 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-es/index.mjs CHANGED
@@ -14,6 +14,181 @@ import {
14
14
  } from "@smithy/core/protocols";
15
15
  import { NormalizedSchema, TypeRegistry } from "@smithy/core/schema";
16
16
  import { decorateServiceException } from "@smithy/core/client";
17
+
18
+ // packages/sdk-core/src/plugin.ts
19
+ import { getOmitRetryHeadersPlugin } from "@smithy/core/retry";
20
+ import { HttpRequest } from "@smithy/core/protocols";
21
+ import { HttpResponse } from "@smithy/core/protocols";
22
+
23
+ // packages/sdk-core/src/retry.ts
24
+ var NO_RETRY = /* @__PURE__ */ Symbol.for("caitlyn.noRetry");
25
+ var Token = class {
26
+ constructor(count, delay) {
27
+ this.count = count;
28
+ this.delay = delay;
29
+ }
30
+ count;
31
+ delay;
32
+ getRetryCount() {
33
+ return this.count;
34
+ }
35
+ getRetryDelay() {
36
+ return this.delay;
37
+ }
38
+ };
39
+ var CaitlynRetryStrategy = class {
40
+ constructor(maxAttempts = 3, maxRetryDelayMs = 6e4) {
41
+ this.maxRetryDelayMs = maxRetryDelayMs;
42
+ this.maxAttempts = typeof maxAttempts === "number" ? async () => maxAttempts : maxAttempts;
43
+ }
44
+ maxRetryDelayMs;
45
+ mode = "standard";
46
+ maxAttempts;
47
+ async acquireInitialRetryToken() {
48
+ return new Token(0, 0);
49
+ }
50
+ async refreshRetryTokenForRetry(token, info) {
51
+ const count = token.getRetryCount();
52
+ const err = info.error;
53
+ if (count + 1 >= await this.maxAttempts()) throw new Error("retry: attempts exhausted");
54
+ if (!err || err[NO_RETRY]) throw new Error("retry: operation is not safe to retry");
55
+ const noResponse = err.$metadata?.httpStatusCode === void 0 && err.name !== "AbortError";
56
+ if (err.$retryable === void 0 && !noResponse) throw new Error("retry: error is not retryable");
57
+ let delay;
58
+ if (info.retryAfterHint instanceof Date) {
59
+ delay = Math.max(0, info.retryAfterHint.getTime() - Date.now());
60
+ if (delay > this.maxRetryDelayMs) throw new Error("retry: Retry-After exceeds maxRetryDelay");
61
+ } else {
62
+ delay = Math.random() * Math.min(8e3, 500 * 2 ** count);
63
+ }
64
+ return new Token(count + 1, delay);
65
+ }
66
+ recordSuccess() {
67
+ }
68
+ };
69
+
70
+ // packages/sdk-core/src/plugin.ts
71
+ var CAITLYN_SDK_USER_AGENT_PREFIX = "caitlyn-sdk-ts";
72
+ var NO_RESPONSE = /* @__PURE__ */ Symbol.for("caitlyn.noResponse");
73
+ function markNoResponse(err) {
74
+ if (err && typeof err === "object" && err.name !== "AbortError") Object.defineProperty(err, NO_RESPONSE, { value: true, configurable: true });
75
+ return err;
76
+ }
77
+ function getCaitlynConnectionErrorPlugin(toError) {
78
+ const mark = (next) => async (args) => {
79
+ try {
80
+ return await next(args);
81
+ } catch (err) {
82
+ const e = err;
83
+ if (!HttpResponse.isInstance(e?.$response) && e?.$metadata?.httpStatusCode === void 0) markNoResponse(err);
84
+ throw err;
85
+ }
86
+ };
87
+ const wrap = (next, context2) => async (args) => {
88
+ try {
89
+ return await next(args);
90
+ } catch (err) {
91
+ if (err && typeof err === "object" && err[NO_RESPONSE]) {
92
+ throw toError(context2.commandName?.replace(/Command$/, "") ?? "call", err);
93
+ }
94
+ throw err;
95
+ }
96
+ };
97
+ return {
98
+ applyToStack(stack) {
99
+ stack.add(mark, { step: "deserialize", name: "caitlynNoResponse", priority: "low", override: true });
100
+ stack.add(wrap, { step: "initialize", name: "caitlynConnectionError", priority: "normal", override: true });
101
+ }
102
+ };
103
+ }
104
+ var NotSupportedByServer = class extends Error {
105
+ constructor(operation, requires, server) {
106
+ super(`NotSupportedByServer(${operation}, requires ${requires}, server is ${server})`);
107
+ this.operation = operation;
108
+ this.requires = requires;
109
+ this.server = server;
110
+ this.name = "NotSupportedByServer";
111
+ }
112
+ operation;
113
+ requires;
114
+ server;
115
+ };
116
+ var semver = (a, b) => {
117
+ const pa = a.split(".").map(Number);
118
+ const pb = b.split(".").map(Number);
119
+ for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return (pa[i] ?? 0) - (pb[i] ?? 0);
120
+ return 0;
121
+ };
122
+ function runtime() {
123
+ const g = globalThis;
124
+ if (g.process?.versions?.bun) return `md/bun#${g.process.versions.bun}`;
125
+ if (g.Deno?.version?.deno) return `md/deno#${g.Deno.version.deno}`;
126
+ if (g.process?.versions?.node) return `md/nodejs#${g.process.versions.node}`;
127
+ return "md/browser";
128
+ }
129
+ function getCaitlynSdkPlugin(config, options) {
130
+ let server;
131
+ const since = new Map((options.since ?? []).map((e) => e.split("=")));
132
+ const sinceCheck = (next, context2) => async (args) => {
133
+ const op = context2.commandName?.replace(/Command$/, "");
134
+ if (op && server) {
135
+ const required = since.get(op);
136
+ if (required && semver(required, server) > 0) throw new NotSupportedByServer(op, required, server);
137
+ for (const [member, value] of Object.entries(args.input)) {
138
+ const memberSince = value === void 0 ? void 0 : since.get(`${op}.${member}`);
139
+ if (memberSince && semver(memberSince, server) > 0) throw new NotSupportedByServer(`${op}.${member}`, memberSince, server);
140
+ }
141
+ }
142
+ return next(args);
143
+ };
144
+ const serverVersion = (next) => async (args) => {
145
+ try {
146
+ const out = await next(args);
147
+ if (HttpResponse.isInstance(out.response)) server = out.response.headers["caitlyn-api-version"] ?? server;
148
+ return out;
149
+ } catch (err) {
150
+ const res = err.$response;
151
+ if (HttpResponse.isInstance(res)) server = res.headers["caitlyn-api-version"] ?? server;
152
+ throw err;
153
+ }
154
+ };
155
+ const userAgent = [`${CAITLYN_SDK_USER_AGENT_PREFIX}/${config.caitlynSdkVersion ?? "0.0.0"}`, "lang/js", runtime(), config.customUserAgent].filter(Boolean).join(" ");
156
+ const identity = (next) => async (args) => {
157
+ if (HttpRequest.isInstance(args.request)) {
158
+ args.request.headers["user-agent"] = userAgent;
159
+ args.request.headers["caitlyn-sdk-model"] = options.sdkModel;
160
+ }
161
+ return next(args);
162
+ };
163
+ const safe = new Set(options.retrySafeOperations);
164
+ const retrySafety = (next, context2) => async (args) => {
165
+ try {
166
+ return await next(args);
167
+ } catch (err) {
168
+ const op = context2.commandName?.replace(/Command$/, "");
169
+ if (op && !safe.has(op) && err && typeof err === "object") Object.defineProperty(err, NO_RETRY, { value: true });
170
+ throw err;
171
+ }
172
+ };
173
+ return {
174
+ applyToStack(stack) {
175
+ stack.add(sinceCheck, { step: "initialize", name: "caitlynSinceCheck", priority: "high", override: true });
176
+ stack.add(serverVersion, { step: "deserialize", name: "caitlynServerVersion", priority: "low", override: true });
177
+ stack.add(identity, { step: "build", name: "caitlynSdkIdentity", priority: "low", override: true });
178
+ stack.addRelativeTo(retrySafety, { toMiddleware: "retryMiddleware", relation: "after", name: "caitlynRetrySafety" });
179
+ getOmitRetryHeadersPlugin({}).applyToStack(stack);
180
+ }
181
+ };
182
+ }
183
+
184
+ // packages/sdk-core/src/protocol.ts
185
+ async function readBody(body, context2) {
186
+ try {
187
+ return await collectBody(body, context2);
188
+ } catch (err) {
189
+ throw markNoResponse(err);
190
+ }
191
+ }
17
192
  var SETTINGS = {
18
193
  timestampFormat: {
19
194
  useTrait: true,
@@ -65,7 +240,7 @@ var CaitlynRestJsonProtocol = class extends HttpBindingProtocol {
65
240
  const ns = NormalizedSchema.of(operationSchema.output);
66
241
  const streaming = [...ns.structIterator()].some(([, m]) => m.getMemberTraits().httpPayload && m.isStreaming());
67
242
  if (response.statusCode >= 300) {
68
- const bytes2 = await collectBody(response.body, context2);
243
+ const bytes2 = await readBody(response.body, context2);
69
244
  response.body = bytes2;
70
245
  if (!isJsonObject(bytes2)) {
71
246
  await this.handleError(operationSchema, context2, response, {}, this.deserializeMetadata(response));
@@ -73,7 +248,7 @@ var CaitlynRestJsonProtocol = class extends HttpBindingProtocol {
73
248
  return super.deserializeResponse(operationSchema, context2, response);
74
249
  }
75
250
  if (streaming) return super.deserializeResponse(operationSchema, context2, response);
76
- const bytes = await collectBody(response.body, context2);
251
+ const bytes = await readBody(response.body, context2);
77
252
  response.body = bytes;
78
253
  const output = await super.deserializeResponse(operationSchema, context2, response);
79
254
  if (bytes.byteLength > 0) {
@@ -94,7 +269,7 @@ var CaitlynRestJsonProtocol = class extends HttpBindingProtocol {
94
269
  const registry = this.compositeErrorRegistry;
95
270
  registry.copyFrom(TypeRegistry.for(this.options.defaultNamespace));
96
271
  const errorMetadata = { $metadata: metadata, $fault: response.statusCode < 500 ? "client" : "server" };
97
- const rawBody = response.body instanceof Uint8Array ? new TextDecoder().decode(response.body) : "";
272
+ const { rawBody, rawBodyTruncated } = rawBodyOf(response.body instanceof Uint8Array ? response.body : new Uint8Array());
98
273
  const body = {
99
274
  ...dataObject,
100
275
  // Always the HTTP status: a body's `status` can be wrong (a proxy, a faulty handler), and the class is
@@ -102,7 +277,8 @@ var CaitlynRestJsonProtocol = class extends HttpBindingProtocol {
102
277
  status: response.statusCode,
103
278
  code: typeof dataObject.code === "string" && dataObject.code ? dataObject.code : `HTTP_${response.statusCode}`,
104
279
  requestId: dataObject.requestId ?? response.headers["caitlyn-request-id"],
105
- rawBody
280
+ rawBody,
281
+ rawBodyTruncated
106
282
  };
107
283
  const message = body.detail ?? body.title ?? `HTTP ${response.statusCode}`;
108
284
  const schema = registry.find((s) => {
@@ -113,7 +289,7 @@ var CaitlynRestJsonProtocol = class extends HttpBindingProtocol {
113
289
  if (!schema) {
114
290
  const base = registry.getBaseException();
115
291
  const Ctor2 = base && registry.getErrorCtor(base) || Error;
116
- const name = body.code;
292
+ const name = "CaitlynError";
117
293
  throw decorateServiceException(Object.assign(new Ctor2({ name, message }), errorMetadata), body);
118
294
  }
119
295
  const ns = NormalizedSchema.of(schema);
@@ -130,6 +306,13 @@ var CaitlynRestJsonProtocol = class extends HttpBindingProtocol {
130
306
  throw decorateServiceException(Object.assign(exception, errorMetadata, { $fault: ns.getMergedTraits().error, message }, fields), body);
131
307
  }
132
308
  };
309
+ var RAW_BODY_LIMIT = 8192;
310
+ function rawBodyOf(bytes) {
311
+ if (bytes.byteLength <= RAW_BODY_LIMIT) return { rawBody: new TextDecoder().decode(bytes), rawBodyTruncated: false };
312
+ let end = RAW_BODY_LIMIT;
313
+ while (end > 0 && (bytes[end] & 192) === 128) end--;
314
+ return { rawBody: new TextDecoder().decode(bytes.subarray(0, end)), rawBodyTruncated: true };
315
+ }
133
316
  function isJsonObject(bytes) {
134
317
  if (bytes.byteLength === 0) return false;
135
318
  try {
@@ -170,140 +353,6 @@ function resolveContentType(input) {
170
353
  return hasBody ? "application/json" : void 0;
171
354
  }
172
355
 
173
- // packages/sdk-core/src/plugin.ts
174
- import { getOmitRetryHeadersPlugin } from "@smithy/core/retry";
175
- import { HttpRequest } from "@smithy/core/protocols";
176
- import { HttpResponse } from "@smithy/core/protocols";
177
-
178
- // packages/sdk-core/src/retry.ts
179
- var NO_RETRY = /* @__PURE__ */ Symbol.for("caitlyn.noRetry");
180
- var Token = class {
181
- constructor(count, delay) {
182
- this.count = count;
183
- this.delay = delay;
184
- }
185
- count;
186
- delay;
187
- getRetryCount() {
188
- return this.count;
189
- }
190
- getRetryDelay() {
191
- return this.delay;
192
- }
193
- };
194
- var CaitlynRetryStrategy = class {
195
- constructor(maxAttempts = 3, maxRetryDelayMs = 6e4) {
196
- this.maxRetryDelayMs = maxRetryDelayMs;
197
- this.maxAttempts = typeof maxAttempts === "number" ? async () => maxAttempts : maxAttempts;
198
- }
199
- maxRetryDelayMs;
200
- mode = "standard";
201
- maxAttempts;
202
- async acquireInitialRetryToken() {
203
- return new Token(0, 0);
204
- }
205
- async refreshRetryTokenForRetry(token, info) {
206
- const count = token.getRetryCount();
207
- const err = info.error;
208
- if (count + 1 >= await this.maxAttempts()) throw new Error("retry: attempts exhausted");
209
- if (!err || err[NO_RETRY]) throw new Error("retry: operation is not safe to retry");
210
- const noResponse = err.$metadata?.httpStatusCode === void 0 && err.name !== "AbortError";
211
- if (err.$retryable === void 0 && !noResponse) throw new Error("retry: error is not retryable");
212
- let delay;
213
- if (info.retryAfterHint instanceof Date) {
214
- delay = Math.max(0, info.retryAfterHint.getTime() - Date.now());
215
- if (delay > this.maxRetryDelayMs) throw new Error("retry: Retry-After exceeds maxRetryDelay");
216
- } else {
217
- delay = Math.random() * Math.min(8e3, 500 * 2 ** count);
218
- }
219
- return new Token(count + 1, delay);
220
- }
221
- recordSuccess() {
222
- }
223
- };
224
-
225
- // packages/sdk-core/src/plugin.ts
226
- var CAITLYN_SDK_USER_AGENT_PREFIX = "caitlyn-sdk-ts";
227
- var NotSupportedByServer = class extends Error {
228
- constructor(operation, requires, server) {
229
- super(`NotSupportedByServer(${operation}, requires ${requires}, server is ${server})`);
230
- this.operation = operation;
231
- this.requires = requires;
232
- this.server = server;
233
- this.name = "NotSupportedByServer";
234
- }
235
- operation;
236
- requires;
237
- server;
238
- };
239
- var semver = (a, b) => {
240
- const pa = a.split(".").map(Number);
241
- const pb = b.split(".").map(Number);
242
- for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return (pa[i] ?? 0) - (pb[i] ?? 0);
243
- return 0;
244
- };
245
- function runtime() {
246
- const g = globalThis;
247
- if (g.process?.versions?.bun) return `md/bun#${g.process.versions.bun}`;
248
- if (g.Deno?.version?.deno) return `md/deno#${g.Deno.version.deno}`;
249
- if (g.process?.versions?.node) return `md/nodejs#${g.process.versions.node}`;
250
- return "md/browser";
251
- }
252
- function getCaitlynSdkPlugin(config, options) {
253
- let server;
254
- const since = new Map((options.since ?? []).map((e) => e.split("=")));
255
- const sinceCheck = (next, context2) => async (args) => {
256
- const op = context2.commandName?.replace(/Command$/, "");
257
- if (op && server) {
258
- const required = since.get(op);
259
- if (required && semver(required, server) > 0) throw new NotSupportedByServer(op, required, server);
260
- for (const [member, value] of Object.entries(args.input)) {
261
- const memberSince = value === void 0 ? void 0 : since.get(`${op}.${member}`);
262
- if (memberSince && semver(memberSince, server) > 0) throw new NotSupportedByServer(`${op}.${member}`, memberSince, server);
263
- }
264
- }
265
- return next(args);
266
- };
267
- const serverVersion = (next) => async (args) => {
268
- try {
269
- const out = await next(args);
270
- if (HttpResponse.isInstance(out.response)) server = out.response.headers["caitlyn-api-version"] ?? server;
271
- return out;
272
- } catch (err) {
273
- const res = err.$response;
274
- if (HttpResponse.isInstance(res)) server = res.headers["caitlyn-api-version"] ?? server;
275
- throw err;
276
- }
277
- };
278
- const userAgent = [`${CAITLYN_SDK_USER_AGENT_PREFIX}/${config.caitlynSdkVersion ?? "0.0.0"}`, "lang/js", runtime(), config.customUserAgent].filter(Boolean).join(" ");
279
- const identity = (next) => async (args) => {
280
- if (HttpRequest.isInstance(args.request)) {
281
- args.request.headers["user-agent"] = userAgent;
282
- args.request.headers["caitlyn-sdk-model"] = options.sdkModel;
283
- }
284
- return next(args);
285
- };
286
- const safe = new Set(options.retrySafeOperations);
287
- const retrySafety = (next, context2) => async (args) => {
288
- try {
289
- return await next(args);
290
- } catch (err) {
291
- const op = context2.commandName?.replace(/Command$/, "");
292
- if (op && !safe.has(op) && err && typeof err === "object") Object.defineProperty(err, NO_RETRY, { value: true });
293
- throw err;
294
- }
295
- };
296
- return {
297
- applyToStack(stack) {
298
- stack.add(sinceCheck, { step: "initialize", name: "caitlynSinceCheck", priority: "high", override: true });
299
- stack.add(serverVersion, { step: "deserialize", name: "caitlynServerVersion", priority: "low", override: true });
300
- stack.add(identity, { step: "build", name: "caitlynSdkIdentity", priority: "low", override: true });
301
- stack.addRelativeTo(retrySafety, { toMiddleware: "retryMiddleware", relation: "after", name: "caitlynRetrySafety" });
302
- getOmitRetryHeadersPlugin({}).applyToStack(stack);
303
- }
304
- };
305
- }
306
-
307
356
  // packages/sdk-core/src/endpoint.ts
308
357
  var caitlynEndpointProvider = (params) => {
309
358
  if (!params.endpoint) throw new Error("Caitlyn: no endpoint. Pass `workspace` or `endpoint` when creating the client.");
@@ -313,23 +362,38 @@ var caitlynEndpointProvider = (params) => {
313
362
  // packages/sdk-core/src/telemetry.ts
314
363
  import { HttpRequest as HttpRequest2, HttpResponse as HttpResponse2 } from "@smithy/core/protocols";
315
364
  import { context, propagation, trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
365
+ function caitlynTracer(provider) {
366
+ return (provider ?? trace.getTracerProvider()).getTracer("@caitlyn-ai/sdk", "0.1.0");
367
+ }
368
+ function urlWithoutQuery(url) {
369
+ const u = new URL(url);
370
+ u.search = "";
371
+ u.hash = "";
372
+ u.username = "";
373
+ u.password = "";
374
+ return u.toString();
375
+ }
376
+ function recordFailure(span, err) {
377
+ const e = err;
378
+ span.recordException(err instanceof Error ? err : { name: String(e?.name ?? "Error"), message: String(e?.message ?? err) });
379
+ span.setStatus({ code: SpanStatusCode.ERROR, message: e?.message });
380
+ span.setAttribute("error.type", e?.name ?? "Error");
381
+ if (typeof e?.code === "string") span.setAttribute("caitlyn.error.code", e.code);
382
+ }
316
383
  function getCaitlynTelemetryPlugin(provider) {
317
- const tracer = (provider ?? trace.getTracerProvider()).getTracer("@caitlyn-ai/sdk", "0.0.1");
384
+ const tracer = caitlynTracer(provider);
318
385
  return {
319
386
  applyToStack(stack) {
320
387
  stack.add(
321
388
  (next, ctx) => async (args) => {
322
389
  const op = ctx.commandName?.replace(/Command$/, "") ?? "unknown";
323
- return tracer.startActiveSpan(`caitlyn ${op}`, { kind: SpanKind.CLIENT, attributes: { "caitlyn.operation": op } }, async (span) => {
390
+ return tracer.startActiveSpan("caitlyn.call", { kind: SpanKind.CLIENT, attributes: { "rpc.system": "caitlyn", "rpc.method": op } }, async (span) => {
324
391
  ctx.__caitlynSpan = span;
325
392
  ctx.__attempt = 0;
326
393
  try {
327
- const out = await next(args);
328
- return out;
394
+ return await next(args);
329
395
  } catch (err) {
330
- span.recordException(err);
331
- span.setStatus({ code: SpanStatusCode.ERROR });
332
- span.setAttribute("error.type", err.name);
396
+ recordFailure(span, err);
333
397
  throw err;
334
398
  } finally {
335
399
  span.end();
@@ -343,26 +407,34 @@ function getCaitlynTelemetryPlugin(provider) {
343
407
  const attempt = ctx.__attempt ?? 0;
344
408
  ctx.__attempt = attempt + 1;
345
409
  const parent = ctx.__caitlynSpan ? trace.setSpan(context.active(), ctx.__caitlynSpan) : context.active();
346
- const span = tracer.startSpan(`HTTP ${args.request.method}`, { kind: SpanKind.CLIENT, attributes: { "http.request.resend_count": attempt } }, parent);
410
+ const span = tracer.startSpan("caitlyn.attempt", { kind: SpanKind.CLIENT, attributes: { "http.request.resend_count": attempt } }, parent);
347
411
  const req = args.request;
348
412
  if (HttpRequest2.isInstance(req)) {
349
- span.setAttribute("server.address", req.hostname);
413
+ span.setAttributes({
414
+ "http.request.method": req.method,
415
+ "server.address": req.hostname,
416
+ "url.full": urlWithoutQuery(`${req.protocol}//${req.hostname}${req.port ? `:${req.port}` : ""}${req.path}`)
417
+ });
350
418
  propagation.inject(trace.setSpan(parent, span), req.headers);
351
419
  }
420
+ const response = (res) => {
421
+ if (!HttpResponse2.isInstance(res)) return;
422
+ span.setAttribute("http.response.status_code", res.statusCode);
423
+ const rid = res.headers["caitlyn-request-id"];
424
+ if (rid) {
425
+ span.setAttribute("caitlyn.request_id", rid);
426
+ ctx.__caitlynSpan?.setAttribute("caitlyn.request_id", rid);
427
+ }
428
+ const api = res.headers["caitlyn-api-version"];
429
+ if (api) span.setAttribute("caitlyn.server.api_version", api);
430
+ if (res.statusCode >= 400) span.setStatus({ code: SpanStatusCode.ERROR });
431
+ };
352
432
  try {
353
433
  const out = await next(args);
354
- if (HttpResponse2.isInstance(out.response)) {
355
- span.setAttribute("http.response.status_code", out.response.statusCode);
356
- const rid = out.response.headers["caitlyn-request-id"];
357
- if (rid) {
358
- span.setAttribute("caitlyn.request_id", rid);
359
- ctx.__caitlynSpan?.setAttribute("caitlyn.request_id", rid);
360
- }
361
- }
434
+ response(out.response);
362
435
  return out;
363
436
  } catch (err) {
364
- const res = err.$response;
365
- if (HttpResponse2.isInstance(res)) span.setAttribute("http.response.status_code", res.statusCode);
437
+ response(err.$response);
366
438
  span.setStatus({ code: SpanStatusCode.ERROR });
367
439
  throw err;
368
440
  } finally {
@@ -425,17 +497,23 @@ var SseStream = class {
425
497
  seen = /* @__PURE__ */ new Set();
426
498
  lastEventId;
427
499
  reconnects = 0;
500
+ /** The Caitlyn-Request-Id of the response that opened the stream; set once iteration has started. */
501
+ requestId;
428
502
  aborted() {
429
503
  return this.signal?.reason ?? new DOMException("Aborted", "AbortError");
430
504
  }
431
505
  async *[Symbol.asyncIterator]() {
432
506
  if (this.consumed) throw new Error("SseStream can only be iterated once");
433
507
  this.consumed = true;
434
- let body = await this.open();
508
+ const opened = await this.open();
509
+ this.requestId = opened.$metadata?.requestId;
510
+ let body = opened.events;
435
511
  for (; ; ) {
436
512
  let ended = false;
513
+ let onResponse = 0;
437
514
  try {
438
515
  for await (const msg of parseSse(body)) {
516
+ onResponse++;
439
517
  if (msg.id) {
440
518
  if (this.seen.has(msg.id)) continue;
441
519
  this.seen.add(msg.id);
@@ -452,7 +530,7 @@ var SseStream = class {
452
530
  return;
453
531
  }
454
532
  }
455
- if (!this.spec.final) return;
533
+ if (!this.spec.final || onResponse === 0) return;
456
534
  } catch (err) {
457
535
  if (this.signal?.aborted) throw this.aborted();
458
536
  if (ended || err?.name === "AbortError" || err === void 0 || err.$fault || err instanceof SyntaxError) throw err;
@@ -460,7 +538,7 @@ var SseStream = class {
460
538
  if (this.signal?.aborted) throw this.aborted();
461
539
  if (!this.resume || ++this.reconnects > this.maxReconnects)
462
540
  throw this.spec.toError({ status: 0, code: "STREAM_LOST", title: "The stream dropped and could not be resumed" });
463
- body = await this.resume(this.lastEventId);
541
+ body = (await this.resume(this.lastEventId)).events;
464
542
  }
465
543
  }
466
544
  /** Drains the stream if needed and returns the final event's member. */
@@ -472,15 +550,40 @@ var SseStream = class {
472
550
  };
473
551
 
474
552
  // packages/sdk-core/src/upload.ts
475
- async function uploadFile(file, fileName, reserve, toError, signal) {
553
+ import { SpanKind as SpanKind2, SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
554
+ var STATUS = /* @__PURE__ */ Symbol("status");
555
+ async function uploadFile(file, fileName, reserve, toError, signal, tracer) {
476
556
  const bytes = new Uint8Array(await file.arrayBuffer());
477
557
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
478
558
  let binary = "";
479
559
  for (const b of digest) binary += String.fromCharCode(b);
480
560
  const slot = await reserve({ fileName, contentType: file.type || "application/octet-stream", byteSize: bytes.byteLength, sha256: btoa(binary) });
481
- const res = await fetch(slot.uploadUrl, { method: slot.uploadMethod, headers: slot.requiredHeaders, body: bytes, signal });
482
- if (!res.ok) throw toError({ status: res.status, code: "UPLOAD_PUT_FAILED", title: "Uploading the bytes failed", detail: await res.text() });
483
- return slot;
561
+ const put = async () => {
562
+ const res = await fetch(slot.uploadUrl, { method: slot.uploadMethod, headers: slot.requiredHeaders, body: bytes, signal });
563
+ if (!res.ok) throw Object.assign(toError({ status: res.status, code: "UPLOAD_PUT_FAILED", title: "Uploading the bytes failed", detail: await res.text() }), { [STATUS]: res.status });
564
+ return res.status;
565
+ };
566
+ if (!tracer) {
567
+ await put();
568
+ return slot;
569
+ }
570
+ const url = new URL(slot.uploadUrl);
571
+ const span = tracer.startSpan("caitlyn.upload", {
572
+ kind: SpanKind2.CLIENT,
573
+ attributes: { "http.request.method": slot.uploadMethod || "PUT", "server.address": url.hostname, "url.full": urlWithoutQuery(slot.uploadUrl) }
574
+ });
575
+ try {
576
+ span.setAttribute("http.response.status_code", await put());
577
+ return slot;
578
+ } catch (err) {
579
+ const status = err[STATUS];
580
+ if (status !== void 0) span.setAttribute("http.response.status_code", status);
581
+ recordFailure(span, err);
582
+ span.setStatus({ code: SpanStatusCode2.ERROR });
583
+ throw err;
584
+ } finally {
585
+ span.end();
586
+ }
484
587
  }
485
588
 
486
589
  // packages/sdk-core/src/auth.ts
@@ -504,8 +607,30 @@ var SESSION_PLACEHOLDER = "session";
504
607
  var refreshAt = (t) => t.expiresAt === void 0 ? void 0 : t.expiresAt - Math.min(REFRESH_EARLY_MS, (t.expiresAt - Date.now()) / 2);
505
608
  var b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
506
609
  var randomString = (n = 32) => b64url(crypto.getRandomValues(new Uint8Array(n)));
507
- var workspaceAudience = (workspace) => `https://${workspaceHost(workspace)}/api/v3`;
610
+ var workspaceAudience = (workspace) => `https://${workspaceHost(workspace)}/api`;
508
611
  var workspaceHost = (workspace) => workspace.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
612
+ var isBrowser = () => {
613
+ const g = globalThis;
614
+ return typeof g.window !== "undefined" && typeof g.document !== "undefined";
615
+ };
616
+ var IN_BROWSER = "not_allowed_in_browser";
617
+ function guardBrowser(o) {
618
+ if (!isBrowser()) return;
619
+ if (o.mode === "clientCredentials" || o.mode === "oauth" && o.clientSecret) {
620
+ throw new CaitlynAuthError(
621
+ `${o.mode === "oauth" ? "oauth with a clientSecret" : "clientCredentials"} cannot be used in a browser: a client secret in a browser is visible to everyone who loads the page. Use oauth without a clientSecret (authorisation code with PKCE), or sign in on your server.`,
622
+ IN_BROWSER
623
+ );
624
+ }
625
+ if (o.mode === "awsIam" || o.mode === "workloadIdentity") {
626
+ throw new CaitlynAuthError(`${o.mode} cannot be used in a browser: it signs in a server workload with that workload's own identity. Use oauth in a browser.`, IN_BROWSER);
627
+ }
628
+ if (o.mode === "token" && !o.allowBrowserToken) {
629
+ console.warn(
630
+ "Caitlyn: a long-lived token in a browser is exposed to anyone who can run script on the page. token mode in a browser is for local testing; use oauth instead. Set auth.allowBrowserToken: true to silence this warning."
631
+ );
632
+ }
633
+ }
509
634
  var AuthSession = class {
510
635
  constructor(options, endpoint, workspace, fetchFn = (...a) => fetch(...a)) {
511
636
  this.options = options;
@@ -516,6 +641,7 @@ var AuthSession = class {
516
641
  this.authorizationEndpoint = `${endpoint}/oauth/authorize`;
517
642
  this.workspaceUrl = `https://${workspace}`;
518
643
  this.resource = "resource" in options && options.resource || workspaceAudience(workspace);
644
+ guardBrowser(options);
519
645
  }
520
646
  options;
521
647
  endpoint;
@@ -528,7 +654,7 @@ var AuthSession = class {
528
654
  memoryTokens;
529
655
  tokenEndpoint;
530
656
  authorizationEndpoint;
531
- /** The workspace's resource URL, https://<workspace>/api/v3: the audience every token is bound to. */
657
+ /** The workspace's resource URL, https://<workspace>/api: the audience every token is bound to. */
532
658
  resource;
533
659
  /** https://<workspace>: the default Origin for session writes. */
534
660
  workspaceUrl;
@@ -752,6 +878,9 @@ function clientConfig(opts, auth = createAuth(opts)) {
752
878
  workspace: workspaceHost(opts.workspace),
753
879
  endpoint: opts.endpoint ?? workspaceEndpoint(opts.workspace),
754
880
  token: tokenIdentityProvider(auth),
881
+ /** The tracer upload helpers record the byte transfer with (PROTOCOL.md 13); absent with telemetry off. */
882
+ caitlynTracer: opts.telemetry?.enabled === false ? void 0 : caitlynTracer(opts.telemetry?.tracerProvider),
883
+ ...opts.requestHandler !== void 0 ? { requestHandler: opts.requestHandler } : {},
755
884
  ...opts.maxAttempts !== void 0 ? { maxAttempts: opts.maxAttempts } : {},
756
885
  ...opts.maxRetryDelayMs !== void 0 ? { retryStrategy: new CaitlynRetryStrategy(opts.maxAttempts ?? 3, opts.maxRetryDelayMs) } : {}
757
886
  };
@@ -821,10 +950,12 @@ export {
821
950
  SseStream,
822
951
  WaiterError,
823
952
  caitlynEndpointProvider,
953
+ caitlynTracer,
824
954
  clientConfig,
825
955
  createAuth,
826
956
  defaultAwsCredentials,
827
957
  getCaitlynAuthPlugin,
958
+ getCaitlynConnectionErrorPlugin,
828
959
  getCaitlynSdkPlugin,
829
960
  getCaitlynTelemetryPlugin,
830
961
  getWebIdentityToken,