@agent-native/core 0.175.3 → 0.175.4-nightly-20260826183934

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.
@@ -453,7 +453,14 @@ class AISDKEngine {
453
453
  yield recovered;
454
454
  }
455
455
  yield { type: "assistant-content", parts: assistantContent };
456
- if (!credentialFailureRecorded) {
456
+ // Clearing the marker asserts "this credential demonstrably works", so
457
+ // only a turn that actually completed may do it. A turn that ended in
458
+ // error proves nothing about the credential, and this ran on every one:
459
+ // a single unrelated 500 re-admitted a credential a 401 had just pinned,
460
+ // so the next person's first prompt spent itself rediscovering the same
461
+ // rejection. `credentialFailureRecorded` only covers the 401 path.
462
+ const stoppedWithError = bufferedStop?.type === "stop" && bufferedStop.reason === "error";
463
+ if (!credentialFailureRecorded && !stoppedWithError) {
457
464
  await clearProviderCredentialAuthFailure({
458
465
  key: PROVIDER_ENV_VARS[this.provider][0],
459
466
  value: this.apiKey,
@@ -42,22 +42,22 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
42
42
  avgEvalScore: number;
43
43
  } | {
44
44
  error?: undefined;
45
+ ok?: undefined;
45
46
  summary: import("./types.js").TraceSummary;
46
47
  spans: import("./types.js").TraceSpan[];
47
48
  id?: undefined;
48
- ok?: undefined;
49
49
  } | {
50
50
  error?: undefined;
51
+ ok?: undefined;
51
52
  summary?: undefined;
52
53
  spans?: undefined;
53
54
  id: string;
54
- ok?: undefined;
55
55
  } | {
56
+ ok?: undefined;
56
57
  summary?: undefined;
57
58
  spans?: undefined;
58
59
  id?: undefined;
59
60
  error: any;
60
- ok?: undefined;
61
61
  } | {
62
62
  error?: undefined;
63
63
  summary?: undefined;
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- status?: undefined;
38
37
  ok?: undefined;
38
+ status?: undefined;
39
39
  } | {
40
+ error?: undefined;
40
41
  ok: boolean;
41
42
  status: string;
42
- error?: undefined;
43
43
  } | {
44
+ ok?: undefined;
44
45
  error: string;
45
46
  removed?: undefined;
46
- ok?: undefined;
47
47
  } | {
48
+ error?: undefined;
48
49
  ok: boolean;
49
50
  removed: boolean;
50
- error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
54
54
  * or the current stored value without changing anything.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
+ ok?: undefined;
57
58
  error: string;
58
59
  note?: undefined;
59
- ok?: undefined;
60
60
  } | {
61
+ error?: undefined;
61
62
  ok: boolean;
62
63
  note?: undefined;
63
- error?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  ok: boolean;
66
67
  note: string;
67
- error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
+ error?: undefined;
98
99
  ok: boolean;
99
100
  key: string;
100
- error?: undefined;
101
101
  } | {
102
+ error?: undefined;
102
103
  ok: boolean;
103
104
  removed: boolean;
104
- error?: undefined;
105
105
  }>>;
@@ -841,6 +841,38 @@ export async function resolveBuilderGatewayAuth() {
841
841
  ? { authorization: `Bearer ${legacyKey}`, spaceId: null, userId: null }
842
842
  : null;
843
843
  }
844
+ /**
845
+ * Both auth-failure markers below are fingerprinted on the credential VALUE, so
846
+ * a rotated or corrected credential never matches the old marker and is usable
847
+ * immediately — the TTL is not what unpins it. The TTL covers only the case
848
+ * where the SAME value starts working again: a plan upgrade, a re-enabled
849
+ * gateway, a transient upstream 401.
850
+ *
851
+ * Re-admitting on a flat timer means a credential that is simply wrong is
852
+ * retested on that cadence forever, and each retest is paid for by whichever
853
+ * user's turn happens to land first — they get a 401 while the next lane serves
854
+ * everyone after them. That is the whole shape of the recurring "the saved
855
+ * provider key was rejected" report. Back off per consecutive strike so a dead
856
+ * credential stops costing a turn every quarter hour, while one that genuinely
857
+ * recovers is still retried within the day.
858
+ */
859
+ const AUTH_FAILURE_MAX_TTL_MS = 24 * 60 * 60 * 1000;
860
+ const AUTH_FAILURE_MAX_STRIKES = 8;
861
+ function authFailureStrikes(row) {
862
+ const raw = row?.strikes;
863
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 1
864
+ ? Math.min(Math.floor(raw), AUTH_FAILURE_MAX_STRIKES)
865
+ : 1;
866
+ }
867
+ function authFailureTtlMs(baseTtlMs, row) {
868
+ return Math.min(baseTtlMs * 2 ** (authFailureStrikes(row) - 1), AUTH_FAILURE_MAX_TTL_MS);
869
+ }
870
+ /** Next strike count for a marker being (re-)armed on the same fingerprint. */
871
+ async function nextAuthFailureStrikes(settingKey) {
872
+ const { getSetting } = await import("../settings/store.js");
873
+ const prior = await getSetting(settingKey, { bypassCache: true });
874
+ return Math.min(authFailureStrikes(prior) + (prior ? 1 : 0), AUTH_FAILURE_MAX_STRIKES);
875
+ }
844
876
  const BUILDER_AUTH_FAILURE_SETTING_PREFIX = "builder-auth-failure:";
845
877
  /**
846
878
  * Stale markers expire so a rejected model or a transient gateway failure
@@ -873,10 +905,10 @@ export async function getBuilderCredentialAuthFailure(creds = {}) {
873
905
  if (!row)
874
906
  return null;
875
907
  const at = typeof row.at === "number" ? row.at : Date.now();
876
- if (Date.now() - at > BUILDER_AUTH_FAILURE_TTL_MS) {
877
- if (typeof settings.deleteSetting === "function") {
878
- await settings.deleteSetting(settingKey).catch(() => { });
879
- }
908
+ // Expired means "usable again", not "never failed": the row stays so the
909
+ // strike count survives, and a credential that fails on re-admission backs
910
+ // off further instead of resetting to the base TTL. A success clears it.
911
+ if (Date.now() - at > authFailureTtlMs(BUILDER_AUTH_FAILURE_TTL_MS, row)) {
880
912
  return null;
881
913
  }
882
914
  return {
@@ -902,12 +934,15 @@ export async function recordBuilderCredentialAuthFailure(details) {
902
934
  if (!fingerprint)
903
935
  return;
904
936
  const { putSetting } = await import("../settings/store.js");
905
- await putSetting(builderAuthFailureSettingKey(fingerprint), {
937
+ const settingKey = builderAuthFailureSettingKey(fingerprint);
938
+ const strikes = await nextAuthFailureStrikes(settingKey);
939
+ await putSetting(settingKey, {
906
940
  fingerprint,
907
941
  message: details?.message ||
908
942
  "Builder rejected the connected credentials. Reconnect Builder.io (free tier available).",
909
943
  ...(typeof details?.status === "number" && { status: details.status }),
910
944
  ...(details?.code && { code: details.code }),
945
+ strikes,
911
946
  at: Date.now(),
912
947
  ownerEmail: getRequestUserEmail() ?? null,
913
948
  orgId: getRequestOrgId() ?? null,
@@ -961,10 +996,9 @@ export async function getProviderCredentialAuthFailure(opts) {
961
996
  if (row.fingerprint !== fingerprint)
962
997
  return null;
963
998
  const at = typeof row.at === "number" ? row.at : Date.now();
964
- if (Date.now() - at > PROVIDER_AUTH_FAILURE_TTL_MS) {
965
- if (typeof settings.deleteSetting === "function") {
966
- await settings.deleteSetting(settingKey).catch(() => { });
967
- }
999
+ // See `getBuilderCredentialAuthFailure`: the row outlives its TTL so the
1000
+ // strike count does, and re-admission backs off instead of resetting.
1001
+ if (Date.now() - at > authFailureTtlMs(PROVIDER_AUTH_FAILURE_TTL_MS, row)) {
968
1002
  return null;
969
1003
  }
970
1004
  return {
@@ -994,12 +1028,15 @@ export async function recordProviderCredentialAuthFailure(opts) {
994
1028
  if (!fingerprint)
995
1029
  return;
996
1030
  const { putSetting } = await import("../settings/store.js");
997
- await putSetting(providerAuthFailureSettingKey(fingerprint), {
1031
+ const settingKey = providerAuthFailureSettingKey(fingerprint);
1032
+ const strikes = await nextAuthFailureStrikes(settingKey);
1033
+ await putSetting(settingKey, {
998
1034
  fingerprint,
999
1035
  key,
1000
1036
  message: opts.message || "The model provider rejected the saved API key.",
1001
1037
  ...(typeof opts.status === "number" && { status: opts.status }),
1002
1038
  ...(opts.code && { code: opts.code }),
1039
+ strikes,
1003
1040
  at: Date.now(),
1004
1041
  ownerEmail: getRequestUserEmail() ?? null,
1005
1042
  orgId: getRequestOrgId() ?? null,
@@ -13,11 +13,11 @@ discover them, and the browser mediates execution.
13
13
 
14
14
  The framework supports both directions:
15
15
 
16
- | You want to... | Use |
17
- | --- | --- |
18
- | Make selected actions in your app available to a browser agent | `createAgentNativeWebMcpRegistration` |
19
- | Let Agent-Native chat discover and call WebMCP tools in the current page | `createAgentNativeWebMcpClient` plus `webmcp` on `AgentNativeEmbedded` or `AgentNativeFrame` |
20
- | Connect a headless or remote agent to an app | [MCP Server](/docs/mcp-protocol), [External Agents](/docs/external-agents), or [A2A](/docs/a2a-protocol) |
16
+ | You want to... | Use |
17
+ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
18
+ | Make selected actions in your app available to a browser agent | `createAgentNativeWebMcpRegistration` |
19
+ | Let Agent-Native chat discover and call WebMCP tools in the current page | `createAgentNativeWebMcpClient` plus `webmcp` on `AgentNativeEmbedded` or `AgentNativeFrame` |
20
+ | Connect a headless or remote agent to an app | [MCP Server](/docs/mcp-protocol), [External Agents](/docs/external-agents), or [A2A](/docs/a2a-protocol) |
21
21
 
22
22
  WebMCP is not a network MCP server and does not make a closed app globally
23
23
  discoverable. A consuming agent needs an open, connected browser page and the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.175.3",
3
+ "version": "0.175.4-nightly-20260826183934",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {