@intentic/sandbox-contract 1.299.0 → 1.300.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/agent.contract.d.ts +771 -8
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +3 -2
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +0 -81
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +6 -1
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +3 -2
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/exit.contract.d.ts +15 -3
- package/dist/contracts/exit.contract.d.ts.map +1 -1
- package/dist/contracts/exit.contract.js +5 -4
- package/dist/contracts/exit.contract.js.map +1 -1
- package/dist/contracts/host.contract.d.ts +24 -2
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +4 -3
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/intentic.contract.d.ts +10 -2
- package/dist/contracts/intentic.contract.d.ts.map +1 -1
- package/dist/contracts/intentic.contract.js +4 -3
- package/dist/contracts/intentic.contract.js.map +1 -1
- package/dist/contracts/netdisk.contract.d.ts +5 -1
- package/dist/contracts/netdisk.contract.d.ts.map +1 -1
- package/dist/contracts/netdisk.contract.js +3 -2
- package/dist/contracts/netdisk.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +505 -18
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.js +4 -3
- package/dist/contracts/runner.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +0 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +0 -33
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +333 -8
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +5 -4
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/vpn.contract.d.ts +5 -1
- package/dist/contracts/vpn.contract.d.ts.map +1 -1
- package/dist/contracts/vpn.contract.js +3 -2
- package/dist/contracts/vpn.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +1 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +1 -15
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +0 -10
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/system-events.d.ts +0 -10
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +0 -6
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +1 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/index.d.ts +1176 -171
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/models/model-order.d.ts +0 -2
- package/dist/models/model-order.d.ts.map +1 -1
- package/dist/models/model-order.js +0 -13
- package/dist/models/model-order.js.map +1 -1
- package/dist/protocol/routes.d.ts +6 -0
- package/dist/protocol/routes.d.ts.map +1 -1
- package/dist/protocol/routes.js +12 -4
- package/dist/protocol/routes.js.map +1 -1
- package/dist/schemas/agent.d.ts +0 -1
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +0 -4
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +0 -15
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +0 -8
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +0 -5
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +1 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +1 -0
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/devices.d.ts +1 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +43 -24
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/providers/usage.d.ts +0 -6
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +0 -6
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/settings.d.ts +0 -31
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +0 -23
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/workspace/workspace-search.d.ts +1 -0
- package/dist/schemas/workspace/workspace-search.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-search.js +5 -0
- package/dist/schemas/workspace/workspace-search.js.map +1 -1
- package/dist/state/definition.d.ts +0 -12
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +1 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +6 -0
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +7 -0
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +0 -4
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts/agent.contract.ts +3 -2
- package/src/contracts/capabilities.contract.ts +3 -2
- package/src/contracts/exit.contract.ts +5 -4
- package/src/contracts/host.contract.ts +4 -3
- package/src/contracts/intentic.contract.ts +4 -3
- package/src/contracts/netdisk.contract.ts +3 -2
- package/src/contracts/runner.contract.ts +4 -3
- package/src/contracts/system.contract.ts +5 -4
- package/src/contracts/vpn.contract.ts +3 -2
- package/src/events/agent-events.ts +0 -14
- package/src/events/transcript.ts +1 -1
- package/src/index.ts +0 -2
- package/src/models/model-order.test.ts +1 -51
- package/src/models/model-order.ts +1 -23
- package/src/models/model-pins.ts +2 -2
- package/src/protocol/routes.test.ts +51 -18
- package/src/protocol/routes.ts +26 -6
- package/src/schemas/agent.ts +0 -8
- package/src/schemas/agents.ts +0 -11
- package/src/schemas/capabilities.ts +3 -0
- package/src/schemas/devices.ts +75 -29
- package/src/schemas/model-route.ts +1 -1
- package/src/schemas/providers/usage.ts +0 -11
- package/src/schemas/settings.ts +0 -52
- package/src/schemas/workspace/workspace-search.ts +7 -0
- package/src/state/history-state.ts +3 -0
- package/src/state/workspace-state.test.ts +13 -5
- package/src/state/workspace-state.ts +11 -0
- package/src/text/transcript-fold.test.ts +0 -17
- package/src/text/transcript-fold.ts +0 -5
- package/dist/models/fast-tier.d.ts +0 -9
- package/dist/models/fast-tier.d.ts.map +0 -1
- package/dist/models/fast-tier.js +0 -19
- package/dist/models/fast-tier.js.map +0 -1
- package/dist/models/prompt-complexity.d.ts +0 -27
- package/dist/models/prompt-complexity.d.ts.map +0 -1
- package/dist/models/prompt-complexity.js +0 -91
- package/dist/models/prompt-complexity.js.map +0 -1
- package/src/models/fast-tier.test.ts +0 -77
- package/src/models/fast-tier.ts +0 -36
- package/src/models/prompt-complexity.test.ts +0 -177
- package/src/models/prompt-complexity.ts +0 -215
|
@@ -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 &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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"}
|
package/dist/models/fast-tier.js
DELETED
|
@@ -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
|
-
});
|
package/src/models/fast-tier.ts
DELETED
|
@@ -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
|
-
};
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "vitest";
|
|
2
|
-
import { type ComplexityInput, FAST_CEILING, judgeComplexity } from "./prompt-complexity.js";
|
|
3
|
-
|
|
4
|
-
// Pins the asymmetry: the judge only ever routes down, so silence resolves to standard, any escalating rule or gate
|
|
5
|
-
// wins regardless of order, and the weights (settings.autoTier "shadow") stay unpinned.
|
|
6
|
-
|
|
7
|
-
const turn = (prompt: string, over: Partial<ComplexityInput> = {}): ComplexityInput => ({
|
|
8
|
-
prompt,
|
|
9
|
-
attachments: 0,
|
|
10
|
-
hasImages: false,
|
|
11
|
-
editorContext: false,
|
|
12
|
-
unattended: false,
|
|
13
|
-
planMode: false,
|
|
14
|
-
afterHardTurn: false,
|
|
15
|
-
...over,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const tierOf = (prompt: string, over: Partial<ComplexityInput> = {}) => judgeComplexity(turn(prompt, over)).tier;
|
|
19
|
-
|
|
20
|
-
test("a prompt matching nothing at all stays on the model the user picked", () => {
|
|
21
|
-
const verdict = judgeComplexity(turn(`Have another go at the thing we were discussing yesterday afternoon`));
|
|
22
|
-
|
|
23
|
-
expect(verdict.tier).toBe(`standard`);
|
|
24
|
-
expect(verdict.score).toBeGreaterThan(FAST_CEILING);
|
|
25
|
-
// Only the two absence features fire on this input; together they cannot reach the ceiling.
|
|
26
|
-
expect(verdict.rules).toEqual([`short-prompt`, `no-workspace-reference`]);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("a short vague request is not read as an easy one", () => {
|
|
30
|
-
expect(tierOf(`fix the bug`)).toBe(`standard`);
|
|
31
|
-
expect(tierOf(`have a look at the thing we discussed`)).toBe(`standard`);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("an empty-ish prompt is not mistaken for an easy one", () => {
|
|
35
|
-
expect(tierOf(` `)).toBe(`standard`);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("a short question about nothing in the workspace is the case this feature exists for", () => {
|
|
39
|
-
expect(tierOf(`what is a closure?`)).toBe(`fast`);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("naming a file keeps an otherwise easy question on the user's own model", () => {
|
|
43
|
-
// Same easy word; naming a real file, not a concept, tips the verdict to standard.
|
|
44
|
-
expect(tierOf(`explain what is a closure`)).toBe(`fast`);
|
|
45
|
-
expect(tierOf(`explain what src/agent/turn-plan.ts does`)).toBe(`standard`);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("a trivial aside inside a hard conversation still gets through", () => {
|
|
49
|
-
expect(tierOf(`what is a closure?`, { afterHardTurn: true })).toBe(`fast`);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("a workspace-adjacent errand stops being cheap once the conversation has done hard work", () => {
|
|
53
|
-
expect(tierOf(`list the exports`, { afterHardTurn: false })).toBe(`fast`);
|
|
54
|
-
expect(tierOf(`list the exports`, { afterHardTurn: true })).toBe(`standard`);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("a screenshot is never sent to the cheap rung, however simple the question about it", () => {
|
|
58
|
-
expect(tierOf(`what is this?`, { hasImages: true, attachments: 1 })).toBe(`standard`);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("plan mode is a request to think, so it is never answered by the model that thinks least", () => {
|
|
62
|
-
expect(tierOf(`what is a closure?`, { planMode: true })).toBe(`standard`);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("a surface-started run is never downgraded, because nobody is watching it fail", () => {
|
|
66
|
-
expect(tierOf(`what is a closure?`, { unattended: true })).toBe(`standard`);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("a gate reports itself and scores 1, so the ledger can tell a gate from a hard sentence", () => {
|
|
70
|
-
const verdict = judgeComplexity(turn(`hi`, { unattended: true }));
|
|
71
|
-
|
|
72
|
-
expect(verdict.score).toBe(1);
|
|
73
|
-
expect(verdict.rules).toContain(`unattended`);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test.each([
|
|
77
|
-
[`pasted code`, "explain this\n```ts\nconst x = 1;\n```"],
|
|
78
|
-
[`a stack trace`, "it broke\n at Object.run (/work/x.ts:12:3)"],
|
|
79
|
-
[`a thrown error`, "help\nTypeError: cannot read properties of undefined"],
|
|
80
|
-
[`a hard word`, `why does the picker reset`],
|
|
81
|
-
[`another hard word`, `refactor this`],
|
|
82
|
-
[`a second job`, `rename it and then update the tests`],
|
|
83
|
-
[`a checklist`, `- rename it\n- update the tests`],
|
|
84
|
-
[`a cross-cutting scope`, `rename it across the codebase`],
|
|
85
|
-
])("%s forces the user's own model even in an otherwise tiny prompt", (_name, prompt) => {
|
|
86
|
-
expect(tierOf(prompt)).toBe(`standard`);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("an escalating rule beats every easing feature at once, so rule order cannot change an answer", () => {
|
|
90
|
-
// This prompt trips every easing weight in the file, plus one escalating rule; escalation still wins.
|
|
91
|
-
const verdict = judgeComplexity(turn(`what is a race condition?`));
|
|
92
|
-
|
|
93
|
-
expect(verdict.tier).toBe(`standard`);
|
|
94
|
-
expect(verdict.score).toBe(1);
|
|
95
|
-
expect(verdict.rules).toEqual([`hard-words`]);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("a long brief is standard on its length alone, whatever words it happens to use", () => {
|
|
99
|
-
expect(tierOf(`explain `.repeat(400))).toBe(`standard`);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("three files in, the job is about a shape rather than about a file", () => {
|
|
103
|
-
expect(tierOf(`have a look`, { attachments: 3 })).toBe(`standard`);
|
|
104
|
-
expect(tierOf(`have a look`, { attachments: 1 })).toBe(`standard`);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("names every rule that fired, because a score alone cannot say which feature did the work", () => {
|
|
108
|
-
const verdict = judgeComplexity(turn(`what is this?`));
|
|
109
|
-
|
|
110
|
-
expect(verdict.rules).toEqual([`short-prompt`, `easy-words`, `bare-question`, `no-workspace-reference`]);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("scores are rounded, so two turns the same rules judged compare equal on the ledger", () => {
|
|
114
|
-
const score = judgeComplexity(turn(`what is this?`)).score;
|
|
115
|
-
|
|
116
|
-
expect(score).toBe(Number(score.toFixed(3)));
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("the score never leaves 0..1, so a stored row is always comparable against the ceiling", () => {
|
|
120
|
-
const floor = judgeComplexity(turn(`what is this?`));
|
|
121
|
-
const ceiling = judgeComplexity(turn(`refactor everything`));
|
|
122
|
-
|
|
123
|
-
expect(floor.score).toBeGreaterThanOrEqual(0);
|
|
124
|
-
expect(ceiling.score).toBeLessThanOrEqual(1);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// eagerness moves only the cutoff; a downgrade still needs a positive easy signal at any setting.
|
|
128
|
-
|
|
129
|
-
test("the dial widens what counts as simple, in the direction it says", () => {
|
|
130
|
-
// Eased by its words, held back by naming a real file; balanced keeps it standard, eager lets it through.
|
|
131
|
-
const aboutAFile = `explain what src/app.ts does`;
|
|
132
|
-
|
|
133
|
-
expect(tierOf(aboutAFile, { eagerness: `balanced` })).toBe(`standard`);
|
|
134
|
-
expect(tierOf(aboutAFile, { eagerness: `eager` })).toBe(`fast`);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("the cautious stop wants every easing signal at once, not merely an easy word", () => {
|
|
138
|
-
// "explain closures" fails the cautious stop (not a bare question); only a bare question clears every stop.
|
|
139
|
-
expect(tierOf(`explain closures`, { eagerness: `balanced` })).toBe(`fast`);
|
|
140
|
-
expect(tierOf(`explain closures`, { eagerness: `cautious` })).toBe(`standard`);
|
|
141
|
-
expect(tierOf(`what is a closure?`, { eagerness: `cautious` })).toBe(`fast`);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("an absent dial is the balanced stop, so every row recorded before it existed still compares", () => {
|
|
145
|
-
const bare = judgeComplexity(turn(`what is this?`));
|
|
146
|
-
|
|
147
|
-
expect(bare.ceiling).toBe(FAST_CEILING);
|
|
148
|
-
expect(bare.tier).toBe(judgeComplexity(turn(`what is this?`, { eagerness: `balanced` })).tier);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("no setting of the dial can downgrade a short vague request", () => {
|
|
152
|
-
for (const eagerness of [`cautious`, `balanced`, `eager`] as const) {
|
|
153
|
-
expect(tierOf(`fix the bug`, { eagerness })).toBe(`standard`);
|
|
154
|
-
expect(tierOf(`have a look at the thing we discussed`, { eagerness })).toBe(`standard`);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("the deceptive follow-up is standard at every stop, because it never says anything easy", () => {
|
|
159
|
-
for (const eagerness of [`cautious`, `balanced`, `eager`] as const) {
|
|
160
|
-
expect(tierOf(`now do the same for the other file`, { eagerness, afterHardTurn: true })).toBe(`standard`);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("a turn following hard work has to clear a higher bar, and at the default an eased one no longer does", () => {
|
|
165
|
-
expect(tierOf(`explain closures`)).toBe(`fast`);
|
|
166
|
-
expect(tierOf(`explain closures`, { afterHardTurn: true })).toBe(`standard`);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("the verdict carries the cutoff it was judged against, because a score alone stopped being an answer", () => {
|
|
170
|
-
const cautious = judgeComplexity(turn(`explain closures`, { eagerness: `cautious` }));
|
|
171
|
-
const eager = judgeComplexity(turn(`explain closures`, { eagerness: `eager` }));
|
|
172
|
-
|
|
173
|
-
expect(cautious.ceiling).toBeLessThan(FAST_CEILING);
|
|
174
|
-
expect(eager.ceiling).toBeGreaterThan(FAST_CEILING);
|
|
175
|
-
expect(cautious.score).toBe(eager.score);
|
|
176
|
-
expect([cautious.tier, eager.tier]).toEqual([`standard`, `fast`]);
|
|
177
|
-
});
|