@intentic/sandbox-contract 1.298.0 → 1.300.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/contracts/agent.contract.d.ts +6 -7
  2. package/dist/contracts/agent.contract.d.ts.map +1 -1
  3. package/dist/contracts/agents.contract.d.ts +0 -81
  4. package/dist/contracts/agents.contract.d.ts.map +1 -1
  5. package/dist/contracts/capabilities.contract.d.ts +1 -0
  6. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  7. package/dist/contracts/runner.contract.d.ts +0 -16
  8. package/dist/contracts/runner.contract.d.ts.map +1 -1
  9. package/dist/contracts/sessions.contract.d.ts +0 -1
  10. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  11. package/dist/contracts/settings.contract.d.ts +0 -33
  12. package/dist/contracts/settings.contract.d.ts.map +1 -1
  13. package/dist/contracts/system.contract.d.ts +0 -5
  14. package/dist/contracts/system.contract.d.ts.map +1 -1
  15. package/dist/contracts/workspace.contract.d.ts +1 -0
  16. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  17. package/dist/events/agent-events.d.ts +1 -15
  18. package/dist/events/agent-events.d.ts.map +1 -1
  19. package/dist/events/agent-events.js +0 -10
  20. package/dist/events/agent-events.js.map +1 -1
  21. package/dist/events/system-events.d.ts +0 -10
  22. package/dist/events/system-events.d.ts.map +1 -1
  23. package/dist/events/transcript.d.ts +0 -6
  24. package/dist/events/transcript.d.ts.map +1 -1
  25. package/dist/events/transcript.js +1 -1
  26. package/dist/events/transcript.js.map +1 -1
  27. package/dist/index.d.ts +38 -159
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +0 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/models/model-order.d.ts +0 -2
  32. package/dist/models/model-order.d.ts.map +1 -1
  33. package/dist/models/model-order.js +0 -13
  34. package/dist/models/model-order.js.map +1 -1
  35. package/dist/schemas/agent.d.ts +0 -1
  36. package/dist/schemas/agent.d.ts.map +1 -1
  37. package/dist/schemas/agent.js +0 -4
  38. package/dist/schemas/agent.js.map +1 -1
  39. package/dist/schemas/agents.d.ts +0 -15
  40. package/dist/schemas/agents.d.ts.map +1 -1
  41. package/dist/schemas/agents.js +0 -8
  42. package/dist/schemas/agents.js.map +1 -1
  43. package/dist/schemas/automations.d.ts +0 -5
  44. package/dist/schemas/automations.d.ts.map +1 -1
  45. package/dist/schemas/capabilities.d.ts +1 -0
  46. package/dist/schemas/capabilities.d.ts.map +1 -1
  47. package/dist/schemas/capabilities.js +1 -0
  48. package/dist/schemas/capabilities.js.map +1 -1
  49. package/dist/schemas/providers/usage.d.ts +0 -6
  50. package/dist/schemas/providers/usage.d.ts.map +1 -1
  51. package/dist/schemas/providers/usage.js +0 -6
  52. package/dist/schemas/providers/usage.js.map +1 -1
  53. package/dist/schemas/settings.d.ts +0 -31
  54. package/dist/schemas/settings.d.ts.map +1 -1
  55. package/dist/schemas/settings.js +0 -23
  56. package/dist/schemas/settings.js.map +1 -1
  57. package/dist/schemas/workspace/workspace-search.d.ts +1 -0
  58. package/dist/schemas/workspace/workspace-search.d.ts.map +1 -1
  59. package/dist/schemas/workspace/workspace-search.js +5 -0
  60. package/dist/schemas/workspace/workspace-search.js.map +1 -1
  61. package/dist/state/definition.d.ts +0 -12
  62. package/dist/state/definition.d.ts.map +1 -1
  63. package/dist/state/workspace-state.d.ts +6 -0
  64. package/dist/state/workspace-state.d.ts.map +1 -1
  65. package/dist/state/workspace-state.js +7 -0
  66. package/dist/state/workspace-state.js.map +1 -1
  67. package/dist/text/transcript-fold.d.ts.map +1 -1
  68. package/dist/text/transcript-fold.js +0 -4
  69. package/dist/text/transcript-fold.js.map +1 -1
  70. package/package.json +5 -5
  71. package/src/events/agent-events.ts +0 -14
  72. package/src/events/transcript.ts +1 -1
  73. package/src/index.ts +0 -2
  74. package/src/models/model-order.test.ts +1 -51
  75. package/src/models/model-order.ts +1 -23
  76. package/src/models/model-pins.ts +2 -2
  77. package/src/schemas/agent.ts +0 -8
  78. package/src/schemas/agents.ts +0 -11
  79. package/src/schemas/capabilities.ts +3 -0
  80. package/src/schemas/model-route.ts +1 -1
  81. package/src/schemas/providers/usage.ts +0 -11
  82. package/src/schemas/settings.ts +0 -52
  83. package/src/schemas/workspace/workspace-search.ts +7 -0
  84. package/src/state/workspace-state.test.ts +13 -5
  85. package/src/state/workspace-state.ts +11 -0
  86. package/src/text/transcript-fold.test.ts +0 -17
  87. package/src/text/transcript-fold.ts +0 -5
  88. package/dist/models/fast-tier.d.ts +0 -9
  89. package/dist/models/fast-tier.d.ts.map +0 -1
  90. package/dist/models/fast-tier.js +0 -19
  91. package/dist/models/fast-tier.js.map +0 -1
  92. package/dist/models/prompt-complexity.d.ts +0 -27
  93. package/dist/models/prompt-complexity.d.ts.map +0 -1
  94. package/dist/models/prompt-complexity.js +0 -91
  95. package/dist/models/prompt-complexity.js.map +0 -1
  96. package/src/models/fast-tier.test.ts +0 -77
  97. package/src/models/fast-tier.ts +0 -36
  98. package/src/models/prompt-complexity.test.ts +0 -177
  99. package/src/models/prompt-complexity.ts +0 -215
@@ -22,10 +22,10 @@ export interface ModelChoice {
22
22
  }
23
23
 
24
24
  // A (provider, model) pair on the wire: `${provider}:${modelId}`, the same key shape the picker mints. Used to compare
25
- // entries, dedupe a role list, and store autoFastModels.
25
+ // entries and dedupe a role list.
26
26
  export const modelPinKey = (choice: ModelChoice): string => `${choice.provider}:${choice.model}`;
27
27
 
28
- // Splits on the first colon only: a provider id never contains one, a model id might. Exported since autoFastModels
28
+ // Splits on the first colon only: a provider id never contains one, a model id might. Exported since a stored key
29
29
  // stores these keys.
30
30
  export const parsePinned = (pinned: string): ModelChoice | undefined => {
31
31
  const separator = pinned.indexOf(`:`);
@@ -260,8 +260,6 @@ export const AgentTurnSchema = z
260
260
  .describe(
261
261
  "Ask for the same work at a higher rate for a higher price. A request rather than a promise: the answer says what actually happened.",
262
262
  ),
263
- // Vetoes automatic tier selection for this turn; the judge still runs and records its verdict, but nothing is
264
- // substituted.
265
263
  // Set by the composer on the one turn whose model the Auto judge chose; the daemon only records it.
266
264
  autoPicked: z
267
265
  .boolean()
@@ -269,12 +267,6 @@ export const AgentTurnSchema = z
269
267
  .describe(
270
268
  "Whether this turn's model was chosen for you by reading the conversation's opening message, rather than picked by hand. Recorded so the choice can be judged later against what you did next.",
271
269
  ),
272
- tierHold: z
273
- .boolean()
274
- .optional()
275
- .describe(
276
- "Run exactly the model that was picked, even when the turn looks simple enough for a cheaper one. The judgement is still recorded; nothing is substituted.",
277
- ),
278
270
  // The opt-in editor context chip: what the user is looking at, folded into the prompt daemon-side.
279
271
  editorContext: EditorContextSchema.optional().describe(
280
272
  'What the user has open in their editor, folded into the prompt so that pointing words like "this" resolve.',
@@ -241,17 +241,6 @@ export const AgentSummarySchema = z.object({
241
241
  effort: z.string().optional().describe("How hard that turn was told to think."),
242
242
  thinking: z.boolean().optional().describe("Whether that turn showed its reasoning."),
243
243
  fast: z.boolean().optional().describe("Whether that turn asked for higher speed. What was asked for, not what was served."),
244
- // The judge's verdict on the last turn, not what actually ran: seeds tomorrow's composer preview, absent means
245
- // nothing judged yet.
246
- tier: z
247
- .enum(["fast", "standard"])
248
- .optional()
249
- .describe("How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran."),
250
- // Standing per-conversation choice, restored into the composer on open and sent back every turn, like `fast` above.
251
- tierHold: z
252
- .boolean()
253
- .optional()
254
- .describe("Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one."),
255
244
  account: z.string().optional().describe("Which connected account paid for it."),
256
245
  // The worktree branch (agent/<id>); absent for a non-isolated (main-tree) conversation.
257
246
  branch: z.string().optional().describe("The branch its private copy works on. Absent for a conversation that works directly in the shared tree."),
@@ -420,5 +420,8 @@ export const CapabilityProbeSchema = z.object({
420
420
  message: z
421
421
  .string()
422
422
  .describe("What happened, in the words a person standing in front of the form needs: the service's own answer, or its refusal."),
423
+ // The account the credential turned out to be: a login, an org slug, a project name. Only when the card declares
424
+ // where to read it and the service answered with one; a form offers it as the connection's name.
425
+ who: z.string().optional().describe("Who the service said the credential belongs to, when it said."),
423
426
  });
424
427
  export type CapabilityProbe = z.infer<typeof CapabilityProbeSchema>;
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { AgentProviderSchema } from "./agent.js";
3
3
 
4
4
  // Asked once per chat, on the message it opens with; answers with the model, effort and account that whole
5
- // conversation runs on. Distinct from prompt-complexity.ts, which judges a turn at a time and may only ever name a
5
+ // conversation runs on. The one place a model is chosen for a chat, and it runs once: from turn two the pick is an
6
6
  // cheaper rung of the provider already picked: this one chooses freely, and only before anything has run.
7
7
 
8
8
  export const ModelRouteAskSchema = z.object({
@@ -91,17 +91,6 @@ export const UsageTurnSchema = z.object({
91
91
  compactions: z.number().optional(),
92
92
  contextTokens: z.number().optional(),
93
93
  contextWindow: z.number().optional(),
94
- // tierScore (0..1 from judgeComplexity, vs FAST_CEILING) absent means the judge did not run. tierRules is which
95
- // named features fired. tierRouted is whether it actually ran on the cheap rung, not implied by the score.
96
- tierScore: z.number().optional(),
97
- tierRules: z.array(z.string()).optional(),
98
- tierRouted: z.boolean().optional(),
99
- // The verdict and the cutoff it was judged against (settings.autoTierEagerness); absent rows predate the knob and
100
- // fall back to FAST_CEILING.
101
- tierFast: z.boolean().optional(),
102
- tierCeiling: z.number().optional(),
103
- // The turn carried AgentTurn.tierHold: the user vetoed a fast verdict. Absent means no veto.
104
- tierDenied: z.boolean().optional(),
105
94
  // This turn's model was chosen by the Auto judge reading the conversation's opening message, not picked by hand.
106
95
  // Marked on that one turn only; a later row of the same conversation naming a different model is the user
107
96
  // overruling it, which is the escalation rate this feature has to be able to answer for.
@@ -314,35 +314,6 @@ export const SandboxSettingsSchema = z.object({
314
314
  .describe(
315
315
  "Which repositories keep a changelog, and so get a user-facing note written alongside each merge. A list rather than a switch, and empty by default, because the commit writer's standing rule is to copy the house style rather than impose one, and a repository that has never written such a note gives it nothing to copy.",
316
316
  ),
317
- // off: the judge never runs.
318
- // shadow (default): the judge scores every turn to the ledger; nothing is routed.
319
- // on: a turn judged fast runs on the cheap rung, where the provider publishes one.
320
- // judge: the deterministic scorer keeps writing shadow rows but routes nothing; the Auto picker row is offered
321
- // instead, and a model reads a new chat's opening message once to choose what the whole conversation runs on.
322
- // Exclusive with `on` because the two answer the same question at different moments, and a per-turn downgrade
323
- // underneath a picked-by-model conversation would be overruling a choice already made about this chat.
324
- autoTier: z
325
- .enum(["off", "shadow", "on", "judge"])
326
- .default("shadow")
327
- .describe(
328
- "Whether an easy-looking turn may run on a cheaper model from the same provider. Three states rather than a switch, because the middle one is the only honest road to the third: it scores every turn and routes nothing, so the guess can become a measurement before it changes anything. It can only ever route down, so the worst case is one turn's quality rather than a bill nobody asked for. The fourth, Auto, answers a different question: rather than downgrading turns one by one, it offers an Auto row in the model picker and has a model read a new chat's first message to choose what that whole conversation runs on.",
329
- ),
330
- // `balanced` is what every verdict recorded before this setting existed was judged against, so shadow history stays
331
- // comparable.
332
- autoTierEagerness: z
333
- .enum(["cautious", "balanced", "eager"])
334
- .default("balanced")
335
- .describe(
336
- "How readily a turn counts as simple enough for the cheaper model. It moves only the cutoff: at every setting a turn still has to say something positively easy, so nothing here can downgrade a short vague request.",
337
- ),
338
- // Auto uses the same cheap-end order as this list (`compareCheapestFirst`), so the two can never disagree.
339
- autoFastModels: z
340
- .array(z.string())
341
- .max(10)
342
- .default([])
343
- .describe(
344
- "Which cheaper model a downgraded turn lands on. A list so a sandbox spanning providers can name a rung on each, but not a fallback ladder: an entry naming a different provider than the turn is on is skipped rather than tried, because switching provider retires the conversation and starting over to save a fraction of a penny is not a saving. Empty picks the cheapest the turn's own provider publishes.",
345
- ),
346
317
  agentRetentionDays: z
347
318
  .number()
348
319
  .min(0)
@@ -522,27 +493,6 @@ export const TurnExperimentSchema = z.object({
522
493
  cohort: z.string().optional(),
523
494
  });
524
495
  export type TurnExperiment = z.infer<typeof TurnExperimentSchema>;
525
- // Absent when the experiment isn't running (flag off, or no holdout set); absence reads as "not measured", not as zero.
526
- // Read off the spend ledger's tier fields over the window; no counterfactual $ saved, since the ledger holds only what
527
- // turns cost. Not a `TurnExperiment`: there's no randomized control, routing follows the settings mode over time, not a
528
- // coin flip. Absent (no turn judged, autoTier off) reads as "not measured", not "measured, found nothing".
529
- export const TierReportSchema = z.object({
530
- // Turns the judge ran on in the window, the denominator under everything below.
531
- judged: z.number(),
532
- // …of which landed at or below FAST_CEILING: the turns that looked simple. fast ÷ judged is the fast share.
533
- fast: z.number(),
534
- // Upper bound on any saving, never an estimate: moving these turns to the cheap rung would have cost something too.
535
- atStakeUsd: z.number(),
536
- // Turns that actually ran the cheap rung, and what they cost there. Realized, not projected.
537
- routed: z.number(),
538
- routedUsd: z.number(),
539
- // The guardrail: fast-judged turns whose very next ledger row asked for a dearer model. Past a few percent of
540
- // `fast`, the judge costs more in trust than it saves.
541
- escalated: z.number(),
542
- // Fast-judged turns the user vetoed outright (UsageTurn.tierDenied): the same signal, said even louder.
543
- denied: z.number(),
544
- });
545
- export type TierReport = z.infer<typeof TierReportSchema>;
546
496
  // One dependency version or library improvement suggested or pinned.
547
497
  export const DependencyImprovementSchema = z.object({
548
498
  prevented: z.string(),
@@ -564,8 +514,6 @@ export const SavingsReportSchema = z.object({
564
514
  search: TurnExperimentSchema.optional(),
565
515
  // Same absence rule as `search`: not measured, never zero.
566
516
  map: TurnExperimentSchema.optional(),
567
- // Automatic tier selection's readout, see TierReportSchema. Absent ⇒ nothing was judged in the window.
568
- tier: TierReportSchema.optional(),
569
517
  dependencies: DependencySavingsSchema.optional(),
570
518
  });
571
519
  export type SavingsReport = z.infer<typeof SavingsReportSchema>;
@@ -11,6 +11,13 @@ export const WorkspaceSearchQuerySchema = z.object({
11
11
  "Narrow the search to one kind: plain text, filenames, definitions, references, symbols, or code structure. Leave it out to blend them, which also answers a question asked in words.",
12
12
  ),
13
13
  includeIgnored: z.stringbool().optional().describe("Search inside installed packages and other ignored folders too."),
14
+ // One subtree, root-relative, no trailing slash: the caller's own scope (a UI narrowed to one project) rather than a
15
+ // pattern. Unlike `include` it is a prefix, so it composes with whatever globs the user typed.
16
+ dir: z
17
+ .string()
18
+ .max(512)
19
+ .optional()
20
+ .describe("Only look inside this folder, given as a path from the workspace root. Leave it out to search everything."),
14
21
  // The three switches every search box has; caseSensitive off means case-insensitive, not ripgrep's smart case.
15
22
  literal: z.stringbool().optional().describe("Treat the query as fixed text rather than a pattern."),
16
23
  word: z.stringbool().optional().describe("Match whole words only."),
@@ -171,11 +171,14 @@ describe(`WORKSPACE_STATE_FILES`, () => {
171
171
  });
172
172
 
173
173
  it(`splits a nested entry from its parent for a reason the parent doesn't already carry`, () => {
174
- // A nest agreeing with its parent is dead weight: stateFileFor resolves to the same answer either way.
174
+ // A nest agreeing with its parent is dead weight: stateFileFor resolves to the same answer either way. Three
175
+ // answers can differ, and a nest earns its place by moving any one of them.
175
176
  for (const file of WORKSPACE_STATE_FILES) {
176
177
  for (const parent of WORKSPACE_STATE_FILES.filter((other) => other !== file && file.path.startsWith(other.path))) {
177
178
  expect(
178
- parent.portability === file.portability && parent.invalidates.join(",") === file.invalidates.join(","),
179
+ parent.portability === file.portability &&
180
+ parent.invalidates.join(",") === file.invalidates.join(",") &&
181
+ (parent.backup ?? true) === (file.backup ?? true),
179
182
  `${file.path} says nothing its parent ${parent.path} doesn't already say`,
180
183
  ).toBe(false);
181
184
  }
@@ -499,9 +502,12 @@ describe(`BACKED_UP_STATE_PATHS`, () => {
499
502
  }
500
503
  });
501
504
 
502
- it(`copies down everything a person wrote and everything that happened`, () => {
505
+ // Opting out is per entry and by hand: the class still decides for everything that says nothing, so a new record
506
+ // store is backed up until somebody writes down why it shouldn't be.
507
+ it(`copies down everything a person wrote and everything that happened, bar the entries that opt out`, () => {
503
508
  for (const path of [...stateGroupPaths(`config`), ...stateGroupPaths(`records`)]) {
504
- expect([path, BACKED_UP_STATE_PATHS.includes(path)]).toEqual([path, true]);
509
+ const optedOut = WORKSPACE_STATE_FILES.find((file) => file.path === path)?.backup === false;
510
+ expect([path, BACKED_UP_STATE_PATHS.includes(path)]).toEqual([path, !optedOut]);
505
511
  }
506
512
  });
507
513
 
@@ -526,8 +532,10 @@ describe(`BACKED_UP_STATE_PATHS`, () => {
526
532
 
527
533
  it(`still withholds the tokens that authenticate against this sandbox`, () => {
528
534
  expect(BACKED_UP_STATE_PATHS).not.toContain(`.intentic/identity/control-tokens.json`);
529
- // The only entry that opts out by hand; everything else follows from its class alone.
535
+ // The whole hand-written list, in declaration order: one credential that would admit its holder to the source
536
+ // sandbox, and one tree of page captures nothing reads after its own turn. Everything else follows from class.
530
537
  expect(WORKSPACE_STATE_FILES.filter((file) => file.backup === false).map((file) => file.path)).toEqual([
538
+ `.intentic/records/artifacts/browser/`,
531
539
  `.intentic/identity/control-tokens.json`,
532
540
  ]);
533
541
  });
@@ -323,6 +323,17 @@ const STATE_FILES = [
323
323
  why: "Durable outputs owned by conversations and extension runs: attachments, browser captures, generated images, acceptance reports, workflow step reports, voice transcripts, and loop ledgers.",
324
324
  portability: "carry",
325
325
  },
326
+ // Nested under the artifacts above for one difference: this is the only member of that tree with no reader once the
327
+ // turn that wrote it has ended, and it is most of its bytes. Still `carry` (an export takes the whole history), but
328
+ // the desktop backup declines it: measured on a dogfooding machine as 1.1 GB across 6,315 files, re-scanned on every
329
+ // poll of a one-way session whose siblings — attachments above all — are the owner's own uploads and do come down.
330
+ {
331
+ path: ".intentic/records/artifacts/browser/",
332
+ invalidates: [],
333
+ why: "Page snapshots, screenshots and console logs a turn's browser wrote while looking at a site; read back by that turn and by nothing after it.",
334
+ portability: "carry",
335
+ backup: false,
336
+ },
326
337
  {
327
338
  path: ".intentic/local/cache/",
328
339
  invalidates: [],
@@ -201,23 +201,6 @@ describe("foldTurn", () => {
201
201
  });
202
202
  });
203
203
 
204
- it("writes down a turn that ran on a cheaper model, and nothing for a verdict that moved nothing", () => {
205
- const routed: AgentEvent[] = [
206
- { kind: "tier", tier: "fast", score: 0.1, rules: ["easy-words"], model: "claude-haiku-4-5", routed: true },
207
- { kind: "delta", text: "a closure is…" },
208
- ];
209
- expect(foldOf("what is a closure?", routed)).toEqual([
210
- { role: "user", text: "what is a closure?", sentAt: SENT_AT },
211
- { role: "notice", text: "This turn looked simple, so it ran on claude-haiku-4-5 instead of your pick.", noticeAction: "tierHold" },
212
- { role: "assistant", text: "a closure is…" },
213
- ]);
214
- const held: AgentEvent[] = [
215
- { kind: "tier", tier: "fast", score: 0.1, rules: ["easy-words"], model: "claude-haiku-4-5", routed: false, held: true },
216
- { kind: "delta", text: "sure" },
217
- ];
218
- expect(foldOf("go", held).map((message) => message.role)).toEqual(["user", "assistant"]);
219
- });
220
-
221
204
  it("writes the turn's own events down as notices", () => {
222
205
  const events: AgentEvent[] = [
223
206
  { kind: "worktree", branch: "agent/x", base: "abc1234", sync: { commits: 2, blocked: [] } },
@@ -259,11 +259,6 @@ export class TranscriptFold {
259
259
  case "error":
260
260
  // Keeps a refusal (no prose from the provider) from reading as a session that ended mid-question.
261
261
  return this.pushRow(errorRow(event));
262
- case "tier":
263
- // Notes a turn served on a cheaper model than requested, only when it actually routed.
264
- return event.routed && event.model !== undefined
265
- ? this.pushRow({ role: "notice", text: `This turn looked simple, so it ran on ${event.model} instead of your pick.`, noticeAction: "tierHold" })
266
- : [];
267
262
  case "plan": {
268
263
  // Folds a plan into an identical retired prose bubble instead of drawing the same markdown twice.
269
264
  const adjacent = this.rows.at(-1);
@@ -1,9 +0,0 @@
1
- import type { AgentProvider } from "../schemas/agent.js";
2
- export interface FastTierInput {
3
- readonly provider: AgentProvider;
4
- readonly model: string;
5
- readonly models: readonly string[];
6
- readonly pinned: readonly string[];
7
- }
8
- export declare const fastTierModel: (input: FastTierInput) => string | undefined;
9
- //# sourceMappingURL=fast-tier.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fast-tier.d.ts","sourceRoot":"","sources":["../../src/models/fast-tier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMzD,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IAEjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAEnC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAID,eAAO,MAAM,aAAa,UAAW,aAAa,KAAG,MAAM,GAAG,SAe7D,CAAC"}
@@ -1,19 +0,0 @@
1
- import { compareCheapestFirst, isCheaperRung } from "./model-order.js";
2
- import { parsePinned } from "./model-pins.js";
3
- export const fastTierModel = (input) => {
4
- if (input.model === "") {
5
- return undefined;
6
- }
7
- const pinned = input.pinned
8
- .flatMap((key) => {
9
- const choice = parsePinned(key);
10
- return choice === undefined || choice.provider !== input.provider ? [] : [choice.model];
11
- })
12
- .find((model) => isCheaperRung(model, input.model));
13
- if (pinned !== undefined) {
14
- return pinned;
15
- }
16
- const cheapest = input.models.toSorted(compareCheapestFirst)[0];
17
- return cheapest !== undefined && isCheaperRung(cheapest, input.model) ? cheapest : undefined;
18
- };
19
- //# sourceMappingURL=fast-tier.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fast-tier.js","sourceRoot":"","sources":["../../src/models/fast-tier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAmB9C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAoB,EAAsB,EAAE;IACtE,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,EAAE,CAAC;QACrB,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;SACtB,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5F,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACxD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC;IAClB,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,QAAQ,KAAK,SAAS,IAAI,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AACjG,CAAC,CAAC"}
@@ -1,27 +0,0 @@
1
- export type ComplexityRule = "images" | "plan-mode" | "unattended" | "code-block" | "stack-trace" | "hard-words" | "multi-step" | "cross-cutting" | "long-prompt" | "many-attachments" | "medium-prompt" | "attachment" | "editor-context" | "paths" | "many-verbs" | "after-hard-turn" | "short-prompt" | "easy-words" | "bare-question" | "no-workspace-reference";
2
- export type ComplexityTier = "fast" | "standard";
3
- export interface ComplexityInput {
4
- readonly prompt: string;
5
- readonly attachments: number;
6
- readonly hasImages: boolean;
7
- readonly editorContext: boolean;
8
- readonly unattended: boolean;
9
- readonly planMode: boolean;
10
- readonly afterHardTurn: boolean;
11
- readonly eagerness?: TierEagerness;
12
- }
13
- export interface ComplexityVerdict {
14
- readonly tier: ComplexityTier;
15
- readonly score: number;
16
- readonly rules: readonly ComplexityRule[];
17
- readonly ceiling: number;
18
- }
19
- export declare const FAST_CEILINGS: {
20
- readonly cautious: 0;
21
- readonly balanced: 0.25;
22
- readonly eager: 0.4;
23
- };
24
- export type TierEagerness = keyof typeof FAST_CEILINGS;
25
- export declare const FAST_CEILING: 0.25;
26
- export declare const judgeComplexity: (input: ComplexityInput) => ComplexityVerdict;
27
- //# sourceMappingURL=prompt-complexity.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prompt-complexity.d.ts","sourceRoot":"","sources":["../../src/models/prompt-complexity.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,cAAc,GAEpB,QAAQ,GACR,WAAW,GACX,YAAY,GAEZ,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,aAAa,GACb,kBAAkB,GAElB,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,OAAO,GACP,YAAY,GACZ,iBAAiB,GACjB,cAAc,GACd,YAAY,GACZ,eAAe,GACf,wBAAwB,CAAC;AAE/B,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAC;AAIjD,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAE5B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAEhC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAE3B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAEhC,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAE9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAE1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AASD,eAAO,MAAM,aAAa;aAAK,QAAQ,EAAE,CAAC;aAAE,QAAQ,EAAE,IAAI;aAAE,KAAK,EAAE,GAAG;CAAW,CAAC;AAClF,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,aAAa,CAAC;AAGvD,eAAO,MAAM,YAAY,MAAyB,CAAC;AA6HnD,eAAO,MAAM,eAAe,UAAW,eAAe,KAAG,iBAgBxD,CAAC"}
@@ -1,91 +0,0 @@
1
- const BASE_SCORE = 0.5;
2
- export const FAST_CEILINGS = { cautious: 0, balanced: 0.25, eager: 0.4 };
3
- export const FAST_CEILING = FAST_CEILINGS.balanced;
4
- const MEDIUM_PROMPT_CHARS = 600;
5
- const LONG_PROMPT_CHARS = 2400;
6
- const SHORT_PROMPT_CHARS = 140;
7
- const MANY_ATTACHMENTS = 3;
8
- const MANY_VERBS = 3;
9
- const EASY_WORDS = /\b(?:what(?:'s| is| are)|explain|describe|summari[sz]e|list|show me|where(?:'s| is| are)|rename|typo|reword|reformat|format this|tidy|define|translate|spell|abbreviat)/i;
10
- const HARD_WORDS = /\b(?:refactor|redesign|architect|architecture|migrat|root cause|debug|investigat|diagnos|optimi[sz]|race condition|deadlock|memory leak|regression|security|threat model|benchmark|profil|audit|design a|plan (?:a|the|out)|why (?:does|is|are|did|would|can't|cannot))/i;
11
- const MULTI_STEP = /\b(?:and then|after that|once (?:that|you)|followed by|as well as|then also)\b/i;
12
- const CROSS_CUTTING = /\b(?:across (?:the|all|every)|every(?: single)? (?:file|module|package|component|usage|call ?site)|all (?:the|of the) (?:files|usages|call ?sites|places)|everywhere|throughout the|codebase-wide|repo-wide)\b/i;
13
- const CODE_BLOCK = /```|^diff --git |^@@ .* @@|^[+-]{3} [ab]\//m;
14
- const STACK_TRACE = /(?:^|\n)\s*(?:at [\w$.<>]+ \(|Traceback \(most recent call last\)|Caused by:|panic:|thread '.*' panicked|Unhandled|[A-Z]\w*(?:Error|Exception): )/;
15
- const PATH_LIKE = /(?:^|\s)(?:@[\w./-]+|[\w-]+\/[\w./-]+\.[a-z]{1,5}\b|\b[\w-]+\.(?:ts|tsx|js|jsx|vue|py|go|rs|java|rb|css|scss|json|ya?ml|md|sql|sh)\b)/i;
16
- const BARE_QUESTION = /^[^.!?]{0,200}\?\s*$/;
17
- const VERBS = /\b(?:add|remove|delete|fix|write|create|make|update|change|move|rename|split|merge|extract|inline|wire|hook|test|check|run|build|deploy|document|implement|replace|convert|handle|support|expose|log|render|validate|parse|sort|filter|cache)\b/gi;
18
- const LIST_LINES = /^\s*(?:[-*+]\s|\d+[.)]\s)/gm;
19
- const forcing = (input, text) => {
20
- const rules = [];
21
- if (input.hasImages) {
22
- rules.push("images");
23
- }
24
- if (input.planMode) {
25
- rules.push("plan-mode");
26
- }
27
- if (input.unattended) {
28
- rules.push("unattended");
29
- }
30
- if (CODE_BLOCK.test(text)) {
31
- rules.push("code-block");
32
- }
33
- if (STACK_TRACE.test(text)) {
34
- rules.push("stack-trace");
35
- }
36
- if (HARD_WORDS.test(text)) {
37
- rules.push("hard-words");
38
- }
39
- if (MULTI_STEP.test(text) || (text.match(LIST_LINES)?.length ?? 0) >= 2) {
40
- rules.push("multi-step");
41
- }
42
- if (CROSS_CUTTING.test(text)) {
43
- rules.push("cross-cutting");
44
- }
45
- if (text.length > LONG_PROMPT_CHARS) {
46
- rules.push("long-prompt");
47
- }
48
- if (input.attachments >= MANY_ATTACHMENTS) {
49
- rules.push("many-attachments");
50
- }
51
- return rules;
52
- };
53
- const GRADED = [
54
- { rule: "medium-prompt", weight: +0.2, of: (_input, text) => text.length > MEDIUM_PROMPT_CHARS },
55
- { rule: "attachment", weight: +0.15, of: (input) => input.attachments > 0 },
56
- { rule: "editor-context", weight: +0.1, of: (input) => input.editorContext },
57
- { rule: "paths", weight: +0.15, of: (_input, text) => PATH_LIKE.test(text) },
58
- {
59
- rule: "many-verbs",
60
- weight: +0.15,
61
- of: (_input, text) => new Set((text.match(VERBS) ?? []).map((verb) => verb.toLowerCase())).size >= MANY_VERBS,
62
- },
63
- { rule: "after-hard-turn", weight: +0.25, of: (input) => input.afterHardTurn },
64
- { rule: "short-prompt", weight: -0.1, of: (_input, text) => text.length <= SHORT_PROMPT_CHARS },
65
- { rule: "easy-words", weight: -0.25, easing: true, of: (_input, text) => EASY_WORDS.test(text) },
66
- { rule: "bare-question", weight: -0.15, easing: true, of: (_input, text) => BARE_QUESTION.test(text) },
67
- {
68
- rule: "no-workspace-reference",
69
- weight: -0.1,
70
- of: (input, text) => input.attachments === 0 && !input.editorContext && !PATH_LIKE.test(text),
71
- },
72
- ];
73
- const round3 = (value) => Math.round(value * 1000) / 1000;
74
- export const judgeComplexity = (input) => {
75
- const ceiling = FAST_CEILINGS[input.eagerness ?? "balanced"];
76
- const text = input.prompt.trim();
77
- const forced = forcing(input, text);
78
- if (forced.length > 0) {
79
- return { tier: "standard", score: 1, rules: forced, ceiling };
80
- }
81
- const hits = GRADED.filter((feature) => feature.of(input, text));
82
- const score = Math.min(1, Math.max(0, BASE_SCORE + hits.reduce((total, feature) => total + feature.weight, 0)));
83
- const eased = hits.some((feature) => feature.easing === true);
84
- return {
85
- tier: eased && score <= ceiling ? "fast" : "standard",
86
- score: round3(score),
87
- rules: hits.map((feature) => feature.rule),
88
- ceiling,
89
- };
90
- };
91
- //# sourceMappingURL=prompt-complexity.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prompt-complexity.js","sourceRoot":"","sources":["../../src/models/prompt-complexity.ts"],"names":[],"mappings":"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"}
@@ -1,77 +0,0 @@
1
- import { expect, test } from "vitest";
2
- import { fastTierModel } from "./fast-tier.js";
3
-
4
- // Whether a downgraded turn has anywhere cheaper to land on its current provider; a downgrade must be a genuinely
5
- // cheaper rung of the same catalog and never cross provider, since that retires the session.
6
-
7
- const CLAUDE = [`claude-opus-5`, `claude-sonnet-5`, `claude-haiku-4-5-20251001`];
8
- const GOOGLE = [`gemini-3-pro`, `gemini-3-flash`, `gemini-3-flash-lite`];
9
-
10
- const fastFor = (model: string, over: { models?: readonly string[]; pinned?: readonly string[]; provider?: string } = {}) =>
11
- fastTierModel({ provider: over.provider ?? `claude`, model, models: over.models ?? CLAUDE, pinned: over.pinned ?? [] });
12
-
13
- // --- Auto: the cheapest rung the provider publishes ----------------------------------------------------
14
-
15
- test("sends a frontier turn to the provider's own cheap rung", () => {
16
- expect(fastFor(`claude-opus-5`)).toBe(`claude-haiku-4-5-20251001`);
17
- });
18
-
19
- test("reads the cheap end the same way the quick model does, on every vendor's vocabulary", () => {
20
- expect(fastFor(`gemini-3-pro`, { provider: `gemini`, models: GOOGLE })).toBe(`gemini-3-flash-lite`);
21
- });
22
-
23
- // --- the ceiling: the user's own pick -------------------------------------------------------------------
24
-
25
- test("a user already on the cheap rung has nowhere to be sent", () => {
26
- // undefined means run the original pick: the safe default when nothing is cheaper.
27
- expect(fastFor(`claude-haiku-4-5-20251001`)).toBeUndefined();
28
- });
29
-
30
- test("never swaps a model for an older build of the same tier", () => {
31
- expect(fastFor(`claude-sonnet-5`, { models: [`claude-sonnet-5`, `claude-sonnet-4`] })).toBeUndefined();
32
- });
33
-
34
- test("never downgrades a model whose family this build does not recognise", () => {
35
- // An id with no tier word is either the provider's own baseline or an unrecognised family.
36
- expect(fastFor(`claude-opus-5`, { models: [`some-new-thing`] })).toBeUndefined();
37
- expect(fastFor(`some-new-thing`, { models: CLAUDE })).toBeUndefined();
38
- });
39
-
40
- test("an unloaded catalog resolves to no downgrade rather than to a guess", () => {
41
- expect(fastFor(`claude-opus-5`, { models: [] })).toBeUndefined();
42
- });
43
-
44
- test("no pick yet means nothing to be cheaper than", () => {
45
- expect(fastFor(``)).toBeUndefined();
46
- });
47
-
48
- // --- pins -----------------------------------------------------------------------------------------------
49
-
50
- test("a pin on this provider wins over the catalog's own cheap end", () => {
51
- expect(fastFor(`claude-opus-5`, { pinned: [`claude:claude-sonnet-5`] })).toBe(`claude-sonnet-5`);
52
- });
53
-
54
- test("takes a pinned id verbatim, so a model the static catalog has not caught up with is still pinnable", () => {
55
- // Takes a pinned id verbatim: second-guessing it would run a different model than the settings row names.
56
- expect(fastFor(`claude-opus-5`, { pinned: [`claude:claude-haiku-9`] })).toBe(`claude-haiku-9`);
57
- });
58
-
59
- test("drops a pin naming another provider rather than crossing to it", () => {
60
- // Crossing provider retires the session (turnRequest.ts's resumes); the pin is ignored instead.
61
- expect(fastFor(`claude-opus-5`, { pinned: [`gemini:gemini-3-flash-lite`] })).toBe(`claude-haiku-4-5-20251001`);
62
- });
63
-
64
- test("skips a pin that is not actually cheaper than what the user picked", () => {
65
- // A pin picks which cheap rung, never licence to swap to an equal or better model.
66
- expect(fastFor(`claude-sonnet-5`, { pinned: [`claude:claude-opus-5`] })).toBe(`claude-haiku-4-5-20251001`);
67
- });
68
-
69
- test("walks past an unusable pin to the next one that names this provider", () => {
70
- const pinned = [`gemini:gemini-3-flash`, `claude:claude-opus-5`, `claude:claude-haiku-4-5`];
71
-
72
- expect(fastFor(`claude-opus-5`, { pinned })).toBe(`claude-haiku-4-5`);
73
- });
74
-
75
- test("falls back to Auto when no pin survives, rather than to no downgrade at all", () => {
76
- expect(fastFor(`claude-opus-5`, { pinned: [`nonsense`, `gemini:gemini-3-flash`] })).toBe(`claude-haiku-4-5-20251001`);
77
- });
@@ -1,36 +0,0 @@
1
- import { compareCheapestFirst, isCheaperRung } from "./model-order.js";
2
- import { parsePinned } from "./model-pins.js";
3
- import type { AgentProvider } from "../schemas/agent.js";
4
-
5
- // Picks which model a downgraded turn runs on, once prompt-complexity.ts decides a turn could be cheaper. Only routes
6
- // down to a cheaper rung of the model the user already picked; ambiguous cases resolve to undefined, meaning run their
7
- // pick. Never crosses provider: that retires the session and loses the context that made the follow-up cheap.
8
-
9
- export interface FastTierInput {
10
- readonly provider: AgentProvider;
11
- // Empty means the composer has not resolved a pick yet; that resolves to no downgrade, not a guess.
12
- readonly model: string;
13
- // Empty is a real state (catalog not loaded yet), and resolves to no downgrade rather than a guess.
14
- readonly models: readonly string[];
15
- // settings.autoFastModels: ordered `${provider}:${modelId}` keys, empty for Auto; other providers drop.
16
- readonly pinned: readonly string[];
17
- }
18
-
19
- // A pin is taken verbatim but must still be cheaper than the pick; Auto falls back to the cheapest rung, in the same
20
- // cheap-first order the quick model uses. Undefined means nothing is cheaper.
21
- export const fastTierModel = (input: FastTierInput): string | undefined => {
22
- if (input.model === "") {
23
- return undefined;
24
- }
25
- const pinned = input.pinned
26
- .flatMap((key) => {
27
- const choice = parsePinned(key);
28
- return choice === undefined || choice.provider !== input.provider ? [] : [choice.model];
29
- })
30
- .find((model) => isCheaperRung(model, input.model));
31
- if (pinned !== undefined) {
32
- return pinned;
33
- }
34
- const cheapest = input.models.toSorted(compareCheapestFirst)[0];
35
- return cheapest !== undefined && isCheaperRung(cheapest, input.model) ? cheapest : undefined;
36
- };