@gajae-code/ai 0.18.11 → 0.18.12

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 (75) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +1 -1
  3. package/dist/types/auth-broker/wire-schemas.d.ts +30 -0
  4. package/dist/types/auth-storage.d.ts +80 -8
  5. package/dist/types/bedrock-claude-cache-policy.d.ts +1 -1
  6. package/dist/types/model-cache.d.ts +6 -4
  7. package/dist/types/model-manager.d.ts +2 -0
  8. package/dist/types/openai-completions-compat.d.ts +2 -1
  9. package/dist/types/providers/anthropic.d.ts +10 -0
  10. package/dist/types/providers/aws-credential-config.d.ts +5 -0
  11. package/dist/types/providers/google-auth.d.ts +7 -0
  12. package/dist/types/providers/google-gemini-headers.d.ts +10 -5
  13. package/dist/types/providers/kiro-api-key.d.ts +5 -0
  14. package/dist/types/providers/openai-chat-server-schema.d.ts +2 -1
  15. package/dist/types/providers/openai-completions.d.ts +1 -1
  16. package/dist/types/providers/openai-responses-shared.d.ts +2 -1
  17. package/dist/types/stream.d.ts +33 -1
  18. package/dist/types/types.d.ts +26 -3
  19. package/dist/types/usage.d.ts +3 -0
  20. package/dist/types/utils/discovery/antigravity.d.ts +11 -0
  21. package/dist/types/utils/idle-iterator.d.ts +3 -2
  22. package/dist/types/utils/oauth/types.d.ts +9 -0
  23. package/package.json +4 -4
  24. package/src/auth-broker/remote-store.ts +62 -11
  25. package/src/auth-broker/wire-schemas.ts +2 -0
  26. package/src/auth-gateway/server.ts +3 -2
  27. package/src/auth-storage.ts +647 -194
  28. package/src/bedrock-claude-cache-policy.d.ts +1 -1
  29. package/src/bedrock-claude-cache-policy.ts +2 -2
  30. package/src/model-cache.ts +45 -10
  31. package/src/model-manager.ts +18 -6
  32. package/src/model-thinking.ts +37 -7
  33. package/src/models.json +2931 -1670
  34. package/src/openai-completions-compat.d.ts +2 -1
  35. package/src/openai-completions-compat.ts +5 -0
  36. package/src/provider-models/google.ts +7 -1
  37. package/src/provider-models/openai-compat.ts +26 -0
  38. package/src/providers/amazon-bedrock.ts +20 -11
  39. package/src/providers/anthropic.d.ts +10 -0
  40. package/src/providers/anthropic.ts +139 -4
  41. package/src/providers/aws-credential-config.d.ts +5 -0
  42. package/src/providers/aws-credential-config.ts +29 -1
  43. package/src/providers/google-auth.d.ts +7 -0
  44. package/src/providers/google-auth.ts +165 -4
  45. package/src/providers/google-gemini-headers.d.ts +10 -5
  46. package/src/providers/google-gemini-headers.ts +11 -6
  47. package/src/providers/google-vertex.ts +2 -4
  48. package/src/providers/kiro-api-key.d.ts +5 -0
  49. package/src/providers/kiro-api-key.ts +379 -68
  50. package/src/providers/ollama.ts +19 -9
  51. package/src/providers/openai-anthropic-shim.ts +1 -0
  52. package/src/providers/openai-chat-server-schema.ts +1 -1
  53. package/src/providers/openai-chat-server.ts +14 -2
  54. package/src/providers/openai-codex-responses.ts +21 -14
  55. package/src/providers/openai-completions.d.ts +1 -1
  56. package/src/providers/openai-completions.ts +65 -4
  57. package/src/providers/openai-responses-server.ts +10 -1
  58. package/src/providers/openai-responses-shared.d.ts +2 -1
  59. package/src/providers/openai-responses-shared.ts +42 -1
  60. package/src/providers/openai-responses.ts +51 -10
  61. package/src/providers/pi-native-server.ts +1 -1
  62. package/src/stream.d.ts +33 -1
  63. package/src/stream.ts +107 -13
  64. package/src/types.d.ts +26 -3
  65. package/src/types.ts +55 -2
  66. package/src/usage/claude.ts +4 -2
  67. package/src/usage.ts +3 -0
  68. package/src/utils/discovery/antigravity.ts +144 -2
  69. package/src/utils/idle-iterator.d.ts +3 -2
  70. package/src/utils/idle-iterator.ts +30 -10
  71. package/src/utils/oauth/anthropic.ts +23 -22
  72. package/src/utils/oauth/api-key-validation.ts +34 -11
  73. package/src/utils/oauth/types.d.ts +9 -0
  74. package/src/utils/oauth/types.ts +9 -0
  75. package/src/utils/parse-bind.d.ts +35 -0
@@ -89,6 +89,8 @@ export interface ToolChoiceCompat {
89
89
  supportsToolChoice?: boolean;
90
90
  /** Legacy flag for forced tool_choice support. */
91
91
  supportsForcedToolChoice?: boolean;
92
+ /** Whether the provider supports OpenAI ultrafast service tier. Default: false. */
93
+ supportsUltrafastTier?: boolean;
92
94
  }
93
95
  export type CacheRetention = "none" | "short" | "long";
94
96
  /**
@@ -107,9 +109,11 @@ export type CacheRetention = "none" | "short" | "long";
107
109
  * - `"openai-only"` → `"priority"` on `openai` and `OpenAI code provider`; ignored elsewhere.
108
110
  * - `"Anthropic model-only"` → `"priority"` on direct `anthropic` (not Bedrock/Vertex Anthropic model).
109
111
  */
110
- export type ServiceTier = "auto" | "default" | "flex" | "scale" | "priority" | "openai-only" | "claude-only";
112
+ export type ServiceTier = "auto" | "default" | "flex" | "scale" | "priority" | "ultrafast" | "openai-only" | "claude-only";
111
113
  /** Resolved tier — one of the values that providers actually consume on the wire. */
112
114
  export type ResolvedServiceTier = Exclude<ServiceTier, "openai-only" | "claude-only">;
115
+ /** Parse a provider-reported service tier, leaving unknown response values for request fallback. */
116
+ export declare function parseResolvedServiceTier(value: unknown): ResolvedServiceTier | undefined;
113
117
  /**
114
118
  * Resolves a possibly scoped `ServiceTier` to the effective tier for the
115
119
  * given provider. Scoped values match their target family and otherwise
@@ -119,13 +123,19 @@ export declare function resolveServiceTier(serviceTier: ServiceTier | null | und
119
123
  /**
120
124
  * True when the (possibly scoped) tier should be sent as an OpenAI-compatible
121
125
  * `service_tier` request field. Custom providers must explicitly opt in through
122
- * `compat.supportsServiceTier`; unknown providers remain fail-closed.
126
+ * `compat.supportsServiceTier` for the existing tiers or a model-specific
127
+ * `supportsUltrafastTier` capability for ultrafast; unknown providers remain fail-closed.
128
+ *
129
+ * Note: ultrafast has an additional gating requirement via `modelSupportsUltrafastTier`
130
+ * to ensure it is only sent to models that explicitly support it.
123
131
  */
124
- export declare function shouldSendServiceTier(serviceTier: ServiceTier | null | undefined, provider: Provider | undefined, supportsServiceTier?: boolean): boolean;
132
+ export declare function shouldSendServiceTier(serviceTier: ServiceTier | null | undefined, provider: Provider | undefined, supportsServiceTier?: boolean, supportsUltrafastTier?: boolean): boolean;
125
133
  /**
126
134
  * True when a priority tier is realized as a fast-mode request on the provider's
127
135
  * wire protocol. Custom OpenAI-compatible proxies opt in explicitly rather than
128
136
  * inheriting support merely because their API shape resembles OpenAI.
137
+ *
138
+ * Note: ultrafast is distinct from priority and is not considered fast-mode.
129
139
  */
130
140
  export declare function isFastModeEffectiveForProvider(serviceTier: ServiceTier | null | undefined, provider: Provider | undefined, supportsServiceTier?: boolean): boolean;
131
141
  /**
@@ -611,6 +621,8 @@ export interface AssistantMessage {
611
621
  provider: Provider;
612
622
  model: string;
613
623
  responseId?: string;
624
+ /** Effective service tier reported by the provider, or the sent tier when the provider omits it. */
625
+ serviceTier?: ResolvedServiceTier;
614
626
  usage: Usage;
615
627
  stopReason: StopReason;
616
628
  errorMessage?: string;
@@ -905,6 +917,7 @@ export type AssistantMessageEvent = {
905
917
  contentIndex?: undefined;
906
918
  reason: Extract<StopReason, "aborted" | "error">;
907
919
  error: AssistantMessage;
920
+ partial?: AssistantMessage;
908
921
  } | {
909
922
  type: "toolChoiceIncapability";
910
923
  contentIndex?: undefined;
@@ -949,6 +962,12 @@ export interface OpenAICompat extends ToolChoiceCompat {
949
962
  * preserves or intentionally realizes OpenAI priority processing.
950
963
  */
951
964
  supportsServiceTier?: boolean;
965
+ /**
966
+ * Whether the model explicitly supports the ultrafast service tier.
967
+ * Only models that set this flag will send ultrafast tier to the API.
968
+ * Models: gpt-6-astra and gpt-6.1-sol.
969
+ */
970
+ supportsUltrafastTier?: boolean;
952
971
  /**
953
972
  * Tool names the provider reserves for its own built-ins and refuses to
954
973
  * accept as custom function declarations. A colliding tool is **dropped**
@@ -1170,6 +1189,8 @@ export interface Model<TApi extends Api = any> {
1170
1189
  requestTransform?: ModelRequestTransform;
1171
1190
  /** Default prompt-cache retention preference for this model when the request omits one. */
1172
1191
  cacheRetention?: CacheRetention;
1192
+ /** Configured inter-event idle timeout for this model, in milliseconds. */
1193
+ streamIdleTimeoutMs?: number;
1173
1194
  /** Provider-assigned priority value (lower = higher priority). */
1174
1195
  priority?: number;
1175
1196
  /** Canonical thinking capability metadata for this model. */
@@ -1194,3 +1215,5 @@ export interface Model<TApi extends Api = any> {
1194
1215
  }
1195
1216
  /** True when a model explicitly opts into OpenAI-compatible `service_tier` forwarding. */
1196
1217
  export declare function modelSupportsServiceTier(model: Pick<Model, "compat"> | undefined): boolean;
1218
+ /** True when a model explicitly opts into OpenAI ultrafast service tier support. */
1219
+ export declare function modelSupportsUltrafastTier(model: Pick<Model, "compat"> | undefined): boolean;
@@ -230,6 +230,9 @@ export interface UsageCredential {
230
230
  email?: string;
231
231
  enterpriseUrl?: string;
232
232
  profileArn?: string;
233
+ /** Organization the OAuth token is scoped to; usage limits are per organization. */
234
+ organizationId?: string;
235
+ organizationName?: string;
233
236
  mcpBinding?: UsageMCPOAuthBinding;
234
237
  metadata?: Record<string, unknown>;
235
238
  }
@@ -1,4 +1,9 @@
1
1
  import type { Model } from "../../types";
2
+ /**
3
+ * Returns the default Antigravity discovery User-Agent.
4
+ * Can be overridden via PI_AI_ANTIGRAVITY_USER_AGENT environment variable.
5
+ */
6
+ export declare function getDefaultAntigravityUserAgent(): string;
2
7
  /**
3
8
  * Raw model metadata returned by Antigravity's `fetchAvailableModels` endpoint.
4
9
  */
@@ -15,6 +20,12 @@ export interface AntigravityDiscoveryApiModel {
15
20
  modelProvider?: string;
16
21
  isInternal?: boolean;
17
22
  supportsVideo?: boolean;
23
+ /** Account-specific entitlement status */
24
+ entitlement?: string;
25
+ /** Whether the model is disabled for this account */
26
+ disabled?: boolean;
27
+ /** Per-account quota/tier information */
28
+ quota?: string;
18
29
  }
19
30
  /**
20
31
  * Grouping metadata used by Antigravity to surface recommended model ids.
@@ -23,11 +23,12 @@ export declare function getStreamIdleTimeoutRemediationMs(): number;
23
23
  * Returns the idle timeout used for OpenAI-family streaming transports.
24
24
  *
25
25
  * Honors `GJC_OPENAI_STREAM_IDLE_TIMEOUT_MS` first (`PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` is the legacy alias). Set `=0` to disable.
26
+ * A configured model timeout takes precedence over the built-in provider/model floors; environment overrides win over it.
26
27
  * When `provider` is given, long-reasoning hosts (xAI Grok and Grok Build) use that floor instead of the 120s default.
27
28
  * Grok models reached through other OpenAI-compatible hosts (`openrouter/x-ai/grok-*`, kilo, litellm, …) get the
28
29
  * same floor keyed on the model id, because long-reasoning silence is a property of the model (#4797).
29
30
  */
30
- export declare function getOpenAIStreamIdleTimeoutMs(provider?: string, modelId?: string): number | undefined;
31
+ export declare function getOpenAIStreamIdleTimeoutMs(provider?: string, modelId?: string, configuredTimeoutMs?: number): number | undefined;
31
32
  /**
32
33
  * Returns the timeout used while waiting for the first stream event.
33
34
  * The first token can legitimately take longer than later inter-event gaps,
@@ -57,7 +58,7 @@ export declare function getStreamFirstEventTimeoutMs(idleTimeoutMs?: number, fal
57
58
  * window so a short post-connect first-event budget cannot kill legitimate
58
59
  * slow setup.
59
60
  */
60
- export declare function resolveOpenAISdkRequestTimeoutMs(provider: string, streamFirstEventTimeoutOverride?: number, modelId?: string): number | undefined;
61
+ export declare function resolveOpenAISdkRequestTimeoutMs(provider: string, streamFirstEventTimeoutOverride?: number, modelId?: string, configuredIdleTimeoutMs?: number, effectiveIdleTimeoutOverride?: number): number | undefined;
61
62
  /**
62
63
  * Resolves the Anthropic SDK client `timeout` so stalled-before-headers requests
63
64
  * are bounded. The Anthropic first-event watchdog deliberately arms only once
@@ -7,6 +7,15 @@ export type OAuthCredentials = {
7
7
  email?: string;
8
8
  accountId?: string;
9
9
  profileArn?: string;
10
+ /**
11
+ * Organization the token is scoped to. One Anthropic account can belong to
12
+ * several organizations (e.g. a Team workspace and a personal Max plan); each
13
+ * login is bound to exactly one of them, so the organization is part of the
14
+ * credential's identity.
15
+ */
16
+ organizationId?: string;
17
+ /** Display name of {@link organizationId}. */
18
+ organizationName?: string;
10
19
  };
11
20
  export type OAuthProvider = "kiro" | "alibaba-token-plan" | "anthropic" | "bizrouter" | "mara" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "commandcode-goat" | "opengateway" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sglang" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "opencodex" | "zai";
12
21
  export type OAuthProviderId = OAuthProvider | (string & {});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.18.11",
4
+ "version": "0.18.12",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "scripts": {
32
32
  "check": "biome check . && bun run check:types",
33
- "check:types": "tsc -p tsconfig.json --noEmit",
33
+ "check:types": "bun ../coding-agent/scripts/generate-docs-index.ts && tsc -p tsconfig.json --noEmit",
34
34
  "lint": "biome lint .",
35
35
  "test": "bun test",
36
36
  "fix": "biome check --write --unsafe .",
@@ -41,8 +41,8 @@
41
41
  "@agentclientprotocol/sdk": "1.3.0",
42
42
  "@anthropic-ai/sdk": "^0.94.0",
43
43
  "@bufbuild/protobuf": "^2.12.0",
44
- "@gajae-code/natives": "0.18.11",
45
- "@gajae-code/utils": "0.18.11",
44
+ "@gajae-code/natives": "0.18.12",
45
+ "@gajae-code/utils": "0.18.12",
46
46
  "openai": "^6.36.0",
47
47
  "partial-json": "^0.1.7",
48
48
  "zod": "4.4.3"
@@ -23,6 +23,7 @@ import {
23
23
  type CachedUsagePresentation,
24
24
  type CredentialDispatchTicket,
25
25
  type CredentialInventoryRecord,
26
+ formatOAuthIdentityLabel,
26
27
  type MCPOAuthRefreshClient,
27
28
  type OAuthCredential,
28
29
  REMOTE_REFRESH_SENTINEL,
@@ -1106,6 +1107,8 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1106
1107
  email: current.email,
1107
1108
  projectId: current.projectId,
1108
1109
  enterpriseUrl: current.enterpriseUrl,
1110
+ organizationId: current.organizationId,
1111
+ organizationName: current.organizationName,
1109
1112
  });
1110
1113
  const observed = this.#snapshot.credentials.find(candidate => candidate.id === credentialId);
1111
1114
  if (
@@ -1315,7 +1318,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1315
1318
  return null;
1316
1319
  }
1317
1320
  if (!reports) return null;
1318
- return matchUsageReport(reports, provider, credential);
1321
+ return matchUsageReport(reports, provider, credential, this.#usageMatchContext(provider));
1319
1322
  }
1320
1323
 
1321
1324
  /**
@@ -1346,6 +1349,18 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1346
1349
  });
1347
1350
  }
1348
1351
 
1352
+ #usageMatchContext(provider: string): UsageMatchContext {
1353
+ let credentialCount = 0;
1354
+ const claimedOrganizationIds = new Set<string>();
1355
+ for (const entry of this.#snapshot.credentials) {
1356
+ if (entry.provider !== provider || entry.credential.type !== "oauth") continue;
1357
+ credentialCount += 1;
1358
+ const organizationId = normalizeOrganizationId(entry.credential.organizationId);
1359
+ if (organizationId) claimedOrganizationIds.add(organizationId);
1360
+ }
1361
+ return { credentialCount, claimedOrganizationIds };
1362
+ }
1363
+
1349
1364
  #identityDigestForEntry(entry: SnapshotEntry): string {
1350
1365
  const credential = entry.credential;
1351
1366
  const identity =
@@ -1356,6 +1371,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1356
1371
  projectId: credential.projectId ?? null,
1357
1372
  enterpriseUrl: credential.enterpriseUrl ?? null,
1358
1373
  mcpBinding: credential.mcpBinding ?? null,
1374
+ ...(credential.organizationId ? { organizationId: credential.organizationId } : {}),
1359
1375
  }
1360
1376
  : { type: credential.type };
1361
1377
  return createHash("sha256")
@@ -1382,7 +1398,12 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1382
1398
  if (this.#usageCacheEpoch !== epoch) return;
1383
1399
  for (const entry of this.#snapshot.credentials) {
1384
1400
  if (entry.credential.type !== "oauth") continue;
1385
- const report = matchUsageReport(reports, entry.provider, entry.credential);
1401
+ const report = matchUsageReport(
1402
+ reports,
1403
+ entry.provider,
1404
+ entry.credential,
1405
+ this.#usageMatchContext(entry.provider),
1406
+ );
1386
1407
  if (!report) continue;
1387
1408
  const fetchedAt = Number.isFinite(report.fetchedAt) ? report.fetchedAt : Date.now();
1388
1409
  this.recordUsagePresentation({
@@ -1485,7 +1506,7 @@ function isErrorStatus(error: unknown, status: number): boolean {
1485
1506
 
1486
1507
  function snapshotIdentityLabel(entry: SnapshotEntry): string | null {
1487
1508
  if (entry.credential.type !== "oauth") return null;
1488
- return entry.credential.email ?? entry.credential.accountId ?? entry.credential.projectId ?? null;
1509
+ return formatOAuthIdentityLabel(entry.credential);
1489
1510
  }
1490
1511
 
1491
1512
  function safePresentationUsageReport(report: UsageReport): SafeUsageReport {
@@ -1531,20 +1552,45 @@ function cloneUsagePresentation(observation: CachedUsagePresentation): CachedUsa
1531
1552
  };
1532
1553
  }
1533
1554
 
1555
+ /** Facts about the provider's stored credentials that a usage report match must respect. */
1556
+ interface UsageMatchContext {
1557
+ /** Stored OAuth credentials for the provider; a report without identity can only belong to a sole credential. */
1558
+ credentialCount: number;
1559
+ /** Organizations the provider's stored credentials are scoped to. */
1560
+ claimedOrganizationIds: ReadonlySet<string>;
1561
+ }
1562
+
1534
1563
  /**
1535
1564
  * Match a broker-supplied usage report to a specific OAuth credential. The
1536
1565
  * broker returns aggregate reports across all credentials it manages, so we
1537
- * pick the one whose identity (accountId / email / projectId) lines up with
1538
- * the credential the caller is asking about.
1566
+ * pick the one whose identity (accountId / email / projectId) and organization
1567
+ * line up with the credential the caller is asking about. Missing or ambiguous
1568
+ * usage data is safer than assigning another credential's limits.
1539
1569
  *
1540
- * Falls back to the lone candidate when only one matches the provider; falls
1541
- * through to `null` when nothing matches, which `AuthStorage` treats as "no
1542
- * usage data" (ranking proceeds without a usage signal for this credential).
1570
+ * Ownership must be shown by the report itself. A lone returned report is not
1571
+ * evidence: when one credential's usage fetch fails, the broker returns only
1572
+ * the others' reports. So a report is taken without an identity match only
1573
+ * when the provider has a single stored credential. A credential stored
1574
+ * without an organization never takes a report whose organization another
1575
+ * stored credential claims.
1543
1576
  */
1544
- function matchUsageReport(reports: UsageReport[], provider: Provider, credential: OAuthCredential): UsageReport | null {
1545
- const candidates = reports.filter(report => report.provider === provider);
1577
+ function matchUsageReport(
1578
+ reports: UsageReport[],
1579
+ provider: Provider,
1580
+ credential: OAuthCredential,
1581
+ context: UsageMatchContext,
1582
+ ): UsageReport | null {
1583
+ const organizationId = normalizeOrganizationId(credential.organizationId);
1584
+ const candidates = reports.filter(report => {
1585
+ if (report.provider !== provider) return false;
1586
+ const reportOrganizationId = normalizeOrganizationId(
1587
+ readMetadataString((report.metadata ?? {}) as Record<string, unknown>, "orgId"),
1588
+ );
1589
+ if (organizationId) return reportOrganizationId === organizationId;
1590
+ return reportOrganizationId === undefined || !context.claimedOrganizationIds.has(reportOrganizationId);
1591
+ });
1546
1592
  if (candidates.length === 0) return null;
1547
- if (candidates.length === 1) return candidates[0];
1593
+ if (candidates.length === 1 && context.credentialCount === 1) return candidates[0];
1548
1594
  const accountId = credential.accountId?.trim().toLowerCase();
1549
1595
  const email = credential.email?.trim().toLowerCase();
1550
1596
  const projectId = credential.projectId?.trim().toLowerCase();
@@ -1554,6 +1600,11 @@ function matchUsageReport(reports: UsageReport[], provider: Provider, credential
1554
1600
  return null;
1555
1601
  }
1556
1602
 
1603
+ function normalizeOrganizationId(organizationId: string | undefined): string | undefined {
1604
+ const normalized = organizationId?.trim().toLowerCase();
1605
+ return normalized ? normalized : undefined;
1606
+ }
1607
+
1557
1608
  function reportMatchesIdentity(
1558
1609
  report: UsageReport,
1559
1610
  accountId: string | undefined,
@@ -45,6 +45,8 @@ export const oauthCredentialSchema = z
45
45
  projectId: z.string().optional(),
46
46
  email: z.string().optional(),
47
47
  accountId: z.string().optional(),
48
+ organizationId: z.string().optional(),
49
+ organizationName: z.string().optional(),
48
50
  mcpBinding: mcpOAuthBindingSchema.optional(),
49
51
  })
50
52
  .strict();
@@ -589,8 +589,9 @@ export function redactGatewayStream(events: AssistantMessageEventStream): Assist
589
589
  async function* redactedEvents(): AsyncGenerator<AssistantMessageEvent> {
590
590
  try {
591
591
  for await (const event of events) {
592
- if (event.type === "error") yield { ...event, error: redactGatewayMessage(event.error) };
593
- else yield event;
592
+ if (event.type === "error") {
593
+ yield { type: "error", reason: event.reason, error: redactGatewayMessage(event.error) };
594
+ } else yield event;
594
595
  }
595
596
  } catch (error) {
596
597
  throw redactGatewayError(error);