@oh-my-pi/pi-ai 18.12.2 → 18.12.4

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 (46) hide show
  1. package/CHANGELOG.md +44 -78
  2. package/dist/types/auth/rank.d.ts +4 -0
  3. package/dist/types/auth/types.d.ts +2 -0
  4. package/dist/types/auth-broker/remote-store.d.ts +16 -6
  5. package/dist/types/providers/amazon-bedrock.d.ts +7 -5
  6. package/dist/types/providers/anthropic-compaction.d.ts +8 -0
  7. package/dist/types/providers/anthropic-state.d.ts +8 -2
  8. package/dist/types/providers/anthropic-wire.d.ts +3 -0
  9. package/dist/types/providers/anthropic.d.ts +19 -2
  10. package/dist/types/providers/factory-droid/tool-names.d.ts +21 -0
  11. package/dist/types/providers/google-shared.d.ts +17 -0
  12. package/dist/types/types.d.ts +2 -0
  13. package/dist/types/utils/http-inspector.d.ts +2 -0
  14. package/dist/types/utils/tool-call-loop-guard.d.ts +2 -0
  15. package/dist/types/utils/validation.d.ts +11 -1
  16. package/package.json +6 -6
  17. package/src/auth/policy.ts +26 -10
  18. package/src/auth/rank.ts +6 -0
  19. package/src/auth/select.ts +25 -6
  20. package/src/auth/types.ts +2 -0
  21. package/src/auth-broker/discover.ts +67 -17
  22. package/src/auth-broker/remote-store.ts +59 -16
  23. package/src/error/flags.ts +2 -2
  24. package/src/images/google-antigravity.ts +0 -1
  25. package/src/providers/amazon-bedrock.ts +421 -142
  26. package/src/providers/anthropic-compaction.ts +13 -0
  27. package/src/providers/anthropic-state.ts +16 -2
  28. package/src/providers/anthropic-wire.ts +9 -1
  29. package/src/providers/anthropic.ts +271 -74
  30. package/src/providers/cursor-pi-args.ts +3 -1
  31. package/src/providers/devin.ts +13 -43
  32. package/src/providers/factory-droid/gemini.ts +9 -30
  33. package/src/providers/factory-droid/tool-names.ts +44 -0
  34. package/src/providers/factory-droid.ts +91 -20
  35. package/src/providers/google-gemini-cli.ts +0 -1
  36. package/src/providers/google-shared.ts +61 -6
  37. package/src/types.ts +2 -0
  38. package/src/usage/devin.ts +3 -3
  39. package/src/utils/http-inspector.ts +3 -2
  40. package/src/utils/leaked-thinking-stream.ts +28 -4
  41. package/src/utils/request-debug.ts +3 -2
  42. package/src/utils/schema/CONSTRAINTS.md +12 -5
  43. package/src/utils/schema/allowlist.ts +144 -18
  44. package/src/utils/thinking-loop.ts +75 -14
  45. package/src/utils/tool-call-loop-guard.ts +14 -2
  46. package/src/utils/validation.ts +47 -7
@@ -27,7 +27,7 @@ import {
27
27
  } from "../auth-storage";
28
28
  import * as AIError from "../error";
29
29
  import { AuthBrokerClient, AuthBrokerError } from "./client";
30
- import { type AuthBrokerAccountPool, RemoteAuthCredentialStore } from "./remote-store";
30
+ import { type AuthBrokerAccountPool, type AuthBrokerProviderPool, RemoteAuthCredentialStore } from "./remote-store";
31
31
  import { readAuthBrokerSnapshotCache, scheduleAuthBrokerSnapshotCacheWrite } from "./snapshot-cache";
32
32
  import { DEFAULT_SNAPSHOT_CACHE_TTL_MS, type SnapshotResponse } from "./types";
33
33
 
@@ -122,7 +122,12 @@ function parseAuthAccountPolicies(value: unknown): AuthAccountPolicies {
122
122
  }
123
123
  const policy = entry as Record<string, unknown>;
124
124
  const unknownPolicyFields = Object.keys(policy).filter(
125
- key => key !== "provider" && key !== "account" && key !== "priority" && key !== "reservePct",
125
+ key =>
126
+ key !== "provider" &&
127
+ key !== "account" &&
128
+ key !== "priority" &&
129
+ key !== "reservePct" &&
130
+ key !== "reclaimAbovePct",
126
131
  );
127
132
  if (unknownPolicyFields.length > 0) {
128
133
  throw new AIError.ConfigurationError(`${path} has unknown fields: ${unknownPolicyFields.join(", ")}`);
@@ -171,6 +176,12 @@ function parseAuthAccountPolicies(value: unknown): AuthAccountPolicies {
171
176
  ) {
172
177
  throw new AIError.ConfigurationError(`${path}.reservePct must be between 0 and 100`);
173
178
  }
179
+ if (
180
+ policy.reclaimAbovePct !== undefined &&
181
+ (typeof policy.reclaimAbovePct !== "number" || !Number.isFinite(policy.reclaimAbovePct))
182
+ ) {
183
+ throw new AIError.ConfigurationError(`${path}.reclaimAbovePct must be a finite number`);
184
+ }
174
185
 
175
186
  return {
176
187
  provider,
@@ -182,6 +193,7 @@ function parseAuthAccountPolicies(value: unknown): AuthAccountPolicies {
182
193
  },
183
194
  ...(typeof policy.priority === "number" ? { priority: policy.priority } : {}),
184
195
  ...(typeof policy.reservePct === "number" ? { reservePct: policy.reservePct } : {}),
196
+ ...(typeof policy.reclaimAbovePct === "number" ? { reclaimAbovePct: policy.reclaimAbovePct } : {}),
185
197
  };
186
198
  });
187
199
  }
@@ -272,7 +284,7 @@ export async function loadAuthBrokerAccountPool(): Promise<AuthBrokerAccountPool
272
284
  throw new AIError.ConfigurationError("OMP_AUTH_BROKER_ACCOUNT_POOL_FILE must contain a JSON object");
273
285
  }
274
286
 
275
- const accountPool = new Map<string, ReadonlySet<string>>();
287
+ const accountPool = new Map<string, ReadonlySet<string> | AuthBrokerProviderPool>();
276
288
  for (const [provider, value] of Object.entries(parsed)) {
277
289
  const normalizedProvider = provider.trim();
278
290
  if (normalizedProvider.length === 0) {
@@ -283,28 +295,66 @@ export async function loadAuthBrokerAccountPool(): Promise<AuthBrokerAccountPool
283
295
  "OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains a provider id with surrounding whitespace",
284
296
  );
285
297
  }
286
- if (!Array.isArray(value)) {
287
- throw new AIError.ConfigurationError(
288
- `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} must be an array of identity keys`,
289
- );
290
- }
291
- const identities = new Set<string>();
292
- for (const identity of value) {
293
- if (typeof identity !== "string" || identity.length === 0) {
298
+ accountPool.set(provider, parseAccountPoolEntry(provider, value));
299
+ }
300
+ return accountPool;
301
+ }
302
+
303
+ /**
304
+ * One pool-file entry: an identity array (visible OAuth accounts, API keys
305
+ * kept), `true` (everything visible), `false` (nothing visible), or a rule
306
+ * object `{ "accounts"?: [...], "apiKeys"?: boolean }`.
307
+ */
308
+ function parseAccountPoolEntry(provider: string, value: unknown): ReadonlySet<string> | AuthBrokerProviderPool {
309
+ if (Array.isArray(value)) return parseAccountPoolIdentities(provider, value);
310
+ if (value === true) return {};
311
+ if (value === false) return { accounts: new Set(), apiKeys: false };
312
+ if (value === null || typeof value !== "object") {
313
+ throw new AIError.ConfigurationError(
314
+ `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} must be an array of identity keys, a boolean, or an object with "accounts"/"apiKeys"`,
315
+ );
316
+ }
317
+ const rule: { accounts?: ReadonlySet<string>; apiKeys?: boolean } = {};
318
+ for (const [key, field] of Object.entries(value)) {
319
+ if (key === "accounts") {
320
+ if (!Array.isArray(field)) {
294
321
  throw new AIError.ConfigurationError(
295
- `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} contains an invalid identity key`,
322
+ `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} has "accounts" that is not an array of identity keys`,
296
323
  );
297
324
  }
298
- if (identity !== identity.trim()) {
325
+ rule.accounts = parseAccountPoolIdentities(provider, field);
326
+ } else if (key === "apiKeys") {
327
+ if (typeof field !== "boolean") {
299
328
  throw new AIError.ConfigurationError(
300
- `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} contains an identity key with surrounding whitespace`,
329
+ `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} has "apiKeys" that is not a boolean`,
301
330
  );
302
331
  }
303
- identities.add(identity);
332
+ rule.apiKeys = field;
333
+ } else {
334
+ throw new AIError.ConfigurationError(
335
+ `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} has unknown field ${JSON.stringify(key)}`,
336
+ );
304
337
  }
305
- accountPool.set(provider, identities);
306
338
  }
307
- return accountPool;
339
+ return rule;
340
+ }
341
+
342
+ function parseAccountPoolIdentities(provider: string, value: unknown[]): Set<string> {
343
+ const identities = new Set<string>();
344
+ for (const identity of value) {
345
+ if (typeof identity !== "string" || identity.length === 0) {
346
+ throw new AIError.ConfigurationError(
347
+ `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} contains an invalid identity key`,
348
+ );
349
+ }
350
+ if (identity !== identity.trim()) {
351
+ throw new AIError.ConfigurationError(
352
+ `OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} contains an identity key with surrounding whitespace`,
353
+ );
354
+ }
355
+ identities.add(identity);
356
+ }
357
+ return identities;
308
358
  }
309
359
 
310
360
  function resolveSnapshotTtlMs(): number {
@@ -41,21 +41,61 @@ import type {
41
41
  const REMOTE_CACHE_MAX_ENTRIES = 4096;
42
42
  const REMOTE_CACHE_MAX_CHARACTERS = 4 * 1024 * 1024;
43
43
 
44
+ /** Visibility rule for one provider's broker credentials. */
45
+ export interface AuthBrokerProviderPool {
46
+ /** OAuth `identityKey`s visible to this client; omitted = every OAuth account. */
47
+ readonly accounts?: ReadonlySet<string>;
48
+ /** Whether API-key credentials are visible. Default `true`. */
49
+ readonly apiKeys?: boolean;
50
+ }
51
+
52
+ /** Pool key whose rule applies to every provider without its own entry. */
53
+ const AUTH_BROKER_ACCOUNT_POOL_DEFAULT_KEY = "*";
54
+
44
55
  /**
45
- * Per-provider OAuth identities visible to this trusted broker client.
46
- * Missing providers are unrestricted; an empty set excludes that provider's
47
- * OAuth credentials. API keys are never filtered.
56
+ * Per-provider credential visibility for this trusted broker client. A set
57
+ * value lists the provider's visible OAuth identities and keeps its API keys
58
+ * (an empty set excludes every OAuth credential); a rule object can also hide
59
+ * API keys. Providers without an entry follow the `"*"` rule, which defaults
60
+ * to unrestricted.
48
61
  */
49
- export type AuthBrokerAccountPool = ReadonlyMap<string, ReadonlySet<string>>;
62
+ export type AuthBrokerAccountPool = ReadonlyMap<string, ReadonlySet<string> | AuthBrokerProviderPool>;
63
+
64
+ interface ResolvedProviderPool {
65
+ accounts: ReadonlySet<string> | undefined;
66
+ apiKeys: boolean;
67
+ }
68
+
69
+ /** Copies `pool` so later caller mutation cannot widen it, normalizing both value forms. */
70
+ function resolveAccountPool(pool: AuthBrokerAccountPool): ReadonlyMap<string, ResolvedProviderPool> {
71
+ const resolved = new Map<string, ResolvedProviderPool>();
72
+ for (const [provider, value] of pool) {
73
+ resolved.set(
74
+ provider,
75
+ "has" in value
76
+ ? { accounts: new Set(value), apiKeys: true }
77
+ : { accounts: value.accounts && new Set(value.accounts), apiKeys: value.apiKeys ?? true },
78
+ );
79
+ }
80
+ return resolved;
81
+ }
82
+
83
+ function providerPoolRule(
84
+ accountPool: ReadonlyMap<string, ResolvedProviderPool> | undefined,
85
+ provider: string,
86
+ ): ResolvedProviderPool | undefined {
87
+ return accountPool?.get(provider) ?? accountPool?.get(AUTH_BROKER_ACCOUNT_POOL_DEFAULT_KEY);
88
+ }
50
89
 
51
90
  function isCredentialInAccountPool(
52
91
  entry: Pick<SnapshotEntry, "provider" | "credential" | "identityKey">,
53
- accountPool: AuthBrokerAccountPool | undefined,
92
+ accountPool: ReadonlyMap<string, ResolvedProviderPool> | undefined,
54
93
  ): boolean {
55
- if (entry.credential.type !== "oauth") return true;
56
- const identities = accountPool?.get(entry.provider);
57
- if (identities === undefined) return true;
58
- return entry.identityKey !== null && identities.has(entry.identityKey);
94
+ const rule = providerPoolRule(accountPool, entry.provider);
95
+ if (!rule) return true;
96
+ if (entry.credential.type !== "oauth") return rule.apiKeys;
97
+ if (!rule.accounts) return true;
98
+ return entry.identityKey !== null && rule.accounts.has(entry.identityKey);
59
99
  }
60
100
 
61
101
  /**
@@ -245,8 +285,9 @@ export interface RemoteAuthCredentialStoreOptions {
245
285
  */
246
286
  onSnapshot?: (snapshot: SnapshotResponse, generation: number) => void;
247
287
  /**
248
- * OAuth identities visible through this store. This is a trusted-client
249
- * routing policy, not broker authorization.
288
+ * Broker credentials visible through this store (OAuth identities and API
289
+ * keys per provider). This is a trusted-client routing policy, not broker
290
+ * authorization.
250
291
  */
251
292
  accountPool?: AuthBrokerAccountPool;
252
293
  /** Flush cadence for batched observed-usage reports. Default 10s. */
@@ -265,7 +306,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
265
306
  readonly #client: AuthBrokerClient;
266
307
  readonly #streamSnapshots: boolean;
267
308
  readonly #onSnapshot?: (snapshot: SnapshotResponse, generation: number) => void;
268
- readonly #accountPool?: AuthBrokerAccountPool;
309
+ readonly #accountPool?: ReadonlyMap<string, ResolvedProviderPool>;
269
310
  #snapshot: SnapshotResponse = emptySnapshot();
270
311
  #snapshotReceivedAt = Date.now();
271
312
  #generation = 0;
@@ -336,9 +377,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
336
377
  this.#streamSnapshots = opts.streamSnapshots ?? true;
337
378
  this.#observedUsageFlushMs = opts.observedUsageFlushMs ?? 10_000;
338
379
  this.#backgroundIdleMs = opts.backgroundIdleMs ?? BACKGROUND_IDLE_MS;
339
- this.#accountPool = opts.accountPool
340
- ? new Map([...opts.accountPool].map(([provider, identities]) => [provider, new Set(identities)]))
341
- : undefined;
380
+ this.#accountPool = opts.accountPool ? resolveAccountPool(opts.accountPool) : undefined;
342
381
  this.#applySnapshot(opts.initialSnapshot ?? emptySnapshot(), opts.initialSnapshot?.generation ?? 0);
343
382
  this.#acknowledgedRevision = this.#credentialRevision;
344
383
  this.#onSnapshot = opts.onSnapshot;
@@ -1372,7 +1411,11 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1372
1411
  if (memo && memo.input === reports && memo.byProvider === lookup.byProvider) return memo.output;
1373
1412
  const byProvider = lookup.byProvider;
1374
1413
  const output = reports.filter(report => {
1375
- if (!accountPool.has(report.provider)) return true;
1414
+ // Unrestricted for this provider: every report stays. Otherwise a
1415
+ // report must belong to a visible OAuth credential, so reports from
1416
+ // hidden accounts or API keys fail closed.
1417
+ const rule = providerPoolRule(accountPool, report.provider);
1418
+ if (!rule || (rule.accounts === undefined && rule.apiKeys)) return true;
1376
1419
  const credentials = byProvider.get(report.provider);
1377
1420
  if (!credentials) return false;
1378
1421
  return credentials.some(credential => usageReportMatchesCredential(report, credential));
@@ -167,7 +167,7 @@ function matchesPayloadRejectionText(text: string): boolean {
167
167
 
168
168
  const TIMEOUT_PATTERN = /\b(?:operation\s+)?timed?\s*out\b|\btimeout\b|\bstream stall\b/i;
169
169
  const TRANSIENT_ENVELOPE_PATTERN = /anthropic stream envelope error:/i;
170
- const TRANSIENT_ENVELOPE_TRUNCATION_PATTERN = /before message_(?:start|stop)/i;
170
+ const TRANSIENT_ENVELOPE_TRUNCATION_PATTERN = /before message_(?:start|delta|stop)/i;
171
171
  export const STREAM_READ_ERROR_PATTERN = /stream[_ -]?read[_ -]?error/i;
172
172
  /** Python h2/httpx diagnostics forwarded through provider or proxy error events. */
173
173
  export const PYTHON_HTTP2_STREAM_RESET_PATTERN = /<StreamReset stream_id:\d+, error_code:(?:2|7), remote_reset:True>/;
@@ -1103,7 +1103,7 @@ export function isTransientStreamParseError(error: unknown): boolean {
1103
1103
  * non-terminal status treat it as transient (#11805).
1104
1104
  */
1105
1105
  const STREAM_DROP_PATTERN =
1106
- /stream disconnected before completion|(?:stream|websocket) closed before response\.completed|stream was interrupted|stream ended before terminal (?:chunk|completion event)|socket disconnected before secure tls connection/i;
1106
+ /stream disconnected before completion|(?:stream|websocket) closed before response\.completed|stream was interrupted|stream ended before (?:terminal (?:chunk|completion event)|message_(?:start|delta|stop))|socket disconnected before secure tls connection/i;
1107
1107
 
1108
1108
  /**
1109
1109
  * Transport stream-drop diagnostic (see {@link STREAM_DROP_PATTERN}). Unlike
@@ -99,7 +99,6 @@ function buildRequest(request: ImageGenerationRequest, model: string, projectId:
99
99
  { category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_ONLY_HIGH" },
100
100
  ],
101
101
  },
102
- requestType: "agent",
103
102
  requestId: `agent-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
104
103
  userAgent: "antigravity",
105
104
  };