omnigateway 0.13.6 → 0.14.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.
package/bin/omni.js CHANGED
@@ -6025,12 +6025,43 @@ var spendWindows = object({
6025
6025
  "5h": spendLimit.optional(),
6026
6026
  "1w": spendLimit.optional()
6027
6027
  }).strict();
6028
- var limitConfigSchema = object({
6028
+ var limitConfigBaseSchema = object({
6029
6029
  requests: countWindows.optional(),
6030
6030
  tokens: countWindows.optional(),
6031
6031
  spend: spendWindows.optional(),
6032
6032
  concurrency: countLimit.optional()
6033
6033
  }).strict();
6034
+ var MAX_MODEL_OVERRIDES = 64;
6035
+ var MAX_MODEL_NAME_LENGTH = 256;
6036
+ function setsAnyLimit(scope) {
6037
+ if (scope.concurrency !== undefined && scope.concurrency !== null)
6038
+ return true;
6039
+ for (const dimension of ["requests", "tokens", "spend"]) {
6040
+ const windows = scope[dimension];
6041
+ if (windows === undefined)
6042
+ continue;
6043
+ for (const window of WINDOWS) {
6044
+ const limit = windows[window];
6045
+ if (limit !== undefined && limit !== null)
6046
+ return true;
6047
+ }
6048
+ }
6049
+ return false;
6050
+ }
6051
+ var modelOverrideSchema = limitConfigBaseSchema.refine(setsAnyLimit, {
6052
+ message: "a model override must set at least one limit; omit the model to remove it"
6053
+ });
6054
+ var limitConfigSchema = limitConfigBaseSchema.extend({
6055
+ models: record(string2().min(1).max(MAX_MODEL_NAME_LENGTH), modelOverrideSchema).refine((models) => Object.keys(models).length <= MAX_MODEL_OVERRIDES, {
6056
+ message: `at most ${MAX_MODEL_OVERRIDES} model overrides per key`
6057
+ }).optional()
6058
+ }).strict();
6059
+ function modelOverride(limits, model) {
6060
+ const models = limits.models;
6061
+ if (models === undefined || !Object.hasOwn(models, model))
6062
+ return;
6063
+ return models[model];
6064
+ }
6034
6065
  function assertNoProto(value, path) {
6035
6066
  if (typeof value !== "object" || value === null)
6036
6067
  return;
@@ -7695,6 +7726,31 @@ var _005_log_filter_indexes_default = `-- An index for the provider filter on \`
7695
7726
  CREATE INDEX idx_request_logs_provider ON request_logs (resolved_provider, at DESC, id DESC);
7696
7727
  `;
7697
7728
 
7729
+ // packages/store/src/postgres/migrations/006_model_window_index.sql
7730
+ var _006_model_window_index_default = `-- The per-model long-window reads behind a key's model overrides, on this
7731
+ -- backend: \`usage.sumSince\`, \`sumBuckets\` and \`oldestSince\` narrowed to one
7732
+ -- requested model, read from the log because \`usage_rollup\` has no model
7733
+ -- column. The SQLite copy is \`018_model_window_index.sql\`, same shape.
7734
+ --
7735
+ -- Without it the planner has no prefix to seek and the narrowed sums plan as a
7736
+ -- parallel sequential scan of the table. Measured at 500,000 rows in one key's
7737
+ -- week, a fifth of them the overridden model and a hundred rows of a rare one:
7738
+ --
7739
+ -- read without index with index
7740
+ -- sumSince 1w, rare model 22.6ms 0.22ms
7741
+ -- sumSince 1w, 100k-row model 27.1ms 29.4ms
7742
+ -- sumBuckets 1w hour, rare 78.2ms 0.26ms
7743
+ -- sumBuckets 1w hour, 100k-row 89.4ms 55.5ms
7744
+ -- oldestSince 1w, rare 0.58ms 0.11ms
7745
+ --
7746
+ -- The heavy-model sum is a bitmap heap scan over that model's rows and does not
7747
+ -- beat the sequential scan it replaces: the cost is linear in the model's own
7748
+ -- traffic on either plan, and this index removes the part that was linear in
7749
+ -- everyone else's. 15MB at 500,000 rows, against 19MB for the key/time index.
7750
+ -- Neither partial nor covering, for the reasons the SQLite file records.
7751
+ CREATE INDEX idx_request_logs_key_model_at ON request_logs (api_key_id, requested_model, at);
7752
+ `;
7753
+
7698
7754
  // packages/store/src/postgres/db.ts
7699
7755
  var num = (v) => Number(v);
7700
7756
  var numOrNull = (v) => v === null ? null : Number(v);
@@ -7704,7 +7760,8 @@ var MIGRATIONS = [
7704
7760
  { id: 2, sql: _002_health_measurements_default },
7705
7761
  { id: 3, sql: _003_key_expiry_default },
7706
7762
  { id: 4, sql: _004_log_keyset_default },
7707
- { id: 5, sql: _005_log_filter_indexes_default }
7763
+ { id: 5, sql: _005_log_filter_indexes_default },
7764
+ { id: 6, sql: _006_model_window_index_default }
7708
7765
  ];
7709
7766
  async function openPg(url) {
7710
7767
  const sql = new SQL({ url });
@@ -9069,6 +9126,24 @@ var sum = (row) => ({
9069
9126
  tokens: row === undefined ? 0 : num(row.tokens),
9070
9127
  costUsd: row === undefined ? 0 : row.cost_usd
9071
9128
  });
9129
+ var NARROWED_SQL = {
9130
+ sumSince: `SELECT COUNT(*) AS requests,
9131
+ COALESCE(SUM(input_tokens + output_tokens
9132
+ + cache_read_tokens + cache_write_tokens), 0) AS tokens,
9133
+ COALESCE(SUM(cost_usd), 0) AS cost_usd
9134
+ FROM request_logs
9135
+ WHERE api_key_id = $1 AND requested_model = $2 AND state = 'done' AND at >= $3`,
9136
+ sumBuckets: `SELECT (at / $1) * $2 AS bucket, COUNT(*) AS requests,
9137
+ COALESCE(SUM(input_tokens + output_tokens
9138
+ + cache_read_tokens + cache_write_tokens), 0) AS tokens,
9139
+ COALESCE(SUM(cost_usd), 0) AS cost_usd
9140
+ FROM request_logs
9141
+ WHERE api_key_id = $3 AND requested_model = $4 AND state = 'done' AND at >= $5
9142
+ GROUP BY 1`,
9143
+ oldestSince: `SELECT MIN(at) AS at
9144
+ FROM request_logs
9145
+ WHERE api_key_id = $1 AND requested_model = $2 AND state = 'done' AND at >= $3`
9146
+ };
9072
9147
  function createUsageRepo(sql, nodeId, dayOffsetMinutes) {
9073
9148
  const complete = async (conn, log) => {
9074
9149
  const written = (await conn.unsafe(COMPLETE, values(log, "done", nodeId))).count;
@@ -9126,7 +9201,15 @@ function createUsageRepo(sql, nodeId, dayOffsetMinutes) {
9126
9201
  ORDER BY at, id LIMIT $3`, [cursor.at, cursor.id, limit]);
9127
9202
  return rows.map(toLog);
9128
9203
  },
9129
- async sumSince(apiKeyId, sinceMs) {
9204
+ async sumSince(apiKeyId, sinceMs, requestedModel) {
9205
+ if (requestedModel !== undefined) {
9206
+ const narrowed = await sql.unsafe(NARROWED_SQL.sumSince, [
9207
+ apiKeyId,
9208
+ requestedModel,
9209
+ sinceMs
9210
+ ]);
9211
+ return sum(narrowed[0]);
9212
+ }
9130
9213
  const boundary = hourOf(sinceMs);
9131
9214
  const whole = await sql.unsafe(`SELECT COALESCE(SUM(requests), 0) AS requests,
9132
9215
  COALESCE(SUM(input_tokens + output_tokens
@@ -9148,7 +9231,18 @@ function createUsageRepo(sql, nodeId, dayOffsetMinutes) {
9148
9231
  costUsd: a.costUsd + b.costUsd
9149
9232
  };
9150
9233
  },
9151
- async sumBuckets(apiKeyId, sinceMs, grainMs) {
9234
+ async sumBuckets(apiKeyId, sinceMs, grainMs, requestedModel) {
9235
+ if (requestedModel !== undefined) {
9236
+ const from = grainMs === HOUR_MS ? hourOf(sinceMs) * HOUR_MS : sinceMs;
9237
+ const narrowed = await sql.unsafe(NARROWED_SQL.sumBuckets, [
9238
+ grainMs,
9239
+ grainMs,
9240
+ apiKeyId,
9241
+ requestedModel,
9242
+ from
9243
+ ]);
9244
+ return narrowed.map((row) => [num(row.bucket), sum(row)]);
9245
+ }
9152
9246
  const rows = grainMs === HOUR_MS ? await sql.unsafe(`SELECT hour * ${HOUR_MS} AS bucket, requests,
9153
9247
  input_tokens + output_tokens + cache_read_tokens + cache_write_tokens
9154
9248
  AS tokens,
@@ -9169,10 +9263,14 @@ function createUsageRepo(sql, nodeId, dayOffsetMinutes) {
9169
9263
  async auditRollup() {
9170
9264
  return auditRollup2(sql);
9171
9265
  },
9172
- async oldestSince(apiKeyId, sinceMs) {
9173
- const row = (await sql.unsafe(`SELECT MIN(at) AS at
9174
- FROM request_logs
9175
- WHERE api_key_id = $1 AND state = 'done' AND at >= $2`, [apiKeyId, sinceMs]))[0];
9266
+ async oldestSince(apiKeyId, sinceMs, requestedModel) {
9267
+ const row = (requestedModel === undefined ? await sql.unsafe(`SELECT MIN(at) AS at
9268
+ FROM request_logs
9269
+ WHERE api_key_id = $1 AND state = 'done' AND at >= $2`, [apiKeyId, sinceMs]) : await sql.unsafe(NARROWED_SQL.oldestSince, [
9270
+ apiKeyId,
9271
+ requestedModel,
9272
+ sinceMs
9273
+ ]))[0];
9176
9274
  return row === undefined ? null : numOrNull(row.at);
9177
9275
  },
9178
9276
  async aggregate(q) {
@@ -10454,6 +10552,42 @@ CREATE INDEX idx_request_logs_at
10454
10552
  ON request_logs (at DESC, id DESC, requested_model, resolved_model, error_code);
10455
10553
  `;
10456
10554
 
10555
+ // packages/store/src/sqlite/migrations/018_model_window_index.sql
10556
+ var _018_model_window_index_default = `-- An index for the per-model long-window reads behind a key's model overrides:
10557
+ -- \`usage.sumSince\`, \`sumBuckets\` and \`oldestSince\` narrowed to one requested
10558
+ -- model. \`usage_rollup\` has no model column, so those reads come from the log,
10559
+ -- and without this they walk \`idx_request_logs_key_at\` across the key's whole
10560
+ -- window and reject every other model's row one at a time. \`bun:sqlite\` is
10561
+ -- synchronous, so that walk is the event loop \u2014 and the console's key listing
10562
+ -- repeats it once per override per window on every fetch.
10563
+ --
10564
+ -- Key, then model, then time: one seek lands on one key's one model, and the
10565
+ -- window is a range from there. Measured at 500,000 rows in one key's week, a
10566
+ -- fifth of them the overridden model and a hundred rows of a rare one:
10567
+ --
10568
+ -- read without index with index
10569
+ -- sumSince 1w, rare model 45.9ms 0.03ms
10570
+ -- sumSince 1w, 100k-row model 56.1ms 38.1ms
10571
+ -- sumBuckets 1w hour, rare 46.1ms 0.07ms
10572
+ -- sumBuckets 1w hour, 100k-row 76.8ms 58.9ms
10573
+ -- oldestSince 1w, rare 0.57ms 0.00ms
10574
+ --
10575
+ -- So the read stops costing the key's traffic and starts costing the model's
10576
+ -- own, which is still linear: a model that carries most of a key's traffic pays
10577
+ -- most of what it did before. That remaining cost is the design's, stated in
10578
+ -- \`ARCHITECTURE.md#rate-limiting\`, not something this index claims to remove.
10579
+ --
10580
+ -- Not partial on \`state = 'done'\`, and not covering, both measured. Partial
10581
+ -- bought nothing (37.4ms against 38.1ms) and, once \`ANALYZE\` had run, the
10582
+ -- planner moved the key-wide edge read onto it as a skip-scan over every model \u2014
10583
+ -- a plan that degrades with the number of models a key calls. Covering the four
10584
+ -- token columns and cost cut the heavy case to 9.0ms, for an index nearly three
10585
+ -- times as wide written on every completed request, and took the same key-wide
10586
+ -- plan. This shape leaves every key-wide read on \`idx_request_logs_key_at\`,
10587
+ -- which \`migrations.test.ts\` asserts beside the narrowed plans.
10588
+ CREATE INDEX idx_request_logs_key_model_at ON request_logs (api_key_id, requested_model, at);
10589
+ `;
10590
+
10457
10591
  // packages/store/src/sqlite/db.ts
10458
10592
  var MIGRATIONS2 = [
10459
10593
  { id: 1, sql: _001_init_default2 },
@@ -10472,7 +10606,8 @@ var MIGRATIONS2 = [
10472
10606
  { id: 14, sql: _014_key_expiry_default },
10473
10607
  { id: 15, sql: _015_log_keyset_default },
10474
10608
  { id: 16, sql: _016_log_filter_indexes_default },
10475
- { id: 17, sql: _017_log_text_search_default }
10609
+ { id: 17, sql: _017_log_text_search_default },
10610
+ { id: 18, sql: _018_model_window_index_default }
10476
10611
  ];
10477
10612
  function statements(sql) {
10478
10613
  return sql.split(`
@@ -10916,6 +11051,24 @@ var COMPLETE2 = `INSERT INTO request_logs ${COLUMNS2} VALUES ${PLACEHOLDERS2}
10916
11051
  rtk_estimated_tokens_saved = excluded.rtk_estimated_tokens_saved,
10917
11052
  rtk_filters = excluded.rtk_filters
10918
11053
  WHERE request_logs.state = 'pending'`;
11054
+ var NARROWED_SQL2 = {
11055
+ sumSince: `SELECT COUNT(*) AS requests,
11056
+ COALESCE(SUM(input_tokens + output_tokens
11057
+ + cache_read_tokens + cache_write_tokens), 0) AS tokens,
11058
+ COALESCE(SUM(cost_usd), 0) AS cost_usd
11059
+ FROM request_logs
11060
+ WHERE api_key_id = ? AND requested_model = ? AND state = 'done' AND at >= ?`,
11061
+ sumBuckets: `SELECT (at / ?) * ? AS bucket, COUNT(*) AS requests,
11062
+ COALESCE(SUM(input_tokens + output_tokens
11063
+ + cache_read_tokens + cache_write_tokens), 0) AS tokens,
11064
+ COALESCE(SUM(cost_usd), 0) AS cost_usd
11065
+ FROM request_logs
11066
+ WHERE api_key_id = ? AND requested_model = ? AND state = 'done' AND at >= ?
11067
+ GROUP BY bucket`,
11068
+ oldestSince: `SELECT MIN(at) AS at
11069
+ FROM request_logs
11070
+ WHERE api_key_id = ? AND requested_model = ? AND state = 'done' AND at >= ?`
11071
+ };
10919
11072
  function createUsageRepo2(db, nodeId, dayOffsetMinutes = hostDayOffsetMinutes()) {
10920
11073
  const complete = db.transaction((log) => {
10921
11074
  if (db.run(COMPLETE2, values2(log, "done", nodeId)).changes === 0)
@@ -10988,7 +11141,15 @@ function createUsageRepo2(db, nodeId, dayOffsetMinutes = hostDayOffsetMinutes())
10988
11141
  ORDER BY at, id LIMIT ?`).all(cursor.at, cursor.at, cursor.id, limit);
10989
11142
  return rows.map(toLog2);
10990
11143
  },
10991
- async sumSince(apiKeyId, sinceMs) {
11144
+ async sumSince(apiKeyId, sinceMs, requestedModel) {
11145
+ if (requestedModel !== undefined) {
11146
+ const row = db.query(NARROWED_SQL2.sumSince).get(apiKeyId, requestedModel, sinceMs);
11147
+ return {
11148
+ requests: row?.requests ?? 0,
11149
+ tokens: row?.tokens ?? 0,
11150
+ costUsd: row?.cost_usd ?? 0
11151
+ };
11152
+ }
10992
11153
  const boundary = hourOf(sinceMs);
10993
11154
  const whole = db.query(`SELECT COALESCE(SUM(requests), 0) AS requests,
10994
11155
  COALESCE(SUM(input_tokens + output_tokens
@@ -11008,7 +11169,15 @@ function createUsageRepo2(db, nodeId, dayOffsetMinutes = hostDayOffsetMinutes())
11008
11169
  costUsd: (whole?.cost_usd ?? 0) + (edge?.cost_usd ?? 0)
11009
11170
  };
11010
11171
  },
11011
- async sumBuckets(apiKeyId, sinceMs, grainMs) {
11172
+ async sumBuckets(apiKeyId, sinceMs, grainMs, requestedModel) {
11173
+ if (requestedModel !== undefined) {
11174
+ const from = grainMs === HOUR_MS ? hourOf(sinceMs) * HOUR_MS : sinceMs;
11175
+ const narrowed = db.query(NARROWED_SQL2.sumBuckets).all(grainMs, grainMs, apiKeyId, requestedModel, from);
11176
+ return narrowed.map((row) => [
11177
+ row.bucket,
11178
+ { requests: row.requests, tokens: row.tokens, costUsd: row.cost_usd }
11179
+ ]);
11180
+ }
11012
11181
  const rows = grainMs === HOUR_MS ? db.query(`SELECT hour * ${HOUR_MS} AS bucket, requests,
11013
11182
  input_tokens + output_tokens + cache_read_tokens + cache_write_tokens
11014
11183
  AS tokens,
@@ -11032,10 +11201,10 @@ function createUsageRepo2(db, nodeId, dayOffsetMinutes = hostDayOffsetMinutes())
11032
11201
  async auditRollup() {
11033
11202
  return auditRollup(db);
11034
11203
  },
11035
- async oldestSince(apiKeyId, sinceMs) {
11036
- const row = db.query(`SELECT MIN(at) AS at
11037
- FROM request_logs
11038
- WHERE api_key_id = ? AND state = 'done' AND at >= ?`).get(apiKeyId, sinceMs);
11204
+ async oldestSince(apiKeyId, sinceMs, requestedModel) {
11205
+ const row = requestedModel === undefined ? db.query(`SELECT MIN(at) AS at
11206
+ FROM request_logs
11207
+ WHERE api_key_id = ? AND state = 'done' AND at >= ?`).get(apiKeyId, sinceMs) : db.query(NARROWED_SQL2.oldestSince).get(apiKeyId, requestedModel, sinceMs);
11039
11208
  return row?.at ?? null;
11040
11209
  },
11041
11210
  async aggregate(q) {
@@ -11176,9 +11345,9 @@ async function createStore(opts) {
11176
11345
  lastUsedByCredential: (credentialId) => handle.usage.lastUsedByCredential(credentialId),
11177
11346
  scan: (cursor, limit) => handle.usage.scan(cursor, limit),
11178
11347
  aggregate: (q) => handle.usage.aggregate(q),
11179
- sumSince: (apiKeyId, sinceMs) => handle.usage.sumSince(apiKeyId, sinceMs),
11180
- sumBuckets: (apiKeyId, sinceMs, grainMs) => handle.usage.sumBuckets(apiKeyId, sinceMs, grainMs),
11181
- oldestSince: (apiKeyId, sinceMs) => handle.usage.oldestSince(apiKeyId, sinceMs),
11348
+ sumSince: (apiKeyId, sinceMs, requestedModel) => handle.usage.sumSince(apiKeyId, sinceMs, requestedModel),
11349
+ sumBuckets: (apiKeyId, sinceMs, grainMs, requestedModel) => handle.usage.sumBuckets(apiKeyId, sinceMs, grainMs, requestedModel),
11350
+ oldestSince: (apiKeyId, sinceMs, requestedModel) => handle.usage.oldestSince(apiKeyId, sinceMs, requestedModel),
11182
11351
  rebuildRollup: () => handle.usage.rebuildRollup(),
11183
11352
  auditRollup: () => handle.usage.auditRollup(),
11184
11353
  prune: (olderThan) => handle.usage.prune(olderThan),
@@ -12289,6 +12458,22 @@ function codecFailure(id, hook, what) {
12289
12458
  gatewayAuthored: true
12290
12459
  });
12291
12460
  }
12461
+ function framingAgrees(headers, body, bodyEncoding) {
12462
+ const named = (name) => headers.filter(([n]) => n.toLowerCase() === name);
12463
+ const lengths = named("content-length");
12464
+ const encodings = named("content-encoding");
12465
+ if (bodyEncoding !== undefined) {
12466
+ return lengths.length === 0 && encodings.length === 1 && encodings[0]?.[1].trim().toLowerCase() === bodyEncoding;
12467
+ }
12468
+ if (lengths.length > 1 || encodings.length > 1)
12469
+ return false;
12470
+ const length = lengths[0]?.[1];
12471
+ const bytes = new TextEncoder().encode(body).byteLength;
12472
+ if (length !== undefined && length !== String(bytes))
12473
+ return false;
12474
+ const encoding = encodings[0]?.[1];
12475
+ return encoding === undefined || encoding.trim().toLowerCase() === "identity";
12476
+ }
12292
12477
  function boundedDegradations(entries) {
12293
12478
  if (!Array.isArray(entries))
12294
12479
  return [];
@@ -12345,23 +12530,26 @@ function codecAdapter(id, capabilities, codec, origins) {
12345
12530
  fail,
12346
12531
  ...req.requestId === undefined ? {} : { requestId: req.requestId },
12347
12532
  ...req.autoCache === undefined ? {} : { autoCacheEnabled: req.autoCache },
12348
- ...req.maxOutputTokens === undefined ? {} : { maxOutputTokens: req.maxOutputTokens }
12533
+ ...req.maxOutputTokens === undefined ? {} : { maxOutputTokens: req.maxOutputTokens },
12534
+ ...req.requestStartedAt === undefined ? {} : { requestStartedAt: req.requestStartedAt }
12349
12535
  }));
12350
12536
  const described = built?.request ?? {};
12351
12537
  const request = {
12352
12538
  url: described.url,
12353
12539
  method: described.method,
12354
12540
  headers: Array.isArray(described.headers) ? [...described.headers] : described.headers,
12355
- body: described.body
12541
+ body: described.body,
12542
+ bodyEncoding: described.bodyEncoding
12356
12543
  };
12357
- if (typeof request.body !== "string" || !isSendableUrl(request.url) || !isHttpMethod(request.method) || !Array.isArray(request.headers) || !request.headers.every((pair) => Array.isArray(pair) && pair.length === 2 && typeof pair[0] === "string" && typeof pair[1] === "string")) {
12544
+ if (typeof request.body !== "string" || request.bodyEncoding !== undefined && request.bodyEncoding !== "zstd" || !isSendableUrl(request.url) || !isHttpMethod(request.method) || !Array.isArray(request.headers) || !request.headers.every((pair) => Array.isArray(pair) && pair.length === 2 && typeof pair[0] === "string" && typeof pair[1] === "string") || !framingAgrees(request.headers, request.body, request.bodyEncoding)) {
12358
12545
  throw codecFailure(id, "buildRequest", "did not return a usable request");
12359
12546
  }
12360
12547
  const sendable = {
12361
12548
  url: request.url,
12362
12549
  method: request.method,
12363
12550
  headers: request.headers,
12364
- body: request.body
12551
+ body: request.body,
12552
+ ...request.bodyEncoding === "zstd" ? { bodyEncoding: request.bodyEncoding } : {}
12365
12553
  };
12366
12554
  if (origins !== undefined && !withinOrigins(request.url, origins)) {
12367
12555
  throw codecFailure(id, "buildRequest", "described a request to an origin its manifest does not declare");
@@ -12719,53 +12907,61 @@ var museBodyOrder = [
12719
12907
  ];
12720
12908
 
12721
12909
  // packages/providers/src/openai/profile.ts
12722
- var OPENAI_CLI_VERSION = env("OMNI_OPENAI_CLI_VERSION", "0.159.2");
12910
+ var OPENAI_CLI_VERSION = env("OMNI_OPENAI_CLI_VERSION", "0.163.0");
12723
12911
  var OPENAI_UA_PLATFORM = env("OMNI_OPENAI_UA_PLATFORM", "Windows 10.0.26200");
12724
12912
  var OPENAI_UA_ARCH = env("OMNI_OPENAI_UA_ARCH", "x86_64");
12725
12913
  var OPENAI_UA_TERMINAL = env("OMNI_OPENAI_UA_TERMINAL", "WindowsTerminal");
12726
- var originator = env("OMNI_OPENAI_ORIGINATOR", "codex_cli_rs");
12914
+ var OPENAI_ORIGINATOR = env("OMNI_OPENAI_ORIGINATOR", "codex-tui");
12915
+ var OPENAI_SANDBOX = OPENAI_UA_PLATFORM.startsWith("Windows") ? "windows_elevated" : /^(Mac OS|macOS)/.test(OPENAI_UA_PLATFORM) ? "seatbelt" : "seccomp";
12727
12916
  var openaiProfile = {
12728
12917
  headers: [
12729
12918
  [
12730
12919
  "user-agent",
12731
- env("OMNI_UA_OPENAI", `${originator}/${OPENAI_CLI_VERSION} (${OPENAI_UA_PLATFORM}; ${OPENAI_UA_ARCH}) ${OPENAI_UA_TERMINAL}`)
12920
+ env("OMNI_UA_OPENAI", `${OPENAI_ORIGINATOR}/${OPENAI_CLI_VERSION} (${OPENAI_UA_PLATFORM}; ${OPENAI_UA_ARCH}) ` + `${OPENAI_UA_TERMINAL} (${OPENAI_ORIGINATOR}; ${OPENAI_CLI_VERSION})`)
12732
12921
  ],
12733
- ["originator", originator],
12922
+ ["originator", OPENAI_ORIGINATOR],
12734
12923
  ["version", OPENAI_CLI_VERSION],
12735
12924
  ["x-codex-beta-features", "remote_compaction_v2"],
12736
12925
  ["accept", "text/event-stream"]
12737
12926
  ],
12738
12927
  order: envOrder("OMNI_ORDER_OPENAI", [
12739
- "host",
12740
12928
  "version",
12741
12929
  "x-codex-beta-features",
12742
- "originator",
12930
+ "x-codex-window-id",
12931
+ "x-codex-turn-metadata",
12932
+ "x-openai-internal-codex-responses-lite",
12933
+ "x-codex-routing-hint",
12743
12934
  "x-client-request-id",
12744
12935
  "session-id",
12745
12936
  "thread-id",
12746
12937
  "accept",
12938
+ "content-encoding",
12747
12939
  "content-type",
12748
12940
  "authorization",
12749
12941
  "chatgpt-account-id",
12942
+ "originator",
12750
12943
  "user-agent",
12751
- "accept-encoding",
12944
+ "host",
12752
12945
  "content-length"
12753
12946
  ])
12754
12947
  };
12755
12948
  var openaiBodyOrder = [
12756
12949
  "model",
12757
12950
  "stream",
12951
+ "service_tier",
12758
12952
  "input",
12759
12953
  "instructions",
12760
- "store",
12761
- "reasoning",
12762
- "prompt_cache_key",
12763
12954
  "tools",
12764
12955
  "tool_choice",
12956
+ "parallel_tool_calls",
12957
+ "reasoning",
12958
+ "store",
12959
+ "stream_options",
12765
12960
  "include",
12766
- "service_tier",
12961
+ "prompt_cache_key",
12962
+ "text",
12767
12963
  "client_metadata",
12768
- "parallel_tool_calls",
12964
+ "access_programs",
12769
12965
  "metadata"
12770
12966
  ];
12771
12967
 
@@ -16561,6 +16757,81 @@ function parseMuseUsage(value, now) {
16561
16757
  ]);
16562
16758
  }
16563
16759
 
16760
+ // packages/providers/src/openai/codexIdentity.ts
16761
+ import { createHash as createHash3, createHmac } from "crypto";
16762
+ var NAMESPACE_OID = "6ba7b812-9dad-11d1-80b4-00c04fd430c8";
16763
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16764
+ var WINDOW_END_MS = Date.UTC(2026, 9, 1);
16765
+ var WINDOW_MS2 = 365 * 24 * 60 * 60 * 1000;
16766
+ function isUuid(value) {
16767
+ return UUID.test(value);
16768
+ }
16769
+ function format(bytes) {
16770
+ const hex = Buffer.from(bytes).toString("hex");
16771
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
16772
+ }
16773
+ function withVersion(bytes, version) {
16774
+ const out = Uint8Array.from(bytes.subarray(0, 16));
16775
+ out[6] = (out[6] ?? 0) & 15 | version << 4;
16776
+ out[8] = (out[8] ?? 0) & 63 | 128;
16777
+ return out;
16778
+ }
16779
+ var sha256 = (seed) => createHash3("sha256").update(seed).digest();
16780
+ function uuidV5(namespace, name) {
16781
+ const digest = createHash3("sha1").update(Buffer.from(namespace.replace(/-/g, ""), "hex")).update(name, "utf8").digest();
16782
+ return format(withVersion(digest, 5));
16783
+ }
16784
+ function uuidV4From(seed) {
16785
+ return format(withVersion(sha256(seed), 4));
16786
+ }
16787
+ function uuidV7From(seed, timestampMs) {
16788
+ const bytes = Uint8Array.from(sha256(seed).subarray(0, 16));
16789
+ let ts = Math.max(0, Math.floor(timestampMs));
16790
+ for (let i = 5;i >= 0; i--) {
16791
+ bytes[i] = ts % 256;
16792
+ ts = Math.floor(ts / 256);
16793
+ }
16794
+ return format(withVersion(bytes, 7));
16795
+ }
16796
+ function threadIdFrom(seed) {
16797
+ const digest = sha256(`thread:${seed}`);
16798
+ const offset = digest.readUIntBE(0, 6) % WINDOW_MS2;
16799
+ return uuidV7From(seed, WINDOW_END_MS - WINDOW_MS2 + offset);
16800
+ }
16801
+ function threadInstant(threadId, now) {
16802
+ const hex = threadId.replace(/-/g, "");
16803
+ const version = hex[12];
16804
+ const ts = Number.parseInt(hex.slice(0, 12), 16);
16805
+ if (version === "7" && Number.isFinite(ts) && ts <= now)
16806
+ return ts;
16807
+ const digest = sha256(`thread:${threadId}`);
16808
+ return WINDOW_END_MS - WINDOW_MS2 + digest.readUIntBE(0, 6) % WINDOW_MS2;
16809
+ }
16810
+ var ID_SALT = /^[0-9a-f]{64}$/;
16811
+ function codexSecretsFrom(providerData) {
16812
+ const { installationId, idSalt } = providerData;
16813
+ return {
16814
+ ...typeof installationId === "string" && isUuid(installationId) ? { installationId } : {},
16815
+ ...typeof idSalt === "string" && ID_SALT.test(idSalt) ? { idSalt } : {}
16816
+ };
16817
+ }
16818
+ function ensureCodexSecrets(providerData) {
16819
+ const stored = codexSecretsFrom(providerData);
16820
+ const salt = () => Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString("hex");
16821
+ return {
16822
+ installationId: stored.installationId ?? globalThis.crypto.randomUUID(),
16823
+ idSalt: stored.idSalt ?? salt()
16824
+ };
16825
+ }
16826
+ function keyed(salt, seed) {
16827
+ if (salt === undefined)
16828
+ return seed;
16829
+ return createHmac("sha256", salt).update(seed).digest("hex");
16830
+ }
16831
+ function prefixNamespace(threadId) {
16832
+ return uuidV5(NAMESPACE_OID, threadId);
16833
+ }
16834
+
16564
16835
  // packages/providers/src/openai/oauth.ts
16565
16836
  var CLIENT_ID6 = "app_EMoamEEZ73f0CkXaXp7hrann";
16566
16837
  var AUTHORIZE_URL3 = "https://auth.openai.com/oauth/authorize";
@@ -16621,7 +16892,7 @@ async function* postToken4(body, fail) {
16621
16892
  }
16622
16893
  return token;
16623
16894
  }
16624
- function toResult6(token, fallbackRefresh, now, fallbackAccountId = null) {
16895
+ function toResult6(token, fallbackRefresh, now, fallbackAccountId, prior) {
16625
16896
  const claims = decodeClaims(token.idToken);
16626
16897
  return {
16627
16898
  secrets: {
@@ -16632,7 +16903,10 @@ function toResult6(token, fallbackRefresh, now, fallbackAccountId = null) {
16632
16903
  },
16633
16904
  expiresAt: token.expiresIn === null ? null : now() + token.expiresIn * 1000,
16634
16905
  accountEmail: claims.email,
16635
- providerData: { accountId: claims.accountId ?? fallbackAccountId }
16906
+ providerData: {
16907
+ accountId: claims.accountId ?? fallbackAccountId,
16908
+ ...ensureCodexSecrets(prior)
16909
+ }
16636
16910
  };
16637
16911
  }
16638
16912
  function windowSecondsOf(value) {
@@ -16689,11 +16963,11 @@ var openaiOAuthFlow = {
16689
16963
  url.searchParams.set("id_token_add_organizations", "true");
16690
16964
  url.searchParams.set("codex_cli_simplified_flow", "true");
16691
16965
  url.searchParams.set("state", state);
16692
- url.searchParams.set("originator", "codex_cli_rs");
16966
+ url.searchParams.set("originator", OPENAI_ORIGINATOR);
16693
16967
  url.searchParams.set("prompt", "login");
16694
16968
  return { authorizeUrl: url.toString(), pending: { verifier, challenge, state, redirectUri } };
16695
16969
  },
16696
- async* exchange({ code, pending, fail, now }) {
16970
+ async* exchange({ code, pending, providerData, fail, now }) {
16697
16971
  const [rawCode, pastedState] = code.split("#");
16698
16972
  if (pastedState !== undefined && pastedState !== pending.state) {
16699
16973
  throw fail("AUTH", "authorization state mismatch");
@@ -16704,11 +16978,11 @@ var openaiOAuthFlow = {
16704
16978
  redirect_uri: pending.redirectUri,
16705
16979
  code_verifier: pending.verifier
16706
16980
  }, fail);
16707
- return toResult6(token, null, now);
16981
+ return toResult6(token, null, now, null, providerData ?? {});
16708
16982
  },
16709
16983
  async* refresh({ refreshToken, providerData, fail, now }) {
16710
16984
  const token = yield* postToken4({ grant_type: "refresh_token", refresh_token: refreshToken, scope: SCOPES4 }, fail);
16711
- return toResult6(token, refreshToken, now, accountIdFromProviderData(providerData));
16985
+ return toResult6(token, refreshToken, now, accountIdFromProviderData(providerData), providerData);
16712
16986
  },
16713
16987
  async* usage({ secrets, providerData, now }) {
16714
16988
  if (secrets.accessToken === null)
@@ -17458,7 +17732,7 @@ var customCodec = {
17458
17732
  // packages/providers/src/custom/index.ts
17459
17733
  var customAdapter = codecAdapter("custom", customDescriptor.capabilities, customCodec);
17460
17734
  // packages/providers/src/grok/codec.ts
17461
- import { createHash as createHash4 } from "crypto";
17735
+ import { createHash as createHash5 } from "crypto";
17462
17736
 
17463
17737
  // packages/providers/src/grok/decode.ts
17464
17738
  var ERROR_CODE2 = {
@@ -17657,7 +17931,7 @@ async function* decodeGrokResponses(messages) {
17657
17931
  }
17658
17932
 
17659
17933
  // packages/providers/src/grok/wire.ts
17660
- import { createHash as createHash3 } from "crypto";
17934
+ import { createHash as createHash4 } from "crypto";
17661
17935
  var MAX_TOOLS = 200;
17662
17936
  function encodeToolChoice3(c) {
17663
17937
  switch (c.type) {
@@ -17673,7 +17947,7 @@ function encodeToolChoice3(c) {
17673
17947
  }
17674
17948
  function promptCacheKey(instructions, firstInput) {
17675
17949
  const stable = JSON.stringify({ instructions, firstInput });
17676
- return createHash3("sha256").update(stable).digest("hex").slice(0, 32);
17950
+ return createHash4("sha256").update(stable).digest("hex").slice(0, 32);
17677
17951
  }
17678
17952
  function toGrokWire(req, model) {
17679
17953
  const degradations = [];
@@ -17813,7 +18087,7 @@ var PROXY_URL = "https://cli-chat-proxy.grok.com/v1/responses";
17813
18087
  var API_URL = "https://api.x.ai/v1/responses";
17814
18088
  var CONV_NAMESPACE = "omni-grok-conv";
17815
18089
  function derivedUuid(value) {
17816
- const h = createHash4("sha256").update(`${CONV_NAMESPACE}:${value}`).digest("hex");
18090
+ const h = createHash5("sha256").update(`${CONV_NAMESPACE}:${value}`).digest("hex");
17817
18091
  const variant = (Number.parseInt(h[16], 16) & 3 | 8).toString(16);
17818
18092
  return [
17819
18093
  h.slice(0, 8),
@@ -17873,11 +18147,12 @@ var grokAdapter = codecAdapter("grok", grokDescriptor.capabilities, grokCodec);
17873
18147
  import { request as httpRequest } from "http";
17874
18148
  import { request as httpsRequest } from "https";
17875
18149
  import { Readable } from "stream";
18150
+ import * as zlib from "zlib";
17876
18151
  var CONNECT_ATTEMPT_TIMEOUT_MS = 3000;
17877
18152
  function nodeHttpClient(options = {}) {
17878
18153
  const logger = options.logger ?? noopLogger;
17879
18154
  const now = options.now ?? (() => Date.now());
17880
- return (req) => new Promise((resolve, reject) => {
18155
+ return (req) => encodeBody(req).then((bodyBytes) => new Promise((resolve, reject) => {
17881
18156
  const url = new URL(req.url);
17882
18157
  const startedAt = now();
17883
18158
  let traced = false;
@@ -17908,7 +18183,6 @@ function nodeHttpClient(options = {}) {
17908
18183
  });
17909
18184
  };
17910
18185
  const send = url.protocol === "https:" ? httpsRequest : httpRequest;
17911
- const bodyBytes = Buffer.from(req.body, "utf8");
17912
18186
  const headers = {};
17913
18187
  for (const [name, value] of req.headers)
17914
18188
  headers[name] = value;
@@ -17966,6 +18240,22 @@ function nodeHttpClient(options = {}) {
17966
18240
  if (bodyBytes.byteLength > 0)
17967
18241
  outgoing.write(bodyBytes);
17968
18242
  outgoing.end();
18243
+ }));
18244
+ }
18245
+ function encodeBody(req) {
18246
+ const raw = Buffer.from(req.body, "utf8");
18247
+ if (req.bodyEncoding !== "zstd")
18248
+ return Promise.resolve(raw);
18249
+ return zstdEncode(raw);
18250
+ }
18251
+ function zstdEncode(raw, create = (options) => zlib.createZstdCompress(options)) {
18252
+ return new Promise((resolve, reject) => {
18253
+ const stream = create({ params: { [zlib.constants.ZSTD_c_compressionLevel]: 3 } });
18254
+ const chunks = [];
18255
+ stream.on("data", (chunk) => chunks.push(chunk));
18256
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
18257
+ stream.on("error", reject);
18258
+ stream.end(raw);
17969
18259
  });
17970
18260
  }
17971
18261
  function hasHeader(req, lowerName) {
@@ -18808,7 +19098,7 @@ async function* decodeMuseResponses(messages, options = {}) {
18808
19098
  }
18809
19099
 
18810
19100
  // packages/providers/src/muse/wire.ts
18811
- import { createHash as createHash5 } from "crypto";
19101
+ import { createHash as createHash6 } from "crypto";
18812
19102
  function suppliedKey(req) {
18813
19103
  const vendor = req.vendor?.openai;
18814
19104
  for (const name of ["prompt_cache_key", "session_id"]) {
@@ -18820,7 +19110,7 @@ function suppliedKey(req) {
18820
19110
  }
18821
19111
  function cacheKey(req, instructions, firstInput) {
18822
19112
  const stable = req.conversationId !== undefined && req.conversationId.length > 0 ? JSON.stringify({ conversation: req.conversationId }) : JSON.stringify({ instructions, firstInput: firstInput ?? null });
18823
- return createHash5("sha256").update(stable).digest("hex").slice(0, 32);
19113
+ return createHash6("sha256").update(stable).digest("hex").slice(0, 32);
18824
19114
  }
18825
19115
  function repairOrphanedCalls(input) {
18826
19116
  const answered = new Set;
@@ -19020,6 +19310,49 @@ var museCodec = {
19020
19310
 
19021
19311
  // packages/providers/src/muse/index.ts
19022
19312
  var museAdapter = codecAdapter("muse", museDescriptor.capabilities, museCodec);
19313
+ // packages/providers/src/openai/codexCatalog.ts
19314
+ var CODEX_EFFORTS = new Set([
19315
+ "none",
19316
+ "minimal",
19317
+ "low",
19318
+ "medium",
19319
+ "high",
19320
+ "xhigh",
19321
+ "max",
19322
+ "ultra",
19323
+ "persistent"
19324
+ ]);
19325
+ function isCodexEffort(value) {
19326
+ return typeof value === "string" && CODEX_EFFORTS.has(value);
19327
+ }
19328
+ var entry = (defaultEffort, nodeReplAutoReviewRequired, over = {}) => ({
19329
+ lite: true,
19330
+ verbosity: "low",
19331
+ defaultEffort,
19332
+ lowestEffort: "low",
19333
+ nodeReplAutoReviewRequired,
19334
+ nodeReplDisabled: false,
19335
+ supportsParallelToolCalls: true,
19336
+ ...over
19337
+ });
19338
+ var UNLISTED = entry("low", false);
19339
+ var MODELS = Object.assign(Object.create(null), {
19340
+ "gpt-6-astra": entry("low", true),
19341
+ "gpt-6.1-sol": entry("low", true),
19342
+ "gpt-6-sol": entry("medium", true),
19343
+ "gpt-6-luna": entry("medium", false),
19344
+ "gpt-5.6-sol": entry("low", false),
19345
+ "gpt-5.6-terra": entry("medium", false),
19346
+ "gpt-5.6-luna": entry("medium", false),
19347
+ "gpt-daybreak-blue-latest": entry("low", false),
19348
+ "gpt-daybreak-red-latest": entry("medium", false, { verbosity: "high" }),
19349
+ "gpt-5.5": entry("medium", false, { lite: false }),
19350
+ "codex-auto-review": entry("medium", false)
19351
+ });
19352
+ function codexModel(model) {
19353
+ return MODELS[model] ?? UNLISTED;
19354
+ }
19355
+
19023
19356
  // packages/providers/src/openai/decode.ts
19024
19357
  var ERROR_CODE4 = {
19025
19358
  rate_limit_exceeded: "RATE_LIMIT",
@@ -19051,6 +19384,7 @@ var KNOWN_EVENTS4 = new Set([
19051
19384
  "response.reasoning_text.done",
19052
19385
  "response.function_call_arguments.delta",
19053
19386
  "response.function_call_arguments.done",
19387
+ "response.metadata",
19054
19388
  "response.completed",
19055
19389
  "response.incomplete",
19056
19390
  "response.failed",
@@ -19236,8 +19570,170 @@ async function* decodeResponses(messages, options = {}) {
19236
19570
  }
19237
19571
  }
19238
19572
 
19573
+ // packages/providers/src/openai/codexLite.ts
19574
+ var PASSTHROUGH = "internal_chat_message_metadata_passthrough";
19575
+ function turnId(threadId, index, threadAt, salt) {
19576
+ return uuidV7From(keyed(salt, `${threadId}:turn:${index}`), threadAt + index);
19577
+ }
19578
+ function contentKinds(content, role) {
19579
+ if (!Array.isArray(content))
19580
+ return;
19581
+ if (role === "assistant")
19582
+ return content.map(() => "unknown");
19583
+ const kinds = [];
19584
+ for (const part of content) {
19585
+ const type = part.type;
19586
+ if (type === "input_text")
19587
+ kinds.push("user.text");
19588
+ else if (type === "input_image")
19589
+ kinds.push("user.image");
19590
+ else
19591
+ return;
19592
+ }
19593
+ return kinds;
19594
+ }
19595
+ function toLiteInput(args) {
19596
+ const { threadId } = args;
19597
+ const threadAt = threadInstant(threadId, args.startedAt ?? Number.POSITIVE_INFINITY);
19598
+ const startedAt = args.startedAt ?? threadAt;
19599
+ const historySeed = args.requestId ?? threadId;
19600
+ const salt = args.idSalt;
19601
+ const namespace = prefixNamespace(threadId);
19602
+ const prefix = [];
19603
+ if (args.tools !== undefined && args.tools.length > 0) {
19604
+ const tools = [{ type: "namespace", name: "functions", description: "", tools: args.tools }];
19605
+ prefix.push({
19606
+ type: "additional_tools",
19607
+ id: `at_${uuidV5(namespace, JSON.stringify(tools))}`,
19608
+ role: "developer",
19609
+ tools
19610
+ });
19611
+ }
19612
+ if (args.instructions !== undefined && args.instructions.length > 0) {
19613
+ prefix.push({
19614
+ type: "message",
19615
+ id: `msg_${uuidV5(namespace, args.instructions)}`,
19616
+ role: "developer",
19617
+ content: [{ type: "input_text", text: args.instructions }],
19618
+ [PASSTHROUGH]: { content_item_kinds: ["model.base_instructions"] }
19619
+ });
19620
+ }
19621
+ let turn = 0;
19622
+ const history = args.input.map((raw, index) => {
19623
+ const item = raw;
19624
+ turn = args.turnOf.get(item) ?? turn;
19625
+ const turn_id = turnId(threadId, turn, threadAt, salt);
19626
+ const fresh = (prefixName) => `${prefixName}_${uuidV7From(keyed(salt, `${historySeed}:item:${index}`), startedAt)}`;
19627
+ const create_time = (startedAt * 1000 + index) / 1e6;
19628
+ switch (item.type) {
19629
+ case "message": {
19630
+ const role = String(item.role);
19631
+ const kinds = contentKinds(item.content, role);
19632
+ if (role === "assistant") {
19633
+ return {
19634
+ type: "message",
19635
+ status: "completed",
19636
+ id: fresh("msg"),
19637
+ role,
19638
+ content: Array.isArray(item.content) ? item.content.map((part) => part.type === "output_text" ? { ...part, annotations: [] } : part) : item.content,
19639
+ [PASSTHROUGH]: {
19640
+ turn_id,
19641
+ ...kinds === undefined ? {} : { content_item_kinds: kinds }
19642
+ }
19643
+ };
19644
+ }
19645
+ return {
19646
+ type: "message",
19647
+ id: fresh("msg"),
19648
+ role,
19649
+ content: item.content,
19650
+ [PASSTHROUGH]: {
19651
+ turn_id,
19652
+ create_time,
19653
+ ...kinds === undefined ? {} : { content_item_kinds: kinds }
19654
+ }
19655
+ };
19656
+ }
19657
+ case "function_call":
19658
+ return {
19659
+ type: "function_call",
19660
+ status: "completed",
19661
+ id: fresh("fc"),
19662
+ name: item.name,
19663
+ arguments: item.arguments,
19664
+ call_id: item.call_id,
19665
+ [PASSTHROUGH]: { turn_id }
19666
+ };
19667
+ case "function_call_output":
19668
+ return {
19669
+ type: "function_call_output",
19670
+ id: fresh("fco"),
19671
+ call_id: item.call_id,
19672
+ output: item.output,
19673
+ [PASSTHROUGH]: { turn_id, create_time }
19674
+ };
19675
+ case "reasoning": {
19676
+ const { [PASSTHROUGH]: _client, ...rest } = item;
19677
+ return { ...rest, [PASSTHROUGH]: { turn_id } };
19678
+ }
19679
+ default:
19680
+ return item;
19681
+ }
19682
+ });
19683
+ return {
19684
+ input: [...prefix, ...history],
19685
+ turnId: turnId(threadId, turn, threadAt, salt),
19686
+ startedAt
19687
+ };
19688
+ }
19689
+ function asciiJson(value) {
19690
+ return JSON.stringify(value).replace(/[^\x20-\x7e]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
19691
+ }
19692
+ function codexTurn(args) {
19693
+ const { threadId, turnId: turn } = args;
19694
+ const windowId = `${threadId}:0`;
19695
+ const installationId = args.installationId ?? uuidV4From(`installation:${args.installationSeed}`);
19696
+ const threadAt = threadInstant(threadId, args.startedAt);
19697
+ const catalog = codexModel(args.model);
19698
+ const turnMetadata = asciiJson({
19699
+ installation_id: installationId,
19700
+ session_id: threadId,
19701
+ thread_id: threadId,
19702
+ agent_name: "/root",
19703
+ turn_id: turn,
19704
+ window_id: windowId,
19705
+ window_number: 0,
19706
+ context_window_id: uuidV7From(keyed(args.idSalt, `${threadId}:context-window`), threadAt),
19707
+ request_kind: "turn",
19708
+ history_initialization: "new",
19709
+ root_turn_id: turn,
19710
+ thread_source: "user",
19711
+ turn_trigger: "user",
19712
+ sandbox: OPENAI_SANDBOX,
19713
+ sandbox_mode: "workspace-write",
19714
+ auto_review_enabled: false,
19715
+ node_repl_auto_review_required: catalog.nodeReplAutoReviewRequired,
19716
+ node_repl_disabled: catalog.nodeReplDisabled,
19717
+ turn_started_at_unix_ms: args.startedAt,
19718
+ analytics_enabled: false,
19719
+ model: args.model,
19720
+ reasoning_effort: args.effort
19721
+ });
19722
+ const clientMetadata = {
19723
+ turn_id: turn,
19724
+ thread_id: threadId,
19725
+ "x-codex-turn-metadata": turnMetadata,
19726
+ mcp_attribution: '{"status":"none"}',
19727
+ guardian_credits_requested: "true",
19728
+ "x-codex-installation-id": installationId,
19729
+ "x-codex-window-id": windowId,
19730
+ root_turn_id: turn,
19731
+ session_id: threadId
19732
+ };
19733
+ return { windowId, turnMetadata, clientMetadata };
19734
+ }
19735
+
19239
19736
  // packages/providers/src/openai/wire.ts
19240
- import { createHash as createHash6 } from "crypto";
19241
19737
  var MAX_CACHE_KEY_LENGTH = 1024;
19242
19738
  var HEADER_SAFE_KEY = /^[\x21-\x7e]+$/;
19243
19739
  function suppliedKey2(req) {
@@ -19249,9 +19745,16 @@ function suppliedKey2(req) {
19249
19745
  }
19250
19746
  return;
19251
19747
  }
19252
- function cacheKey2(req, instructions, firstInput) {
19253
- const stable = req.conversationId !== undefined && req.conversationId.length > 0 ? JSON.stringify({ conversation: req.conversationId }) : JSON.stringify({ instructions, firstInput: firstInput ?? null });
19254
- return createHash6("sha256").update(stable).digest("hex").slice(0, 32);
19748
+ function cacheKey2(req, instructions, firstInput, salt) {
19749
+ const supplied = suppliedKey2(req);
19750
+ if (supplied !== undefined) {
19751
+ if (isUuid(supplied))
19752
+ return { key: supplied, seed: supplied };
19753
+ const seed = JSON.stringify({ key: supplied });
19754
+ return { key: threadIdFrom(keyed(salt, seed)), seed };
19755
+ }
19756
+ const seed = req.conversationId !== undefined && req.conversationId.length > 0 ? JSON.stringify({ conversation: req.conversationId }) : JSON.stringify({ instructions, firstInput: firstInput ?? null });
19757
+ return { key: threadIdFrom(keyed(salt, seed)), seed };
19255
19758
  }
19256
19759
  function repairOrphanedCalls2(input) {
19257
19760
  const answered = new Set;
@@ -19282,6 +19785,44 @@ function encodeToolChoice7(c) {
19282
19785
  return { type: "function", name: c.name };
19283
19786
  }
19284
19787
  }
19788
+ function codexReasoning(req, model, note) {
19789
+ const catalog = codexModel(model);
19790
+ const context = catalog.lite ? { context: "all_turns" } : {};
19791
+ const r = req.reasoning;
19792
+ if (r === undefined)
19793
+ return { effort: catalog.defaultEffort, ...context };
19794
+ if (r.mode === "off") {
19795
+ note("openai:reasoning-off-as-lowest");
19796
+ return { effort: catalog.lowestEffort, ...context };
19797
+ }
19798
+ if (r.mode === "betweenTools")
19799
+ return { effort: catalog.lowestEffort, ...context };
19800
+ if (r.mode === "budget") {
19801
+ note("openai:reasoning-budget-dropped");
19802
+ return { effort: catalog.defaultEffort, ...context };
19803
+ }
19804
+ return {
19805
+ effort: r.effort ?? catalog.defaultEffort,
19806
+ ...r.display === "omitted" || r.display === "updates" ? {} : { summary: "auto" },
19807
+ ...context
19808
+ };
19809
+ }
19810
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19811
+ function mergeCodexVendor(body, vendor, note) {
19812
+ const target = body;
19813
+ for (const [name, value] of Object.entries(vendor)) {
19814
+ if (name === "client_metadata") {
19815
+ note("openai:client-metadata-replaced");
19816
+ continue;
19817
+ }
19818
+ const ours = target[name];
19819
+ if ((name === "text" || name === "reasoning") && isRecord8(ours) && isRecord8(value)) {
19820
+ target[name] = { ...ours, ...value };
19821
+ continue;
19822
+ }
19823
+ target[name] = value;
19824
+ }
19825
+ }
19285
19826
  function toResponsesWire(req, model, opts = { oauth: false }) {
19286
19827
  const degradations = [];
19287
19828
  const input = [];
@@ -19289,6 +19830,12 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19289
19830
  if (!degradations.includes(d))
19290
19831
  degradations.push(d);
19291
19832
  };
19833
+ const turnOf = new WeakMap;
19834
+ let turn = -1;
19835
+ const push = (item) => {
19836
+ input.push(item);
19837
+ turnOf.set(item, Math.max(0, turn));
19838
+ };
19292
19839
  if (req.betas?.includes(CONTEXT_1M_BETA))
19293
19840
  note("openai:context-1m-dropped");
19294
19841
  for (const message of req.messages) {
@@ -19300,10 +19847,15 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19300
19847
  const flush = () => {
19301
19848
  if (parts.length === 0)
19302
19849
  return;
19303
- input.push({ type: "message", role, content: [...parts] });
19850
+ push({ type: "message", role, content: [...parts] });
19304
19851
  parts.length = 0;
19305
19852
  };
19853
+ let opened = false;
19306
19854
  for (const block of message.content) {
19855
+ if (message.role === "user" && !opened && block.type !== "toolResult") {
19856
+ opened = true;
19857
+ turn++;
19858
+ }
19307
19859
  switch (block.type) {
19308
19860
  case "text":
19309
19861
  parts.push({
@@ -19324,7 +19876,7 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19324
19876
  break;
19325
19877
  case "toolUse":
19326
19878
  flush();
19327
- input.push({
19879
+ push({
19328
19880
  type: "function_call",
19329
19881
  call_id: block.id,
19330
19882
  name: block.name,
@@ -19336,7 +19888,7 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19336
19888
  note("openai:anthropic-native-block-dropped");
19337
19889
  flush();
19338
19890
  const files = responsesFiles(block.files ?? []);
19339
- input.push({
19891
+ push({
19340
19892
  type: "function_call_output",
19341
19893
  call_id: block.toolUseId,
19342
19894
  output: withFileText(block.content, files.text)
@@ -19364,7 +19916,7 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19364
19916
  if (block.provider === "openai") {
19365
19917
  flush();
19366
19918
  const { id: _id, ...rest } = block.data;
19367
- input.push({ type: block.blockType, ...rest });
19919
+ push({ type: block.blockType, ...rest });
19368
19920
  break;
19369
19921
  }
19370
19922
  note("openai:anthropic-native-block-dropped");
@@ -19376,8 +19928,10 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19376
19928
  repairOrphanedCalls2(input);
19377
19929
  const body = { model, input, stream: req.stream, store: false };
19378
19930
  const instructions = systemText(req.system, "openai", note);
19379
- if (instructions !== undefined && instructions.length > 0)
19931
+ const lite = opts.oauth && codexModel(model).lite;
19932
+ if (instructions !== undefined && instructions.length > 0 && !opts.oauth) {
19380
19933
  body.instructions = instructions;
19934
+ }
19381
19935
  if (req.maxTokens !== undefined) {
19382
19936
  if (opts.oauth)
19383
19937
  note("openai:max-tokens-dropped");
@@ -19390,22 +19944,45 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19390
19944
  else
19391
19945
  body.temperature = req.temperature;
19392
19946
  }
19947
+ let liteTools;
19393
19948
  if (req.tools !== undefined) {
19394
19949
  const custom = req.tools.filter((t) => t.kind === "portable");
19395
19950
  if (custom.length !== req.tools.length)
19396
19951
  note("openai:anthropic-tool-dropped");
19397
- body.tools = custom.map((t) => ({
19398
- type: "function",
19399
- name: t.name,
19400
- description: t.description,
19401
- parameters: t.inputSchema
19402
- }));
19952
+ if (opts.oauth) {
19953
+ const tools = custom.map((t) => ({
19954
+ type: "function",
19955
+ name: t.name,
19956
+ description: t.description,
19957
+ strict: false,
19958
+ parameters: t.inputSchema
19959
+ }));
19960
+ if (lite)
19961
+ liteTools = tools;
19962
+ else
19963
+ body.tools = tools;
19964
+ } else {
19965
+ body.tools = custom.map((t) => ({
19966
+ type: "function",
19967
+ name: t.name,
19968
+ description: t.description,
19969
+ parameters: t.inputSchema
19970
+ }));
19971
+ }
19403
19972
  }
19404
19973
  if (req.toolChoice !== undefined)
19405
19974
  body.tool_choice = encodeToolChoice7(req.toolChoice);
19406
- if (req.reasoning?.mode === "betweenTools")
19407
- degradations.push("openai:reasoning-between-tools-as-off");
19408
- if (req.reasoning !== undefined && req.reasoning.mode !== "off" && req.reasoning.mode !== "betweenTools") {
19975
+ else if (opts.oauth)
19976
+ body.tool_choice = "auto";
19977
+ if (opts.oauth) {
19978
+ body.parallel_tool_calls = !lite && codexModel(model).supportsParallelToolCalls;
19979
+ }
19980
+ if (req.reasoning?.mode === "betweenTools") {
19981
+ note(opts.oauth ? "openai:reasoning-between-tools-as-lowest" : "openai:reasoning-between-tools-as-off");
19982
+ }
19983
+ if (opts.oauth) {
19984
+ body.reasoning = codexReasoning(req, model, note);
19985
+ } else if (req.reasoning !== undefined && req.reasoning.mode !== "off" && req.reasoning.mode !== "betweenTools") {
19409
19986
  if (req.reasoning.mode === "budget") {
19410
19987
  degradations.push("openai:reasoning-budget-dropped");
19411
19988
  } else {
@@ -19416,36 +19993,98 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
19416
19993
  };
19417
19994
  }
19418
19995
  }
19419
- const key = suppliedKey2(req) ?? cacheKey2(req, instructions ?? "", input[0]);
19420
- Object.assign(body, req.vendor?.openai ?? {});
19996
+ const salt = opts.oauth ? opts.idSalt : undefined;
19997
+ const { key, seed } = cacheKey2(req, instructions ?? "", input[0], salt);
19998
+ if (opts.oauth) {
19999
+ body.include = ["reasoning.encrypted_content"];
20000
+ body.text = { verbosity: codexModel(model).verbosity };
20001
+ mergeCodexVendor(body, req.vendor?.openai ?? {}, note);
20002
+ } else {
20003
+ Object.assign(body, req.vendor?.openai ?? {});
20004
+ }
19421
20005
  const moved = moveToResponsesText(req, body, "openai");
19422
20006
  if (moved !== undefined)
19423
20007
  note(moved);
19424
20008
  body.prompt_cache_key = key;
19425
- return { body, degradations, cacheKey: key };
20009
+ if (!opts.oauth)
20010
+ return { body, degradations, cacheKey: key };
20011
+ const built = toLiteInput({
20012
+ threadId: key,
20013
+ input,
20014
+ turnOf,
20015
+ tools: liteTools,
20016
+ instructions,
20017
+ requestId: opts.requestId,
20018
+ startedAt: opts.startedAt,
20019
+ idSalt: salt
20020
+ });
20021
+ body.input = built.input;
20022
+ const sent = body.reasoning?.effort;
20023
+ const codex = codexTurn({
20024
+ threadId: key,
20025
+ turnId: built.turnId,
20026
+ installationId: opts.installationId,
20027
+ installationSeed: opts.accountId ?? seed,
20028
+ idSalt: salt,
20029
+ model,
20030
+ effort: isCodexEffort(sent) ? sent : codexModel(model).defaultEffort,
20031
+ startedAt: built.startedAt
20032
+ });
20033
+ body.client_metadata = codex.clientMetadata;
20034
+ return { body, degradations, cacheKey: key, codex };
19426
20035
  }
19427
20036
 
19428
20037
  // packages/providers/src/openai/codec.ts
19429
20038
  var OAUTH_URL2 = "https://chatgpt.com/backend-api/codex/responses";
19430
20039
  var API_URL3 = "https://api.openai.com/v1/responses";
20040
+ var HEADER_SAFE = /^[\x21-\x7e]+$/;
20041
+ function routingHint(model, tier) {
20042
+ if (!HEADER_SAFE.test(model))
20043
+ return;
20044
+ if (typeof tier === "string" && HEADER_SAFE.test(tier) && !tier.includes(";")) {
20045
+ return `model=${model};tier=${tier}`;
20046
+ }
20047
+ return `model=${model}`;
20048
+ }
19431
20049
  var openaiCodec = {
19432
20050
  buildRequest(input) {
19433
20051
  const oauth = input.credentials.accessToken !== null;
19434
- const { body, degradations, cacheKey } = toResponsesWire(input.request, input.model, { oauth });
19435
- const protocol = [["content-type", "application/json"]];
20052
+ const accountId = input.credentials.providerData.accountId;
20053
+ const secrets = oauth ? codexSecretsFrom(input.credentials.providerData) : {};
20054
+ const { body, degradations, cacheKey, codex } = toResponsesWire(input.request, input.model, {
20055
+ oauth,
20056
+ requestId: input.requestId,
20057
+ startedAt: input.requestStartedAt,
20058
+ accountId: typeof accountId === "string" ? accountId : undefined,
20059
+ installationId: secrets.installationId,
20060
+ idSalt: secrets.idSalt
20061
+ });
20062
+ const protocol = [];
20063
+ if (codex !== undefined) {
20064
+ protocol.push(["x-codex-window-id", codex.windowId], ["x-codex-turn-metadata", codex.turnMetadata]);
20065
+ if (codexModel(input.model).lite) {
20066
+ protocol.push(["x-openai-internal-codex-responses-lite", "true"]);
20067
+ }
20068
+ const hint = routingHint(input.model, body.service_tier);
20069
+ if (hint !== undefined)
20070
+ protocol.push(["x-codex-routing-hint", hint]);
20071
+ }
20072
+ protocol.push(["x-client-request-id", cacheKey], ["session-id", cacheKey], ["thread-id", cacheKey]);
20073
+ if (oauth)
20074
+ protocol.push(["content-encoding", "zstd"]);
20075
+ protocol.push(["content-type", "application/json"]);
19436
20076
  if (oauth) {
19437
20077
  protocol.push(["authorization", `Bearer ${input.credentials.accessToken}`]);
19438
- const accountId = input.credentials.providerData.accountId;
19439
20078
  if (typeof accountId === "string")
19440
20079
  protocol.push(["chatgpt-account-id", accountId]);
20080
+ protocol.push(["host", "chatgpt.com"]);
19441
20081
  } else if (input.credentials.apiKey !== null) {
19442
20082
  protocol.push(["authorization", `Bearer ${input.credentials.apiKey}`]);
19443
20083
  } else {
19444
20084
  throw input.fail("AUTH", "openai credential has no token");
19445
20085
  }
19446
- protocol.push(["x-client-request-id", cacheKey], ["session-id", cacheKey], ["thread-id", cacheKey]);
19447
20086
  const headers = orderHeaders(mergeHeaders(openaiProfile.headers, protocol), openaiProfile.order);
19448
- const include = body.include;
20087
+ const include = input.request.vendor?.openai?.include;
19449
20088
  const nativeReasoning = Array.isArray(include) && include.includes("reasoning.encrypted_content");
19450
20089
  return {
19451
20090
  decodeState: { nativeReasoning },
@@ -19453,7 +20092,8 @@ var openaiCodec = {
19453
20092
  url: oauth ? OAUTH_URL2 : API_URL3,
19454
20093
  method: "POST",
19455
20094
  headers,
19456
- body: JSON.stringify(orderFields({ ...body, stream: true }, openaiBodyOrder))
20095
+ body: JSON.stringify(orderFields({ ...body, stream: true }, openaiBodyOrder)),
20096
+ ...oauth ? { bodyEncoding: "zstd" } : {}
19457
20097
  },
19458
20098
  degradations
19459
20099
  };
@@ -19545,11 +20185,17 @@ function createConnectFlows(deps) {
19545
20185
  const provider = deps.providers[flow.provider];
19546
20186
  if (provider === undefined)
19547
20187
  throw new GatewayError("BAD_REQUEST", "provider does not support OAuth");
19548
- const result = await provider.exchange({ code, pending: flow.pending }, { http: deps.http, now: deps.now });
20188
+ const existing = flow.credentialId === undefined ? null : await deps.store.credentials.get(flow.credentialId);
20189
+ if (flow.credentialId !== undefined && existing === null) {
20190
+ throw new GatewayError("BAD_REQUEST", "credential not found");
20191
+ }
20192
+ const prior = existing !== null && existing.provider === flow.provider ? existing.providerData : undefined;
20193
+ const result = await provider.exchange({
20194
+ code,
20195
+ pending: flow.pending,
20196
+ ...prior === undefined ? {} : { providerData: prior }
20197
+ }, { http: deps.http, now: deps.now });
19549
20198
  if (flow.credentialId !== undefined) {
19550
- const existing = await deps.store.credentials.get(flow.credentialId);
19551
- if (existing === null)
19552
- throw new GatewayError("BAD_REQUEST", "credential not found");
19553
20199
  await deps.store.credentials.updateSecrets(flow.credentialId, result.secrets, result.expiresAt);
19554
20200
  await deps.store.credentials.update(flow.credentialId, {
19555
20201
  enabled: true,
@@ -20821,9 +21467,20 @@ function configuredWindows(limits) {
20821
21467
  async function readLimitUsage(store, id, limits, now) {
20822
21468
  if (limits === null)
20823
21469
  return [];
21470
+ const readings = await readScope(store, id, limits, null, now);
21471
+ for (const model of Object.keys(limits.models ?? {})) {
21472
+ const override = modelOverride(limits, model);
21473
+ if (override === undefined)
21474
+ continue;
21475
+ readings.push(...await readScope(store, id, override, model, now));
21476
+ }
21477
+ return readings;
21478
+ }
21479
+ async function readScope(store, id, limits, model, now) {
20824
21480
  const sums = new Map;
20825
21481
  for (const window of configuredWindows(limits)) {
20826
- sums.set(window, await store.usage.sumSince(id, now - WINDOW_MS[window]));
21482
+ const since = now - WINDOW_MS[window];
21483
+ sums.set(window, await store.usage.sumSince(id, since, model ?? undefined));
20827
21484
  }
20828
21485
  const readings = [];
20829
21486
  for (const dimension of ["requests", "tokens", "spend"]) {
@@ -20836,7 +21493,7 @@ async function readLimitUsage(store, id, limits, now) {
20836
21493
  continue;
20837
21494
  const sum = sums.get(window);
20838
21495
  const used = sum === undefined ? null : dimension === "requests" ? sum.requests : dimension === "tokens" ? sum.tokens : sum.costUsd;
20839
- readings.push({ dimension, window, limit, used });
21496
+ readings.push({ dimension, window, limit, used, model });
20840
21497
  }
20841
21498
  }
20842
21499
  if (limits.concurrency !== undefined && limits.concurrency !== null) {
@@ -20844,7 +21501,8 @@ async function readLimitUsage(store, id, limits, now) {
20844
21501
  dimension: "concurrency",
20845
21502
  window: null,
20846
21503
  limit: limits.concurrency,
20847
- used: null
21504
+ used: null,
21505
+ model
20848
21506
  });
20849
21507
  }
20850
21508
  return readings;
@@ -22397,7 +23055,7 @@ function createRefresher(deps) {
22397
23055
  }
22398
23056
  // packages/control/src/pluginProviders.ts
22399
23057
  import { join as join5 } from "path";
22400
- function isRecord8(value) {
23058
+ function isRecord9(value) {
22401
23059
  return typeof value === "object" && value !== null && !Array.isArray(value);
22402
23060
  }
22403
23061
  function isNumber(value) {
@@ -22408,7 +23066,7 @@ function checkAt(ok, id, path, expected) {
22408
23066
  throw new Error(`provider ${id} descriptor: ${path} must be ${expected}`);
22409
23067
  }
22410
23068
  function checkLimits(id, path, value) {
22411
- checkAt(isRecord8(value), id, path, "an object");
23069
+ checkAt(isRecord9(value), id, path, "an object");
22412
23070
  const limits = value;
22413
23071
  checkAt(isNumber(limits.contextWindow), id, `${path}.contextWindow`, "a finite number");
22414
23072
  checkAt(isNumber(limits.maxOutputTokens), id, `${path}.maxOutputTokens`, "a finite number");
@@ -22426,19 +23084,19 @@ function checkDescriptor(id, record) {
22426
23084
  }
22427
23085
  }
22428
23086
  const capabilities = record.capabilities;
22429
- checkAt(isRecord8(capabilities), id, "capabilities", "an object");
23087
+ checkAt(isRecord9(capabilities), id, "capabilities", "an object");
22430
23088
  for (const flag of ["tools", "images", "reasoning"]) {
22431
23089
  const value = capabilities[flag];
22432
23090
  checkAt(typeof value === "boolean", id, `capabilities.${flag}`, "a boolean");
22433
23091
  }
22434
23092
  const write = record.writeOverInput;
22435
- checkAt(isRecord8(write), id, "writeOverInput", "an object");
23093
+ checkAt(isRecord9(write), id, "writeOverInput", "an object");
22436
23094
  for (const ttl of ["fiveMinute", "oneHour"]) {
22437
23095
  const value = write[ttl];
22438
23096
  checkAt(isNumber(value), id, `writeOverInput.${ttl}`, "a finite number");
22439
23097
  }
22440
23098
  const catalog = record.catalog;
22441
- checkAt(isRecord8(catalog), id, "catalog", "an object");
23099
+ checkAt(isRecord9(catalog), id, "catalog", "an object");
22442
23100
  const entry = catalog;
22443
23101
  checkAt(typeof entry.defaultModel === "string", id, "catalog.defaultModel", "a string");
22444
23102
  checkAt(Array.isArray(entry.authTypes), id, "catalog.authTypes", "an array");
@@ -22449,11 +23107,11 @@ function checkDescriptor(id, record) {
22449
23107
  checkAt(Array.isArray(entry.models), id, "catalog.models", "an array");
22450
23108
  for (const [index, model] of entry.models.entries()) {
22451
23109
  const at = `catalog.models[${index}]`;
22452
- checkAt(isRecord8(model), id, at, "an object");
23110
+ checkAt(isRecord9(model), id, at, "an object");
22453
23111
  const choice = model;
22454
23112
  checkAt(typeof choice.id === "string", id, `${at}.id`, "a string");
22455
23113
  checkAt(typeof choice.label === "string", id, `${at}.label`, "a string");
22456
- checkAt(isRecord8(choice.pricing), id, `${at}.pricing`, "an object");
23114
+ checkAt(isRecord9(choice.pricing), id, `${at}.pricing`, "an object");
22457
23115
  const pricing = choice.pricing;
22458
23116
  for (const field of ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h"]) {
22459
23117
  checkAt(isNumber(pricing[field]), id, `${at}.pricing.${field}`, "a finite number");
@@ -22469,13 +23127,13 @@ function checkDescriptor(id, record) {
22469
23127
  checkAt(typeof prefix === "string", id, `modelPrefixes[${index}]`, "a string");
22470
23128
  }
22471
23129
  const presentation = record.presentation;
22472
- checkAt(isRecord8(presentation), id, "presentation", "an object");
23130
+ checkAt(isRecord9(presentation), id, "presentation", "an object");
22473
23131
  const shown = presentation;
22474
23132
  for (const field of ["label", "tone"]) {
22475
23133
  checkAt(typeof shown[field] === "string", id, `presentation.${field}`, "a string");
22476
23134
  }
22477
23135
  checkAt(isNumber(shown.order), id, "presentation.order", "a finite number");
22478
- checkAt(isRecord8(shown.colour), id, "presentation.colour", "an object");
23136
+ checkAt(isRecord9(shown.colour), id, "presentation.colour", "an object");
22479
23137
  for (const scheme of ["light", "dark"]) {
22480
23138
  const value = shown.colour[scheme];
22481
23139
  checkAt(typeof value === "string", id, `presentation.colour.${scheme}`, "a string");
@@ -22487,7 +23145,7 @@ function checkDescriptor(id, record) {
22487
23145
  function validateOAuthFlow(id, value) {
22488
23146
  if (value === undefined)
22489
23147
  return;
22490
- if (!isRecord8(value))
23148
+ if (!isRecord9(value))
22491
23149
  throw new Error(`provider ${id} oauth must be an object`);
22492
23150
  const kind = value.kind;
22493
23151
  if (kind !== "pkce" && kind !== "device") {
@@ -23159,7 +23817,7 @@ function requirePositional(parsed, index, name) {
23159
23817
  return value;
23160
23818
  }
23161
23819
  // packages/plugin-api/src/events.ts
23162
- var WINDOW_MS2 = {
23820
+ var WINDOW_MS3 = {
23163
23821
  "1m": 60000,
23164
23822
  "5h": 5 * 60 * 60 * 1000,
23165
23823
  "1w": 7 * 24 * 60 * 60 * 1000
@@ -24399,6 +25057,23 @@ var dbRestore = {
24399
25057
 
24400
25058
  // apps/cli/src/commands/keys.ts
24401
25059
  var WINDOWED = new Set(["requests", "tokens", "spend"]);
25060
+ var GAUGE = "concurrency";
25061
+ var MODELS2 = "models";
25062
+ function emptyLimits() {
25063
+ return { scope: {}, models: Object.create(null) };
25064
+ }
25065
+ function toWire2(limits) {
25066
+ const models = Object.keys(limits.models);
25067
+ if (models.length === 0)
25068
+ return { ...limits.scope };
25069
+ const out = {};
25070
+ for (const model of models) {
25071
+ const scope = limits.models[model];
25072
+ if (scope !== undefined)
25073
+ out[model] = scope;
25074
+ }
25075
+ return { ...limits.scope, [MODELS2]: out };
25076
+ }
24402
25077
  var PROTO = "__proto__";
24403
25078
  function rejectProto(flag, name) {
24404
25079
  if (name === PROTO)
@@ -24423,6 +25098,7 @@ function parseLimitFlags(entries, flag, into = {}) {
24423
25098
  throw new UsageError(`${flag} ${pair} needs a window, e.g. ${flag} ${pair}:1m=60`);
24424
25099
  }
24425
25100
  rejectProto(flag, pair);
25101
+ rejectModels(flag, pair);
24426
25102
  limits[pair] = value;
24427
25103
  continue;
24428
25104
  }
@@ -24433,14 +25109,109 @@ function parseLimitFlags(entries, flag, into = {}) {
24433
25109
  }
24434
25110
  rejectProto(flag, dimension);
24435
25111
  rejectProto(flag, window);
24436
- const existing = limits[dimension];
24437
- const windows = typeof existing === "object" ? existing : {};
24438
- windows[window] = value;
24439
- limits[dimension] = windows;
25112
+ rejectModels(flag, dimension);
25113
+ setSlot(limits, dimension, window, value);
24440
25114
  }
24441
25115
  return limits;
24442
25116
  }
25117
+ function rejectModels(flag, name) {
25118
+ if (name === MODELS2) {
25119
+ throw new UsageError(`${flag} cannot name "${MODELS2}"; per-model limits use --model-limit`);
25120
+ }
25121
+ }
25122
+ function setSlot(scope, dimension, window, value) {
25123
+ const existing = scope[dimension];
25124
+ const windows = typeof existing === "object" ? existing : {};
25125
+ windows[window] = value;
25126
+ scope[dimension] = windows;
25127
+ }
25128
+ function splitModelSlot(spec, flag, shape) {
25129
+ const malformed = () => new UsageError(`${flag} must be ${shape}, got "${spec}"`);
25130
+ const last = spec.lastIndexOf(":");
25131
+ if (last < 0)
25132
+ throw malformed();
25133
+ const tail = spec.slice(last + 1);
25134
+ if (tail === GAUGE) {
25135
+ const model = spec.slice(0, last);
25136
+ if (model.length === 0)
25137
+ throw malformed();
25138
+ rejectProto(flag, model);
25139
+ return { model, dimension: GAUGE, window: null };
25140
+ }
25141
+ if (WINDOWED.has(tail)) {
25142
+ throw new UsageError(`${flag} ${spec} needs a window, e.g. ${flag} ${spec}:1m`);
25143
+ }
25144
+ const head = spec.slice(0, last);
25145
+ const second = head.lastIndexOf(":");
25146
+ if (second < 0)
25147
+ throw malformed();
25148
+ const model = head.slice(0, second);
25149
+ const dimension = head.slice(second + 1);
25150
+ if (model.length === 0 || dimension.length === 0 || tail.length === 0)
25151
+ throw malformed();
25152
+ rejectProto(flag, model);
25153
+ rejectProto(flag, dimension);
25154
+ rejectProto(flag, tail);
25155
+ return { model, dimension, window: tail };
25156
+ }
25157
+ var MODEL_LIMIT_SHAPE = "model:dimension:window=value or model:concurrency=value";
25158
+ function parseModelLimitFlags(entries, into) {
25159
+ const flag = "--model-limit";
25160
+ for (const entry of entries) {
25161
+ const equals = entry.lastIndexOf("=");
25162
+ if (equals <= 0)
25163
+ throw new UsageError(`${flag} must be ${MODEL_LIMIT_SHAPE}, got "${entry}"`);
25164
+ const spec = entry.slice(0, equals);
25165
+ const raw = entry.slice(equals + 1);
25166
+ const value = Number(raw);
25167
+ if (raw.trim().length === 0 || !Number.isFinite(value)) {
25168
+ throw new UsageError(`${flag} ${spec} must be a number, got "${raw}"`);
25169
+ }
25170
+ const { model, dimension, window } = splitModelSlot(spec, flag, MODEL_LIMIT_SHAPE);
25171
+ const scope = into.models[model] ?? {};
25172
+ if (window === null)
25173
+ scope[dimension] = value;
25174
+ else
25175
+ setSlot(scope, dimension, window, value);
25176
+ into.models[model] = scope;
25177
+ }
25178
+ }
25179
+ function applyModelUnsetFlags(limits, entries) {
25180
+ const flag = "--unset-model-limit";
25181
+ for (const entry of entries) {
25182
+ if (entry.includes("=")) {
25183
+ throw new UsageError(`${flag} names a limit to remove, not a value, got "${entry}"`);
25184
+ }
25185
+ const { model, dimension, window } = splitModelSlot(entry, flag, "model:dimension:window or model:concurrency");
25186
+ const scope = limits.models[model];
25187
+ const missing = () => new UsageError(`this key has no ${entry} limit`);
25188
+ if (scope === undefined || !Object.hasOwn(scope, dimension))
25189
+ throw missing();
25190
+ if (window === null) {
25191
+ delete scope[dimension];
25192
+ } else {
25193
+ const windows = scope[dimension];
25194
+ if (typeof windows !== "object" || !Object.hasOwn(windows, window))
25195
+ throw missing();
25196
+ delete windows[window];
25197
+ if (Object.keys(windows).length === 0)
25198
+ delete scope[dimension];
25199
+ }
25200
+ if (Object.keys(scope).length === 0)
25201
+ delete limits.models[model];
25202
+ }
25203
+ }
24443
25204
  function toLoose(limits) {
25205
+ const loose = emptyLimits();
25206
+ loose.scope = toLooseScope(limits);
25207
+ for (const [model, override] of Object.entries(limits.models ?? {})) {
25208
+ const scope = toLooseScope(override);
25209
+ if (Object.keys(scope).length > 0)
25210
+ loose.models[model] = scope;
25211
+ }
25212
+ return loose;
25213
+ }
25214
+ function toLooseScope(limits) {
24444
25215
  const loose = {};
24445
25216
  for (const dimension of ["requests", "tokens", "spend"]) {
24446
25217
  const windows = limits[dimension];
@@ -24496,6 +25267,7 @@ function limitRow(reading) {
24496
25267
  const money = reading.dimension === "spend";
24497
25268
  const amount = (value) => money ? formatUsd(value) : String(value);
24498
25269
  return [
25270
+ reading.model ?? "key",
24499
25271
  reading.dimension,
24500
25272
  reading.window ?? "\u2014",
24501
25273
  amount(reading.limit),
@@ -24504,6 +25276,13 @@ function limitRow(reading) {
24504
25276
  ];
24505
25277
  }
24506
25278
  function summarizeLimits(limits) {
25279
+ const parts = summarizeScope(limits, "");
25280
+ for (const [model, override] of Object.entries(limits.models ?? {})) {
25281
+ parts.push(...summarizeScope(override, `${model}:`));
25282
+ }
25283
+ return parts.length === 0 ? "\u2014" : parts.join(" ");
25284
+ }
25285
+ function summarizeScope(limits, prefix) {
24507
25286
  const parts = [];
24508
25287
  for (const dimension of ["requests", "tokens", "spend"]) {
24509
25288
  const windows = limits[dimension];
@@ -24512,13 +25291,13 @@ function summarizeLimits(limits) {
24512
25291
  for (const [window, value] of Object.entries(windows)) {
24513
25292
  if (value === null || value === undefined)
24514
25293
  continue;
24515
- parts.push(`${dimension}:${window}=${value}`);
25294
+ parts.push(`${prefix}${dimension}:${window}=${value}`);
24516
25295
  }
24517
25296
  }
24518
25297
  if (limits.concurrency !== undefined && limits.concurrency !== null) {
24519
- parts.push(`concurrency=${limits.concurrency}`);
25298
+ parts.push(`${prefix}concurrency=${limits.concurrency}`);
24520
25299
  }
24521
- return parts.length === 0 ? "\u2014" : parts.join(" ");
25300
+ return parts;
24522
25301
  }
24523
25302
  function parseWhen(flag, raw) {
24524
25303
  const at = Date.parse(raw);
@@ -24566,22 +25345,26 @@ var keysList = {
24566
25345
  }
24567
25346
  };
24568
25347
  var keysCreate = {
24569
- usage: "keys create [--label L] [--allow <model> ...] [--limit <d>:<w>=N ...] [--expires <when>] [--no-bodies]",
25348
+ usage: "keys create [--label L] [--allow <model> ...] [--limit <d>:<w>=N ...] [--model-limit <model>:<d>:<w>=N ...] [--expires <when>] [--no-bodies]",
24570
25349
  summary: "Mint a gateway API key, printed once",
24571
25350
  options: {
24572
25351
  label: { type: "string" },
24573
25352
  allow: { type: "string", multiple: true },
24574
25353
  limit: { type: "string", multiple: true },
25354
+ "model-limit": { type: "string", multiple: true },
24575
25355
  expires: { type: "string" },
24576
25356
  "no-bodies": { type: "boolean" }
24577
25357
  },
24578
25358
  async run(args, { ctx, writer }) {
24579
25359
  const allow = listFlag(args.values, "allow");
24580
25360
  const expires = stringFlag(args.values, "expires");
25361
+ const limits = emptyLimits();
25362
+ parseLimitFlags(listFlag(args.values, "limit") ?? [], "--limit", limits.scope);
25363
+ parseModelLimitFlags(listFlag(args.values, "model-limit") ?? [], limits);
24581
25364
  const created = await createKey(await ctx.store(), {
24582
25365
  ...stringFlag(args.values, "label") === undefined ? {} : { label: stringFlag(args.values, "label") },
24583
25366
  modelAllowlist: allow ?? null,
24584
- limits: parseLimitFlags(listFlag(args.values, "limit") ?? [], "--limit"),
25367
+ limits: toWire2(limits),
24585
25368
  bodyLoggingOptOut: boolFlag(args.values, "no-bodies"),
24586
25369
  expiresAt: expires === undefined ? null : parseWhen("--expires", expires)
24587
25370
  });
@@ -24592,16 +25375,24 @@ var keysCreate = {
24592
25375
  }
24593
25376
  };
24594
25377
  var keysLimits = {
24595
- usage: "keys limits <id> [--set <d>:<w>=N ...] [--unset <d>:<w> ...]",
25378
+ usage: "keys limits <id> [--set <d>:<w>=N ...] [--unset <d>:<w> ...] [--model-limit <model>:<d>:<w>=N ...] [--unset-model-limit <model>:<d>[:<w>] ...]",
24596
25379
  summary: "Show or edit one key's limits, with what has been used against them",
24597
25380
  options: {
24598
25381
  set: { type: "string", multiple: true },
24599
- unset: { type: "string", multiple: true }
25382
+ unset: { type: "string", multiple: true },
25383
+ "model-limit": { type: "string", multiple: true },
25384
+ "unset-model-limit": { type: "string", multiple: true }
24600
25385
  },
24601
25386
  async run(args, { ctx, writer }) {
24602
25387
  const id = requirePositional(args, 0, "key id");
24603
- const sets = listFlag(args.values, "set") ?? [];
24604
- const unsets = listFlag(args.values, "unset") ?? [];
25388
+ const sets = [
25389
+ ...listFlag(args.values, "set") ?? [],
25390
+ ...listFlag(args.values, "model-limit") ?? []
25391
+ ];
25392
+ const unsets = [
25393
+ ...listFlag(args.values, "unset") ?? [],
25394
+ ...listFlag(args.values, "unset-model-limit") ?? []
25395
+ ];
24605
25396
  const store = await ctx.store();
24606
25397
  const existing = (await listKeys(store)).find((entry) => entry.id === id);
24607
25398
  if (existing === undefined)
@@ -24616,10 +25407,12 @@ var keysLimits = {
24616
25407
  replaced = true;
24617
25408
  note(ctx, writer, paint(ctx, "yellow", "the stored limits could not be read and are being replaced"));
24618
25409
  }
24619
- const next = existing.limits === null ? {} : toLoose(existing.limits);
24620
- applyUnsetFlags(next, unsets);
24621
- parseLimitFlags(sets, "--set", next);
24622
- key = await setKeyLimits(store, id, { limits: next });
25410
+ const next = existing.limits === null ? emptyLimits() : toLoose(existing.limits);
25411
+ applyUnsetFlags(next.scope, listFlag(args.values, "unset") ?? []);
25412
+ applyModelUnsetFlags(next, listFlag(args.values, "unset-model-limit") ?? []);
25413
+ parseLimitFlags(listFlag(args.values, "set") ?? [], "--set", next.scope);
25414
+ parseModelLimitFlags(listFlag(args.values, "model-limit") ?? [], next);
25415
+ key = await setKeyLimits(store, id, { limits: toWire2(next) });
24623
25416
  }
24624
25417
  emit(ctx, writer, replaced ? { ...key, limitsReplaced: true } : key, () => {
24625
25418
  const head = fields([
@@ -24640,6 +25433,7 @@ no limits configured; this key is unlimited`;
24640
25433
  return `${head}
24641
25434
 
24642
25435
  ${table([
25436
+ { header: "SCOPE" },
24643
25437
  { header: "DIMENSION" },
24644
25438
  { header: "WINDOW" },
24645
25439
  { header: "LIMIT", align: "right" },
@@ -25287,7 +26081,7 @@ function createServiceDeps(input) {
25287
26081
  }
25288
26082
 
25289
26083
  // apps/cli/src/version.ts
25290
- var VERSION = "0.13.6";
26084
+ var VERSION = "0.14.0";
25291
26085
 
25292
26086
  // apps/cli/src/commands/service.ts
25293
26087
  function gatewayArgv(root) {