@aglyn/plugins-ai 1.0.0-beta.250 → 1.0.0-beta.253

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 (126) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-brief-dialog.component.js +15 -1
  3. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +1 -1
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-job-plan.component.js +20 -2
  7. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  8. package/src/lib/components/ai-jobs-store.d.ts +11 -0
  9. package/src/lib/components/ai-jobs-store.js +22 -0
  10. package/src/lib/components/ai-jobs-store.js.map +1 -1
  11. package/src/lib/jobs/ai-job-crm-step.js +7 -4
  12. package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
  13. package/src/lib/jobs/ai-job-drafts.d.ts +24 -0
  14. package/src/lib/jobs/ai-job-drafts.js +46 -0
  15. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  16. package/src/lib/jobs/ai-job-experiment-step.js +6 -1
  17. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  18. package/src/lib/jobs/ai-job-form-dataset.d.ts +6 -1
  19. package/src/lib/jobs/ai-job-form-dataset.js +7 -2
  20. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
  21. package/src/lib/jobs/ai-job-form-reuse.d.ts +78 -0
  22. package/src/lib/jobs/ai-job-form-reuse.js +136 -0
  23. package/src/lib/jobs/ai-job-form-reuse.js.map +1 -0
  24. package/src/lib/jobs/ai-job-form-step.d.ts +17 -0
  25. package/src/lib/jobs/ai-job-form-step.js +63 -11
  26. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-insight-step.js +6 -1
  28. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  29. package/src/lib/jobs/ai-job-page-language.d.ts +10 -5
  30. package/src/lib/jobs/ai-job-page-language.js +10 -5
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-plan-step.d.ts +32 -1
  33. package/src/lib/jobs/ai-job-plan-step.js +53 -2
  34. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-seo-step.js +5 -3
  36. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-content.d.ts +7 -5
  38. package/src/lib/jobs/ai-job-site-content.js +7 -5
  39. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  40. package/src/lib/jobs/ai-job-site-datasets.d.ts +1 -1
  41. package/src/lib/jobs/ai-job-site-datasets.js +2 -2
  42. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  43. package/src/lib/jobs/ai-job-site-step.js +2 -1
  44. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  45. package/src/lib/jobs/ai-job-text-step.js +6 -3
  46. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  47. package/src/lib/jobs/ai-layout-stock-photos.d.ts +11 -7
  48. package/src/lib/jobs/ai-layout-stock-photos.js +25 -10
  49. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  50. package/src/lib/jobs/ai-stock-business-nouns.d.ts +62 -0
  51. package/src/lib/jobs/ai-stock-business-nouns.js +264 -0
  52. package/src/lib/jobs/ai-stock-business-nouns.js.map +1 -0
  53. package/src/lib/layout-language/ai-layout-language.js +5 -1
  54. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-links.js +5 -2
  56. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  57. package/src/lib/model/ai-build-plan.d.ts +7 -3
  58. package/src/lib/model/ai-build-plan.js +11 -1
  59. package/src/lib/model/ai-build-plan.js.map +1 -1
  60. package/src/lib/model/ai-credit-estimate.d.ts +39 -19
  61. package/src/lib/model/ai-credit-estimate.js +43 -19
  62. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  63. package/src/lib/model/ai-job-activity.d.ts +30 -0
  64. package/src/lib/model/ai-job-activity.js +35 -0
  65. package/src/lib/model/ai-job-activity.js.map +1 -1
  66. package/src/lib/model/ai-media-credits.d.ts +5 -3
  67. package/src/lib/model/ai-media-credits.js +7 -5
  68. package/src/lib/model/ai-media-credits.js.map +1 -1
  69. package/src/lib/model/ai-site-job.js +6 -3
  70. package/src/lib/model/ai-site-job.js.map +1 -1
  71. package/src/lib/model/ai-site-start-seo.d.ts +11 -2
  72. package/src/lib/model/ai-site-start-seo.js +29 -15
  73. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  74. package/src/lib/model/ai-site-store-pages.d.ts +5 -0
  75. package/src/lib/model/ai-site-store-pages.js +2 -0
  76. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  77. package/src/lib/plugin-config.js +2 -2
  78. package/src/lib/plugin-config.js.map +1 -1
  79. package/src/lib/providers/anthropic-crm-wording-superseded.d.ts +29 -0
  80. package/src/lib/providers/anthropic-crm-wording-superseded.js +31 -0
  81. package/src/lib/providers/anthropic-crm-wording-superseded.js.map +1 -0
  82. package/src/lib/providers/anthropic.js +29 -7
  83. package/src/lib/providers/anthropic.js.map +1 -1
  84. package/src/lib/providers/catalog.d.ts +24 -17
  85. package/src/lib/providers/catalog.js +63 -30
  86. package/src/lib/providers/catalog.js.map +1 -1
  87. package/src/lib/providers/contract.d.ts +19 -1
  88. package/src/lib/providers/contract.js +10 -0
  89. package/src/lib/providers/contract.js.map +1 -1
  90. package/src/lib/providers/model-choice.d.ts +1 -1
  91. package/src/lib/providers/model-choice.js +39 -30
  92. package/src/lib/providers/model-choice.js.map +1 -1
  93. package/src/lib/providers/routing.d.ts +20 -1
  94. package/src/lib/providers/routing.js +71 -12
  95. package/src/lib/providers/routing.js.map +1 -1
  96. package/src/lib/runtime/ai-dev-env.d.ts +58 -1
  97. package/src/lib/runtime/ai-dev-env.js +86 -6
  98. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  99. package/src/lib/runtime/ai-dev-replay.d.ts +4 -0
  100. package/src/lib/runtime/ai-dev-replay.js +4 -0
  101. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  102. package/src/lib/runtime/ai-doctrine-validators.js +13 -3
  103. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  104. package/src/lib/runtime/ai-doctrine.js +8 -4
  105. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  106. package/src/lib/runtime/ai-eval-live.d.ts +43 -1
  107. package/src/lib/runtime/ai-eval-live.js +79 -9
  108. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  109. package/src/lib/runtime/ai-live-batch.d.ts +20 -4
  110. package/src/lib/runtime/ai-live-batch.js +57 -10
  111. package/src/lib/runtime/ai-live-batch.js.map +1 -1
  112. package/src/lib/runtime/ai-palette.generated.js +4 -0
  113. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  114. package/src/lib/runtime/ai-runtime.js +21 -4
  115. package/src/lib/runtime/ai-runtime.js.map +1 -1
  116. package/src/lib/runtime/seo-fields.js +4 -3
  117. package/src/lib/runtime/seo-fields.js.map +1 -1
  118. package/src/lib/server/ai-assist.js +3 -2
  119. package/src/lib/server/ai-assist.js.map +1 -1
  120. package/src/lib/server/ai-media-image.d.ts +5 -1
  121. package/src/lib/server/ai-media-image.js +6 -2
  122. package/src/lib/server/ai-media-image.js.map +1 -1
  123. package/src/lib/server/ai-media-svg.js +4 -0
  124. package/src/lib/server/ai-media-svg.js.map +1 -1
  125. package/src/lib/usage/ai-allotment-alerts.js +2 -5
  126. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2026 Aglyn LLC
@@ -14,7 +15,7 @@
14
15
  * See the License for the specific language governing permissions and
15
16
  * limitations under the License.
16
17
  */ import { AI_SETTING_PLATFORM, AI_STEP_MODEL_SETTING } from "../plugin-config.js";
17
- import { AI_STEP_TIERS, aiCatalogEntry, aiDefaultModelFor } from "./catalog.js";
18
+ import { AI_MODEL_CATALOG, AI_STEP_TIERS, aiCatalogEntry, aiDefaultModelFor } from "./catalog.js";
18
19
  import { aiProviderById, defaultAiProvider } from "./registry.js";
19
20
  /**
20
21
  * The routing table (AGL-2937, AGL-2939): step kind → provider and model,
@@ -31,6 +32,20 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
31
32
  * 3. the catalog's tier for the step kind on the resolved provider.
32
33
  */ /** The environment override for every step kind. */ export const AI_DEFAULT_MODEL_ENV = 'AI_DEFAULT_MODEL';
33
34
  /** The assistant's own override, honored before the general one. */ export const ASSIST_MODEL_ENV = 'ASSIST_MODEL';
35
+ /**
36
+ * The model that serves a catalog id (AGL-3781): the id itself, or for a
37
+ * retired row the successor it names, followed to a current row. An id the
38
+ * catalog does not know is returned as it is, for the caller to refuse.
39
+ */ export function aiServingModelId(modelId) {
40
+ let current = modelId;
41
+ for(let hops = 0; hops < AI_MODEL_CATALOG.length; hops += 1){
42
+ var _aiCatalogEntry;
43
+ const successor = (_aiCatalogEntry = aiCatalogEntry(current)) == null ? void 0 : _aiCatalogEntry.successor;
44
+ if (!successor) return current;
45
+ current = successor;
46
+ }
47
+ return current;
48
+ }
34
49
  /** A settings value that names a real choice, or `undefined` for the platform default. */ function chosen(settings, key) {
35
50
  const value = settings == null ? void 0 : settings[key];
36
51
  return typeof value === 'string' && value && value !== AI_SETTING_PLATFORM ? value : undefined;
@@ -52,9 +67,12 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
52
67
  var _process_env_ASSIST_MODEL_ENV, _process_env_AI_DEFAULT_MODEL_ENV;
53
68
  const provider = resolveAiProvider(settings);
54
69
  if (!provider) return undefined;
70
+ // A setting or an override that names a retired model is served by its
71
+ // successor, never by the retired model itself.
55
72
  const servedBy = (modelId)=>{
56
73
  var _aiCatalogEntry;
57
- return modelId && ((_aiCatalogEntry = aiCatalogEntry(modelId)) == null ? void 0 : _aiCatalogEntry.provider) === provider.id ? modelId : undefined;
74
+ const serving = modelId ? aiServingModelId(modelId) : undefined;
75
+ return serving && ((_aiCatalogEntry = aiCatalogEntry(serving)) == null ? void 0 : _aiCatalogEntry.provider) === provider.id ? serving : undefined;
58
76
  };
59
77
  const model = (_ref = (_ref1 = (_servedBy = servedBy(chosen(settings, AI_STEP_MODEL_SETTING[kind]))) != null ? _servedBy : kind === 'assist.chat' ? servedBy((_process_env_ASSIST_MODEL_ENV = process.env[ASSIST_MODEL_ENV]) == null ? void 0 : _process_env_ASSIST_MODEL_ENV.trim()) : undefined) != null ? _ref1 : servedBy((_process_env_AI_DEFAULT_MODEL_ENV = process.env[AI_DEFAULT_MODEL_ENV]) == null ? void 0 : _process_env_AI_DEFAULT_MODEL_ENV.trim())) != null ? _ref : aiDefaultModelFor(provider.id, AI_STEP_TIERS[kind]);
60
78
  return model ? {
@@ -96,7 +114,10 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
96
114
  }
97
115
  },
98
116
  'copy.element': {
99
- thinking: null,
117
+ // One element's copy on the fast tier, whose model thinks unless told not
118
+ // to (AGL-3781): the ceiling holds the copy alone, as it always has, and
119
+ // the effort is the model's own.
120
+ thinking: 'off',
100
121
  effort: null,
101
122
  maxTokens: 1024,
102
123
  maxTokensBasis: 'one element of copy, about the length of the text it replaces',
@@ -157,6 +178,9 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
157
178
  },
158
179
  'job.layout': {
159
180
  // The plan was reasoned out and confirmed before the step, so it writes without thinking.
181
+ // A site's header and footer in the layout language think adaptively, at
182
+ // low since AGL-3781 (`AI_LAYOUT_LANGUAGE_THINKING`): the live round of
183
+ // 2026-10-14 built 13 of 13 frames at low, none cut off.
160
184
  thinking: 'off',
161
185
  effort: null,
162
186
  maxTokens: 8000,
@@ -172,8 +196,11 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
172
196
  }
173
197
  },
174
198
  'job.template': {
199
+ // Medium since the golden A/B of 2026-10-14 (AGL-3781): both template
200
+ // briefs passed at medium as at the default, with about a third fewer
201
+ // output tokens.
175
202
  thinking: 'off',
176
- effort: null,
203
+ effort: 'medium',
177
204
  maxTokens: 8000,
178
205
  maxTokensBasis: "tighter than the doctrine's default for a template, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way",
179
206
  eval: {
@@ -207,9 +234,11 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
207
234
  // CRM by AI (AGL-2917): short answers through a strict tool, written from
208
235
  // the facts the CRM reports about the record the member opened, with no
209
236
  // thinking. The ceiling fits an inline door, so a member is answered in
210
- // the request that asked.
211
- thinking: null,
212
- effort: null,
237
+ // the request that asked, and has no room for thinking: the fast tier's
238
+ // model thinks unless told not to, so the row says off, and its effort
239
+ // is the low one a short answer read off given facts needs (AGL-3781).
240
+ thinking: 'off',
241
+ effort: 'low',
213
242
  maxTokens: 700,
214
243
  maxTokensBasis: "the largest answer a CRM tool accepts: sixty columns matched to fields by number, or an email draft at its length limits, at three characters a token with room, as ai-job-crm-step.spec.ts measures it",
215
244
  eval: {
@@ -283,7 +312,10 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
283
312
  // confirmed, so it writes without thinking (AGL-2907). The ceiling here
284
313
  // is the MOST a pass may ask for; each pass sizes its own down from it to
285
314
  // what fits the time the job beat can give the step, which on the
286
- // balanced tier is about half of this.
315
+ // balanced tier is about half of this. A page in the layout language
316
+ // thinks adaptively, at low since AGL-3781 (`AI_LAYOUT_LANGUAGE_THINKING`):
317
+ // the live round of 2026-10-14 built 39 of 39 pages at low, 64 of 65
318
+ // answers held their rules on the first ask, none cut off.
287
319
  thinking: 'off',
288
320
  effort: null,
289
321
  maxTokens: 2000,
@@ -409,8 +441,12 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
409
441
  }
410
442
  },
411
443
  'job.seo': {
412
- thinking: null,
413
- effort: null,
444
+ // A listing written from the page's own text to fixed lengths, sized for
445
+ // the answer alone: no thinking, which the fast tier's model does unless
446
+ // told not to, at the low effort a short bounded answer needs (AGL-3781).
447
+ // The audit's site proposal and fix batches run on the same row.
448
+ thinking: 'off',
449
+ effort: 'low',
414
450
  maxTokens: 1024,
415
451
  maxTokensBasis: "the largest listing the tool accepts, at three characters a token with a quarter again, as ai-job-seo-step.spec.ts measures it; an audit's site proposal and fix batches keep ceilings of their own, measured the same way",
416
452
  eval: {
@@ -424,8 +460,10 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
424
460
  }
425
461
  },
426
462
  'job.text': {
463
+ // Low since the golden A/B of 2026-10-14 (AGL-3781): the tagline brief
464
+ // passed at low, its copy scored higher than at the default.
427
465
  thinking: 'adaptive',
428
- effort: null,
466
+ effort: 'low',
429
467
  maxTokens: 1024,
430
468
  maxTokensBasis: 'a few paragraphs of copy; a brief asking for more gets a draft to extend',
431
469
  eval: {
@@ -439,8 +477,11 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
439
477
  }
440
478
  },
441
479
  'job.theme': {
480
+ // Medium since the golden A/B of 2026-10-14 (AGL-3781): the theme brief
481
+ // passed at medium with the default's scores and half its output tokens.
482
+ // A site start's look runs its own setting (`ai-job-site-look.ts`).
442
483
  thinking: 'adaptive',
443
- effort: null,
484
+ effort: 'medium',
444
485
  maxTokens: 8000,
445
486
  maxTokensBasis: 'the largest answer the theme tool accepts, about 2,500 tokens of JSON as ai-job-theme-step.spec.ts measures it, with as much again to think in',
446
487
  eval: {
@@ -454,6 +495,12 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
454
495
  }
455
496
  },
456
497
  'job.plan': {
498
+ // A site's plan is an outline written without thinking (AGL-3594), and a
499
+ // paid site's asks medium (`AI_PAID_SITE_PLAN_EFFORT`, AGL-3781): the live
500
+ // round of 2026-10-14 planned 11 of 11 paid site briefs at medium in 16
501
+ // attempts to the default's 22, $0.129 to $0.164, with no refusal and no
502
+ // rule broken. Every other plan, and a Free site's on the fast tier, asks
503
+ // this row's effort — the model's default — until a round holds it lower.
457
504
  thinking: 'adaptive',
458
505
  effort: null,
459
506
  maxTokens: 8000,
@@ -473,6 +520,18 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
473
520
  }
474
521
  }
475
522
  };
523
+ /**
524
+ * What a step kind's row asks of its model, as the fields a request carries:
525
+ * only those the row names, so a row that names none sends none and the
526
+ * model's own default stands.
527
+ */ export function aiRouteSettings(kind) {
528
+ const row = AI_ROUTING_TABLE[kind];
529
+ return _extends({}, row.thinking ? {
530
+ thinking: row.thinking
531
+ } : {}, row.effort ? {
532
+ effort: row.effort
533
+ } : {});
534
+ }
476
535
  /** The model id alone — what a door records on the meter and the job. */ export function aiModelForStep(kind, settings) {
477
536
  const route = resolveAiRoute(kind, settings);
478
537
  if (!route) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/routing.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_SETTING_PLATFORM, AI_STEP_MODEL_SETTING } from '../plugin-config'\nimport {\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiDefaultModelFor,\n type AiStepKind,\n} from './catalog'\nimport type { AiEvalKind } from '../runtime/ai-eval'\nimport type { AiEffort, AiProvider, AiThinking } from './contract'\nimport { aiProviderById, defaultAiProvider } from './registry'\n\n/**\n * The routing table (AGL-2937, AGL-2939): step kind → provider and model,\n * through the catalog and never a vendor literal.\n *\n * Precedence, dearest override first:\n *\n * 1. the org's `pluginSettings/ai` — a provider, and a model per step kind\n * that must be one the provider serves;\n * 2. the deployment's environment — `AI_PROVIDER` for the provider,\n * `AI_DEFAULT_MODEL` for every step, and `ASSIST_MODEL` for the\n * assistant alone (the incident-response override the chat door has\n * always honored);\n * 3. the catalog's tier for the step kind on the resolved provider.\n */\n\n/** The environment override for every step kind. */\nexport const AI_DEFAULT_MODEL_ENV = 'AI_DEFAULT_MODEL'\n/** The assistant's own override, honored before the general one. */\nexport const ASSIST_MODEL_ENV = 'ASSIST_MODEL'\n\n/** The org's resolved `pluginSettings/ai` values, as `getPluginConfig` merges them. */\nexport type AiPluginSettings = Record<string, unknown>\n\nexport interface AiRoute {\n provider: AiProvider\n model: string\n}\n\n/** A settings value that names a real choice, or `undefined` for the platform default. */\nfunction chosen(settings: AiPluginSettings | undefined, key: string): string | undefined {\n const value = settings?.[key]\n return typeof value === 'string' && value && value !== AI_SETTING_PLATFORM\n ? value\n : undefined\n}\n\n/** The provider a workspace runs on: its own choice when registered, else the platform's. */\nexport function resolveAiProvider(settings?: AiPluginSettings): AiProvider | undefined {\n const wanted = chosen(settings, 'provider')\n if (wanted) {\n const named = aiProviderById(wanted)\n if (named) return named\n }\n return defaultAiProvider()\n}\n\n/**\n * The model a step kind runs on for a workspace, on the resolved provider.\n * A chosen model on another provider is not served — the provider decides\n * what it can run — and falls back to the provider's default for the tier.\n */\nexport function resolveAiRoute(\n kind: AiStepKind,\n settings?: AiPluginSettings,\n): AiRoute | undefined {\n const provider = resolveAiProvider(settings)\n if (!provider) return undefined\n const servedBy = (modelId: string | undefined): string | undefined =>\n modelId && aiCatalogEntry(modelId)?.provider === provider.id ? modelId : undefined\n const model =\n servedBy(chosen(settings, AI_STEP_MODEL_SETTING[kind])) ??\n (kind === 'assist.chat' ? servedBy(process.env[ASSIST_MODEL_ENV]?.trim()) : undefined) ??\n servedBy(process.env[AI_DEFAULT_MODEL_ENV]?.trim()) ??\n aiDefaultModelFor(provider.id, AI_STEP_TIERS[kind])\n return model ? { provider, model } : undefined\n}\n\n/**\n * What one step kind asks of its model, beside the tier it is served from\n * (`AI_STEP_TIERS` in the catalog): whether it thinks and how hard, its\n * answer ceiling, and the eval score that tier holds on the step's golden\n * briefs (AGL-2937).\n */\nexport interface AiRoutingRow {\n /** `null` sends no setting: the model's own default, and all a fast-tier model accepts. */\n thinking: AiThinking | null\n effort: AiEffort | null\n /** The answer ceiling, thinking included. */\n maxTokens: number\n /** What the ceiling is sized from. */\n maxTokensBasis: string\n eval: AiRoutingEval\n}\n\nexport interface AiRoutingEval {\n /** The golden-brief kinds the step answers. */\n kinds: readonly AiEvalKind[]\n /** `answers` scores the step's answers; `plans` scores the plans those kinds' answers carry. */\n scores: 'answers' | 'plans'\n /** `authored` while the score is the hand-written references'; `recorded` once a live run records answers. */\n source: 'authored' | 'recorded'\n passRate: number\n /** The mean answer score; `null` for a plan score, which passes or fails. */\n meanScore: number | null\n}\n\n/**\n * THE ROUTING TABLE (AGL-2937): what each step kind asks of the model, in one\n * place, with the evidence beside it.\n *\n * A step runs on the smallest tier that holds its eval floor. A row moves to a\n * cheaper tier, or a lower effort, only when a recorded eval\n * (`npm run eval:ai-live`) holds the floor there; while a row's score is the\n * authored references', it says so, and the row stays where production has\n * run it. `runtime/ai-eval.spec.ts` recomputes every row's score from the\n * harness, so a row cannot claim a score its briefs do not hold.\n *\n * A ceiling is sized from the p95 output the staff signals page reports for\n * the kind once production has served it; until then `maxTokensBasis` names\n * what it is sized from. The same spec holds every reference answer under its\n * row's ceiling at three characters a token, a reading that errs high for a\n * tokenizer that splits finer than four.\n */\nexport const AI_ROUTING_TABLE: Readonly<Record<AiStepKind, AiRoutingRow>> = {\n 'assist.chat': {\n // A scoped, latency-sensitive chat turn: no thinking, the low effort rung.\n thinking: 'off',\n effort: 'low',\n maxTokens: 1024,\n maxTokensBasis:\n 'a docs-grounded answer of a few short paragraphs; the edit rung raises it for its tool call',\n eval: { kinds: ['chat'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.element': {\n thinking: null,\n effort: null,\n maxTokens: 1024,\n maxTokensBasis: 'one element of copy, about the length of the text it replaces',\n eval: { kinds: ['element'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.blog': {\n thinking: null,\n effort: null,\n maxTokens: 2048,\n maxTokensBasis: 'one blog post body in markdown-lite',\n eval: { kinds: ['blog'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'generate.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.layout': {\n // The plan was reasoned out and confirmed before the step, so it writes without thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a layout, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['layout'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.template': {\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a template, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['template'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.component': {\n // A component's tree and the properties it declares, in one answer, from a\n // plan reasoned out and confirmed before the step (AGL-2908).\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the ceiling the layout and template steps keep, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['component'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.crm': {\n // CRM by AI (AGL-2917): short answers through a strict tool, written from\n // the facts the CRM reports about the record the member opened, with no\n // thinking. The ceiling fits an inline door, so a member is answered in\n // the request that asked.\n thinking: null,\n effort: null,\n maxTokens: 700,\n maxTokensBasis:\n \"the largest answer a CRM tool accepts: sixty columns matched to fields by number, or an email draft at its length limits, at three characters a token with room, as ai-job-crm-step.spec.ts measures it\",\n eval: { kinds: ['crm'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9938 },\n },\n 'job.experiment': {\n // Variants of one piece of copy, or a result explained against a verdict\n // code reached before the request was made (AGL-2914). The judgment is\n // already made either way, so it writes without thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n \"four variants at the tool's own lengths — a name, a headline, a body and a rationale each — written as JSON, about 3,000 characters at three characters a token with room; an explanation is a headline, four sentences and a next step, well under it, as ai-job-experiment-step.spec.ts measures both\",\n eval: { kinds: ['experiment'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.form': {\n // A form's two halves in one answer: the field declaration and the design\n // that renders it, as JSON, with no extended thinking (AGL-2913). The step\n // asks this ceiling, which is the doctrine's own for a form, and registers\n // the time it takes (AGL-3035).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"the largest form the doctrine's output budget admits, a form's fields, its consent and routing declaration and the design that renders them written as JSON at the wordiest golden's characters a stored byte, under it with room; ai-job-form-step.spec.ts measures it\",\n eval: { kinds: ['form'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.insight': {\n // Readers chosen, then their tables phrased (AGL-2915): short sentences\n // over figures code computed, each held to the rows it cites, so no\n // extended thinking. The read call before the answer asks for at most\n // `AI_INSIGHT_READ_MAX_TOKENS` of the same allowance.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n 'five insights at their 280-character bound, each citing three tables of ten rows, and the gap sentence, written as JSON: about 2,700 characters, under 1,000 tokens at three characters a token, with room',\n eval: { kinds: ['insight'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.page': {\n // One section of a page, written against a plan a member already\n // confirmed, so it writes without thinking (AGL-2907). The ceiling here\n // is the MOST a pass may ask for; each pass sizes its own down from it to\n // what fits the time the job beat can give the step, which on the\n // balanced tier is about half of this.\n thinking: 'off',\n effort: null,\n maxTokens: 2000,\n maxTokensBasis:\n \"a section of a page written as JSON, about forty elements at the step's measured 45 tokens an element; a pass lowers it to the worst case that fits AI_JOB_PAGE_STEP_MINIMUM_MS on the model it runs\",\n eval: { kinds: ['page'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.products': {\n // A product's copy from its name, text and photo, or a store's first\n // products, categories and discounts from a brief (AGL-2916): answers held\n // to the storefront claim rules, written with no extended thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the largest catalog the tool accepts, twelve proposed products at every bound written as JSON, which the balanced tier asks as much of as fits a beat; one product's copy asks 1,500 and categories with discounts 2,000, as ai-job-products-step.spec.ts measures them\",\n eval: {\n kinds: ['product', 'catalog', 'categories'],\n scores: 'answers',\n source: 'authored',\n passRate: 1,\n meanScore: 0.9922,\n },\n },\n 'job.email': {\n // One email design from the email palette, written against the brief with\n // no extended thinking (AGL-2912). A campaign job runs the same\n // generation, so it carries the same ceiling.\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.campaign': {\n // The campaign job generates its email through the email step, so it is\n // routed identically; the draft campaign it also writes asks nothing of\n // the model (AGL-2912).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.workflow': {\n // An automation drafted from a description, or one explained (AGL-2919):\n // which trigger and which steps a description means is the judgment the\n // step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 4000,\n maxTokensBasis:\n 'the largest automation the tool accepts, 6,000 characters written out as JSON, at three characters a token with as much again to think in, as ai-job-workflow-step.spec.ts measures it',\n eval: { kinds: ['workflow'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.logic': {\n // A site function or variable proposed, or a function explained\n // (AGL-3603): which conditions and arithmetic a description means is the\n // judgment the step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 3000,\n maxTokensBasis:\n 'the largest function the tool accepts — twelve parameters, twenty-four operations — is far past any description; a function of eight operations is about 2,400 characters of JSON, at three characters a token with as much again to think in, as ai-job-logic-step.spec.ts measures it',\n eval: { kinds: ['logic'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.edit': {\n // A change to a page or a layout the site has (AGL-3616): which elements\n // a request means, on an outline with no selection, is the judgment the\n // step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 6144,\n maxTokensBasis:\n \"the Assist edit rung's own ceiling for the proposal's tool call (ASSIST_EDIT_MAX_OUTPUT_TOKENS, 4,096: forty new elements written as name/value pairs), with half as much again to think in, as ai-job-edit-step.spec.ts holds it; the whole of it twice, as a re-ask may, still fits one beat on the balanced tier\",\n eval: { kinds: ['edit'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.seo': {\n thinking: null,\n effort: null,\n maxTokens: 1024,\n maxTokensBasis:\n \"the largest listing the tool accepts, at three characters a token with a quarter again, as ai-job-seo-step.spec.ts measures it; an audit's site proposal and fix batches keep ceilings of their own, measured the same way\",\n eval: { kinds: ['seo'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.text': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 1024,\n maxTokensBasis: 'a few paragraphs of copy; a brief asking for more gets a draft to extend',\n eval: { kinds: ['text'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.theme': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n 'the largest answer the theme tool accepts, about 2,500 tokens of JSON as ai-job-theme-step.spec.ts measures it, with as much again to think in',\n eval: { kinds: ['theme'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.plan': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis: 'a plan of up to twelve screens with sixteen sections each, with room to think',\n eval: {\n kinds: ['page', 'template', 'component', 'layout', 'form'],\n scores: 'plans',\n source: 'authored',\n passRate: 1,\n meanScore: null,\n },\n },\n}\n\n/** The model id alone — what a door records on the meter and the job. */\nexport function aiModelForStep(kind: AiStepKind, settings?: AiPluginSettings): string {\n const route = resolveAiRoute(kind, settings)\n if (!route) {\n // Every door gates on the provider being configured before it asks\n // for a model, so this is a wiring fault rather than a customer path.\n throw new Error('no AI provider is registered')\n }\n return route.model\n}\n"],"names":["AI_SETTING_PLATFORM","AI_STEP_MODEL_SETTING","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","aiProviderById","defaultAiProvider","AI_DEFAULT_MODEL_ENV","ASSIST_MODEL_ENV","chosen","settings","key","value","undefined","resolveAiProvider","wanted","named","resolveAiRoute","kind","servedBy","process","provider","modelId","id","model","env","trim","AI_ROUTING_TABLE","thinking","effort","maxTokens","maxTokensBasis","eval","kinds","scores","source","passRate","meanScore","aiModelForStep","route","Error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,mBAAmB,EAAEC,qBAAqB,QAAQ,sBAAkB;AAC7E,SACEC,aAAa,EACbC,cAAc,EACdC,iBAAiB,QAEZ,eAAW;AAGlB,SAASC,cAAc,EAAEC,iBAAiB,QAAQ,gBAAY;AAE9D;;;;;;;;;;;;;CAaC,GAED,kDAAkD,GAClD,OAAO,MAAMC,uBAAuB,mBAAkB;AACtD,kEAAkE,GAClE,OAAO,MAAMC,mBAAmB,eAAc;AAU9C,wFAAwF,GACxF,SAASC,OAAOC,QAAsC,EAAEC,GAAW;IACjE,MAAMC,QAAQF,4BAAAA,QAAU,CAACC,IAAI;IAC7B,OAAO,OAAOC,UAAU,YAAYA,SAASA,UAAUZ,sBACnDY,QACAC;AACN;AAEA,2FAA2F,GAC3F,OAAO,SAASC,kBAAkBJ,QAA2B;IAC3D,MAAMK,SAASN,OAAOC,UAAU;IAChC,IAAIK,QAAQ;QACV,MAAMC,QAAQX,eAAeU;QAC7B,IAAIC,OAAO,OAAOA;IACpB;IACA,OAAOV;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASW,eACdC,IAAgB,EAChBR,QAA2B;QAOzBS,MAAAA,OAAAA;QACmCC,+BAC1BA;IAPX,MAAMC,WAAWP,kBAAkBJ;IACnC,IAAI,CAACW,UAAU,OAAOR;IACtB,MAAMM,WAAW,CAACG;YACLnB;eAAXmB,WAAWnB,EAAAA,kBAAAA,eAAemB,6BAAfnB,gBAAyBkB,QAAQ,MAAKA,SAASE,EAAE,GAAGD,UAAUT;;IAC3E,MAAMW,SACJL,QAAAA,SAAAA,YAAAA,SAASV,OAAOC,UAAUT,qBAAqB,CAACiB,KAAK,cAArDC,YACCD,SAAS,gBAAgBC,UAASC,gCAAAA,QAAQK,GAAG,CAACjB,iBAAiB,qBAA7BY,8BAA+BM,IAAI,MAAMb,qBAD5EM,QAEAA,UAASC,oCAAAA,QAAQK,GAAG,CAAClB,qBAAqB,qBAAjCa,kCAAmCM,IAAI,eAFhDP,OAGAf,kBAAkBiB,SAASE,EAAE,EAAErB,aAAa,CAACgB,KAAK;IACpD,OAAOM,QAAQ;QAAEH;QAAUG;IAAM,IAAIX;AACvC;AA+BA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMc,mBAA+D;IAC1E,eAAe;QACb,2EAA2E;QAC3EC,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAO;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACjG;IACA,gBAAgB;QACdT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBAAgB;QAChBC,MAAM;YAAEC,OAAO;gBAAC;aAAU;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACpG;IACA,gBAAgB;QACdT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBAAgB;QAChBC,MAAM;YAAEC,OAAO;gBAAC;aAAU;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACpG;IACA,aAAa;QACXT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBAAgB;QAChBC,MAAM;YAAEC,OAAO;gBAAC;aAAO;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACjG;IACA,oBAAoB;QAClBT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBAAgB;QAChBC,MAAM;YAAEC,OAAO;gBAAC;aAAU;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACpG;IACA,cAAc;QACZ,0FAA0F;QAC1FT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAS;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACnG;IACA,gBAAgB;QACdT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAW;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACrG;IACA,iBAAiB;QACf,2EAA2E;QAC3E,8DAA8D;QAC9DT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAY;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACtG;IACA,WAAW;QACT,0EAA0E;QAC1E,wEAAwE;QACxE,wEAAwE;QACxE,0BAA0B;QAC1BT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAM;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IAChG;IACA,kBAAkB;QAChB,yEAAyE;QACzE,uEAAuE;QACvE,0DAA0D;QAC1DT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAa;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAE;IAClG;IACA,YAAY;QACV,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,gCAAgC;QAChCT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAO;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACjG;IACA,eAAe;QACb,wEAAwE;QACxE,oEAAoE;QACpE,sEAAsE;QACtE,sDAAsD;QACtDT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAU;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACpG;IACA,YAAY;QACV,iEAAiE;QACjE,wEAAwE;QACxE,0EAA0E;QAC1E,kEAAkE;QAClE,uCAAuC;QACvCT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAO;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACjG;IACA,gBAAgB;QACd,qEAAqE;QACrE,2EAA2E;QAC3E,oEAAoE;QACpET,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YACJC,OAAO;gBAAC;gBAAW;gBAAW;aAAa;YAC3CC,QAAQ;YACRC,QAAQ;YACRC,UAAU;YACVC,WAAW;QACb;IACF;IACA,aAAa;QACX,0EAA0E;QAC1E,gEAAgE;QAChE,8CAA8C;QAC9CT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAQ;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IAClG;IACA,gBAAgB;QACd,wEAAwE;QACxE,wEAAwE;QACxE,wBAAwB;QACxBT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAQ;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IAClG;IACA,gBAAgB;QACd,yEAAyE;QACzE,wEAAwE;QACxE,8CAA8C;QAC9CT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAW;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IACrG;IACA,aAAa;QACX,gEAAgE;QAChE,yEAAyE;QACzE,2DAA2D;QAC3DT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAQ;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAE;IAC7F;IACA,YAAY;QACV,yEAAyE;QACzE,wEAAwE;QACxE,8CAA8C;QAC9CT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAO;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAE;IAC5F;IACA,WAAW;QACTT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAM;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAE;IAC3F;IACA,YAAY;QACVT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBAAgB;QAChBC,MAAM;YAAEC,OAAO;gBAAC;aAAO;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAE;IAC5F;IACA,aAAa;QACXT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBACE;QACFC,MAAM;YAAEC,OAAO;gBAAC;aAAQ;YAAEC,QAAQ;YAAWC,QAAQ;YAAYC,UAAU;YAAGC,WAAW;QAAO;IAClG;IACA,YAAY;QACVT,UAAU;QACVC,QAAQ;QACRC,WAAW;QACXC,gBAAgB;QAChBC,MAAM;YACJC,OAAO;gBAAC;gBAAQ;gBAAY;gBAAa;gBAAU;aAAO;YAC1DC,QAAQ;YACRC,QAAQ;YACRC,UAAU;YACVC,WAAW;QACb;IACF;AACF,EAAC;AAED,uEAAuE,GACvE,OAAO,SAASC,eAAepB,IAAgB,EAAER,QAA2B;IAC1E,MAAM6B,QAAQtB,eAAeC,MAAMR;IACnC,IAAI,CAAC6B,OAAO;QACV,mEAAmE;QACnE,sEAAsE;QACtE,MAAM,IAAIC,MAAM;IAClB;IACA,OAAOD,MAAMf,KAAK;AACpB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/routing.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_SETTING_PLATFORM, AI_STEP_MODEL_SETTING } from '../plugin-config'\nimport {\n AI_MODEL_CATALOG,\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiDefaultModelFor,\n type AiStepKind,\n} from './catalog'\nimport type { AiEvalKind } from '../runtime/ai-eval'\nimport type { AiEffort, AiProvider, AiThinking } from './contract'\nimport { aiProviderById, defaultAiProvider } from './registry'\n\n/**\n * The routing table (AGL-2937, AGL-2939): step kind → provider and model,\n * through the catalog and never a vendor literal.\n *\n * Precedence, dearest override first:\n *\n * 1. the org's `pluginSettings/ai` — a provider, and a model per step kind\n * that must be one the provider serves;\n * 2. the deployment's environment — `AI_PROVIDER` for the provider,\n * `AI_DEFAULT_MODEL` for every step, and `ASSIST_MODEL` for the\n * assistant alone (the incident-response override the chat door has\n * always honored);\n * 3. the catalog's tier for the step kind on the resolved provider.\n */\n\n/** The environment override for every step kind. */\nexport const AI_DEFAULT_MODEL_ENV = 'AI_DEFAULT_MODEL'\n/** The assistant's own override, honored before the general one. */\nexport const ASSIST_MODEL_ENV = 'ASSIST_MODEL'\n\n/** The org's resolved `pluginSettings/ai` values, as `getPluginConfig` merges them. */\nexport type AiPluginSettings = Record<string, unknown>\n\n/**\n * The model that serves a catalog id (AGL-3781): the id itself, or for a\n * retired row the successor it names, followed to a current row. An id the\n * catalog does not know is returned as it is, for the caller to refuse.\n */\nexport function aiServingModelId(modelId: string): string {\n let current = modelId\n for (let hops = 0; hops < AI_MODEL_CATALOG.length; hops += 1) {\n const successor = aiCatalogEntry(current)?.successor\n if (!successor) return current\n current = successor\n }\n return current\n}\n\nexport interface AiRoute {\n provider: AiProvider\n model: string\n}\n\n/** A settings value that names a real choice, or `undefined` for the platform default. */\nfunction chosen(settings: AiPluginSettings | undefined, key: string): string | undefined {\n const value = settings?.[key]\n return typeof value === 'string' && value && value !== AI_SETTING_PLATFORM\n ? value\n : undefined\n}\n\n/** The provider a workspace runs on: its own choice when registered, else the platform's. */\nexport function resolveAiProvider(settings?: AiPluginSettings): AiProvider | undefined {\n const wanted = chosen(settings, 'provider')\n if (wanted) {\n const named = aiProviderById(wanted)\n if (named) return named\n }\n return defaultAiProvider()\n}\n\n/**\n * The model a step kind runs on for a workspace, on the resolved provider.\n * A chosen model on another provider is not served — the provider decides\n * what it can run — and falls back to the provider's default for the tier.\n */\nexport function resolveAiRoute(\n kind: AiStepKind,\n settings?: AiPluginSettings,\n): AiRoute | undefined {\n const provider = resolveAiProvider(settings)\n if (!provider) return undefined\n // A setting or an override that names a retired model is served by its\n // successor, never by the retired model itself.\n const servedBy = (modelId: string | undefined): string | undefined => {\n const serving = modelId ? aiServingModelId(modelId) : undefined\n return serving && aiCatalogEntry(serving)?.provider === provider.id ? serving : undefined\n }\n const model =\n servedBy(chosen(settings, AI_STEP_MODEL_SETTING[kind])) ??\n (kind === 'assist.chat' ? servedBy(process.env[ASSIST_MODEL_ENV]?.trim()) : undefined) ??\n servedBy(process.env[AI_DEFAULT_MODEL_ENV]?.trim()) ??\n aiDefaultModelFor(provider.id, AI_STEP_TIERS[kind])\n return model ? { provider, model } : undefined\n}\n\n/**\n * What one step kind asks of its model, beside the tier it is served from\n * (`AI_STEP_TIERS` in the catalog): whether it thinks and how hard, its\n * answer ceiling, and the eval score that tier holds on the step's golden\n * briefs (AGL-2937).\n */\nexport interface AiRoutingRow {\n /**\n * `null` sends no setting: the model's own default, which on every current\n * model is adaptive thinking. A row whose ceiling holds the answer alone\n * says `off` (AGL-3781).\n */\n thinking: AiThinking | null\n effort: AiEffort | null\n /** The answer ceiling, thinking included. */\n maxTokens: number\n /** What the ceiling is sized from. */\n maxTokensBasis: string\n eval: AiRoutingEval\n}\n\nexport interface AiRoutingEval {\n /** The golden-brief kinds the step answers. */\n kinds: readonly AiEvalKind[]\n /** `answers` scores the step's answers; `plans` scores the plans those kinds' answers carry. */\n scores: 'answers' | 'plans'\n /** `authored` while the score is the hand-written references'; `recorded` once a live run records answers. */\n source: 'authored' | 'recorded'\n passRate: number\n /** The mean answer score; `null` for a plan score, which passes or fails. */\n meanScore: number | null\n}\n\n/**\n * THE ROUTING TABLE (AGL-2937): what each step kind asks of the model, in one\n * place, with the evidence beside it.\n *\n * A step runs on the smallest tier that holds its eval floor. A row moves to a\n * cheaper tier, or a lower effort, only when a recorded eval\n * (`npm run eval:ai-live`) holds the floor there; while a row's score is the\n * authored references', it says so, and the row stays where production has\n * run it. `runtime/ai-eval.spec.ts` recomputes every row's score from the\n * harness, so a row cannot claim a score its briefs do not hold.\n *\n * A ceiling is sized from the p95 output the staff signals page reports for\n * the kind once production has served it; until then `maxTokensBasis` names\n * what it is sized from. The same spec holds every reference answer under its\n * row's ceiling at three characters a token, a reading that errs high for a\n * tokenizer that splits finer than four.\n */\nexport const AI_ROUTING_TABLE: Readonly<Record<AiStepKind, AiRoutingRow>> = {\n 'assist.chat': {\n // A scoped, latency-sensitive chat turn: no thinking, the low effort rung.\n thinking: 'off',\n effort: 'low',\n maxTokens: 1024,\n maxTokensBasis:\n 'a docs-grounded answer of a few short paragraphs; the edit rung raises it for its tool call',\n eval: { kinds: ['chat'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.element': {\n // One element's copy on the fast tier, whose model thinks unless told not\n // to (AGL-3781): the ceiling holds the copy alone, as it always has, and\n // the effort is the model's own.\n thinking: 'off',\n effort: null,\n maxTokens: 1024,\n maxTokensBasis: 'one element of copy, about the length of the text it replaces',\n eval: { kinds: ['element'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.blog': {\n thinking: null,\n effort: null,\n maxTokens: 2048,\n maxTokensBasis: 'one blog post body in markdown-lite',\n eval: { kinds: ['blog'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'generate.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.layout': {\n // The plan was reasoned out and confirmed before the step, so it writes without thinking.\n // A site's header and footer in the layout language think adaptively, at\n // low since AGL-3781 (`AI_LAYOUT_LANGUAGE_THINKING`): the live round of\n // 2026-10-14 built 13 of 13 frames at low, none cut off.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a layout, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['layout'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.template': {\n // Medium since the golden A/B of 2026-10-14 (AGL-3781): both template\n // briefs passed at medium as at the default, with about a third fewer\n // output tokens.\n thinking: 'off',\n effort: 'medium',\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a template, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['template'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.component': {\n // A component's tree and the properties it declares, in one answer, from a\n // plan reasoned out and confirmed before the step (AGL-2908).\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the ceiling the layout and template steps keep, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['component'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.crm': {\n // CRM by AI (AGL-2917): short answers through a strict tool, written from\n // the facts the CRM reports about the record the member opened, with no\n // thinking. The ceiling fits an inline door, so a member is answered in\n // the request that asked, and has no room for thinking: the fast tier's\n // model thinks unless told not to, so the row says off, and its effort\n // is the low one a short answer read off given facts needs (AGL-3781).\n thinking: 'off',\n effort: 'low',\n maxTokens: 700,\n maxTokensBasis:\n \"the largest answer a CRM tool accepts: sixty columns matched to fields by number, or an email draft at its length limits, at three characters a token with room, as ai-job-crm-step.spec.ts measures it\",\n eval: { kinds: ['crm'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9938 },\n },\n 'job.experiment': {\n // Variants of one piece of copy, or a result explained against a verdict\n // code reached before the request was made (AGL-2914). The judgment is\n // already made either way, so it writes without thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n \"four variants at the tool's own lengths — a name, a headline, a body and a rationale each — written as JSON, about 3,000 characters at three characters a token with room; an explanation is a headline, four sentences and a next step, well under it, as ai-job-experiment-step.spec.ts measures both\",\n eval: { kinds: ['experiment'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.form': {\n // A form's two halves in one answer: the field declaration and the design\n // that renders it, as JSON, with no extended thinking (AGL-2913). The step\n // asks this ceiling, which is the doctrine's own for a form, and registers\n // the time it takes (AGL-3035).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"the largest form the doctrine's output budget admits, a form's fields, its consent and routing declaration and the design that renders them written as JSON at the wordiest golden's characters a stored byte, under it with room; ai-job-form-step.spec.ts measures it\",\n eval: { kinds: ['form'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.insight': {\n // Readers chosen, then their tables phrased (AGL-2915): short sentences\n // over figures code computed, each held to the rows it cites, so no\n // extended thinking. The read call before the answer asks for at most\n // `AI_INSIGHT_READ_MAX_TOKENS` of the same allowance.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n 'five insights at their 280-character bound, each citing three tables of ten rows, and the gap sentence, written as JSON: about 2,700 characters, under 1,000 tokens at three characters a token, with room',\n eval: { kinds: ['insight'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.page': {\n // One section of a page, written against a plan a member already\n // confirmed, so it writes without thinking (AGL-2907). The ceiling here\n // is the MOST a pass may ask for; each pass sizes its own down from it to\n // what fits the time the job beat can give the step, which on the\n // balanced tier is about half of this. A page in the layout language\n // thinks adaptively, at low since AGL-3781 (`AI_LAYOUT_LANGUAGE_THINKING`):\n // the live round of 2026-10-14 built 39 of 39 pages at low, 64 of 65\n // answers held their rules on the first ask, none cut off.\n thinking: 'off',\n effort: null,\n maxTokens: 2000,\n maxTokensBasis:\n \"a section of a page written as JSON, about forty elements at the step's measured 45 tokens an element; a pass lowers it to the worst case that fits AI_JOB_PAGE_STEP_MINIMUM_MS on the model it runs\",\n eval: { kinds: ['page'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.products': {\n // A product's copy from its name, text and photo, or a store's first\n // products, categories and discounts from a brief (AGL-2916): answers held\n // to the storefront claim rules, written with no extended thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the largest catalog the tool accepts, twelve proposed products at every bound written as JSON, which the balanced tier asks as much of as fits a beat; one product's copy asks 1,500 and categories with discounts 2,000, as ai-job-products-step.spec.ts measures them\",\n eval: {\n kinds: ['product', 'catalog', 'categories'],\n scores: 'answers',\n source: 'authored',\n passRate: 1,\n meanScore: 0.9922,\n },\n },\n 'job.email': {\n // One email design from the email palette, written against the brief with\n // no extended thinking (AGL-2912). A campaign job runs the same\n // generation, so it carries the same ceiling.\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.campaign': {\n // The campaign job generates its email through the email step, so it is\n // routed identically; the draft campaign it also writes asks nothing of\n // the model (AGL-2912).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.workflow': {\n // An automation drafted from a description, or one explained (AGL-2919):\n // which trigger and which steps a description means is the judgment the\n // step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 4000,\n maxTokensBasis:\n 'the largest automation the tool accepts, 6,000 characters written out as JSON, at three characters a token with as much again to think in, as ai-job-workflow-step.spec.ts measures it',\n eval: { kinds: ['workflow'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.logic': {\n // A site function or variable proposed, or a function explained\n // (AGL-3603): which conditions and arithmetic a description means is the\n // judgment the step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 3000,\n maxTokensBasis:\n 'the largest function the tool accepts — twelve parameters, twenty-four operations — is far past any description; a function of eight operations is about 2,400 characters of JSON, at three characters a token with as much again to think in, as ai-job-logic-step.spec.ts measures it',\n eval: { kinds: ['logic'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.edit': {\n // A change to a page or a layout the site has (AGL-3616): which elements\n // a request means, on an outline with no selection, is the judgment the\n // step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 6144,\n maxTokensBasis:\n \"the Assist edit rung's own ceiling for the proposal's tool call (ASSIST_EDIT_MAX_OUTPUT_TOKENS, 4,096: forty new elements written as name/value pairs), with half as much again to think in, as ai-job-edit-step.spec.ts holds it; the whole of it twice, as a re-ask may, still fits one beat on the balanced tier\",\n eval: { kinds: ['edit'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.seo': {\n // A listing written from the page's own text to fixed lengths, sized for\n // the answer alone: no thinking, which the fast tier's model does unless\n // told not to, at the low effort a short bounded answer needs (AGL-3781).\n // The audit's site proposal and fix batches run on the same row.\n thinking: 'off',\n effort: 'low',\n maxTokens: 1024,\n maxTokensBasis:\n \"the largest listing the tool accepts, at three characters a token with a quarter again, as ai-job-seo-step.spec.ts measures it; an audit's site proposal and fix batches keep ceilings of their own, measured the same way\",\n eval: { kinds: ['seo'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.text': {\n // Low since the golden A/B of 2026-10-14 (AGL-3781): the tagline brief\n // passed at low, its copy scored higher than at the default.\n thinking: 'adaptive',\n effort: 'low',\n maxTokens: 1024,\n maxTokensBasis: 'a few paragraphs of copy; a brief asking for more gets a draft to extend',\n eval: { kinds: ['text'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.theme': {\n // Medium since the golden A/B of 2026-10-14 (AGL-3781): the theme brief\n // passed at medium with the default's scores and half its output tokens.\n // A site start's look runs its own setting (`ai-job-site-look.ts`).\n thinking: 'adaptive',\n effort: 'medium',\n maxTokens: 8000,\n maxTokensBasis:\n 'the largest answer the theme tool accepts, about 2,500 tokens of JSON as ai-job-theme-step.spec.ts measures it, with as much again to think in',\n eval: { kinds: ['theme'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.plan': {\n // A site's plan is an outline written without thinking (AGL-3594), and a\n // paid site's asks medium (`AI_PAID_SITE_PLAN_EFFORT`, AGL-3781): the live\n // round of 2026-10-14 planned 11 of 11 paid site briefs at medium in 16\n // attempts to the default's 22, $0.129 to $0.164, with no refusal and no\n // rule broken. Every other plan, and a Free site's on the fast tier, asks\n // this row's effort — the model's default — until a round holds it lower.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis: 'a plan of up to twelve screens with sixteen sections each, with room to think',\n eval: {\n kinds: ['page', 'template', 'component', 'layout', 'form'],\n scores: 'plans',\n source: 'authored',\n passRate: 1,\n meanScore: null,\n },\n },\n}\n\n/**\n * What a step kind's row asks of its model, as the fields a request carries:\n * only those the row names, so a row that names none sends none and the\n * model's own default stands.\n */\nexport function aiRouteSettings(kind: AiStepKind): { thinking?: AiThinking; effort?: AiEffort } {\n const row = AI_ROUTING_TABLE[kind]\n return {\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n }\n}\n\n/** The model id alone — what a door records on the meter and the job. */\nexport function aiModelForStep(kind: AiStepKind, settings?: AiPluginSettings): string {\n const route = resolveAiRoute(kind, settings)\n if (!route) {\n // Every door gates on the provider being configured before it asks\n // for a model, so this is a wiring fault rather than a customer path.\n throw new Error('no AI provider is registered')\n }\n return route.model\n}\n"],"names":["AI_SETTING_PLATFORM","AI_STEP_MODEL_SETTING","AI_MODEL_CATALOG","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","aiProviderById","defaultAiProvider","AI_DEFAULT_MODEL_ENV","ASSIST_MODEL_ENV","aiServingModelId","modelId","current","hops","length","successor","chosen","settings","key","value","undefined","resolveAiProvider","wanted","named","resolveAiRoute","kind","servedBy","process","provider","serving","id","model","env","trim","AI_ROUTING_TABLE","thinking","effort","maxTokens","maxTokensBasis","eval","kinds","scores","source","passRate","meanScore","aiRouteSettings","row","aiModelForStep","route","Error"],"mappings":"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"}
@@ -22,6 +22,7 @@
22
22
  * system module into a server that will never use one. Every answer is `off`
23
23
  * in a deployed server.
24
24
  */
25
+ import type { AiEffort } from '../providers/contract';
25
26
  export type AiDevReplayMode = 'off' | 'replay' | 'refresh';
26
27
  /** The variable that turns the replay cache on, off, or to refresh. */
27
28
  export declare const AI_REPLAY_ENV = "AGLYN_AI_REPLAY";
@@ -34,6 +35,17 @@ export declare const AI_LIVE_LAUNCHER_ENV = "AI_EVAL_LIVE_LAUNCHER";
34
35
  export type AiDevEnv = Readonly<Record<string, string | undefined>>;
35
36
  /** Whether this process is a deployed server, where no development layer may run. */
36
37
  export declare function aiIsDeployedRuntime(env?: AiDevEnv): boolean;
38
+ /** The variable every live spec is started with. */
39
+ export declare const AI_LIVE_RUN_ENV = "AGLYN_LIVE_AI";
40
+ /**
41
+ * Whether this process is a development run that asked for the live
42
+ * provider: through the launcher, or a live spec started with
43
+ * `AGLYN_LIVE_AI=1` (AGL-3781). Every development layer is on by default in
44
+ * such a run — a live spec started without the launcher's mark used to send
45
+ * every request again, at the five-minute cache and full price — and each can
46
+ * still be turned off by its own variable.
47
+ */
48
+ export declare function aiUnderLiveRun(env?: AiDevEnv): boolean;
37
49
  /** The replay mode this process runs under; `off` in any deployed server. */
38
50
  export declare function aiDevReplayMode(env?: AiDevEnv): AiDevReplayMode;
39
51
  /**
@@ -49,5 +61,50 @@ export declare function aiDevCacheTtl(env?: AiDevEnv): '1h' | undefined;
49
61
  export declare function aiWarnIfReplayAskedInDeployment(env?: AiDevEnv): void;
50
62
  /** The variable that turns batch mode on (`runtime/ai-live-batch.ts`). */
51
63
  export declare const AI_LIVE_BATCH_ENV = "AGLYN_LIVE_AI_BATCH";
52
- /** Whether this process batches its live requests. */
64
+ /**
65
+ * Whether this process batches its live requests: what `AGLYN_LIVE_AI_BATCH`
66
+ * says, and in a live run that says nothing, yes (AGL-3781). Nobody waits on
67
+ * an eval's answers, and a batch is half price on every token, cache reads
68
+ * and writes included; `AGLYN_LIVE_AI_BATCH=0` is for the quick single run an
69
+ * iteration wants back in seconds.
70
+ */
53
71
  export declare function aiLiveBatchWanted(env?: AiDevEnv): boolean;
72
+ /** The variable that bounds how long a live run waits on one batch (AGL-3781). */
73
+ export declare const AI_LIVE_BATCH_WAIT_ENV = "AGLYN_LIVE_AI_BATCH_WAIT_MS";
74
+ /** How long a batch is waited on when the run names no wait. */
75
+ export declare const AI_LIVE_BATCH_DEFAULT_WAIT_MS: number;
76
+ /**
77
+ * How long a live run waits on one batch (AGL-3781): what
78
+ * `AGLYN_LIVE_AI_BATCH_WAIT_MS` says, or half an hour. Most batches end in
79
+ * minutes. One that has not by then is canceled, which keeps every answer it
80
+ * already has, and the requests it left unanswered are asked on their own at
81
+ * full price — so a slow batch costs a round part of its discount, never the
82
+ * round.
83
+ */
84
+ export declare function aiLiveBatchWaitMs(env?: AiDevEnv): number;
85
+ /**
86
+ * The most a batched live run may take: a run's requests go as a few batches
87
+ * one after another — the answers, their re-asks, the grades — and each is
88
+ * waited on for `aiLiveBatchWaitMs`, then given time to end its cancel and to
89
+ * have what it left asked on its own. A spec that timed out would keep none
90
+ * of the answers it already paid for, so the bound is the worst case.
91
+ */
92
+ export declare const AI_LIVE_BATCH_TIMEOUT_MS: number;
93
+ /** A live spec's timeout: `baseMs`, or the batched run's when this run batches. */
94
+ export declare function aiLiveTimeoutMs(baseMs: number, env?: AiDevEnv): number;
95
+ /**
96
+ * The variable an effort A/B names its arm with (AGL-3781): a step kind's
97
+ * effort for this run, as `job.form:medium,job.text:low`, or `*:medium` for
98
+ * every kind. A development run only, so the defaults stay where production
99
+ * runs them until a recorded round shows a lower effort holds.
100
+ */
101
+ export declare const AI_EFFORT_ARM_ENV = "AGLYN_AI_EFFORT";
102
+ /**
103
+ * Every step kind this development run's A/B arm names, with its effort, in
104
+ * kind order — `*` for every kind it does not name. A pair whose effort is no
105
+ * level is ignored; a kind named twice keeps its first effort, and `*` its
106
+ * last. Empty when the run names no arm, and in any deployed server.
107
+ */
108
+ export declare function aiDevEffortArm(env?: AiDevEnv): Record<string, AiEffort>;
109
+ /** The effort this development run's A/B arm asks `step` at, or `undefined` for the step's own. */
110
+ export declare function aiDevEffortFor(step: string | undefined, env?: AiDevEnv): AiEffort | undefined;
@@ -27,8 +27,18 @@
27
27
  /** Whether this process is a deployed server, where no development layer may run. */ export function aiIsDeployedRuntime(env = process.env) {
28
28
  return env['NODE_ENV'] === 'production' || env['VERCEL_ENV'] === 'production' || env['VERCEL_ENV'] === 'preview' || Boolean(env['K_SERVICE']);
29
29
  }
30
- /** Whether this process is a development run that asked for the live provider through the launcher. */ function underLiveLauncher(env) {
31
- return Boolean(env[AI_LIVE_LAUNCHER_ENV]);
30
+ /** The variable every live spec is started with. */ export const AI_LIVE_RUN_ENV = 'AGLYN_LIVE_AI';
31
+ /**
32
+ * Whether this process is a development run that asked for the live
33
+ * provider: through the launcher, or a live spec started with
34
+ * `AGLYN_LIVE_AI=1` (AGL-3781). Every development layer is on by default in
35
+ * such a run — a live spec started without the launcher's mark used to send
36
+ * every request again, at the five-minute cache and full price — and each can
37
+ * still be turned off by its own variable.
38
+ */ export function aiUnderLiveRun(env = process.env) {
39
+ var _env_AI_LIVE_RUN_ENV;
40
+ if (aiIsDeployedRuntime(env)) return false;
41
+ return Boolean(env[AI_LIVE_LAUNCHER_ENV]) || String((_env_AI_LIVE_RUN_ENV = env[AI_LIVE_RUN_ENV]) != null ? _env_AI_LIVE_RUN_ENV : '').trim() === '1';
32
42
  }
33
43
  /** The replay mode this process runs under; `off` in any deployed server. */ export function aiDevReplayMode(env = process.env) {
34
44
  var _env_AI_REPLAY_ENV;
@@ -37,7 +47,7 @@
37
47
  if (raw === 'refresh') return 'refresh';
38
48
  if (raw === '1' || raw === 'on' || raw === 'true' || raw === 'replay') return 'replay';
39
49
  if (raw === '0' || raw === 'off' || raw === 'false') return 'off';
40
- return underLiveLauncher(env) ? 'replay' : 'off';
50
+ return aiUnderLiveRun(env) ? 'replay' : 'off';
41
51
  }
42
52
  /**
43
53
  * The prompt-cache lifetime a development run asks for. An agent re-runs a
@@ -53,7 +63,7 @@
53
63
  const raw = String((_env_AI_DEV_CACHE_TTL_ENV = env[AI_DEV_CACHE_TTL_ENV]) != null ? _env_AI_DEV_CACHE_TTL_ENV : '').trim().toLowerCase();
54
64
  if (raw === '1h') return '1h';
55
65
  if (raw === '5m') return undefined;
56
- return underLiveLauncher(env) ? '1h' : undefined;
66
+ return aiUnderLiveRun(env) ? '1h' : undefined;
57
67
  }
58
68
  /** A deployed server that was handed the variable says so once, and runs without it. */ let warnedDeployed = false;
59
69
  export function aiWarnIfReplayAskedInDeployment(env = process.env) {
@@ -63,10 +73,80 @@ export function aiWarnIfReplayAskedInDeployment(env = process.env) {
63
73
  console.error(`[ai] ${AI_REPLAY_ENV} / ${AI_DEV_CACHE_TTL_ENV} are development-only and are ignored in a deployed server`);
64
74
  }
65
75
  /** The variable that turns batch mode on (`runtime/ai-live-batch.ts`). */ export const AI_LIVE_BATCH_ENV = 'AGLYN_LIVE_AI_BATCH';
66
- /** Whether this process batches its live requests. */ export function aiLiveBatchWanted(env = process.env) {
76
+ /**
77
+ * Whether this process batches its live requests: what `AGLYN_LIVE_AI_BATCH`
78
+ * says, and in a live run that says nothing, yes (AGL-3781). Nobody waits on
79
+ * an eval's answers, and a batch is half price on every token, cache reads
80
+ * and writes included; `AGLYN_LIVE_AI_BATCH=0` is for the quick single run an
81
+ * iteration wants back in seconds.
82
+ */ export function aiLiveBatchWanted(env = process.env) {
67
83
  var _env_AI_LIVE_BATCH_ENV;
68
84
  if (aiIsDeployedRuntime(env)) return false;
69
- return /^(?:1|true|yes|on)$/i.test(String((_env_AI_LIVE_BATCH_ENV = env[AI_LIVE_BATCH_ENV]) != null ? _env_AI_LIVE_BATCH_ENV : '').trim());
85
+ const raw = String((_env_AI_LIVE_BATCH_ENV = env[AI_LIVE_BATCH_ENV]) != null ? _env_AI_LIVE_BATCH_ENV : '').trim();
86
+ if (/^(?:1|true|yes|on)$/i.test(raw)) return true;
87
+ if (/^(?:0|false|no|off)$/i.test(raw)) return false;
88
+ return aiUnderLiveRun(env);
89
+ }
90
+ /** The variable that bounds how long a live run waits on one batch (AGL-3781). */ export const AI_LIVE_BATCH_WAIT_ENV = 'AGLYN_LIVE_AI_BATCH_WAIT_MS';
91
+ /** How long a batch is waited on when the run names no wait. */ export const AI_LIVE_BATCH_DEFAULT_WAIT_MS = 30 * 60000;
92
+ /**
93
+ * How long a live run waits on one batch (AGL-3781): what
94
+ * `AGLYN_LIVE_AI_BATCH_WAIT_MS` says, or half an hour. Most batches end in
95
+ * minutes. One that has not by then is canceled, which keeps every answer it
96
+ * already has, and the requests it left unanswered are asked on their own at
97
+ * full price — so a slow batch costs a round part of its discount, never the
98
+ * round.
99
+ */ export function aiLiveBatchWaitMs(env = process.env) {
100
+ var _env_AI_LIVE_BATCH_WAIT_ENV;
101
+ const raw = String((_env_AI_LIVE_BATCH_WAIT_ENV = env[AI_LIVE_BATCH_WAIT_ENV]) != null ? _env_AI_LIVE_BATCH_WAIT_ENV : '').trim();
102
+ const parsed = raw ? Number(raw) : Number.NaN;
103
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : AI_LIVE_BATCH_DEFAULT_WAIT_MS;
104
+ }
105
+ /**
106
+ * The most a batched live run may take: a run's requests go as a few batches
107
+ * one after another — the answers, their re-asks, the grades — and each is
108
+ * waited on for `aiLiveBatchWaitMs`, then given time to end its cancel and to
109
+ * have what it left asked on its own. A spec that timed out would keep none
110
+ * of the answers it already paid for, so the bound is the worst case.
111
+ */ export const AI_LIVE_BATCH_TIMEOUT_MS = 5 * 60 * 60000;
112
+ /** A live spec's timeout: `baseMs`, or the batched run's when this run batches. */ export function aiLiveTimeoutMs(baseMs, env = process.env) {
113
+ if (!aiLiveBatchWanted(env)) return baseMs;
114
+ // Four batches in a row, each waited on, canceled and finished by hand.
115
+ const longWait = 4 * (aiLiveBatchWaitMs(env) + 20 * 60000);
116
+ return Math.max(baseMs, AI_LIVE_BATCH_TIMEOUT_MS, longWait);
117
+ }
118
+ /**
119
+ * The variable an effort A/B names its arm with (AGL-3781): a step kind's
120
+ * effort for this run, as `job.form:medium,job.text:low`, or `*:medium` for
121
+ * every kind. A development run only, so the defaults stay where production
122
+ * runs them until a recorded round shows a lower effort holds.
123
+ */ export const AI_EFFORT_ARM_ENV = 'AGLYN_AI_EFFORT';
124
+ const AI_EFFORT_LEVELS = [
125
+ 'low',
126
+ 'medium',
127
+ 'high'
128
+ ];
129
+ /**
130
+ * Every step kind this development run's A/B arm names, with its effort, in
131
+ * kind order — `*` for every kind it does not name. A pair whose effort is no
132
+ * level is ignored; a kind named twice keeps its first effort, and `*` its
133
+ * last. Empty when the run names no arm, and in any deployed server.
134
+ */ export function aiDevEffortArm(env = process.env) {
135
+ var _env_AI_EFFORT_ARM_ENV;
136
+ if (aiIsDeployedRuntime(env)) return {};
137
+ const raw = String((_env_AI_EFFORT_ARM_ENV = env[AI_EFFORT_ARM_ENV]) != null ? _env_AI_EFFORT_ARM_ENV : '').trim();
138
+ const arm = {};
139
+ for (const pair of raw ? raw.split(',') : []){
140
+ const [kind, level] = pair.split(':').map((part)=>part.trim());
141
+ if (!kind || !AI_EFFORT_LEVELS.includes(level)) continue;
142
+ if (kind === '*' || !(kind in arm)) arm[kind] = level;
143
+ }
144
+ return Object.fromEntries(Object.entries(arm).sort(([a], [b])=>a < b ? -1 : a > b ? 1 : 0));
145
+ }
146
+ /** The effort this development run's A/B arm asks `step` at, or `undefined` for the step's own. */ export function aiDevEffortFor(step, env = process.env) {
147
+ var _ref;
148
+ const arm = aiDevEffortArm(env);
149
+ return (_ref = step !== undefined && step !== '*' ? arm[step] : undefined) != null ? _ref : arm['*'];
70
150
  }
71
151
 
72
152
  //# sourceMappingURL=ai-dev-env.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-dev-env.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Which development layers this process runs (AGL-3660): the replay cache,\n * the hour-long prompt cache and batch mode, each read from the environment.\n * Kept apart from `ai-dev-replay.ts`, which reads and writes files, so the\n * runtime asks these questions on every request without loading a file\n * system module into a server that will never use one. Every answer is `off`\n * in a deployed server.\n */\n\nexport type AiDevReplayMode = 'off' | 'replay' | 'refresh'\n\n/** The variable that turns the replay cache on, off, or to refresh. */\nexport const AI_REPLAY_ENV = 'AGLYN_AI_REPLAY'\n/** Where the answers are kept, when not under the repository's `.cache/ai-replay`. */\nexport const AI_REPLAY_DIR_ENV = 'AGLYN_AI_REPLAY_DIR'\n/** The prompt-cache lifetime a development run asks for: `1h` or `5m`. */\nexport const AI_DEV_CACHE_TTL_ENV = 'AGLYN_AI_CACHE_TTL'\n/** The mark the live-eval launcher leaves, which `jest.setup.js` also reads. */\nexport const AI_LIVE_LAUNCHER_ENV = 'AI_EVAL_LIVE_LAUNCHER'\n\nexport type AiDevEnv = Readonly<Record<string, string | undefined>>\n\n/** Whether this process is a deployed server, where no development layer may run. */\nexport function aiIsDeployedRuntime(env: AiDevEnv = process.env): boolean {\n return (\n env['NODE_ENV'] === 'production' ||\n env['VERCEL_ENV'] === 'production' ||\n env['VERCEL_ENV'] === 'preview' ||\n Boolean(env['K_SERVICE'])\n )\n}\n\n/** Whether this process is a development run that asked for the live provider through the launcher. */\nfunction underLiveLauncher(env: AiDevEnv): boolean {\n return Boolean(env[AI_LIVE_LAUNCHER_ENV])\n}\n\n/** The replay mode this process runs under; `off` in any deployed server. */\nexport function aiDevReplayMode(env: AiDevEnv = process.env): AiDevReplayMode {\n if (aiIsDeployedRuntime(env)) return 'off'\n const raw = String(env[AI_REPLAY_ENV] ?? '').trim().toLowerCase()\n if (raw === 'refresh') return 'refresh'\n if (raw === '1' || raw === 'on' || raw === 'true' || raw === 'replay') return 'replay'\n if (raw === '0' || raw === 'off' || raw === 'false') return 'off'\n return underLiveLauncher(env) ? 'replay' : 'off'\n}\n\n/**\n * The prompt-cache lifetime a development run asks for. An agent re-runs a\n * live eval every ten to forty minutes while it iterates, which is past the\n * default five-minute entry and inside an hour, so under the launcher the\n * breakpoints ask for the hour: the second run reads the prefix the first one\n * wrote instead of writing it again. Production keeps the default — its\n * steps run a minute apart on the beat, inside five minutes — and never reads\n * this: `undefined` there.\n */\nexport function aiDevCacheTtl(env: AiDevEnv = process.env): '1h' | undefined {\n if (aiIsDeployedRuntime(env)) return undefined\n const raw = String(env[AI_DEV_CACHE_TTL_ENV] ?? '').trim().toLowerCase()\n if (raw === '1h') return '1h'\n if (raw === '5m') return undefined\n return underLiveLauncher(env) ? '1h' : undefined\n}\n\n/** A deployed server that was handed the variable says so once, and runs without it. */\nlet warnedDeployed = false\nexport function aiWarnIfReplayAskedInDeployment(env: AiDevEnv = process.env): void {\n if (warnedDeployed || !aiIsDeployedRuntime(env)) return\n if (!env[AI_REPLAY_ENV] && !env[AI_DEV_CACHE_TTL_ENV]) return\n warnedDeployed = true\n console.error(\n `[ai] ${AI_REPLAY_ENV} / ${AI_DEV_CACHE_TTL_ENV} are development-only and are ignored in a deployed server`,\n )\n}\n\n/** The variable that turns batch mode on (`runtime/ai-live-batch.ts`). */\nexport const AI_LIVE_BATCH_ENV = 'AGLYN_LIVE_AI_BATCH'\n\n/** Whether this process batches its live requests. */\nexport function aiLiveBatchWanted(env: AiDevEnv = process.env): boolean {\n if (aiIsDeployedRuntime(env)) return false\n return /^(?:1|true|yes|on)$/i.test(String(env[AI_LIVE_BATCH_ENV] ?? '').trim())\n}\n"],"names":["AI_REPLAY_ENV","AI_REPLAY_DIR_ENV","AI_DEV_CACHE_TTL_ENV","AI_LIVE_LAUNCHER_ENV","aiIsDeployedRuntime","env","process","Boolean","underLiveLauncher","aiDevReplayMode","raw","String","trim","toLowerCase","aiDevCacheTtl","undefined","warnedDeployed","aiWarnIfReplayAskedInDeployment","console","error","AI_LIVE_BATCH_ENV","aiLiveBatchWanted","test"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAID,qEAAqE,GACrE,OAAO,MAAMA,gBAAgB,kBAAiB;AAC9C,oFAAoF,GACpF,OAAO,MAAMC,oBAAoB,sBAAqB;AACtD,wEAAwE,GACxE,OAAO,MAAMC,uBAAuB,qBAAoB;AACxD,8EAA8E,GAC9E,OAAO,MAAMC,uBAAuB,wBAAuB;AAI3D,mFAAmF,GACnF,OAAO,SAASC,oBAAoBC,MAAgBC,QAAQD,GAAG;IAC7D,OACEA,GAAG,CAAC,WAAW,KAAK,gBACpBA,GAAG,CAAC,aAAa,KAAK,gBACtBA,GAAG,CAAC,aAAa,KAAK,aACtBE,QAAQF,GAAG,CAAC,YAAY;AAE5B;AAEA,qGAAqG,GACrG,SAASG,kBAAkBH,GAAa;IACtC,OAAOE,QAAQF,GAAG,CAACF,qBAAqB;AAC1C;AAEA,2EAA2E,GAC3E,OAAO,SAASM,gBAAgBJ,MAAgBC,QAAQD,GAAG;QAEtCA;IADnB,IAAID,oBAAoBC,MAAM,OAAO;IACrC,MAAMK,MAAMC,QAAON,qBAAAA,GAAG,CAACL,cAAc,YAAlBK,qBAAsB,IAAIO,IAAI,GAAGC,WAAW;IAC/D,IAAIH,QAAQ,WAAW,OAAO;IAC9B,IAAIA,QAAQ,OAAOA,QAAQ,QAAQA,QAAQ,UAAUA,QAAQ,UAAU,OAAO;IAC9E,IAAIA,QAAQ,OAAOA,QAAQ,SAASA,QAAQ,SAAS,OAAO;IAC5D,OAAOF,kBAAkBH,OAAO,WAAW;AAC7C;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASS,cAAcT,MAAgBC,QAAQD,GAAG;QAEpCA;IADnB,IAAID,oBAAoBC,MAAM,OAAOU;IACrC,MAAML,MAAMC,QAAON,4BAAAA,GAAG,CAACH,qBAAqB,YAAzBG,4BAA6B,IAAIO,IAAI,GAAGC,WAAW;IACtE,IAAIH,QAAQ,MAAM,OAAO;IACzB,IAAIA,QAAQ,MAAM,OAAOK;IACzB,OAAOP,kBAAkBH,OAAO,OAAOU;AACzC;AAEA,sFAAsF,GACtF,IAAIC,iBAAiB;AACrB,OAAO,SAASC,gCAAgCZ,MAAgBC,QAAQD,GAAG;IACzE,IAAIW,kBAAkB,CAACZ,oBAAoBC,MAAM;IACjD,IAAI,CAACA,GAAG,CAACL,cAAc,IAAI,CAACK,GAAG,CAACH,qBAAqB,EAAE;IACvDc,iBAAiB;IACjBE,QAAQC,KAAK,CACX,CAAC,KAAK,EAAEnB,cAAc,GAAG,EAAEE,qBAAqB,0DAA0D,CAAC;AAE/G;AAEA,wEAAwE,GACxE,OAAO,MAAMkB,oBAAoB,sBAAqB;AAEtD,oDAAoD,GACpD,OAAO,SAASC,kBAAkBhB,MAAgBC,QAAQD,GAAG;QAEjBA;IAD1C,IAAID,oBAAoBC,MAAM,OAAO;IACrC,OAAO,uBAAuBiB,IAAI,CAACX,QAAON,yBAAAA,GAAG,CAACe,kBAAkB,YAAtBf,yBAA0B,IAAIO,IAAI;AAC9E"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-dev-env.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Which development layers this process runs (AGL-3660): the replay cache,\n * the hour-long prompt cache and batch mode, each read from the environment.\n * Kept apart from `ai-dev-replay.ts`, which reads and writes files, so the\n * runtime asks these questions on every request without loading a file\n * system module into a server that will never use one. Every answer is `off`\n * in a deployed server.\n */\n\nimport type { AiEffort } from '../providers/contract'\n\nexport type AiDevReplayMode = 'off' | 'replay' | 'refresh'\n\n/** The variable that turns the replay cache on, off, or to refresh. */\nexport const AI_REPLAY_ENV = 'AGLYN_AI_REPLAY'\n/** Where the answers are kept, when not under the repository's `.cache/ai-replay`. */\nexport const AI_REPLAY_DIR_ENV = 'AGLYN_AI_REPLAY_DIR'\n/** The prompt-cache lifetime a development run asks for: `1h` or `5m`. */\nexport const AI_DEV_CACHE_TTL_ENV = 'AGLYN_AI_CACHE_TTL'\n/** The mark the live-eval launcher leaves, which `jest.setup.js` also reads. */\nexport const AI_LIVE_LAUNCHER_ENV = 'AI_EVAL_LIVE_LAUNCHER'\n\nexport type AiDevEnv = Readonly<Record<string, string | undefined>>\n\n/** Whether this process is a deployed server, where no development layer may run. */\nexport function aiIsDeployedRuntime(env: AiDevEnv = process.env): boolean {\n return (\n env['NODE_ENV'] === 'production' ||\n env['VERCEL_ENV'] === 'production' ||\n env['VERCEL_ENV'] === 'preview' ||\n Boolean(env['K_SERVICE'])\n )\n}\n\n/** The variable every live spec is started with. */\nexport const AI_LIVE_RUN_ENV = 'AGLYN_LIVE_AI'\n\n/**\n * Whether this process is a development run that asked for the live\n * provider: through the launcher, or a live spec started with\n * `AGLYN_LIVE_AI=1` (AGL-3781). Every development layer is on by default in\n * such a run — a live spec started without the launcher's mark used to send\n * every request again, at the five-minute cache and full price — and each can\n * still be turned off by its own variable.\n */\nexport function aiUnderLiveRun(env: AiDevEnv = process.env): boolean {\n if (aiIsDeployedRuntime(env)) return false\n return Boolean(env[AI_LIVE_LAUNCHER_ENV]) || String(env[AI_LIVE_RUN_ENV] ?? '').trim() === '1'\n}\n\n/** The replay mode this process runs under; `off` in any deployed server. */\nexport function aiDevReplayMode(env: AiDevEnv = process.env): AiDevReplayMode {\n if (aiIsDeployedRuntime(env)) return 'off'\n const raw = String(env[AI_REPLAY_ENV] ?? '').trim().toLowerCase()\n if (raw === 'refresh') return 'refresh'\n if (raw === '1' || raw === 'on' || raw === 'true' || raw === 'replay') return 'replay'\n if (raw === '0' || raw === 'off' || raw === 'false') return 'off'\n return aiUnderLiveRun(env) ? 'replay' : 'off'\n}\n\n/**\n * The prompt-cache lifetime a development run asks for. An agent re-runs a\n * live eval every ten to forty minutes while it iterates, which is past the\n * default five-minute entry and inside an hour, so under the launcher the\n * breakpoints ask for the hour: the second run reads the prefix the first one\n * wrote instead of writing it again. Production keeps the default — its\n * steps run a minute apart on the beat, inside five minutes — and never reads\n * this: `undefined` there.\n */\nexport function aiDevCacheTtl(env: AiDevEnv = process.env): '1h' | undefined {\n if (aiIsDeployedRuntime(env)) return undefined\n const raw = String(env[AI_DEV_CACHE_TTL_ENV] ?? '').trim().toLowerCase()\n if (raw === '1h') return '1h'\n if (raw === '5m') return undefined\n return aiUnderLiveRun(env) ? '1h' : undefined\n}\n\n/** A deployed server that was handed the variable says so once, and runs without it. */\nlet warnedDeployed = false\nexport function aiWarnIfReplayAskedInDeployment(env: AiDevEnv = process.env): void {\n if (warnedDeployed || !aiIsDeployedRuntime(env)) return\n if (!env[AI_REPLAY_ENV] && !env[AI_DEV_CACHE_TTL_ENV]) return\n warnedDeployed = true\n console.error(\n `[ai] ${AI_REPLAY_ENV} / ${AI_DEV_CACHE_TTL_ENV} are development-only and are ignored in a deployed server`,\n )\n}\n\n/** The variable that turns batch mode on (`runtime/ai-live-batch.ts`). */\nexport const AI_LIVE_BATCH_ENV = 'AGLYN_LIVE_AI_BATCH'\n\n/**\n * Whether this process batches its live requests: what `AGLYN_LIVE_AI_BATCH`\n * says, and in a live run that says nothing, yes (AGL-3781). Nobody waits on\n * an eval's answers, and a batch is half price on every token, cache reads\n * and writes included; `AGLYN_LIVE_AI_BATCH=0` is for the quick single run an\n * iteration wants back in seconds.\n */\nexport function aiLiveBatchWanted(env: AiDevEnv = process.env): boolean {\n if (aiIsDeployedRuntime(env)) return false\n const raw = String(env[AI_LIVE_BATCH_ENV] ?? '').trim()\n if (/^(?:1|true|yes|on)$/i.test(raw)) return true\n if (/^(?:0|false|no|off)$/i.test(raw)) return false\n return aiUnderLiveRun(env)\n}\n\n/** The variable that bounds how long a live run waits on one batch (AGL-3781). */\nexport const AI_LIVE_BATCH_WAIT_ENV = 'AGLYN_LIVE_AI_BATCH_WAIT_MS'\n\n/** How long a batch is waited on when the run names no wait. */\nexport const AI_LIVE_BATCH_DEFAULT_WAIT_MS = 30 * 60_000\n\n/**\n * How long a live run waits on one batch (AGL-3781): what\n * `AGLYN_LIVE_AI_BATCH_WAIT_MS` says, or half an hour. Most batches end in\n * minutes. One that has not by then is canceled, which keeps every answer it\n * already has, and the requests it left unanswered are asked on their own at\n * full price — so a slow batch costs a round part of its discount, never the\n * round.\n */\nexport function aiLiveBatchWaitMs(env: AiDevEnv = process.env): number {\n const raw = String(env[AI_LIVE_BATCH_WAIT_ENV] ?? '').trim()\n const parsed = raw ? Number(raw) : Number.NaN\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : AI_LIVE_BATCH_DEFAULT_WAIT_MS\n}\n\n/**\n * The most a batched live run may take: a run's requests go as a few batches\n * one after another — the answers, their re-asks, the grades — and each is\n * waited on for `aiLiveBatchWaitMs`, then given time to end its cancel and to\n * have what it left asked on its own. A spec that timed out would keep none\n * of the answers it already paid for, so the bound is the worst case.\n */\nexport const AI_LIVE_BATCH_TIMEOUT_MS = 5 * 60 * 60_000\n\n/** A live spec's timeout: `baseMs`, or the batched run's when this run batches. */\nexport function aiLiveTimeoutMs(baseMs: number, env: AiDevEnv = process.env): number {\n if (!aiLiveBatchWanted(env)) return baseMs\n // Four batches in a row, each waited on, canceled and finished by hand.\n const longWait = 4 * (aiLiveBatchWaitMs(env) + 20 * 60_000)\n return Math.max(baseMs, AI_LIVE_BATCH_TIMEOUT_MS, longWait)\n}\n\n/**\n * The variable an effort A/B names its arm with (AGL-3781): a step kind's\n * effort for this run, as `job.form:medium,job.text:low`, or `*:medium` for\n * every kind. A development run only, so the defaults stay where production\n * runs them until a recorded round shows a lower effort holds.\n */\nexport const AI_EFFORT_ARM_ENV = 'AGLYN_AI_EFFORT'\n\nconst AI_EFFORT_LEVELS: readonly AiEffort[] = ['low', 'medium', 'high']\n\n/**\n * Every step kind this development run's A/B arm names, with its effort, in\n * kind order — `*` for every kind it does not name. A pair whose effort is no\n * level is ignored; a kind named twice keeps its first effort, and `*` its\n * last. Empty when the run names no arm, and in any deployed server.\n */\nexport function aiDevEffortArm(env: AiDevEnv = process.env): Record<string, AiEffort> {\n if (aiIsDeployedRuntime(env)) return {}\n const raw = String(env[AI_EFFORT_ARM_ENV] ?? '').trim()\n const arm: Record<string, AiEffort> = {}\n for (const pair of raw ? raw.split(',') : []) {\n const [kind, level] = pair.split(':').map((part) => part.trim())\n if (!kind || !AI_EFFORT_LEVELS.includes(level as AiEffort)) continue\n if (kind === '*' || !(kind in arm)) arm[kind] = level as AiEffort\n }\n return Object.fromEntries(Object.entries(arm).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))\n}\n\n/** The effort this development run's A/B arm asks `step` at, or `undefined` for the step's own. */\nexport function aiDevEffortFor(step: string | undefined, env: AiDevEnv = process.env): AiEffort | undefined {\n const arm = aiDevEffortArm(env)\n return (step !== undefined && step !== '*' ? arm[step] : undefined) ?? arm['*']\n}\n"],"names":["AI_REPLAY_ENV","AI_REPLAY_DIR_ENV","AI_DEV_CACHE_TTL_ENV","AI_LIVE_LAUNCHER_ENV","aiIsDeployedRuntime","env","process","Boolean","AI_LIVE_RUN_ENV","aiUnderLiveRun","String","trim","aiDevReplayMode","raw","toLowerCase","aiDevCacheTtl","undefined","warnedDeployed","aiWarnIfReplayAskedInDeployment","console","error","AI_LIVE_BATCH_ENV","aiLiveBatchWanted","test","AI_LIVE_BATCH_WAIT_ENV","AI_LIVE_BATCH_DEFAULT_WAIT_MS","aiLiveBatchWaitMs","parsed","Number","NaN","isFinite","AI_LIVE_BATCH_TIMEOUT_MS","aiLiveTimeoutMs","baseMs","longWait","Math","max","AI_EFFORT_ARM_ENV","AI_EFFORT_LEVELS","aiDevEffortArm","arm","pair","split","kind","level","map","part","includes","Object","fromEntries","entries","sort","a","b","aiDevEffortFor","step"],"mappings":"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"}
@@ -108,9 +108,13 @@ export declare function resetAiDevWarmPrefixes(): void;
108
108
  * the live ones cost at list, and how many batches carried them. A table
109
109
  * whose `live` is 0 proved nothing new — every prompt in it had been answered
110
110
  * before — and is not a live pass; `AGLYN_AI_REPLAY=refresh` asks again.
111
+ * `batchedRequests` are the ones a batch answered, at half price;
112
+ * `batchFallbacks` are the ones a batch left unanswered past its wait and
113
+ * that were asked on their own, at full price (AGL-3781). Both count in `live`.
111
114
  */
112
115
  export declare function aiLiveRunLedger(env?: Env): AiDevReplayStats & {
113
116
  batches: number;
114
117
  batchedRequests: number;
118
+ batchFallbacks: number;
115
119
  allReplayed: boolean;
116
120
  };
@@ -300,12 +300,16 @@ export async function aiDevWarmPrefixFirst(prefixKey, ttlMs, send, now = Date.no
300
300
  * the live ones cost at list, and how many batches carried them. A table
301
301
  * whose `live` is 0 proved nothing new — every prompt in it had been answered
302
302
  * before — and is not a live pass; `AGLYN_AI_REPLAY=refresh` asks again.
303
+ * `batchedRequests` are the ones a batch answered, at half price;
304
+ * `batchFallbacks` are the ones a batch left unanswered past its wait and
305
+ * that were asked on their own, at full price (AGL-3781). Both count in `live`.
303
306
  */ export function aiLiveRunLedger(env = process.env) {
304
307
  const replay = aiDevReplayStats(env);
305
308
  const batch = aiLiveBatchStats();
306
309
  return _extends({}, replay, {
307
310
  batches: batch.batches,
308
311
  batchedRequests: batch.requests,
312
+ batchFallbacks: batch.fallbacks,
309
313
  allReplayed: replay.live === 0 && replay.replayed > 0
310
314
  });
311
315
  }