@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.
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,42 @@ 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.1");
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
+ const noResponse = !!e?.[NO_RESPONSE];
379
+ const name = noResponse ? "CaitlynConnectionError" : e?.name ?? "Error";
380
+ const message = String(e?.message ?? err);
381
+ span.recordException({ name, message, stack: err instanceof Error ? err.stack : void 0 });
382
+ span.setStatus({ code: SpanStatusCode.ERROR, message });
383
+ span.setAttribute("error.type", name);
384
+ const code = noResponse ? "CONNECTION_ERROR" : e?.code;
385
+ if (typeof code === "string") span.setAttribute("caitlyn.error.code", code);
386
+ }
316
387
  function getCaitlynTelemetryPlugin(provider) {
317
- const tracer = (provider ?? trace.getTracerProvider()).getTracer("@caitlyn-ai/sdk", "0.0.2");
388
+ const tracer = caitlynTracer(provider);
318
389
  return {
319
390
  applyToStack(stack) {
320
391
  stack.add(
321
392
  (next, ctx) => async (args) => {
322
393
  const op = ctx.commandName?.replace(/Command$/, "") ?? "unknown";
323
- return tracer.startActiveSpan(`caitlyn ${op}`, { kind: SpanKind.CLIENT, attributes: { "caitlyn.operation": op } }, async (span) => {
394
+ return tracer.startActiveSpan("caitlyn.call", { kind: SpanKind.CLIENT, attributes: { "rpc.system": "caitlyn", "rpc.method": op } }, async (span) => {
324
395
  ctx.__caitlynSpan = span;
325
396
  ctx.__attempt = 0;
326
397
  try {
327
- const out = await next(args);
328
- return out;
398
+ return await next(args);
329
399
  } catch (err) {
330
- span.recordException(err);
331
- span.setStatus({ code: SpanStatusCode.ERROR });
332
- span.setAttribute("error.type", err.name);
400
+ recordFailure(span, err);
333
401
  throw err;
334
402
  } finally {
335
403
  span.end();
@@ -343,27 +411,34 @@ function getCaitlynTelemetryPlugin(provider) {
343
411
  const attempt = ctx.__attempt ?? 0;
344
412
  ctx.__attempt = attempt + 1;
345
413
  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);
414
+ const span = tracer.startSpan("caitlyn.attempt", { kind: SpanKind.CLIENT, attributes: { "http.request.resend_count": attempt } }, parent);
347
415
  const req = args.request;
348
416
  if (HttpRequest2.isInstance(req)) {
349
- span.setAttribute("server.address", req.hostname);
417
+ span.setAttributes({
418
+ "http.request.method": req.method,
419
+ "server.address": req.hostname,
420
+ "url.full": urlWithoutQuery(`${req.protocol}//${req.hostname}${req.port ? `:${req.port}` : ""}${req.path}`)
421
+ });
350
422
  propagation.inject(trace.setSpan(parent, span), req.headers);
351
423
  }
424
+ const response = (res) => {
425
+ if (!HttpResponse2.isInstance(res)) return;
426
+ span.setAttribute("http.response.status_code", res.statusCode);
427
+ const rid = res.headers["caitlyn-request-id"];
428
+ if (rid) {
429
+ span.setAttribute("caitlyn.request_id", rid);
430
+ ctx.__caitlynSpan?.setAttribute("caitlyn.request_id", rid);
431
+ }
432
+ const api = res.headers["caitlyn-api-version"];
433
+ if (api) span.setAttribute("caitlyn.server.api_version", api);
434
+ };
352
435
  try {
353
436
  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
- }
437
+ response(out.response);
362
438
  return out;
363
439
  } catch (err) {
364
- const res = err.$response;
365
- if (HttpResponse2.isInstance(res)) span.setAttribute("http.response.status_code", res.statusCode);
366
- span.setStatus({ code: SpanStatusCode.ERROR });
440
+ response(err.$response);
441
+ recordFailure(span, err);
367
442
  throw err;
368
443
  } finally {
369
444
  span.end();
@@ -425,17 +500,23 @@ var SseStream = class {
425
500
  seen = /* @__PURE__ */ new Set();
426
501
  lastEventId;
427
502
  reconnects = 0;
503
+ /** The Caitlyn-Request-Id of the response that opened the stream; set once iteration has started. */
504
+ requestId;
428
505
  aborted() {
429
506
  return this.signal?.reason ?? new DOMException("Aborted", "AbortError");
430
507
  }
431
508
  async *[Symbol.asyncIterator]() {
432
509
  if (this.consumed) throw new Error("SseStream can only be iterated once");
433
510
  this.consumed = true;
434
- let body = await this.open();
511
+ const opened = await this.open();
512
+ this.requestId = opened.$metadata?.requestId;
513
+ let body = opened.events;
435
514
  for (; ; ) {
436
515
  let ended = false;
516
+ let onResponse = 0;
437
517
  try {
438
518
  for await (const msg of parseSse(body)) {
519
+ onResponse++;
439
520
  if (msg.id) {
440
521
  if (this.seen.has(msg.id)) continue;
441
522
  this.seen.add(msg.id);
@@ -452,7 +533,7 @@ var SseStream = class {
452
533
  return;
453
534
  }
454
535
  }
455
- if (!this.spec.final) return;
536
+ if (!this.spec.final || onResponse === 0) return;
456
537
  } catch (err) {
457
538
  if (this.signal?.aborted) throw this.aborted();
458
539
  if (ended || err?.name === "AbortError" || err === void 0 || err.$fault || err instanceof SyntaxError) throw err;
@@ -460,7 +541,7 @@ var SseStream = class {
460
541
  if (this.signal?.aborted) throw this.aborted();
461
542
  if (!this.resume || ++this.reconnects > this.maxReconnects)
462
543
  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);
544
+ body = (await this.resume(this.lastEventId)).events;
464
545
  }
465
546
  }
466
547
  /** Drains the stream if needed and returns the final event's member. */
@@ -472,15 +553,40 @@ var SseStream = class {
472
553
  };
473
554
 
474
555
  // packages/sdk-core/src/upload.ts
475
- async function uploadFile(file, fileName, reserve, toError, signal) {
556
+ import { SpanKind as SpanKind2, SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
557
+ var STATUS = /* @__PURE__ */ Symbol("status");
558
+ async function uploadFile(file, fileName, reserve, toError, signal, tracer) {
476
559
  const bytes = new Uint8Array(await file.arrayBuffer());
477
560
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
478
561
  let binary = "";
479
562
  for (const b of digest) binary += String.fromCharCode(b);
480
563
  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;
564
+ const put = async () => {
565
+ const res = await fetch(slot.uploadUrl, { method: slot.uploadMethod, headers: slot.requiredHeaders, body: bytes, signal });
566
+ 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 });
567
+ return res.status;
568
+ };
569
+ if (!tracer) {
570
+ await put();
571
+ return slot;
572
+ }
573
+ const url = new URL(slot.uploadUrl);
574
+ const span = tracer.startSpan("caitlyn.upload", {
575
+ kind: SpanKind2.CLIENT,
576
+ attributes: { "http.request.method": slot.uploadMethod || "PUT", "server.address": url.hostname, "url.full": urlWithoutQuery(slot.uploadUrl) }
577
+ });
578
+ try {
579
+ span.setAttribute("http.response.status_code", await put());
580
+ return slot;
581
+ } catch (err) {
582
+ const status = err[STATUS];
583
+ if (status !== void 0) span.setAttribute("http.response.status_code", status);
584
+ recordFailure(span, err);
585
+ span.setStatus({ code: SpanStatusCode2.ERROR });
586
+ throw err;
587
+ } finally {
588
+ span.end();
589
+ }
484
590
  }
485
591
 
486
592
  // packages/sdk-core/src/auth.ts
@@ -504,8 +610,30 @@ var SESSION_PLACEHOLDER = "session";
504
610
  var refreshAt = (t) => t.expiresAt === void 0 ? void 0 : t.expiresAt - Math.min(REFRESH_EARLY_MS, (t.expiresAt - Date.now()) / 2);
505
611
  var b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
506
612
  var randomString = (n = 32) => b64url(crypto.getRandomValues(new Uint8Array(n)));
507
- var workspaceAudience = (workspace) => `https://${workspaceHost(workspace)}/api/v3`;
613
+ var workspaceAudience = (workspace) => `https://${workspaceHost(workspace)}/api`;
508
614
  var workspaceHost = (workspace) => workspace.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
615
+ var isBrowser = () => {
616
+ const g = globalThis;
617
+ return typeof g.window !== "undefined" && typeof g.document !== "undefined";
618
+ };
619
+ var IN_BROWSER = "not_allowed_in_browser";
620
+ function guardBrowser(o) {
621
+ if (!isBrowser()) return;
622
+ if (o.mode === "clientCredentials" || o.mode === "oauth" && o.clientSecret) {
623
+ throw new CaitlynAuthError(
624
+ `${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.`,
625
+ IN_BROWSER
626
+ );
627
+ }
628
+ if (o.mode === "awsIam" || o.mode === "workloadIdentity") {
629
+ 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);
630
+ }
631
+ if (o.mode === "token" && !o.allowBrowserToken) {
632
+ console.warn(
633
+ "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."
634
+ );
635
+ }
636
+ }
509
637
  var AuthSession = class {
510
638
  constructor(options, endpoint, workspace, fetchFn = (...a) => fetch(...a)) {
511
639
  this.options = options;
@@ -516,6 +644,7 @@ var AuthSession = class {
516
644
  this.authorizationEndpoint = `${endpoint}/oauth/authorize`;
517
645
  this.workspaceUrl = `https://${workspace}`;
518
646
  this.resource = "resource" in options && options.resource || workspaceAudience(workspace);
647
+ guardBrowser(options);
519
648
  }
520
649
  options;
521
650
  endpoint;
@@ -528,7 +657,7 @@ var AuthSession = class {
528
657
  memoryTokens;
529
658
  tokenEndpoint;
530
659
  authorizationEndpoint;
531
- /** The workspace's resource URL, https://<workspace>/api/v3: the audience every token is bound to. */
660
+ /** The workspace's resource URL, https://<workspace>/api: the audience every token is bound to. */
532
661
  resource;
533
662
  /** https://<workspace>: the default Origin for session writes. */
534
663
  workspaceUrl;
@@ -752,6 +881,9 @@ function clientConfig(opts, auth = createAuth(opts)) {
752
881
  workspace: workspaceHost(opts.workspace),
753
882
  endpoint: opts.endpoint ?? workspaceEndpoint(opts.workspace),
754
883
  token: tokenIdentityProvider(auth),
884
+ /** The tracer upload helpers record the byte transfer with (PROTOCOL.md 13); absent with telemetry off. */
885
+ caitlynTracer: opts.telemetry?.enabled === false ? void 0 : caitlynTracer(opts.telemetry?.tracerProvider),
886
+ ...opts.requestHandler !== void 0 ? { requestHandler: opts.requestHandler } : {},
755
887
  ...opts.maxAttempts !== void 0 ? { maxAttempts: opts.maxAttempts } : {},
756
888
  ...opts.maxRetryDelayMs !== void 0 ? { retryStrategy: new CaitlynRetryStrategy(opts.maxAttempts ?? 3, opts.maxRetryDelayMs) } : {}
757
889
  };
@@ -821,10 +953,12 @@ export {
821
953
  SseStream,
822
954
  WaiterError,
823
955
  caitlynEndpointProvider,
956
+ caitlynTracer,
824
957
  clientConfig,
825
958
  createAuth,
826
959
  defaultAwsCredentials,
827
960
  getCaitlynAuthPlugin,
961
+ getCaitlynConnectionErrorPlugin,
828
962
  getCaitlynSdkPlugin,
829
963
  getCaitlynTelemetryPlugin,
830
964
  getWebIdentityToken,