@aexhq/sdk 0.34.0 → 0.36.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.
Files changed (63) hide show
  1. package/README.md +16 -15
  2. package/dist/_contracts/index.d.ts +3 -4
  3. package/dist/_contracts/index.js +1 -4
  4. package/dist/_contracts/operations.d.ts +2 -1
  5. package/dist/_contracts/operations.js +10 -0
  6. package/dist/_contracts/run-config.d.ts +1 -3
  7. package/dist/_contracts/run-config.js +2 -7
  8. package/dist/_contracts/run-trace.d.ts +0 -86
  9. package/dist/_contracts/run-trace.js +1 -184
  10. package/dist/_contracts/run-unit.d.ts +2 -25
  11. package/dist/_contracts/run-unit.js +1 -2
  12. package/dist/_contracts/runtime-manifest.d.ts +1 -1
  13. package/dist/_contracts/runtime-security-profile.d.ts +0 -2
  14. package/dist/_contracts/runtime-security-profile.js +0 -9
  15. package/dist/_contracts/runtime-types.d.ts +25 -4
  16. package/dist/_contracts/stable.d.ts +1 -1
  17. package/dist/_contracts/stable.js +1 -1
  18. package/dist/_contracts/submission.d.ts +62 -95
  19. package/dist/_contracts/submission.js +59 -482
  20. package/dist/cli.mjs +99 -442
  21. package/dist/cli.mjs.sha256 +1 -1
  22. package/dist/client.d.ts +49 -25
  23. package/dist/client.js +341 -70
  24. package/dist/client.js.map +1 -1
  25. package/dist/index.d.ts +9 -15
  26. package/dist/index.js +11 -17
  27. package/dist/index.js.map +1 -1
  28. package/dist/retry.d.ts +162 -0
  29. package/dist/retry.js +320 -0
  30. package/dist/retry.js.map +1 -0
  31. package/dist/secret.d.ts +2 -2
  32. package/dist/secret.js +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/docs/concepts/composition.md +8 -14
  36. package/docs/credentials.md +59 -101
  37. package/docs/defaults.md +0 -8
  38. package/docs/events.md +8 -9
  39. package/docs/limits-and-quotas.md +1 -4
  40. package/docs/limits.md +2 -6
  41. package/docs/mcp.md +4 -5
  42. package/docs/networking.md +6 -16
  43. package/docs/outputs.md +0 -4
  44. package/docs/public-surface.json +3 -3
  45. package/docs/quickstart.md +3 -7
  46. package/docs/retries.md +129 -0
  47. package/docs/run-config.md +6 -3
  48. package/docs/secrets.md +1 -1
  49. package/docs/skills.md +3 -3
  50. package/docs/vision-skills.md +52 -101
  51. package/examples/feature-tour.ts +284 -0
  52. package/package.json +1 -1
  53. package/dist/_contracts/proxy-protocol.d.ts +0 -305
  54. package/dist/_contracts/proxy-protocol.js +0 -297
  55. package/dist/_contracts/proxy-validation.d.ts +0 -19
  56. package/dist/_contracts/proxy-validation.js +0 -51
  57. package/dist/data-tools.d.ts +0 -82
  58. package/dist/data-tools.js +0 -251
  59. package/dist/data-tools.js.map +0 -1
  60. package/dist/proxy-endpoint.d.ts +0 -131
  61. package/dist/proxy-endpoint.js +0 -144
  62. package/dist/proxy-endpoint.js.map +0 -1
  63. package/examples/chat-corpus.ts +0 -84
package/dist/client.js CHANGED
@@ -1,9 +1,9 @@
1
- import { AexError, DEFAULT_RUN_PROVIDER, HttpClient, RunConfigValidationError, RunStateError, SecretString, customName, isRunSettled, operations, providersForModel, streamCoordinatorEvents, decodeAssistantText, summarizeRunTrace, textOf, parseRunLimits, BUILTIN_TOOL_NAMES, TERMINAL_RUN_STATUSES } from "./_contracts/index.js";
1
+ import { AexApiError, AexError, DEFAULT_RUN_PROVIDER, HttpClient, RunConfigValidationError, RunStateError, SecretString, customName, isRunSettled, operations, providersForModel, streamCoordinatorEvents, parseRunLimits, BUILTIN_TOOL_NAMES, TERMINAL_RUN_STATUSES } from "./_contracts/index.js";
2
2
  import { AgentsMd } from "./agents-md.js";
3
3
  import { uploadAsset } from "./asset-upload.js";
4
4
  import { File } from "./file.js";
5
5
  import { McpServer } from "./mcp-server.js";
6
- import { splitProxyEndpoints } from "./proxy-endpoint.js";
6
+ import { AexRateLimitError, isThrottleFault, parseProviderFault, withRetry } from "./retry.js";
7
7
  import { splitSecretEnv } from "./secret.js";
8
8
  import { SkillTool } from "./skill-tool.js";
9
9
  import { Tool } from "./tool.js";
@@ -40,6 +40,8 @@ export class SessionHandle {
40
40
  #http;
41
41
  #fetch;
42
42
  #session;
43
+ /** The last message sent on this handle, for {@link SessionHandle.replayLast}. */
44
+ #lastSend;
43
45
  constructor(http, session, fetch) {
44
46
  this.#http = http;
45
47
  this.#session = session;
@@ -56,8 +58,28 @@ export class SessionHandle {
56
58
  assertNoSessionSendSignal(options, "SessionHandle.send");
57
59
  return sendSessionInternal(this, input, options);
58
60
  }
61
+ /**
62
+ * Re-send the last message on this session — the clean way to retry a turn a
63
+ * throttle or transient failure interrupted. By default it REUSES the previous
64
+ * message's idempotency key, so if the original turn actually landed
65
+ * server-side the replay de-duplicates instead of creating a second billable
66
+ * turn; pass a fresh `idempotencyKey` to force a brand-new turn.
67
+ */
68
+ replayLast(options = {}) {
69
+ assertNoSessionSendSignal(options, "SessionHandle.replayLast");
70
+ const last = this.#lastSend;
71
+ if (last === undefined) {
72
+ throw new RunStateError("SessionHandle.replayLast: no message has been sent on this session yet");
73
+ }
74
+ return sendSessionInternal(this, last.input, {
75
+ ...options,
76
+ idempotencyKey: options.idempotencyKey ?? last.idempotencyKey
77
+ });
78
+ }
59
79
  async *#send(input, options) {
60
- const accepted = await operations.sendSessionMessage(this.#http, this.id, { input }, { idempotencyKey: options.idempotencyKey ?? generateIdempotencyKey() });
80
+ const idempotencyKey = options.idempotencyKey ?? generateIdempotencyKey();
81
+ this.#lastSend = { input, idempotencyKey };
82
+ const accepted = await operations.sendSessionMessage(this.#http, this.id, { input }, { idempotencyKey });
61
83
  this.#session = accepted.session;
62
84
  const turn = accepted.turn;
63
85
  const events = [];
@@ -72,14 +94,16 @@ export class SessionHandle {
72
94
  const readSession = await operations.getSession(this.#http, this.id).catch(() => this.#session);
73
95
  this.#session = withTerminalSessionStatus(readSession, terminalStatus);
74
96
  const outputs = await operations.listSessionOutputs(this.#http, this.id).catch(() => []);
97
+ const messages = projectAssistantMessages(events);
75
98
  return {
76
99
  sessionId: this.id,
77
100
  session: this.#session,
78
101
  turn,
79
102
  status: this.#session.status,
80
- text: textOf(events),
103
+ text: assistantTextFromEvents(events),
81
104
  events,
82
- outputs
105
+ outputs,
106
+ messages
83
107
  };
84
108
  }
85
109
  async suspend(options = {}) {
@@ -104,19 +128,39 @@ export class SessionHandle {
104
128
  }
105
129
  }
106
130
  /**
107
- * Accessor for the session's decoded assistant messages (buffered output
108
- * mode: one entry per assistant message). `list()` returns them oldest-first;
109
- * `last()`/`first()` return a single entry or `undefined` when empty.
131
+ * Accessor for the session's assistant messages. `all()` returns them
132
+ * oldest-first; `last()`/`first()` return a single entry or `undefined` when
133
+ * empty. The accessor is callable as a compatibility shim for older
134
+ * `session.messages().list()` callers.
110
135
  */
111
- messages() {
136
+ get messages() {
112
137
  const http = this.#http;
113
138
  const id = this.id;
114
- const list = async () => decodeAssistantText((await operations.listSessionEvents(http, id)));
115
- return {
116
- list,
117
- last: async () => (await list()).at(-1),
118
- first: async () => (await list())[0]
139
+ const fromEvents = async () => projectAssistantMessages(await operations.listSessionEvents(http, id));
140
+ const all = async () => {
141
+ try {
142
+ const page = await operations.listSessionMessages(http, id);
143
+ if (!Array.isArray(page.messages)) {
144
+ return fromEvents();
145
+ }
146
+ return page.messages.map(messageFromWire);
147
+ }
148
+ catch (err) {
149
+ if (!isMissingMessagesEndpoint(err)) {
150
+ throw err;
151
+ }
152
+ return fromEvents();
153
+ }
119
154
  };
155
+ let accessor;
156
+ accessor = (() => accessor);
157
+ Object.assign(accessor, {
158
+ all,
159
+ list: all,
160
+ last: async () => (await all()).at(-1),
161
+ first: async () => (await all())[0]
162
+ });
163
+ return accessor;
120
164
  }
121
165
  /**
122
166
  * Accessor for the session's event stream: the buffered `SessionEvent`
@@ -303,10 +347,15 @@ export class SessionClient {
303
347
  const { message, deleteAfter, messageIdempotencyKey, stream, ...createOptions } = options;
304
348
  assertNoLegacySessionFields(options, "Aex.sessions.run");
305
349
  const input = normaliseSessionInput(message, "Aex.sessions.run", "message");
306
- const session = await this.create(createOptions);
350
+ // Derive the message key from the create key (like the CLI) so a retried run
351
+ // with the same `idempotencyKey` de-duplicates BOTH the create and the
352
+ // billable turn — never a duplicate billable run.
353
+ const createKey = createOptions.idempotencyKey ?? generateIdempotencyKey();
354
+ const messageKey = messageIdempotencyKey ?? deriveMessageKey(createKey);
355
+ const session = await this.create({ ...createOptions, idempotencyKey: createKey });
307
356
  const result = await session.send(input, {
308
357
  ...(stream ?? {}),
309
- idempotencyKey: messageIdempotencyKey ?? generateIdempotencyKey()
358
+ idempotencyKey: messageKey
310
359
  }).done();
311
360
  if (deleteAfter) {
312
361
  await session.delete();
@@ -419,6 +468,199 @@ function sessionOutputs(http, id, fetchLike) {
419
468
  download: (selector, options) => downloadSessionOutput(http, id, selector, options)
420
469
  };
421
470
  }
471
+ function messageFromWire(message) {
472
+ return {
473
+ id: message.id,
474
+ sender: message.sender,
475
+ text: message.text,
476
+ ...(message.timestamp !== undefined ? { timestamp: message.timestamp } : {}),
477
+ ...(message.turnSeq !== undefined ? { turnSeq: message.turnSeq } : {}),
478
+ ...(message.sequence !== undefined ? { sequence: message.sequence } : {})
479
+ };
480
+ }
481
+ function isMissingMessagesEndpoint(err) {
482
+ return err instanceof AexApiError && (err.status === 404 || err.status === 405 || err.status === 501);
483
+ }
484
+ function projectAssistantMessages(events) {
485
+ const out = [];
486
+ const byMessageId = new Map();
487
+ for (let i = 0; i < events.length; i++) {
488
+ const event = events[i];
489
+ if (event.type !== "TEXT_MESSAGE_CONTENT")
490
+ continue;
491
+ const data = asRecord(event.data);
492
+ const text = typeof data.text === "string" ? data.text : undefined;
493
+ if (text === undefined)
494
+ continue;
495
+ const messageId = typeof data.messageId === "string" && data.messageId ? data.messageId : undefined;
496
+ const sequence = event.sequence ?? event.seq;
497
+ const timestamp = event.time ?? event.recordedAt ?? timestampFromEpochMs(event.receivedAt);
498
+ const turnSeq = typeof data.turnSeq === "number" ? data.turnSeq : undefined;
499
+ if (messageId !== undefined) {
500
+ const existing = byMessageId.get(messageId);
501
+ if (existing !== undefined) {
502
+ const current = out[existing];
503
+ out[existing] = {
504
+ ...current,
505
+ text: `${current.text}${text}`,
506
+ ...(timestamp !== undefined ? { timestamp } : {}),
507
+ ...(sequence !== undefined ? { sequence } : {}),
508
+ ...(turnSeq !== undefined ? { turnSeq } : {})
509
+ };
510
+ continue;
511
+ }
512
+ byMessageId.set(messageId, out.length);
513
+ }
514
+ out.push({
515
+ id: messageId ?? (typeof event.id === "string" && event.id ? event.id : `message-${i}`),
516
+ sender: "assistant",
517
+ text,
518
+ ...(timestamp !== undefined ? { timestamp } : {}),
519
+ ...(sequence !== undefined ? { sequence } : {}),
520
+ ...(turnSeq !== undefined ? { turnSeq } : {})
521
+ });
522
+ }
523
+ return out;
524
+ }
525
+ function assistantTextFromEvents(events) {
526
+ return assistantTextEntriesFromEvents(events).map((entry) => entry.text).join("");
527
+ }
528
+ function runTraceFromEvents(events) {
529
+ return {
530
+ toolCalls: toolCallsFromEvents(events),
531
+ usage: usageFromEvents(events),
532
+ text: assistantTextEntriesFromEvents(events)
533
+ };
534
+ }
535
+ function assistantTextEntriesFromEvents(events) {
536
+ const out = [];
537
+ for (const raw of events) {
538
+ const event = raw;
539
+ if (event.type !== "TEXT_MESSAGE_CONTENT")
540
+ continue;
541
+ const data = asRecord(event.data);
542
+ const text = typeof data.text === "string" ? data.text : undefined;
543
+ if (text === undefined)
544
+ continue;
545
+ const entry = { text };
546
+ const messageId = typeof data.messageId === "string" ? data.messageId : undefined;
547
+ if (messageId !== undefined)
548
+ entry.messageId = messageId;
549
+ if (typeof event.seq === "number")
550
+ entry.seq = event.seq;
551
+ const recordedAt = typeof event.recordedAt === "string" ? event.recordedAt : undefined;
552
+ if (recordedAt !== undefined)
553
+ entry.recordedAt = recordedAt;
554
+ out.push(entry);
555
+ }
556
+ return out;
557
+ }
558
+ function toolCallsFromEvents(events) {
559
+ const order = [];
560
+ const byId = new Map();
561
+ for (const event of events) {
562
+ const data = asRecord(event.data);
563
+ if (event.type === "TOOL_CALL_START") {
564
+ const id = typeof data.id === "string" ? data.id : undefined;
565
+ if (id === undefined)
566
+ continue;
567
+ const trace = {
568
+ id,
569
+ name: typeof data.name === "string" ? data.name : "",
570
+ args: asRecord(data.arguments)
571
+ };
572
+ const messageId = typeof data.messageId === "string" ? data.messageId : undefined;
573
+ if (messageId !== undefined)
574
+ trace.messageId = messageId;
575
+ if (typeof event.seq === "number")
576
+ trace.startSeq = event.seq;
577
+ if (typeof event.recordedAt === "string")
578
+ trace.startedAt = event.recordedAt;
579
+ if (!byId.has(id))
580
+ order.push(id);
581
+ byId.set(id, trace);
582
+ continue;
583
+ }
584
+ if (event.type === "TOOL_CALL_RESULT") {
585
+ const id = typeof data.id === "string" ? data.id : undefined;
586
+ if (id === undefined)
587
+ continue;
588
+ const result = {
589
+ isError: data.isError === true,
590
+ content: data.content ?? null
591
+ };
592
+ if (typeof event.seq === "number")
593
+ result.seq = event.seq;
594
+ if (typeof event.recordedAt === "string")
595
+ result.recordedAt = event.recordedAt;
596
+ let trace = byId.get(id);
597
+ if (trace === undefined) {
598
+ trace = { id, name: "", args: {} };
599
+ order.push(id);
600
+ byId.set(id, trace);
601
+ }
602
+ trace.result = result;
603
+ const duration = durationMs(trace.startedAt, result.recordedAt);
604
+ if (duration !== undefined)
605
+ trace.durationMs = duration;
606
+ }
607
+ }
608
+ return order.map((id) => byId.get(id));
609
+ }
610
+ function usageFromEvents(events) {
611
+ const totals = { inputTokens: 0, outputTokens: 0, cacheReadInputTokens: 0, cacheCreationInputTokens: 0 };
612
+ let seen = false;
613
+ for (const event of events) {
614
+ if (event.type !== "CUSTOM")
615
+ continue;
616
+ const data = asRecord(event.data);
617
+ if (data.name !== "aex.usage")
618
+ continue;
619
+ const value = asRecord(data.value);
620
+ const fields = [
621
+ ["input_tokens", "inputTokens"],
622
+ ["output_tokens", "outputTokens"],
623
+ ["cache_read_input_tokens", "cacheReadInputTokens"],
624
+ ["cache_creation_input_tokens", "cacheCreationInputTokens"]
625
+ ];
626
+ for (const [wireName, apiName] of fields) {
627
+ const n = value[wireName];
628
+ if (typeof n === "number" && Number.isFinite(n)) {
629
+ totals[apiName] += n;
630
+ seen = true;
631
+ }
632
+ }
633
+ }
634
+ if (!seen)
635
+ return {};
636
+ return {
637
+ inputTokens: totals.inputTokens,
638
+ outputTokens: totals.outputTokens,
639
+ cacheReadInputTokens: totals.cacheReadInputTokens,
640
+ cacheCreationInputTokens: totals.cacheCreationInputTokens,
641
+ totalTokens: totals.inputTokens + totals.outputTokens
642
+ };
643
+ }
644
+ function asRecord(value) {
645
+ return value && typeof value === "object" && !Array.isArray(value)
646
+ ? value
647
+ : {};
648
+ }
649
+ function timestampFromEpochMs(value) {
650
+ return typeof value === "number" && Number.isFinite(value)
651
+ ? new Date(value).toISOString()
652
+ : undefined;
653
+ }
654
+ function durationMs(start, end) {
655
+ if (start === undefined || end === undefined)
656
+ return undefined;
657
+ const a = Date.parse(start);
658
+ const b = Date.parse(end);
659
+ if (!Number.isFinite(a) || !Number.isFinite(b))
660
+ return undefined;
661
+ const delta = b - a;
662
+ return delta >= 0 ? delta : undefined;
663
+ }
422
664
  function isSessionTurnTerminalEvent(event, turnSeq) {
423
665
  const name = customName(event);
424
666
  if (name !== "aex.session.idle" &&
@@ -571,7 +813,7 @@ function unwrapSecretValue(value) {
571
813
  * `client.whoami()` if you want to introspect which workspace the
572
814
  * token resolves to.
573
815
  */
574
- export class AgentExecutor {
816
+ export class Aex {
575
817
  #http;
576
818
  /** The same fetch the HttpClient uses, threaded into `_uploadAsset`. */
577
819
  #fetch;
@@ -579,22 +821,30 @@ export class AgentExecutor {
579
821
  files;
580
822
  secrets;
581
823
  sessions;
582
- constructor(options) {
583
- if (!options.apiToken) {
584
- throw new Error("AgentExecutor: apiToken is required");
824
+ constructor(options, overrides = {}) {
825
+ const resolved = typeof options === "string" ? { ...overrides, apiKey: options } : options;
826
+ const apiKey = resolved.apiKey ?? resolved.apiToken;
827
+ if (!apiKey) {
828
+ throw new Error("Aex: apiKey is required");
585
829
  }
830
+ // Wrap the transport fetch (the caller's override, or global `fetch`) with
831
+ // the bounded-retry layer so every BFF request gets default resilience.
832
+ // The raw `#fetch` below stays unwrapped for the direct-to-storage asset PUT
833
+ // and presigned output GETs, which target object storage, not the API plane.
834
+ const baseFetch = resolved.fetch ?? ((input, init) => fetch(input, init));
835
+ const retryingFetch = withRetry(baseFetch, resolved.retry);
586
836
  this.#http = new HttpClient({
587
- ...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),
588
- apiToken: options.apiToken,
589
- ...(options.fetch ? { fetch: options.fetch } : {}),
837
+ ...(resolved.baseUrl ? { baseUrl: resolved.baseUrl } : {}),
838
+ apiToken: apiKey,
839
+ fetch: retryingFetch,
590
840
  // Opt-in local diagnostics: emit a redacted per-request trace to
591
841
  // stderr. Uploads nothing. A caller wanting a custom sink can pass
592
842
  // a function instead of `true`.
593
- ...(options.debug
594
- ? { debug: typeof options.debug === "function" ? options.debug : (line) => console.error(line) }
843
+ ...(resolved.debug
844
+ ? { debug: typeof resolved.debug === "function" ? resolved.debug : (line) => console.error(line) }
595
845
  : {})
596
846
  });
597
- this.#fetch = options.fetch;
847
+ this.#fetch = resolved.fetch;
598
848
  this.agentsMd = new AgentsMdClient(this.#http);
599
849
  this.files = new FilesClient(this.#http);
600
850
  this.secrets = new SecretsClient(this.#http);
@@ -646,10 +896,15 @@ export class AgentExecutor {
646
896
  ...(opts.idleTimeoutMs !== undefined ? { idleTimeoutMs: opts.idleTimeoutMs } : {}),
647
897
  ...(opts.pingIntervalMs !== undefined ? { pingIntervalMs: opts.pingIntervalMs } : {})
648
898
  };
649
- const session = await this.sessions.create(createOptions);
899
+ // Derive the message key from the create key (like the CLI) so a retried
900
+ // run with the same `idempotencyKey` de-duplicates BOTH the create and the
901
+ // billable turn server-side — never a duplicate billable run (sdk-dx-3).
902
+ const createKey = createOptions.idempotencyKey ?? generateIdempotencyKey();
903
+ const messageKey = messageIdempotencyKey ?? deriveMessageKey(createKey);
904
+ const session = await this.sessions.create({ ...createOptions, idempotencyKey: createKey });
650
905
  const turnResult = await sendSessionInternal(session, input, {
651
906
  ...streamOptions,
652
- idempotencyKey: messageIdempotencyKey ?? generateIdempotencyKey()
907
+ idempotencyKey: messageKey
653
908
  }).done();
654
909
  if (deleteAfter) {
655
910
  await session.delete();
@@ -670,15 +925,29 @@ export class AgentExecutor {
670
925
  status: turnResult.status,
671
926
  ok,
672
927
  text: turnResult.text,
928
+ messages: turnResult.messages,
673
929
  events,
674
- trace: summarizeRunTrace(events),
930
+ trace: runTraceFromEvents(events),
675
931
  outputs,
676
932
  ...(turnResult.session.usage ? { usage: turnResult.session.usage } : {}),
677
933
  ...(typeof costUsd === "number" ? { costUsd } : {}),
678
934
  ...(!ok && errorMessage ? { error: errorMessage } : {})
679
935
  };
680
936
  if (opts.throwOnFailure && !ok) {
681
- throw new RunStateError(`AgentExecutor.run: session ${runId} ended ${turnResult.status}${errorMessage ? `: ${errorMessage}` : ""}`, { runId, status: turnResult.status });
937
+ // A turn that failed because the upstream provider throttled us surfaces
938
+ // as a structured, non-leaky AexRateLimitError carrying the provider
939
+ // fault, so callers can branch on `isRateLimited(err)` and replay.
940
+ const throttle = throttleFromSession(turnResult.session);
941
+ if (throttle) {
942
+ throw new AexRateLimitError({
943
+ status: throttle.status ?? 429,
944
+ attempts: 1,
945
+ source: "provider",
946
+ providerFault: throttle,
947
+ ...(throttle.retryAfterMs !== undefined ? { retryAfterMs: throttle.retryAfterMs } : {})
948
+ });
949
+ }
950
+ throw new RunStateError(`Aex.run: session ${runId} ended ${turnResult.status}${errorMessage ? `: ${errorMessage}` : ""}`, { runId, status: turnResult.status });
682
951
  }
683
952
  return result;
684
953
  }
@@ -708,8 +977,6 @@ export class AgentExecutor {
708
977
  if (typeof options.model !== "string" || !options.model) {
709
978
  throw new RunConfigValidationError("Aex.openSession: model is required");
710
979
  }
711
- const { endpoints: proxyEndpointDeclarations, auth: proxyEndpointAuthFromInstances } = splitProxyEndpoints(options.proxyEndpoints ?? []);
712
- const mergedProxyAuth = mergeProxyEndpointAuth(proxyEndpointAuthFromInstances, []);
713
980
  const { declarations: secretEnvDeclarations, values: envSecretValues } = splitSecretEnv(options.environment?.secrets);
714
981
  let limits;
715
982
  try {
@@ -752,7 +1019,6 @@ export class AgentExecutor {
752
1019
  const secrets = {
753
1020
  ...(options.apiKeys ? { apiKeys: options.apiKeys } : {}),
754
1021
  ...(mergedMcpSecrets.length > 0 ? { mcpServers: mergedMcpSecrets } : {}),
755
- ...(mergedProxyAuth.length > 0 ? { proxyEndpointAuth: mergedProxyAuth } : {}),
756
1022
  ...(Object.keys(envSecretValues).length > 0 ? { envSecrets: envSecretValues } : {})
757
1023
  };
758
1024
  const retention = sessionRetentionForWire(options);
@@ -766,10 +1032,7 @@ export class AgentExecutor {
766
1032
  // Operational/delivery concern — sibling of secrets, NOT part of the
767
1033
  // hashed submission. Delivered at the settle-consistent barrier.
768
1034
  ...(options.webhook ? { webhook: options.webhook } : {}),
769
- secrets,
770
- ...(proxyEndpointDeclarations.length > 0
771
- ? { proxyEndpoints: proxyEndpointDeclarations }
772
- : {})
1035
+ secrets
773
1036
  };
774
1037
  }
775
1038
  /**
@@ -784,9 +1047,6 @@ export class AgentExecutor {
784
1047
  return operations.whoami(this.#http);
785
1048
  }
786
1049
  }
787
- /** Canonical SDK client name. `AgentExecutor` remains as a compatibility alias. */
788
- export class Aex extends AgentExecutor {
789
- }
790
1050
  // `Run.status` is a loose `string` on the wire shape, so we membership-test
791
1051
  // against the canonical terminal set rather than re-deriving one (which is how
792
1052
  // `timed_out` got dropped from the old hardcoded list).
@@ -843,7 +1103,7 @@ function resolveOutputFileSelector(outputs, selector, runId) {
843
1103
  if (isOutputPathSelector(selector)) {
844
1104
  const target = normalizeOutputLookupPath(selector.path);
845
1105
  if (!target) {
846
- throw new RunStateError("AgentExecutor.downloadOutput: output path must be non-empty", {
1106
+ throw new RunStateError("Aex.downloadOutput: output path must be non-empty", {
847
1107
  runId,
848
1108
  path: selector.path
849
1109
  });
@@ -860,15 +1120,15 @@ function resolveOutputFileSelector(outputs, selector, runId) {
860
1120
  if (matches.length === 1)
861
1121
  return matches[0];
862
1122
  if (matches.length > 1) {
863
- throw new RunStateError(`AgentExecutor.downloadOutput: output path "${selector.path}" matched multiple files`, { runId, path: selector.path, matches: matches.map((output) => output.filename ?? output.id) });
1123
+ throw new RunStateError(`Aex.downloadOutput: output path "${selector.path}" matched multiple files`, { runId, path: selector.path, matches: matches.map((output) => output.filename ?? output.id) });
864
1124
  }
865
- throw new RunStateError(`AgentExecutor.downloadOutput: output path "${selector.path}" was not found`, {
1125
+ throw new RunStateError(`Aex.downloadOutput: output path "${selector.path}" was not found`, {
866
1126
  runId,
867
1127
  path: selector.path
868
1128
  });
869
1129
  }
870
1130
  if (typeof selector.id !== "string" || selector.id.length === 0) {
871
- throw new RunStateError("AgentExecutor.downloadOutput: selector must include an output id or path", { runId });
1131
+ throw new RunStateError("Aex.downloadOutput: selector must include an output id or path", { runId });
872
1132
  }
873
1133
  return { ...selector, id: selector.id };
874
1134
  }
@@ -905,6 +1165,40 @@ function generateIdempotencyKey() {
905
1165
  return cryptoObj.randomUUID();
906
1166
  return `idem-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
907
1167
  }
1168
+ /**
1169
+ * Derive the message idempotency key from the session-create key. Mirrors the
1170
+ * CLI (`<createKey>:message`) so a retried `run` / `sessions.run` that reuses
1171
+ * one `idempotencyKey` de-duplicates BOTH the create and the billable turn.
1172
+ */
1173
+ function deriveMessageKey(createKey) {
1174
+ return `${createKey}:message`;
1175
+ }
1176
+ /**
1177
+ * Extract a throttle-class {@link ProviderFault} from a failed session record.
1178
+ * Reads a structured `providerFault` / `error` field first (the shape the
1179
+ * runtime is expected to emit on a throttled turn), then falls back to a
1180
+ * heuristic scan of `errorMessage`. Returns `undefined` when the failure is not
1181
+ * a throttle.
1182
+ */
1183
+ function throttleFromSession(session) {
1184
+ const fault = parseProviderFault(session.providerFault) ??
1185
+ parseProviderFault(session.error) ??
1186
+ faultFromErrorMessage(typeof session.errorMessage === "string" ? session.errorMessage : undefined);
1187
+ return fault && isThrottleFault(fault) ? fault : undefined;
1188
+ }
1189
+ /** Last-resort throttle detection from a free-text run error message. */
1190
+ function faultFromErrorMessage(message) {
1191
+ if (message === undefined || message.length === 0)
1192
+ return undefined;
1193
+ const lower = message.toLowerCase();
1194
+ if (/\b429\b|rate.?limit|too many requests/.test(lower)) {
1195
+ return { kind: "rate_limit", message };
1196
+ }
1197
+ if (/\b529\b|overloaded/.test(lower)) {
1198
+ return { kind: "overloaded", message };
1199
+ }
1200
+ return undefined;
1201
+ }
908
1202
  function normaliseSessionInput(input, surface, field) {
909
1203
  if (typeof input === "string") {
910
1204
  if (!input) {
@@ -924,6 +1218,7 @@ function normaliseSessionInput(input, surface, field) {
924
1218
  }
925
1219
  function assertNoLegacySessionFields(options, surface) {
926
1220
  const record = options;
1221
+ const removedProxyField = "proxy" + "Endpoints";
927
1222
  const messages = {
928
1223
  input: "send user messages with session.send(...) or use run({ message }).",
929
1224
  prompt: "use message for one-shot run input or session.send(...) for follow-up messages.",
@@ -939,7 +1234,8 @@ function assertNoLegacySessionFields(options, surface) {
939
1234
  limits: "use overrides.",
940
1235
  timeout: "use overrides.timeout.",
941
1236
  signal: "use session.cancel() / session.suspend() for remote control.",
942
- postHook: "send a follow-up validation message when the session returns idle."
1237
+ postHook: "send a follow-up validation message when the session returns idle.",
1238
+ [removedProxyField]: "proxy endpoints are not part of the public SDK session API."
943
1239
  };
944
1240
  for (const [field, message] of Object.entries(messages)) {
945
1241
  if (Object.prototype.hasOwnProperty.call(record, field)) {
@@ -1161,29 +1457,4 @@ function mergeMcpServers(inputs, explicitSecrets) {
1161
1457
  mergedMcpSecrets: Array.from(secretByName.values())
1162
1458
  };
1163
1459
  }
1164
- /**
1165
- * Merge `ProxyEndpoint`-derived auth entries with any
1166
- * `secrets.proxyEndpointAuth` the caller passed explicitly. Per-instance
1167
- * auth values win on the same `name`; a type mismatch (e.g. instance
1168
- * declares `bearer` but secrets carry `header` for the same name) is a
1169
- * call-site error and we throw at the SDK boundary instead of letting
1170
- * the BFF reject the submission an HTTP request later.
1171
- */
1172
- function mergeProxyEndpointAuth(fromInstances, fromExplicitSecrets) {
1173
- if (fromInstances.length === 0 && fromExplicitSecrets.length === 0)
1174
- return [];
1175
- const byName = new Map();
1176
- for (const entry of fromExplicitSecrets) {
1177
- byName.set(entry.name, entry);
1178
- }
1179
- for (const entry of fromInstances) {
1180
- const existing = byName.get(entry.name);
1181
- if (existing && existing.value.type !== entry.value.type) {
1182
- throw new RunConfigValidationError(`aex: proxyEndpoint "${entry.name}" auth type conflicts ` +
1183
- `with secrets.proxyEndpointAuth (instance=${entry.value.type}, secrets=${existing.value.type})`);
1184
- }
1185
- byName.set(entry.name, entry);
1186
- }
1187
- return Array.from(byName.values());
1188
- }
1189
1460
  //# sourceMappingURL=client.js.map