cascade-ai 0.49.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -196,6 +196,12 @@ interface ToolExecuteOptions {
196
196
  tierId: string;
197
197
  sessionId: string;
198
198
  requireApproval: boolean;
199
+ /**
200
+ * Cancels a long-running tool call. Media generation can take a minute or
201
+ * more, so a cancelled run must be able to stop one mid-flight rather than
202
+ * paying it out and discarding the result.
203
+ */
204
+ signal?: AbortSignal;
199
205
  saveSnapshot?: (filePath: string, content: string) => Promise<void>;
200
206
  sendPeerSync?: (to: string, syncType: PeerSyncType, content: string | Record<string, unknown>) => void;
201
207
  getPeerMessages?: () => Array<{
@@ -647,6 +653,11 @@ interface BudgetConfig {
647
653
  maxTokensPerRun?: number;
648
654
  /** Optional hard per-task cost ceiling (USD). */
649
655
  maxCostPerRunUsd?: number;
656
+ /**
657
+ * Consecutive systemic failures against one model before the run stops
658
+ * launching work certain to fail the same way. Default 3.
659
+ */
660
+ failureThreshold?: number;
650
661
  warnAtPct: number;
651
662
  }
652
663
  interface WorkspaceConfig {
@@ -977,6 +988,20 @@ declare class ModelPerformanceTracker {
977
988
  costEfficiencyScore(model: ModelInfo, complexity: 1 | 2 | 3 | 4 | 5): number;
978
989
  }
979
990
 
991
+ interface FeedbackCounts {
992
+ good: number;
993
+ bad: number;
994
+ }
995
+ /**
996
+ * Lookup of per-model feedback, keyed by model id.
997
+ *
998
+ * Deliberately just counts: no conversation text, no prompts, no timestamps.
999
+ * A routing prior needs to know that a model earned N good and M bad verdicts
1000
+ * and nothing else, and storing more would put chat content in a second place
1001
+ * with different retention rules for no gain.
1002
+ */
1003
+ type FeedbackSource = (modelId: string) => FeedbackCounts | undefined;
1004
+
980
1005
  type TaskType = 'code' | 'analysis' | 'creative' | 'data' | 'mixed';
981
1006
  /** Cascade Auto cost/quality trade-off bias. See CascadeConfig.autoBias. */
982
1007
  type AutoBias = 'balanced' | 'quality' | 'cost';
@@ -992,10 +1017,18 @@ interface TaskProfile {
992
1017
  }
993
1018
  declare class TaskAnalyzer {
994
1019
  private tracker?;
1020
+ /** Per-model thumbs counts, when the host collects them. */
1021
+ private feedback?;
995
1022
  private bias;
996
1023
  private lastProfile;
997
1024
  private lastSelectedModels;
998
1025
  constructor(tracker?: ModelPerformanceTracker, bias?: AutoBias);
1026
+ /**
1027
+ * Supply per-model rating counts. Optional by design: a host that collects no
1028
+ * feedback (the CLI, a fresh install) scores exactly as it did before, since
1029
+ * feedbackAdjustment returns 0 for an empty record.
1030
+ */
1031
+ setFeedbackSource(source: FeedbackSource): void;
999
1032
  setTracker(tracker: ModelPerformanceTracker): void;
1000
1033
  /** Change the cost/quality bias at runtime (e.g. when config reloads). */
1001
1034
  setBias(bias: AutoBias): void;
@@ -1463,6 +1496,7 @@ declare class CascadeRouter extends EventEmitter {
1463
1496
  private tpmLimiter;
1464
1497
  private localQueue;
1465
1498
  private taskAnalyzer?;
1499
+ private pendingFeedbackSource?;
1466
1500
  private worldStateDB?;
1467
1501
  private privacyPaths?;
1468
1502
  private guidanceQueue?;
@@ -1578,6 +1612,12 @@ declare class CascadeRouter extends EventEmitter {
1578
1612
  */
1579
1613
  getReferenceContextWindow(): number | undefined;
1580
1614
  /** Wire the Cascade Auto task analyzer used for per-subtask model routing. */
1615
+ /**
1616
+ * Feed per-model thumbs counts into Auto routing. Forwarded to the analyzer
1617
+ * because that is where the public benchmark score is combined; the prior
1618
+ * adjusts that number rather than sitting anywhere else in the pipeline.
1619
+ */
1620
+ setFeedbackSource(source: FeedbackSource): void;
1581
1621
  setTaskAnalyzer(analyzer: TaskAnalyzer): void;
1582
1622
  setWorldStateDB(db: WorldStateDB | undefined): void;
1583
1623
  getWorldStateDB(): WorldStateDB | undefined;
@@ -1858,6 +1898,12 @@ declare abstract class BaseTier extends EventEmitter {
1858
1898
  getStatus(): TierStatus;
1859
1899
  protected setStatus(status: TierStatus, output?: string): void;
1860
1900
  /** Record the model serving this tier; future status events carry it. */
1901
+ /**
1902
+ * The model actually serving this tier ("provider:id"), once one has been
1903
+ * chosen. The run breaker reads it to attribute a failure to a model rather
1904
+ * than to the subtask that happened to hit it.
1905
+ */
1906
+ getServingModel(): string | undefined;
1861
1907
  protected setServingModel(model: string | undefined): void;
1862
1908
  protected setLabel(label: string): void;
1863
1909
  setSystemPromptOverride(prompt: string): void;
@@ -1873,6 +1919,61 @@ declare abstract class BaseTier extends EventEmitter {
1873
1919
  protected throwIfCancelled(): void;
1874
1920
  }
1875
1921
 
1922
+ /**
1923
+ * What kind of failure this is, and — crucially — whether retrying a DIFFERENT
1924
+ * subtask against the same model could plausibly succeed.
1925
+ */
1926
+ type ProviderErrorKind = 'rate_limit' | 'quota_exhausted' | 'auth' | 'model_unavailable' | 'context_length' | 'content_filter' | 'network' | 'unknown';
1927
+ interface ClassifiedError {
1928
+ kind: ProviderErrorKind;
1929
+ /** True when every other worker on this model is expected to fail the same way. */
1930
+ systemic: boolean;
1931
+ /** HTTP status, when the provider gave us one. */
1932
+ status?: number;
1933
+ /** The provider's own message, trimmed — never invented or paraphrased. */
1934
+ raw: string;
1935
+ }
1936
+
1937
+ interface BreakerTrip {
1938
+ modelId: string;
1939
+ kind: ClassifiedError['kind'];
1940
+ /** How many systemic failures on this model were seen before tripping. */
1941
+ failures: number;
1942
+ /** User-facing explanation, including the provider's own words. */
1943
+ message: string;
1944
+ }
1945
+ declare class RunBreaker {
1946
+ private readonly threshold;
1947
+ /** modelId → consecutive systemic failures of the SAME kind. */
1948
+ private counts;
1949
+ private trip;
1950
+ constructor(threshold?: number);
1951
+ /**
1952
+ * Record a failed model call. Returns the trip if this failure is the one
1953
+ * that opened the breaker, otherwise null.
1954
+ *
1955
+ * A non-systemic failure RESETS the model's counter: it is evidence the model
1956
+ * is reachable and answering, which is exactly what a systemic streak claims
1957
+ * it is not.
1958
+ */
1959
+ record(err: unknown, modelId: string | undefined): BreakerTrip | null;
1960
+ /** True once the run should stop launching work that would fail identically. */
1961
+ isOpen(): boolean;
1962
+ /** Why the breaker opened, or null while it is closed. */
1963
+ reason(): BreakerTrip | null;
1964
+ /**
1965
+ * One line for a worker/section that never ran because the breaker was
1966
+ * already open. Says the work was skipped deliberately — not that it failed
1967
+ * on its own merits, which is what an empty FAILED status would imply.
1968
+ */
1969
+ skipMessage(): string;
1970
+ /** Test/diagnostic view of the per-model streaks. */
1971
+ streaks(): Record<string, {
1972
+ kind: string;
1973
+ n: number;
1974
+ }>;
1975
+ }
1976
+
1876
1977
  type T2Evaluator = (req: PermissionRequest) => Promise<PermissionDecision | null>;
1877
1978
  type T1Evaluator = (req: PermissionRequest) => Promise<PermissionDecision | null>;
1878
1979
  /**
@@ -2020,6 +2121,7 @@ declare class T1Administrator extends BaseTier {
2020
2121
  private t2Managers;
2021
2122
  private escalations;
2022
2123
  private store?;
2124
+ private runBreaker?;
2023
2125
  private t2PeerBus;
2024
2126
  private permissionEscalator?;
2025
2127
  private toolCreator?;
@@ -2029,6 +2131,8 @@ declare class T1Administrator extends BaseTier {
2029
2131
  private peerMessageSessionId;
2030
2132
  private planApprovalCallback?;
2031
2133
  constructor(router: CascadeRouter, toolRegistry: ToolRegistry, config: CascadeConfig);
2134
+ /** Share the run-wide circuit breaker with every section manager. */
2135
+ setRunBreaker(breaker: RunBreaker): void;
2032
2136
  setStore(store: MemoryStore): void;
2033
2137
  /**
2034
2138
  * Inject the shared PermissionEscalator for this task run.
@@ -2087,6 +2191,7 @@ interface DecisionLogEntry {
2087
2191
  declare class Cascade extends EventEmitter {
2088
2192
  private router;
2089
2193
  private toolRegistry;
2194
+ private multimodal;
2090
2195
  private mcpClient;
2091
2196
  private codeIndex?;
2092
2197
  private config;
@@ -2172,6 +2277,26 @@ declare class Cascade extends EventEmitter {
2172
2277
  * Idempotent init guard, so it works before the first run.
2173
2278
  */
2174
2279
  previewPlan(prompt: string): Promise<TaskPlan>;
2280
+ /**
2281
+ * Register image / speech / transcription tools for the modalities this
2282
+ * config can actually serve.
2283
+ *
2284
+ * The sink writes bytes into the workspace, because that is what a desktop or
2285
+ * CLI run wants and it keeps a generated asset indistinguishable from any
2286
+ * other file the run produced. A host with different storage (the cloud
2287
+ * server stores a file row) constructs the tools itself via buildMediaTools
2288
+ * with its own sink.
2289
+ */
2290
+ private registerMediaTools;
2291
+ /**
2292
+ * Supply per-model thumbs-up/down counts so Auto routing can weigh how models
2293
+ * have actually performed for this user, on top of public benchmarks.
2294
+ *
2295
+ * Optional everywhere: with no source, or an empty record, scoring is
2296
+ * byte-identical to before. The adjustment is capped and sample-size shrunk —
2297
+ * see core/router/feedback-prior.ts.
2298
+ */
2299
+ setFeedbackSource(source: FeedbackSource): void;
2175
2300
  /** True when a task stopped at the budget cap and can be resumed via /continue. */
2176
2301
  hasResumableRun(): boolean;
2177
2302
  /**
@@ -2404,6 +2529,7 @@ declare class T2Manager extends BaseTier {
2404
2529
  private escalations;
2405
2530
  private peerSyncBuffer;
2406
2531
  private store?;
2532
+ private runBreaker?;
2407
2533
  private t3PeerBus;
2408
2534
  private t2PeerBus?;
2409
2535
  private permissionEscalator?;
@@ -2415,6 +2541,13 @@ declare class T2Manager extends BaseTier {
2415
2541
  setPeerBus(bus: PeerBus): void;
2416
2542
  setPeerMessageCallback(cb: (event: PeerMessageEvent) => void, sessionId: string): void;
2417
2543
  constructor(router: CascadeRouter, toolRegistry: ToolRegistry, parentId: string);
2544
+ /**
2545
+ * Share the run-wide circuit breaker. Every T2 in a run gets the SAME
2546
+ * instance: three sections each failing once against a dead model is the
2547
+ * same fact as one section failing three times, and a per-section breaker
2548
+ * would never see it.
2549
+ */
2550
+ setRunBreaker(breaker: RunBreaker): void;
2418
2551
  setStore(store: MemoryStore): void;
2419
2552
  /**
2420
2553
  * Inject the shared PermissionEscalator for this task run.
package/dist/index.d.ts CHANGED
@@ -196,6 +196,12 @@ interface ToolExecuteOptions {
196
196
  tierId: string;
197
197
  sessionId: string;
198
198
  requireApproval: boolean;
199
+ /**
200
+ * Cancels a long-running tool call. Media generation can take a minute or
201
+ * more, so a cancelled run must be able to stop one mid-flight rather than
202
+ * paying it out and discarding the result.
203
+ */
204
+ signal?: AbortSignal;
199
205
  saveSnapshot?: (filePath: string, content: string) => Promise<void>;
200
206
  sendPeerSync?: (to: string, syncType: PeerSyncType, content: string | Record<string, unknown>) => void;
201
207
  getPeerMessages?: () => Array<{
@@ -647,6 +653,11 @@ interface BudgetConfig {
647
653
  maxTokensPerRun?: number;
648
654
  /** Optional hard per-task cost ceiling (USD). */
649
655
  maxCostPerRunUsd?: number;
656
+ /**
657
+ * Consecutive systemic failures against one model before the run stops
658
+ * launching work certain to fail the same way. Default 3.
659
+ */
660
+ failureThreshold?: number;
650
661
  warnAtPct: number;
651
662
  }
652
663
  interface WorkspaceConfig {
@@ -977,6 +988,20 @@ declare class ModelPerformanceTracker {
977
988
  costEfficiencyScore(model: ModelInfo, complexity: 1 | 2 | 3 | 4 | 5): number;
978
989
  }
979
990
 
991
+ interface FeedbackCounts {
992
+ good: number;
993
+ bad: number;
994
+ }
995
+ /**
996
+ * Lookup of per-model feedback, keyed by model id.
997
+ *
998
+ * Deliberately just counts: no conversation text, no prompts, no timestamps.
999
+ * A routing prior needs to know that a model earned N good and M bad verdicts
1000
+ * and nothing else, and storing more would put chat content in a second place
1001
+ * with different retention rules for no gain.
1002
+ */
1003
+ type FeedbackSource = (modelId: string) => FeedbackCounts | undefined;
1004
+
980
1005
  type TaskType = 'code' | 'analysis' | 'creative' | 'data' | 'mixed';
981
1006
  /** Cascade Auto cost/quality trade-off bias. See CascadeConfig.autoBias. */
982
1007
  type AutoBias = 'balanced' | 'quality' | 'cost';
@@ -992,10 +1017,18 @@ interface TaskProfile {
992
1017
  }
993
1018
  declare class TaskAnalyzer {
994
1019
  private tracker?;
1020
+ /** Per-model thumbs counts, when the host collects them. */
1021
+ private feedback?;
995
1022
  private bias;
996
1023
  private lastProfile;
997
1024
  private lastSelectedModels;
998
1025
  constructor(tracker?: ModelPerformanceTracker, bias?: AutoBias);
1026
+ /**
1027
+ * Supply per-model rating counts. Optional by design: a host that collects no
1028
+ * feedback (the CLI, a fresh install) scores exactly as it did before, since
1029
+ * feedbackAdjustment returns 0 for an empty record.
1030
+ */
1031
+ setFeedbackSource(source: FeedbackSource): void;
999
1032
  setTracker(tracker: ModelPerformanceTracker): void;
1000
1033
  /** Change the cost/quality bias at runtime (e.g. when config reloads). */
1001
1034
  setBias(bias: AutoBias): void;
@@ -1463,6 +1496,7 @@ declare class CascadeRouter extends EventEmitter {
1463
1496
  private tpmLimiter;
1464
1497
  private localQueue;
1465
1498
  private taskAnalyzer?;
1499
+ private pendingFeedbackSource?;
1466
1500
  private worldStateDB?;
1467
1501
  private privacyPaths?;
1468
1502
  private guidanceQueue?;
@@ -1578,6 +1612,12 @@ declare class CascadeRouter extends EventEmitter {
1578
1612
  */
1579
1613
  getReferenceContextWindow(): number | undefined;
1580
1614
  /** Wire the Cascade Auto task analyzer used for per-subtask model routing. */
1615
+ /**
1616
+ * Feed per-model thumbs counts into Auto routing. Forwarded to the analyzer
1617
+ * because that is where the public benchmark score is combined; the prior
1618
+ * adjusts that number rather than sitting anywhere else in the pipeline.
1619
+ */
1620
+ setFeedbackSource(source: FeedbackSource): void;
1581
1621
  setTaskAnalyzer(analyzer: TaskAnalyzer): void;
1582
1622
  setWorldStateDB(db: WorldStateDB | undefined): void;
1583
1623
  getWorldStateDB(): WorldStateDB | undefined;
@@ -1858,6 +1898,12 @@ declare abstract class BaseTier extends EventEmitter {
1858
1898
  getStatus(): TierStatus;
1859
1899
  protected setStatus(status: TierStatus, output?: string): void;
1860
1900
  /** Record the model serving this tier; future status events carry it. */
1901
+ /**
1902
+ * The model actually serving this tier ("provider:id"), once one has been
1903
+ * chosen. The run breaker reads it to attribute a failure to a model rather
1904
+ * than to the subtask that happened to hit it.
1905
+ */
1906
+ getServingModel(): string | undefined;
1861
1907
  protected setServingModel(model: string | undefined): void;
1862
1908
  protected setLabel(label: string): void;
1863
1909
  setSystemPromptOverride(prompt: string): void;
@@ -1873,6 +1919,61 @@ declare abstract class BaseTier extends EventEmitter {
1873
1919
  protected throwIfCancelled(): void;
1874
1920
  }
1875
1921
 
1922
+ /**
1923
+ * What kind of failure this is, and — crucially — whether retrying a DIFFERENT
1924
+ * subtask against the same model could plausibly succeed.
1925
+ */
1926
+ type ProviderErrorKind = 'rate_limit' | 'quota_exhausted' | 'auth' | 'model_unavailable' | 'context_length' | 'content_filter' | 'network' | 'unknown';
1927
+ interface ClassifiedError {
1928
+ kind: ProviderErrorKind;
1929
+ /** True when every other worker on this model is expected to fail the same way. */
1930
+ systemic: boolean;
1931
+ /** HTTP status, when the provider gave us one. */
1932
+ status?: number;
1933
+ /** The provider's own message, trimmed — never invented or paraphrased. */
1934
+ raw: string;
1935
+ }
1936
+
1937
+ interface BreakerTrip {
1938
+ modelId: string;
1939
+ kind: ClassifiedError['kind'];
1940
+ /** How many systemic failures on this model were seen before tripping. */
1941
+ failures: number;
1942
+ /** User-facing explanation, including the provider's own words. */
1943
+ message: string;
1944
+ }
1945
+ declare class RunBreaker {
1946
+ private readonly threshold;
1947
+ /** modelId → consecutive systemic failures of the SAME kind. */
1948
+ private counts;
1949
+ private trip;
1950
+ constructor(threshold?: number);
1951
+ /**
1952
+ * Record a failed model call. Returns the trip if this failure is the one
1953
+ * that opened the breaker, otherwise null.
1954
+ *
1955
+ * A non-systemic failure RESETS the model's counter: it is evidence the model
1956
+ * is reachable and answering, which is exactly what a systemic streak claims
1957
+ * it is not.
1958
+ */
1959
+ record(err: unknown, modelId: string | undefined): BreakerTrip | null;
1960
+ /** True once the run should stop launching work that would fail identically. */
1961
+ isOpen(): boolean;
1962
+ /** Why the breaker opened, or null while it is closed. */
1963
+ reason(): BreakerTrip | null;
1964
+ /**
1965
+ * One line for a worker/section that never ran because the breaker was
1966
+ * already open. Says the work was skipped deliberately — not that it failed
1967
+ * on its own merits, which is what an empty FAILED status would imply.
1968
+ */
1969
+ skipMessage(): string;
1970
+ /** Test/diagnostic view of the per-model streaks. */
1971
+ streaks(): Record<string, {
1972
+ kind: string;
1973
+ n: number;
1974
+ }>;
1975
+ }
1976
+
1876
1977
  type T2Evaluator = (req: PermissionRequest) => Promise<PermissionDecision | null>;
1877
1978
  type T1Evaluator = (req: PermissionRequest) => Promise<PermissionDecision | null>;
1878
1979
  /**
@@ -2020,6 +2121,7 @@ declare class T1Administrator extends BaseTier {
2020
2121
  private t2Managers;
2021
2122
  private escalations;
2022
2123
  private store?;
2124
+ private runBreaker?;
2023
2125
  private t2PeerBus;
2024
2126
  private permissionEscalator?;
2025
2127
  private toolCreator?;
@@ -2029,6 +2131,8 @@ declare class T1Administrator extends BaseTier {
2029
2131
  private peerMessageSessionId;
2030
2132
  private planApprovalCallback?;
2031
2133
  constructor(router: CascadeRouter, toolRegistry: ToolRegistry, config: CascadeConfig);
2134
+ /** Share the run-wide circuit breaker with every section manager. */
2135
+ setRunBreaker(breaker: RunBreaker): void;
2032
2136
  setStore(store: MemoryStore): void;
2033
2137
  /**
2034
2138
  * Inject the shared PermissionEscalator for this task run.
@@ -2087,6 +2191,7 @@ interface DecisionLogEntry {
2087
2191
  declare class Cascade extends EventEmitter {
2088
2192
  private router;
2089
2193
  private toolRegistry;
2194
+ private multimodal;
2090
2195
  private mcpClient;
2091
2196
  private codeIndex?;
2092
2197
  private config;
@@ -2172,6 +2277,26 @@ declare class Cascade extends EventEmitter {
2172
2277
  * Idempotent init guard, so it works before the first run.
2173
2278
  */
2174
2279
  previewPlan(prompt: string): Promise<TaskPlan>;
2280
+ /**
2281
+ * Register image / speech / transcription tools for the modalities this
2282
+ * config can actually serve.
2283
+ *
2284
+ * The sink writes bytes into the workspace, because that is what a desktop or
2285
+ * CLI run wants and it keeps a generated asset indistinguishable from any
2286
+ * other file the run produced. A host with different storage (the cloud
2287
+ * server stores a file row) constructs the tools itself via buildMediaTools
2288
+ * with its own sink.
2289
+ */
2290
+ private registerMediaTools;
2291
+ /**
2292
+ * Supply per-model thumbs-up/down counts so Auto routing can weigh how models
2293
+ * have actually performed for this user, on top of public benchmarks.
2294
+ *
2295
+ * Optional everywhere: with no source, or an empty record, scoring is
2296
+ * byte-identical to before. The adjustment is capped and sample-size shrunk —
2297
+ * see core/router/feedback-prior.ts.
2298
+ */
2299
+ setFeedbackSource(source: FeedbackSource): void;
2175
2300
  /** True when a task stopped at the budget cap and can be resumed via /continue. */
2176
2301
  hasResumableRun(): boolean;
2177
2302
  /**
@@ -2404,6 +2529,7 @@ declare class T2Manager extends BaseTier {
2404
2529
  private escalations;
2405
2530
  private peerSyncBuffer;
2406
2531
  private store?;
2532
+ private runBreaker?;
2407
2533
  private t3PeerBus;
2408
2534
  private t2PeerBus?;
2409
2535
  private permissionEscalator?;
@@ -2415,6 +2541,13 @@ declare class T2Manager extends BaseTier {
2415
2541
  setPeerBus(bus: PeerBus): void;
2416
2542
  setPeerMessageCallback(cb: (event: PeerMessageEvent) => void, sessionId: string): void;
2417
2543
  constructor(router: CascadeRouter, toolRegistry: ToolRegistry, parentId: string);
2544
+ /**
2545
+ * Share the run-wide circuit breaker. Every T2 in a run gets the SAME
2546
+ * instance: three sections each failing once against a dead model is the
2547
+ * same fact as one section failing three times, and a per-section breaker
2548
+ * would never see it.
2549
+ */
2550
+ setRunBreaker(breaker: RunBreaker): void;
2418
2551
  setStore(store: MemoryStore): void;
2419
2552
  /**
2420
2553
  * Inject the shared PermissionEscalator for this task run.