{"version":3,"file":"provider-health.d.ts","sourceRoot":"","sources":["../../../../src/core/long-horizon/adaptive/provider-health.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,YAAY,GACrB,SAAS,GACT,YAAY,GACZ,SAAS,GACT,wBAAwB,GACxB,wBAAwB,GACxB,sBAAsB,GACtB,gBAAgB,GAChB,kBAAkB,GAClB,iBAAiB,GACjB,cAAc,CAAC;AAElB,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,+CAA+C;IAC/C,KAAK,EAAE,WAAW,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAE1G;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACd,mBAAmB,CAkBrB;AAcD,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAUlG;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAEpF","sourcesContent":["/**\n * Provider health and degradation.\n *\n * Bounded rolling evidence per provider/model/endpoint. Uses decay rather than\n * permanent blacklisting. Authentication failures never trigger futile retries;\n * rate limits respect retry hints; fallback is deterministic and preserves task\n * requirements. Health state is durable for resume but bounded in retention.\n */\n\nexport type HealthSignal =\n\t| \"success\"\n\t| \"rate_limit\"\n\t| \"timeout\"\n\t| \"authentication_failure\"\n\t| \"schema_incompatibility\"\n\t| \"tool_call_corruption\"\n\t| \"empty_response\"\n\t| \"context_overflow\"\n\t| \"provider_outage\"\n\t| \"cancellation\";\n\nexport type HealthLevel = \"healthy\" | \"degraded\" | \"unhealthy\" | \"unknown\";\n\nexport interface HealthSample {\n\tsignal: HealthSignal;\n\trecordedAt: string;\n\tsequence: number;\n}\n\nexport interface ProviderHealthState {\n\tprovider: string;\n\tmodel?: string;\n\tendpoint?: string;\n\t/** Bounded window of recent samples (oldest first). */\n\tsamples: readonly HealthSample[];\n\t/** Deterministic snapshot used for routing. */\n\tlevel: HealthLevel;\n\tlastSuccessfulAt?: string;\n}\n\nconst RETRYABLE: ReadonlySet<HealthSignal> = new Set([\"rate_limit\", \"timeout\", \"provider_outage\"]);\n\nconst AUTH_FAILURE: HealthSignal = \"authentication_failure\";\n\nexport const MAX_HEALTH_SAMPLES = 32;\n\nexport function createHealthState(provider: string, model?: string, endpoint?: string): ProviderHealthState {\n\treturn { provider, model, endpoint, samples: Object.freeze([]), level: \"unknown\" };\n}\n\n/**\n * Record a health signal, keeping a bounded window of samples, and derive the\n * level from the window. Authentication failures mark the provider degraded\n * (never retried blindly). Successful newest sample restores health.\n */\nexport function recordHealthSignal(\n\tstate: ProviderHealthState,\n\tsignal: HealthSignal,\n\trecordedAt: string,\n\tsequence: number,\n): ProviderHealthState {\n\tconst sample: HealthSample = Object.freeze({ signal, recordedAt, sequence });\n\tlet samples: readonly HealthSample[] = [...state.samples, sample];\n\tif (samples.length > MAX_HEALTH_SAMPLES) {\n\t\tsamples = samples.slice(samples.length - MAX_HEALTH_SAMPLES);\n\t}\n\n\tconst level = computeLevel(samples);\n\tconst lastSuccessfulAt = signal === \"success\" ? recordedAt : state.lastSuccessfulAt;\n\n\treturn {\n\t\tprovider: state.provider,\n\t\tmodel: state.model,\n\t\tendpoint: state.endpoint,\n\t\tsamples: Object.freeze(samples),\n\t\tlevel,\n\t\tlastSuccessfulAt,\n\t};\n}\n\nfunction computeLevel(samples: readonly HealthSample[]): HealthLevel {\n\tif (samples.length === 0) return \"unknown\";\n\tconst last = samples[samples.length - 1];\n\tif (last.signal === \"success\") return \"healthy\";\n\tif (last.signal === AUTH_FAILURE) return \"degraded\";\n\tif (last.signal === \"rate_limit\" || last.signal === \"timeout\" || last.signal === \"cancellation\") {\n\t\treturn \"degraded\";\n\t}\n\tif (last.signal === \"provider_outage\") return \"unhealthy\";\n\treturn \"unhealthy\";\n}\n\n/** A retry is allowed only for retryable signals, and only with a cooldown. */\nexport function shouldRetry(state: ProviderHealthState, cooldownMs: number, nowMs: number): boolean {\n\tconst last = state.samples[state.samples.length - 1];\n\tif (!last) return true;\n\tif (last.signal === AUTH_FAILURE) return false;\n\tif (!RETRYABLE.has(last.signal)) return false;\n\tif (state.lastSuccessfulAt) {\n\t\tconst lastSuccess = Date.parse(state.lastSuccessfulAt);\n\t\tif (Number.isFinite(lastSuccess) && nowMs - lastSuccess < cooldownMs) return false;\n\t}\n\treturn true;\n}\n\n/** Count of a given signal within the window (used for bounded evidence). */\nexport function countSignal(state: ProviderHealthState, signal: HealthSignal): number {\n\treturn state.samples.reduce((n, s) => (s.signal === signal ? n + 1 : n), 0);\n}\n"]}