@caitlyn-ai/sdk-core 0.0.2 → 0.1.1

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.
@@ -132,10 +132,12 @@ __export(src_exports, {
132
132
  SseStream: () => SseStream,
133
133
  WaiterError: () => WaiterError,
134
134
  caitlynEndpointProvider: () => caitlynEndpointProvider,
135
+ caitlynTracer: () => caitlynTracer,
135
136
  clientConfig: () => clientConfig,
136
137
  createAuth: () => createAuth,
137
138
  defaultAwsCredentials: () => defaultAwsCredentials,
138
139
  getCaitlynAuthPlugin: () => getCaitlynAuthPlugin,
140
+ getCaitlynConnectionErrorPlugin: () => getCaitlynConnectionErrorPlugin,
139
141
  getCaitlynSdkPlugin: () => getCaitlynSdkPlugin,
140
142
  getCaitlynTelemetryPlugin: () => getCaitlynTelemetryPlugin,
141
143
  getWebIdentityToken: () => getWebIdentityToken,
@@ -151,9 +153,184 @@ __export(src_exports, {
151
153
  module.exports = __toCommonJS(src_exports);
152
154
 
153
155
  // packages/sdk-core/src/protocol.ts
154
- var import_protocols = require("@smithy/core/protocols");
156
+ var import_protocols3 = require("@smithy/core/protocols");
155
157
  var import_schema = require("@smithy/core/schema");
156
158
  var import_client = require("@smithy/core/client");
159
+
160
+ // packages/sdk-core/src/plugin.ts
161
+ var import_retry = require("@smithy/core/retry");
162
+ var import_protocols = require("@smithy/core/protocols");
163
+ var import_protocols2 = require("@smithy/core/protocols");
164
+
165
+ // packages/sdk-core/src/retry.ts
166
+ var NO_RETRY = /* @__PURE__ */ Symbol.for("caitlyn.noRetry");
167
+ var Token = class {
168
+ constructor(count, delay) {
169
+ this.count = count;
170
+ this.delay = delay;
171
+ }
172
+ count;
173
+ delay;
174
+ getRetryCount() {
175
+ return this.count;
176
+ }
177
+ getRetryDelay() {
178
+ return this.delay;
179
+ }
180
+ };
181
+ var CaitlynRetryStrategy = class {
182
+ constructor(maxAttempts = 3, maxRetryDelayMs = 6e4) {
183
+ this.maxRetryDelayMs = maxRetryDelayMs;
184
+ this.maxAttempts = typeof maxAttempts === "number" ? async () => maxAttempts : maxAttempts;
185
+ }
186
+ maxRetryDelayMs;
187
+ mode = "standard";
188
+ maxAttempts;
189
+ async acquireInitialRetryToken() {
190
+ return new Token(0, 0);
191
+ }
192
+ async refreshRetryTokenForRetry(token, info) {
193
+ const count = token.getRetryCount();
194
+ const err = info.error;
195
+ if (count + 1 >= await this.maxAttempts()) throw new Error("retry: attempts exhausted");
196
+ if (!err || err[NO_RETRY]) throw new Error("retry: operation is not safe to retry");
197
+ const noResponse = err.$metadata?.httpStatusCode === void 0 && err.name !== "AbortError";
198
+ if (err.$retryable === void 0 && !noResponse) throw new Error("retry: error is not retryable");
199
+ let delay;
200
+ if (info.retryAfterHint instanceof Date) {
201
+ delay = Math.max(0, info.retryAfterHint.getTime() - Date.now());
202
+ if (delay > this.maxRetryDelayMs) throw new Error("retry: Retry-After exceeds maxRetryDelay");
203
+ } else {
204
+ delay = Math.random() * Math.min(8e3, 500 * 2 ** count);
205
+ }
206
+ return new Token(count + 1, delay);
207
+ }
208
+ recordSuccess() {
209
+ }
210
+ };
211
+
212
+ // packages/sdk-core/src/plugin.ts
213
+ var CAITLYN_SDK_USER_AGENT_PREFIX = "caitlyn-sdk-ts";
214
+ var NO_RESPONSE = /* @__PURE__ */ Symbol.for("caitlyn.noResponse");
215
+ function markNoResponse(err) {
216
+ if (err && typeof err === "object" && err.name !== "AbortError") Object.defineProperty(err, NO_RESPONSE, { value: true, configurable: true });
217
+ return err;
218
+ }
219
+ function getCaitlynConnectionErrorPlugin(toError) {
220
+ const mark = (next) => async (args) => {
221
+ try {
222
+ return await next(args);
223
+ } catch (err) {
224
+ const e = err;
225
+ if (!import_protocols2.HttpResponse.isInstance(e?.$response) && e?.$metadata?.httpStatusCode === void 0) markNoResponse(err);
226
+ throw err;
227
+ }
228
+ };
229
+ const wrap = (next, context2) => async (args) => {
230
+ try {
231
+ return await next(args);
232
+ } catch (err) {
233
+ if (err && typeof err === "object" && err[NO_RESPONSE]) {
234
+ throw toError(context2.commandName?.replace(/Command$/, "") ?? "call", err);
235
+ }
236
+ throw err;
237
+ }
238
+ };
239
+ return {
240
+ applyToStack(stack) {
241
+ stack.add(mark, { step: "deserialize", name: "caitlynNoResponse", priority: "low", override: true });
242
+ stack.add(wrap, { step: "initialize", name: "caitlynConnectionError", priority: "normal", override: true });
243
+ }
244
+ };
245
+ }
246
+ var NotSupportedByServer = class extends Error {
247
+ constructor(operation, requires, server) {
248
+ super(`NotSupportedByServer(${operation}, requires ${requires}, server is ${server})`);
249
+ this.operation = operation;
250
+ this.requires = requires;
251
+ this.server = server;
252
+ this.name = "NotSupportedByServer";
253
+ }
254
+ operation;
255
+ requires;
256
+ server;
257
+ };
258
+ var semver = (a, b) => {
259
+ const pa = a.split(".").map(Number);
260
+ const pb = b.split(".").map(Number);
261
+ for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return (pa[i] ?? 0) - (pb[i] ?? 0);
262
+ return 0;
263
+ };
264
+ function runtime() {
265
+ const g = globalThis;
266
+ if (g.process?.versions?.bun) return `md/bun#${g.process.versions.bun}`;
267
+ if (g.Deno?.version?.deno) return `md/deno#${g.Deno.version.deno}`;
268
+ if (g.process?.versions?.node) return `md/nodejs#${g.process.versions.node}`;
269
+ return "md/browser";
270
+ }
271
+ function getCaitlynSdkPlugin(config, options) {
272
+ let server;
273
+ const since = new Map((options.since ?? []).map((e) => e.split("=")));
274
+ const sinceCheck = (next, context2) => async (args) => {
275
+ const op = context2.commandName?.replace(/Command$/, "");
276
+ if (op && server) {
277
+ const required = since.get(op);
278
+ if (required && semver(required, server) > 0) throw new NotSupportedByServer(op, required, server);
279
+ for (const [member, value] of Object.entries(args.input)) {
280
+ const memberSince = value === void 0 ? void 0 : since.get(`${op}.${member}`);
281
+ if (memberSince && semver(memberSince, server) > 0) throw new NotSupportedByServer(`${op}.${member}`, memberSince, server);
282
+ }
283
+ }
284
+ return next(args);
285
+ };
286
+ const serverVersion = (next) => async (args) => {
287
+ try {
288
+ const out = await next(args);
289
+ if (import_protocols2.HttpResponse.isInstance(out.response)) server = out.response.headers["caitlyn-api-version"] ?? server;
290
+ return out;
291
+ } catch (err) {
292
+ const res = err.$response;
293
+ if (import_protocols2.HttpResponse.isInstance(res)) server = res.headers["caitlyn-api-version"] ?? server;
294
+ throw err;
295
+ }
296
+ };
297
+ const userAgent = [`${CAITLYN_SDK_USER_AGENT_PREFIX}/${config.caitlynSdkVersion ?? "0.0.0"}`, "lang/js", runtime(), config.customUserAgent].filter(Boolean).join(" ");
298
+ const identity = (next) => async (args) => {
299
+ if (import_protocols.HttpRequest.isInstance(args.request)) {
300
+ args.request.headers["user-agent"] = userAgent;
301
+ args.request.headers["caitlyn-sdk-model"] = options.sdkModel;
302
+ }
303
+ return next(args);
304
+ };
305
+ const safe = new Set(options.retrySafeOperations);
306
+ const retrySafety = (next, context2) => async (args) => {
307
+ try {
308
+ return await next(args);
309
+ } catch (err) {
310
+ const op = context2.commandName?.replace(/Command$/, "");
311
+ if (op && !safe.has(op) && err && typeof err === "object") Object.defineProperty(err, NO_RETRY, { value: true });
312
+ throw err;
313
+ }
314
+ };
315
+ return {
316
+ applyToStack(stack) {
317
+ stack.add(sinceCheck, { step: "initialize", name: "caitlynSinceCheck", priority: "high", override: true });
318
+ stack.add(serverVersion, { step: "deserialize", name: "caitlynServerVersion", priority: "low", override: true });
319
+ stack.add(identity, { step: "build", name: "caitlynSdkIdentity", priority: "low", override: true });
320
+ stack.addRelativeTo(retrySafety, { toMiddleware: "retryMiddleware", relation: "after", name: "caitlynRetrySafety" });
321
+ (0, import_retry.getOmitRetryHeadersPlugin)({}).applyToStack(stack);
322
+ }
323
+ };
324
+ }
325
+
326
+ // packages/sdk-core/src/protocol.ts
327
+ async function readBody(body, context2) {
328
+ try {
329
+ return await (0, import_protocols3.collectBody)(body, context2);
330
+ } catch (err) {
331
+ throw markNoResponse(err);
332
+ }
333
+ }
157
334
  var SETTINGS = {
158
335
  timestampFormat: {
159
336
  useTrait: true,
@@ -163,15 +340,15 @@ var SETTINGS = {
163
340
  httpBindings: true,
164
341
  jsonName: true
165
342
  };
166
- var CaitlynRestJsonProtocol = class extends import_protocols.HttpBindingProtocol {
343
+ var CaitlynRestJsonProtocol = class extends import_protocols3.HttpBindingProtocol {
167
344
  serializer;
168
345
  deserializer;
169
346
  codec;
170
347
  constructor({ defaultNamespace, errorTypeRegistries }) {
171
348
  super({ defaultNamespace, errorTypeRegistries });
172
- this.codec = new import_protocols.JsonCodec2(SETTINGS);
173
- this.serializer = new import_protocols.HttpInterceptingShapeSerializer(this.codec.createSerializer(), SETTINGS);
174
- this.deserializer = new import_protocols.HttpInterceptingShapeDeserializer(this.codec.createDeserializer(), SETTINGS);
349
+ this.codec = new import_protocols3.JsonCodec2(SETTINGS);
350
+ this.serializer = new import_protocols3.HttpInterceptingShapeSerializer(this.codec.createSerializer(), SETTINGS);
351
+ this.deserializer = new import_protocols3.HttpInterceptingShapeDeserializer(this.codec.createDeserializer(), SETTINGS);
175
352
  }
176
353
  getShapeId() {
177
354
  return "ai.caitlyn.protocols#restJson";
@@ -205,7 +382,7 @@ var CaitlynRestJsonProtocol = class extends import_protocols.HttpBindingProtocol
205
382
  const ns = import_schema.NormalizedSchema.of(operationSchema.output);
206
383
  const streaming = [...ns.structIterator()].some(([, m]) => m.getMemberTraits().httpPayload && m.isStreaming());
207
384
  if (response.statusCode >= 300) {
208
- const bytes2 = await (0, import_protocols.collectBody)(response.body, context2);
385
+ const bytes2 = await readBody(response.body, context2);
209
386
  response.body = bytes2;
210
387
  if (!isJsonObject(bytes2)) {
211
388
  await this.handleError(operationSchema, context2, response, {}, this.deserializeMetadata(response));
@@ -213,7 +390,7 @@ var CaitlynRestJsonProtocol = class extends import_protocols.HttpBindingProtocol
213
390
  return super.deserializeResponse(operationSchema, context2, response);
214
391
  }
215
392
  if (streaming) return super.deserializeResponse(operationSchema, context2, response);
216
- const bytes = await (0, import_protocols.collectBody)(response.body, context2);
393
+ const bytes = await readBody(response.body, context2);
217
394
  response.body = bytes;
218
395
  const output = await super.deserializeResponse(operationSchema, context2, response);
219
396
  if (bytes.byteLength > 0) {
@@ -234,7 +411,7 @@ var CaitlynRestJsonProtocol = class extends import_protocols.HttpBindingProtocol
234
411
  const registry = this.compositeErrorRegistry;
235
412
  registry.copyFrom(import_schema.TypeRegistry.for(this.options.defaultNamespace));
236
413
  const errorMetadata = { $metadata: metadata, $fault: response.statusCode < 500 ? "client" : "server" };
237
- const rawBody = response.body instanceof Uint8Array ? new TextDecoder().decode(response.body) : "";
414
+ const { rawBody, rawBodyTruncated } = rawBodyOf(response.body instanceof Uint8Array ? response.body : new Uint8Array());
238
415
  const body = {
239
416
  ...dataObject,
240
417
  // Always the HTTP status: a body's `status` can be wrong (a proxy, a faulty handler), and the class is
@@ -242,7 +419,8 @@ var CaitlynRestJsonProtocol = class extends import_protocols.HttpBindingProtocol
242
419
  status: response.statusCode,
243
420
  code: typeof dataObject.code === "string" && dataObject.code ? dataObject.code : `HTTP_${response.statusCode}`,
244
421
  requestId: dataObject.requestId ?? response.headers["caitlyn-request-id"],
245
- rawBody
422
+ rawBody,
423
+ rawBodyTruncated
246
424
  };
247
425
  const message = body.detail ?? body.title ?? `HTTP ${response.statusCode}`;
248
426
  const schema = registry.find((s) => {
@@ -253,7 +431,7 @@ var CaitlynRestJsonProtocol = class extends import_protocols.HttpBindingProtocol
253
431
  if (!schema) {
254
432
  const base = registry.getBaseException();
255
433
  const Ctor2 = base && registry.getErrorCtor(base) || Error;
256
- const name = body.code;
434
+ const name = "CaitlynError";
257
435
  throw (0, import_client.decorateServiceException)(Object.assign(new Ctor2({ name, message }), errorMetadata), body);
258
436
  }
259
437
  const ns = import_schema.NormalizedSchema.of(schema);
@@ -270,6 +448,13 @@ var CaitlynRestJsonProtocol = class extends import_protocols.HttpBindingProtocol
270
448
  throw (0, import_client.decorateServiceException)(Object.assign(exception, errorMetadata, { $fault: ns.getMergedTraits().error, message }, fields), body);
271
449
  }
272
450
  };
451
+ var RAW_BODY_LIMIT = 8192;
452
+ function rawBodyOf(bytes) {
453
+ if (bytes.byteLength <= RAW_BODY_LIMIT) return { rawBody: new TextDecoder().decode(bytes), rawBodyTruncated: false };
454
+ let end = RAW_BODY_LIMIT;
455
+ while (end > 0 && (bytes[end] & 192) === 128) end--;
456
+ return { rawBody: new TextDecoder().decode(bytes.subarray(0, end)), rawBodyTruncated: true };
457
+ }
273
458
  function isJsonObject(bytes) {
274
459
  if (bytes.byteLength === 0) return false;
275
460
  try {
@@ -310,140 +495,6 @@ function resolveContentType(input) {
310
495
  return hasBody ? "application/json" : void 0;
311
496
  }
312
497
 
313
- // packages/sdk-core/src/plugin.ts
314
- var import_retry = require("@smithy/core/retry");
315
- var import_protocols2 = require("@smithy/core/protocols");
316
- var import_protocols3 = require("@smithy/core/protocols");
317
-
318
- // packages/sdk-core/src/retry.ts
319
- var NO_RETRY = /* @__PURE__ */ Symbol.for("caitlyn.noRetry");
320
- var Token = class {
321
- constructor(count, delay) {
322
- this.count = count;
323
- this.delay = delay;
324
- }
325
- count;
326
- delay;
327
- getRetryCount() {
328
- return this.count;
329
- }
330
- getRetryDelay() {
331
- return this.delay;
332
- }
333
- };
334
- var CaitlynRetryStrategy = class {
335
- constructor(maxAttempts = 3, maxRetryDelayMs = 6e4) {
336
- this.maxRetryDelayMs = maxRetryDelayMs;
337
- this.maxAttempts = typeof maxAttempts === "number" ? async () => maxAttempts : maxAttempts;
338
- }
339
- maxRetryDelayMs;
340
- mode = "standard";
341
- maxAttempts;
342
- async acquireInitialRetryToken() {
343
- return new Token(0, 0);
344
- }
345
- async refreshRetryTokenForRetry(token, info) {
346
- const count = token.getRetryCount();
347
- const err = info.error;
348
- if (count + 1 >= await this.maxAttempts()) throw new Error("retry: attempts exhausted");
349
- if (!err || err[NO_RETRY]) throw new Error("retry: operation is not safe to retry");
350
- const noResponse = err.$metadata?.httpStatusCode === void 0 && err.name !== "AbortError";
351
- if (err.$retryable === void 0 && !noResponse) throw new Error("retry: error is not retryable");
352
- let delay;
353
- if (info.retryAfterHint instanceof Date) {
354
- delay = Math.max(0, info.retryAfterHint.getTime() - Date.now());
355
- if (delay > this.maxRetryDelayMs) throw new Error("retry: Retry-After exceeds maxRetryDelay");
356
- } else {
357
- delay = Math.random() * Math.min(8e3, 500 * 2 ** count);
358
- }
359
- return new Token(count + 1, delay);
360
- }
361
- recordSuccess() {
362
- }
363
- };
364
-
365
- // packages/sdk-core/src/plugin.ts
366
- var CAITLYN_SDK_USER_AGENT_PREFIX = "caitlyn-sdk-ts";
367
- var NotSupportedByServer = class extends Error {
368
- constructor(operation, requires, server) {
369
- super(`NotSupportedByServer(${operation}, requires ${requires}, server is ${server})`);
370
- this.operation = operation;
371
- this.requires = requires;
372
- this.server = server;
373
- this.name = "NotSupportedByServer";
374
- }
375
- operation;
376
- requires;
377
- server;
378
- };
379
- var semver = (a, b) => {
380
- const pa = a.split(".").map(Number);
381
- const pb = b.split(".").map(Number);
382
- for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return (pa[i] ?? 0) - (pb[i] ?? 0);
383
- return 0;
384
- };
385
- function runtime() {
386
- const g = globalThis;
387
- if (g.process?.versions?.bun) return `md/bun#${g.process.versions.bun}`;
388
- if (g.Deno?.version?.deno) return `md/deno#${g.Deno.version.deno}`;
389
- if (g.process?.versions?.node) return `md/nodejs#${g.process.versions.node}`;
390
- return "md/browser";
391
- }
392
- function getCaitlynSdkPlugin(config, options) {
393
- let server;
394
- const since = new Map((options.since ?? []).map((e) => e.split("=")));
395
- const sinceCheck = (next, context2) => async (args) => {
396
- const op = context2.commandName?.replace(/Command$/, "");
397
- if (op && server) {
398
- const required = since.get(op);
399
- if (required && semver(required, server) > 0) throw new NotSupportedByServer(op, required, server);
400
- for (const [member, value] of Object.entries(args.input)) {
401
- const memberSince = value === void 0 ? void 0 : since.get(`${op}.${member}`);
402
- if (memberSince && semver(memberSince, server) > 0) throw new NotSupportedByServer(`${op}.${member}`, memberSince, server);
403
- }
404
- }
405
- return next(args);
406
- };
407
- const serverVersion = (next) => async (args) => {
408
- try {
409
- const out = await next(args);
410
- if (import_protocols3.HttpResponse.isInstance(out.response)) server = out.response.headers["caitlyn-api-version"] ?? server;
411
- return out;
412
- } catch (err) {
413
- const res = err.$response;
414
- if (import_protocols3.HttpResponse.isInstance(res)) server = res.headers["caitlyn-api-version"] ?? server;
415
- throw err;
416
- }
417
- };
418
- const userAgent = [`${CAITLYN_SDK_USER_AGENT_PREFIX}/${config.caitlynSdkVersion ?? "0.0.0"}`, "lang/js", runtime(), config.customUserAgent].filter(Boolean).join(" ");
419
- const identity = (next) => async (args) => {
420
- if (import_protocols2.HttpRequest.isInstance(args.request)) {
421
- args.request.headers["user-agent"] = userAgent;
422
- args.request.headers["caitlyn-sdk-model"] = options.sdkModel;
423
- }
424
- return next(args);
425
- };
426
- const safe = new Set(options.retrySafeOperations);
427
- const retrySafety = (next, context2) => async (args) => {
428
- try {
429
- return await next(args);
430
- } catch (err) {
431
- const op = context2.commandName?.replace(/Command$/, "");
432
- if (op && !safe.has(op) && err && typeof err === "object") Object.defineProperty(err, NO_RETRY, { value: true });
433
- throw err;
434
- }
435
- };
436
- return {
437
- applyToStack(stack) {
438
- stack.add(sinceCheck, { step: "initialize", name: "caitlynSinceCheck", priority: "high", override: true });
439
- stack.add(serverVersion, { step: "deserialize", name: "caitlynServerVersion", priority: "low", override: true });
440
- stack.add(identity, { step: "build", name: "caitlynSdkIdentity", priority: "low", override: true });
441
- stack.addRelativeTo(retrySafety, { toMiddleware: "retryMiddleware", relation: "after", name: "caitlynRetrySafety" });
442
- (0, import_retry.getOmitRetryHeadersPlugin)({}).applyToStack(stack);
443
- }
444
- };
445
- }
446
-
447
498
  // packages/sdk-core/src/endpoint.ts
448
499
  var caitlynEndpointProvider = (params) => {
449
500
  if (!params.endpoint) throw new Error("Caitlyn: no endpoint. Pass `workspace` or `endpoint` when creating the client.");
@@ -453,23 +504,42 @@ var caitlynEndpointProvider = (params) => {
453
504
  // packages/sdk-core/src/telemetry.ts
454
505
  var import_protocols4 = require("@smithy/core/protocols");
455
506
  var import_api = require("@opentelemetry/api");
507
+ function caitlynTracer(provider) {
508
+ return (provider ?? import_api.trace.getTracerProvider()).getTracer("@caitlyn-ai/sdk", "0.1.1");
509
+ }
510
+ function urlWithoutQuery(url) {
511
+ const u = new URL(url);
512
+ u.search = "";
513
+ u.hash = "";
514
+ u.username = "";
515
+ u.password = "";
516
+ return u.toString();
517
+ }
518
+ function recordFailure(span, err) {
519
+ const e = err;
520
+ const noResponse = !!e?.[NO_RESPONSE];
521
+ const name = noResponse ? "CaitlynConnectionError" : e?.name ?? "Error";
522
+ const message = String(e?.message ?? err);
523
+ span.recordException({ name, message, stack: err instanceof Error ? err.stack : void 0 });
524
+ span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
525
+ span.setAttribute("error.type", name);
526
+ const code = noResponse ? "CONNECTION_ERROR" : e?.code;
527
+ if (typeof code === "string") span.setAttribute("caitlyn.error.code", code);
528
+ }
456
529
  function getCaitlynTelemetryPlugin(provider) {
457
- const tracer = (provider ?? import_api.trace.getTracerProvider()).getTracer("@caitlyn-ai/sdk", "0.0.2");
530
+ const tracer = caitlynTracer(provider);
458
531
  return {
459
532
  applyToStack(stack) {
460
533
  stack.add(
461
534
  (next, ctx) => async (args) => {
462
535
  const op = ctx.commandName?.replace(/Command$/, "") ?? "unknown";
463
- return tracer.startActiveSpan(`caitlyn ${op}`, { kind: import_api.SpanKind.CLIENT, attributes: { "caitlyn.operation": op } }, async (span) => {
536
+ return tracer.startActiveSpan("caitlyn.call", { kind: import_api.SpanKind.CLIENT, attributes: { "rpc.system": "caitlyn", "rpc.method": op } }, async (span) => {
464
537
  ctx.__caitlynSpan = span;
465
538
  ctx.__attempt = 0;
466
539
  try {
467
- const out = await next(args);
468
- return out;
540
+ return await next(args);
469
541
  } catch (err) {
470
- span.recordException(err);
471
- span.setStatus({ code: import_api.SpanStatusCode.ERROR });
472
- span.setAttribute("error.type", err.name);
542
+ recordFailure(span, err);
473
543
  throw err;
474
544
  } finally {
475
545
  span.end();
@@ -483,27 +553,34 @@ function getCaitlynTelemetryPlugin(provider) {
483
553
  const attempt = ctx.__attempt ?? 0;
484
554
  ctx.__attempt = attempt + 1;
485
555
  const parent = ctx.__caitlynSpan ? import_api.trace.setSpan(import_api.context.active(), ctx.__caitlynSpan) : import_api.context.active();
486
- const span = tracer.startSpan(`HTTP ${args.request.method}`, { kind: import_api.SpanKind.CLIENT, attributes: { "http.request.resend_count": attempt } }, parent);
556
+ const span = tracer.startSpan("caitlyn.attempt", { kind: import_api.SpanKind.CLIENT, attributes: { "http.request.resend_count": attempt } }, parent);
487
557
  const req = args.request;
488
558
  if (import_protocols4.HttpRequest.isInstance(req)) {
489
- span.setAttribute("server.address", req.hostname);
559
+ span.setAttributes({
560
+ "http.request.method": req.method,
561
+ "server.address": req.hostname,
562
+ "url.full": urlWithoutQuery(`${req.protocol}//${req.hostname}${req.port ? `:${req.port}` : ""}${req.path}`)
563
+ });
490
564
  import_api.propagation.inject(import_api.trace.setSpan(parent, span), req.headers);
491
565
  }
566
+ const response = (res) => {
567
+ if (!import_protocols4.HttpResponse.isInstance(res)) return;
568
+ span.setAttribute("http.response.status_code", res.statusCode);
569
+ const rid = res.headers["caitlyn-request-id"];
570
+ if (rid) {
571
+ span.setAttribute("caitlyn.request_id", rid);
572
+ ctx.__caitlynSpan?.setAttribute("caitlyn.request_id", rid);
573
+ }
574
+ const api = res.headers["caitlyn-api-version"];
575
+ if (api) span.setAttribute("caitlyn.server.api_version", api);
576
+ };
492
577
  try {
493
578
  const out = await next(args);
494
- if (import_protocols4.HttpResponse.isInstance(out.response)) {
495
- span.setAttribute("http.response.status_code", out.response.statusCode);
496
- const rid = out.response.headers["caitlyn-request-id"];
497
- if (rid) {
498
- span.setAttribute("caitlyn.request_id", rid);
499
- ctx.__caitlynSpan?.setAttribute("caitlyn.request_id", rid);
500
- }
501
- }
579
+ response(out.response);
502
580
  return out;
503
581
  } catch (err) {
504
- const res = err.$response;
505
- if (import_protocols4.HttpResponse.isInstance(res)) span.setAttribute("http.response.status_code", res.statusCode);
506
- span.setStatus({ code: import_api.SpanStatusCode.ERROR });
582
+ response(err.$response);
583
+ recordFailure(span, err);
507
584
  throw err;
508
585
  } finally {
509
586
  span.end();
@@ -565,17 +642,23 @@ var SseStream = class {
565
642
  seen = /* @__PURE__ */ new Set();
566
643
  lastEventId;
567
644
  reconnects = 0;
645
+ /** The Caitlyn-Request-Id of the response that opened the stream; set once iteration has started. */
646
+ requestId;
568
647
  aborted() {
569
648
  return this.signal?.reason ?? new DOMException("Aborted", "AbortError");
570
649
  }
571
650
  async *[Symbol.asyncIterator]() {
572
651
  if (this.consumed) throw new Error("SseStream can only be iterated once");
573
652
  this.consumed = true;
574
- let body = await this.open();
653
+ const opened = await this.open();
654
+ this.requestId = opened.$metadata?.requestId;
655
+ let body = opened.events;
575
656
  for (; ; ) {
576
657
  let ended = false;
658
+ let onResponse = 0;
577
659
  try {
578
660
  for await (const msg of parseSse(body)) {
661
+ onResponse++;
579
662
  if (msg.id) {
580
663
  if (this.seen.has(msg.id)) continue;
581
664
  this.seen.add(msg.id);
@@ -592,7 +675,7 @@ var SseStream = class {
592
675
  return;
593
676
  }
594
677
  }
595
- if (!this.spec.final) return;
678
+ if (!this.spec.final || onResponse === 0) return;
596
679
  } catch (err) {
597
680
  if (this.signal?.aborted) throw this.aborted();
598
681
  if (ended || err?.name === "AbortError" || err === void 0 || err.$fault || err instanceof SyntaxError) throw err;
@@ -600,7 +683,7 @@ var SseStream = class {
600
683
  if (this.signal?.aborted) throw this.aborted();
601
684
  if (!this.resume || ++this.reconnects > this.maxReconnects)
602
685
  throw this.spec.toError({ status: 0, code: "STREAM_LOST", title: "The stream dropped and could not be resumed" });
603
- body = await this.resume(this.lastEventId);
686
+ body = (await this.resume(this.lastEventId)).events;
604
687
  }
605
688
  }
606
689
  /** Drains the stream if needed and returns the final event's member. */
@@ -612,15 +695,40 @@ var SseStream = class {
612
695
  };
613
696
 
614
697
  // packages/sdk-core/src/upload.ts
615
- async function uploadFile(file, fileName, reserve, toError, signal) {
698
+ var import_api2 = require("@opentelemetry/api");
699
+ var STATUS = /* @__PURE__ */ Symbol("status");
700
+ async function uploadFile(file, fileName, reserve, toError, signal, tracer) {
616
701
  const bytes = new Uint8Array(await file.arrayBuffer());
617
702
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
618
703
  let binary = "";
619
704
  for (const b of digest) binary += String.fromCharCode(b);
620
705
  const slot = await reserve({ fileName, contentType: file.type || "application/octet-stream", byteSize: bytes.byteLength, sha256: btoa(binary) });
621
- const res = await fetch(slot.uploadUrl, { method: slot.uploadMethod, headers: slot.requiredHeaders, body: bytes, signal });
622
- if (!res.ok) throw toError({ status: res.status, code: "UPLOAD_PUT_FAILED", title: "Uploading the bytes failed", detail: await res.text() });
623
- return slot;
706
+ const put = async () => {
707
+ const res = await fetch(slot.uploadUrl, { method: slot.uploadMethod, headers: slot.requiredHeaders, body: bytes, signal });
708
+ 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 });
709
+ return res.status;
710
+ };
711
+ if (!tracer) {
712
+ await put();
713
+ return slot;
714
+ }
715
+ const url = new URL(slot.uploadUrl);
716
+ const span = tracer.startSpan("caitlyn.upload", {
717
+ kind: import_api2.SpanKind.CLIENT,
718
+ attributes: { "http.request.method": slot.uploadMethod || "PUT", "server.address": url.hostname, "url.full": urlWithoutQuery(slot.uploadUrl) }
719
+ });
720
+ try {
721
+ span.setAttribute("http.response.status_code", await put());
722
+ return slot;
723
+ } catch (err) {
724
+ const status = err[STATUS];
725
+ if (status !== void 0) span.setAttribute("http.response.status_code", status);
726
+ recordFailure(span, err);
727
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
728
+ throw err;
729
+ } finally {
730
+ span.end();
731
+ }
624
732
  }
625
733
 
626
734
  // packages/sdk-core/src/auth.ts
@@ -644,8 +752,30 @@ var SESSION_PLACEHOLDER = "session";
644
752
  var refreshAt = (t) => t.expiresAt === void 0 ? void 0 : t.expiresAt - Math.min(REFRESH_EARLY_MS, (t.expiresAt - Date.now()) / 2);
645
753
  var b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
646
754
  var randomString = (n = 32) => b64url(crypto.getRandomValues(new Uint8Array(n)));
647
- var workspaceAudience = (workspace) => `https://${workspaceHost(workspace)}/api/v3`;
755
+ var workspaceAudience = (workspace) => `https://${workspaceHost(workspace)}/api`;
648
756
  var workspaceHost = (workspace) => workspace.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
757
+ var isBrowser = () => {
758
+ const g = globalThis;
759
+ return typeof g.window !== "undefined" && typeof g.document !== "undefined";
760
+ };
761
+ var IN_BROWSER = "not_allowed_in_browser";
762
+ function guardBrowser(o) {
763
+ if (!isBrowser()) return;
764
+ if (o.mode === "clientCredentials" || o.mode === "oauth" && o.clientSecret) {
765
+ throw new CaitlynAuthError(
766
+ `${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.`,
767
+ IN_BROWSER
768
+ );
769
+ }
770
+ if (o.mode === "awsIam" || o.mode === "workloadIdentity") {
771
+ 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);
772
+ }
773
+ if (o.mode === "token" && !o.allowBrowserToken) {
774
+ console.warn(
775
+ "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."
776
+ );
777
+ }
778
+ }
649
779
  var AuthSession = class {
650
780
  constructor(options, endpoint, workspace, fetchFn = (...a) => fetch(...a)) {
651
781
  this.options = options;
@@ -656,6 +786,7 @@ var AuthSession = class {
656
786
  this.authorizationEndpoint = `${endpoint}/oauth/authorize`;
657
787
  this.workspaceUrl = `https://${workspace}`;
658
788
  this.resource = "resource" in options && options.resource || workspaceAudience(workspace);
789
+ guardBrowser(options);
659
790
  }
660
791
  options;
661
792
  endpoint;
@@ -668,7 +799,7 @@ var AuthSession = class {
668
799
  memoryTokens;
669
800
  tokenEndpoint;
670
801
  authorizationEndpoint;
671
- /** The workspace's resource URL, https://<workspace>/api/v3: the audience every token is bound to. */
802
+ /** The workspace's resource URL, https://<workspace>/api: the audience every token is bound to. */
672
803
  resource;
673
804
  /** https://<workspace>: the default Origin for session writes. */
674
805
  workspaceUrl;
@@ -892,6 +1023,9 @@ function clientConfig(opts, auth = createAuth(opts)) {
892
1023
  workspace: workspaceHost(opts.workspace),
893
1024
  endpoint: opts.endpoint ?? workspaceEndpoint(opts.workspace),
894
1025
  token: tokenIdentityProvider(auth),
1026
+ /** The tracer upload helpers record the byte transfer with (PROTOCOL.md 13); absent with telemetry off. */
1027
+ caitlynTracer: opts.telemetry?.enabled === false ? void 0 : caitlynTracer(opts.telemetry?.tracerProvider),
1028
+ ...opts.requestHandler !== void 0 ? { requestHandler: opts.requestHandler } : {},
895
1029
  ...opts.maxAttempts !== void 0 ? { maxAttempts: opts.maxAttempts } : {},
896
1030
  ...opts.maxRetryDelayMs !== void 0 ? { retryStrategy: new CaitlynRetryStrategy(opts.maxAttempts ?? 3, opts.maxRetryDelayMs) } : {}
897
1031
  };
@@ -965,10 +1099,12 @@ async function waitUntil(name, read, accept, opts = {}) {
965
1099
  SseStream,
966
1100
  WaiterError,
967
1101
  caitlynEndpointProvider,
1102
+ caitlynTracer,
968
1103
  clientConfig,
969
1104
  createAuth,
970
1105
  defaultAwsCredentials,
971
1106
  getCaitlynAuthPlugin,
1107
+ getCaitlynConnectionErrorPlugin,
972
1108
  getCaitlynSdkPlugin,
973
1109
  getCaitlynTelemetryPlugin,
974
1110
  getWebIdentityToken,