@yanlinglabs/winter-provider-runtime 0.0.2

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +41 -0
  3. package/README.md +109 -0
  4. package/dist/adapters/anthropic/console-oauth.d.ts +101 -0
  5. package/dist/adapters/anthropic/index.d.ts +4 -0
  6. package/dist/adapters/anthropic/messages.d.ts +76 -0
  7. package/dist/adapters/bedrock/converse.d.ts +143 -0
  8. package/dist/adapters/bedrock/crc32.d.ts +9 -0
  9. package/dist/adapters/bedrock/credentials.d.ts +32 -0
  10. package/dist/adapters/bedrock/eventstream.d.ts +65 -0
  11. package/dist/adapters/bedrock/index.d.ts +8 -0
  12. package/dist/adapters/bedrock/sigv4.d.ts +119 -0
  13. package/dist/adapters/bedrock/testing.d.ts +46 -0
  14. package/dist/adapters/content-blocks.d.ts +7 -0
  15. package/dist/adapters/google/adc.d.ts +35 -0
  16. package/dist/adapters/google/generate-content.d.ts +136 -0
  17. package/dist/adapters/google/index.d.ts +8 -0
  18. package/dist/adapters/google/jwt-rs256.d.ts +36 -0
  19. package/dist/adapters/google/vertex.d.ts +15 -0
  20. package/dist/adapters/index.d.ts +34 -0
  21. package/dist/adapters/oauth/device-code.d.ts +32 -0
  22. package/dist/adapters/oauth/refresh.d.ts +40 -0
  23. package/dist/adapters/openai/azure.d.ts +38 -0
  24. package/dist/adapters/openai/chat-completions.d.ts +86 -0
  25. package/dist/adapters/openai/codex-config.d.ts +42 -0
  26. package/dist/adapters/openai/codex-oauth.d.ts +47 -0
  27. package/dist/adapters/openai/index.d.ts +20 -0
  28. package/dist/adapters/openai/local.d.ts +16 -0
  29. package/dist/adapters/openai/pkce.d.ts +111 -0
  30. package/dist/adapters/openai/quota.d.ts +99 -0
  31. package/dist/adapters/openai/responses.d.ts +142 -0
  32. package/dist/adapters/openai/shared.d.ts +359 -0
  33. package/dist/adapters/openai/testing.d.ts +59 -0
  34. package/dist/adapters/openai/xai-derived-shapes.d.ts +67 -0
  35. package/dist/adapters/openai/xai-oauth.d.ts +102 -0
  36. package/dist/adapters/openai/xai-oauth.testing.d.ts +62 -0
  37. package/dist/adapters/privileged-headers.d.ts +51 -0
  38. package/dist/adapters/refusals.d.ts +10 -0
  39. package/dist/address-classifier.d.ts +17 -0
  40. package/dist/bun-required.d.ts +54 -0
  41. package/dist/continuity/decoration.d.ts +89 -0
  42. package/dist/continuity/domains.d.ts +92 -0
  43. package/dist/continuity/fixtures.d.ts +44 -0
  44. package/dist/continuity/handoff.d.ts +94 -0
  45. package/dist/continuity/index.d.ts +10 -0
  46. package/dist/continuity/renderer.d.ts +111 -0
  47. package/dist/continuity/warnings.d.ts +46 -0
  48. package/dist/credentials/env.d.ts +6 -0
  49. package/dist/credentials/file.d.ts +21 -0
  50. package/dist/credentials/memory.d.ts +8 -0
  51. package/dist/credentials/types.d.ts +38 -0
  52. package/dist/discovery.d.ts +15 -0
  53. package/dist/endpoint-policy.d.ts +127 -0
  54. package/dist/errors.d.ts +68 -0
  55. package/dist/http.d.ts +29 -0
  56. package/dist/identity.d.ts +62 -0
  57. package/dist/index-5z94gxhk.js +43790 -0
  58. package/dist/index.d.ts +39 -0
  59. package/dist/index.js +3194 -0
  60. package/dist/registry.d.ts +136 -0
  61. package/dist/retry.d.ts +38 -0
  62. package/dist/sse.d.ts +12 -0
  63. package/dist/testing.d.ts +19 -0
  64. package/dist/testing.js +432 -0
  65. package/dist/types.d.ts +376 -0
  66. package/package.json +49 -0
@@ -0,0 +1,51 @@
1
+ import { type EndpointPolicy } from "../endpoint-policy.js";
2
+ /**
3
+ * Header names that carry an ORGANISATION / PROJECT / ACCOUNT identity.
4
+ *
5
+ * Lowercase, because a raw record's keys are compared case-insensitively here (a `Headers` object
6
+ * would do it for us; a plain object will not).
7
+ */
8
+ export declare const PRIVILEGED_IDENTITY_HEADERS: readonly string[];
9
+ /**
10
+ * WINTER'S OWN IDENTITY NAMES — dropped from a host's map on a GENERATED endpoint (whole-branch
11
+ * review M-1).
12
+ *
13
+ * The INVERSE of the privileged rule above, and deliberately so. `identity.ts` says the User-Agent
14
+ * is "deliberately NOT configurable" and names exactly one sanctioned override: "the operator
15
+ * speaking about their own proxy" — that is a USER endpoint. What the code did was let the same
16
+ * `ConnectionProfile.headers['user-agent']` replace Winter's identity on the VENDOR'S OWN reviewed
17
+ * endpoint too, which is the one place the admission rule cares about: a host could present Winter
18
+ * to a vendor as an editor or a first-party CLI, through a field nothing else guards.
19
+ *
20
+ * `client-agent` is here for the same reason and by the same reading (fix-wave R-FW-2): it is the
21
+ * second half of the same identity, so one rule governs both rather than two that can drift.
22
+ *
23
+ * NOT ON THE PRIVILEGED LIST, because the two rules point opposite ways: a privileged name is
24
+ * dropped on a USER endpoint and kept on a generated one; an identity name is kept on a user
25
+ * endpoint (the operator's proxy is theirs to describe) and dropped on a generated one.
26
+ */
27
+ export declare const WINTER_IDENTITY_HEADERS: readonly string[];
28
+ /**
29
+ * The host's own connection headers, filtered for the endpoint they are about to be sent to.
30
+ *
31
+ * TWO RULES, with deliberately different reach:
32
+ *
33
+ * CREDENTIALS — every name on `CREDENTIAL_HEADER_NAMES` is dropped ALWAYS, generated endpoint or
34
+ * not. A profile is non-secret connection metadata by contract; a credential in it is a
35
+ * misconfiguration, and a reviewed endpoint does not make it less of one.
36
+ *
37
+ * PRIVILEGED — the privileged names are dropped on a USER endpoint only; on a generated one a
38
+ * reviewed descriptor endpoint vouches for the identity headers that belong to it. (Three of the
39
+ * four privileged names are on the credential list too, so `x-goog-quota-project` is the only
40
+ * header where the two rules actually differ.)
41
+ *
42
+ * WINTER'S IDENTITY — `user-agent`/`client-agent` are dropped on a GENERATED endpoint, the exact
43
+ * inverse (M-1). The sanctioned override is the operator describing THEIR OWN proxy; letting the
44
+ * same field replace Winter's name at the vendor's own endpoint is how a host would present
45
+ * Winter as an editor or a first-party CLI, which is the thing the admission rule forbids.
46
+ *
47
+ * Everything else is kept on both — a host's proxy token, a tracing header — as its own business.
48
+ *
49
+ * Returns a COPY either way, so a `ConnectionProfile` reused across requests is never mutated.
50
+ */
51
+ export declare function hostHeaders(policy: EndpointPolicy, headers: Record<string, string> | undefined, extraPrivileged?: readonly string[]): Record<string, string>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `thinking: { type: "enabled" }` with no `budgetTokens`, on a family whose reasoning is
3
+ * BUDGET-controlled (Anthropic Messages, Bedrock Converse, Google GenerateContent).
4
+ *
5
+ * The OpenAI family does NOT use this sentence: it has no budget field at all, so the same config
6
+ * resolves against the row's `defaultEffort` and is refused only when there is none — a different
7
+ * situation with a different way out (`THINKING_ENABLED_NEEDS_EFFORT`, in the family's own
8
+ * `shared.ts`, which is where the effort vocabulary lives).
9
+ */
10
+ export declare const THINKING_ENABLED_NEEDS_BUDGET = "thinking `{ type: \"enabled\" }` carries no budgetTokens, which this endpoint requires. Pass `budgetTokens`, or ask for `{ type: \"adaptive\" }` if the model should decide.";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * What an IP address IS, rather than merely whether some particular caller should refuse it. The
3
+ * two consumers want opposite answers from the same facts — Monitor refuses every non-public
4
+ * address, while the provider endpoint policy ACCEPTS a loopback/private one when a host declares
5
+ * the endpoint local — so the shared primitive has to be the classification, not the verdict.
6
+ */
7
+ export type AddressClass = "public" | "loopback" | "private" | "link-local" | "unique-local" | "cgnat" | "multicast" | "unspecified" | "invalid";
8
+ /** True for the classes an endpoint may serve over plain `http` — loopback, RFC 1918, link-local, unique-local (R6-11). */
9
+ export declare function isLocalAddressClass(cls: AddressClass): boolean;
10
+ /** Classifies a literal IP address. `family` is 4 or 6, exactly as `dns.lookup`'s own results report it. */
11
+ export declare function classifyAddress(address: string, family: number): AddressClass;
12
+ /**
13
+ * Monitor's verdict, unchanged from P3: everything that is not a routable public address is refused,
14
+ * unparseable IPv4 included (fail closed). Re-exported by `monitor.ts` so that file's own callers
15
+ * and its existing fixture set are untouched by this module's existence.
16
+ */
17
+ export declare function isDisallowedAddress(address: string, family: number): boolean;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A function that needs the Bun runtime was called somewhere else.
3
+ *
4
+ * `name` is the exported function the caller actually invoked (never the internal helper that
5
+ * reaches for Bun), because that is the name in their code.
6
+ */
7
+ /**
8
+ * P7a fix wave round 3 (F2): CROSS-BUNDLE `instanceof`.
9
+ *
10
+ * THE PROBLEM, measured on the compiled emit under Node. `build-packages.ts` runs `bun build` ONCE
11
+ * PER EXPORT ENTRY, so every entry bundle carries its own copy of every internal module: the class
12
+ * declared in one source file exists as TWO DISTINCT CLASSES at runtime, one in `dist/index.js` and
13
+ * one in `dist/<subpath>/index.js`. A consumer who imports the function from one subpath and the
14
+ * class from the other -- the pattern both new READMEs teach -- gets a silent `false` from
15
+ * `instanceof` and rethrows the very error the guard exists to make catchable. Under Bun the `bun`
16
+ * condition resolves both entries to the same `src/*.ts`, so the classes ARE identical, which is why
17
+ * no Bun-side test could see it.
18
+ *
19
+ * THE FIX, applied to the CLASS of the problem rather than to one error: every error class exported
20
+ * from more than one subpath of a package carries a PACKAGE-SCOPED `Symbol.for(...)` brand and a
21
+ * `static [Symbol.hasInstance]` that tests for it. `Symbol.for` is cross-realm and cross-copy, so
22
+ * every duplicated bundle of ONE package agrees -- while a DIFFERENT package's class, whose key
23
+ * names a different package, still does not match. The two packages stay deliberately distinct
24
+ * (they share no dependency and cannot share a module), and the existing distinctness test passes
25
+ * unchanged.
26
+ *
27
+ * Considered and recorded as a carry rather than done here: `bun build --splitting`, so shared
28
+ * internals emit once per package. It is the more fundamental answer and it changes the emit shape
29
+ * for every package and every `.d.ts` -- not a round-3-sized change.
30
+ */
31
+ export declare function brandedInstanceOf(brand: symbol): (candidate: unknown) => boolean;
32
+ /** The cross-bundle identity of THIS package's `BunRequiredError`. Package-scoped on purpose. */
33
+ declare const BUN_REQUIRED_BRAND: unique symbol;
34
+ export declare class BunRequiredError extends Error {
35
+ readonly name = "BunRequiredError";
36
+ /** F2: the brand `Symbol.hasInstance` below tests for. Present on every instance, in every bundle. */
37
+ readonly [BUN_REQUIRED_BRAND] = true;
38
+ /** F2: `instanceof` holds across this package's duplicated entry bundles, and only this package's. */
39
+ static [Symbol.hasInstance]: (candidate: unknown) => boolean;
40
+ /** The exported function the caller invoked. */
41
+ readonly functionName: string;
42
+ /** The Bun API that has no Node equivalent this package implements, e.g. `Bun.serve`. */
43
+ readonly bunApi: string;
44
+ constructor(functionName: string, bunApi: string, detail: string);
45
+ }
46
+ /** True when this process is Bun. Separated so a test can assert the guard without a subprocess. */
47
+ export declare function hasBunRuntime(): boolean;
48
+ /**
49
+ * Throws `BunRequiredError` unless this process is Bun. Called FIRST in each guarded function, before
50
+ * any network call, any file write and any credential read — a guard that fired after a side effect
51
+ * would be a worse failure than the `ReferenceError` it replaces.
52
+ */
53
+ export declare function requireBunRuntime(functionName: string, bunApi: string, detail: string): void;
54
+ export {};
@@ -0,0 +1,89 @@
1
+ /** Which door a decoration goes through. Mirrors `ProviderMessageLike.decoration.door`. */
2
+ export type DecorationDoor = "tag" | "thinking-channel";
3
+ /** The tag name of the text door, in ONE place: the renderer, the corpus and the escaping all name it from here. */
4
+ export declare const RECOVERED_REASONING_TAG = "recovered_reasoning_summary";
5
+ /** Who produced the reasoning being carried. Ids only -- never a credential, never opaque state. */
6
+ export interface DecorationSource {
7
+ providerId: string;
8
+ modelKey: string;
9
+ }
10
+ export interface DecorationInput {
11
+ /** The readable material: a provider-produced SUMMARY or complete exposed reasoning. Never opaque state -- there is deliberately no parameter for one. */
12
+ text: string;
13
+ source: DecorationSource;
14
+ door: DecorationDoor;
15
+ /**
16
+ * §9.6's context budget for this one decoration, counted on the FINISHED text -- wrapper included.
17
+ *
18
+ * Counting only the body would let a caller's budget be quietly overspent by the delimiter and the
19
+ * origin labels, which is most of a short decoration; a budget that does not bound what actually
20
+ * reaches the target is not a budget. Absent means unbounded.
21
+ */
22
+ maxChars?: number;
23
+ }
24
+ export interface Decoration {
25
+ text: string;
26
+ door: DecorationDoor;
27
+ /** §9.6: ANY truncation flips the transfer to warned-lossy. The flag is what makes that flip observable rather than a silent claim of losslessness. */
28
+ truncated: boolean;
29
+ }
30
+ /**
31
+ * Which door a target's own reasoning transport admits.
32
+ *
33
+ * `full-exposed` targets read plain reasoning text, so their own channel is available. `summary` and
34
+ * `none` targets either validate their reasoning channel (Anthropic signatures) or have none at all;
35
+ * for both, the text tag is the only door.
36
+ */
37
+ export declare function doorFor(target: {
38
+ readableState: "none" | "summary" | "full-exposed";
39
+ }): DecorationDoor;
40
+ /**
41
+ * Builds ONE decoration.
42
+ *
43
+ * The escaping below is not cosmetic. `providerId`/`modelKey` are catalog strings and a summary is
44
+ * MODEL-GENERATED text: both are untrusted (WS-13 §7 says as much about model ids), and either could
45
+ * contain a forged closing delimiter. A body that could terminate its own wrapper would let quoted
46
+ * prior-model text escape the delimiter and read as first-class instruction to the target -- the one
47
+ * failure the injection floor exists to prevent -- so a forged delimiter is neutralised into inert
48
+ * text and the wrapper's own closing tag remains the only one.
49
+ */
50
+ export declare function buildDecoration(input: DecorationInput): Decoration;
51
+ /**
52
+ * How many characters a decoration spends on its wrapper alone, for THIS source and door.
53
+ *
54
+ * Exported because the renderer has to decide whether a remaining budget can hold a decoration at all
55
+ * BEFORE building one: a wrapper with an empty body is not a decoration, it is noise with a delimiter
56
+ * around it, and the honest response to "no room" is to drop the material and say so.
57
+ */
58
+ export declare function decorationOverhead(source: DecorationSource, door: DecorationDoor): number;
59
+ /** The minimum body a decoration must be able to carry to be worth sending at all. */
60
+ export declare const MIN_DECORATION_BODY_CHARS = 32;
61
+ /**
62
+ * §9.6's trimming, mechanically.
63
+ *
64
+ * KEEPS THE HEAD AND THE TAIL with an explicit elision between them. §9.6 orders what to retain by
65
+ * MEANING (concrete decisions, evidence, exact identifiers and pending work before redundant
66
+ * speculation), and Winter cannot rank text semantically without another model call -- so this is an
67
+ * approximation, chosen because objective and evidence cluster at the start of a reasoning trace and
68
+ * decisions and pending work at the end, while restatement accumulates in the middle. The
69
+ * approximation is only defensible because it is NEVER silent: `truncated` flips the whole transfer
70
+ * to warned-lossy, which is §9.6's closing rule ("never silently truncate reasoning while still
71
+ * classifying the handoff as lossless").
72
+ */
73
+ export declare function trimToBudget(text: string, maxChars: number | undefined): {
74
+ text: string;
75
+ truncated: boolean;
76
+ };
77
+ /** Escapes an XML-ish attribute value. `&` first, or the escapes escape each other. */
78
+ export declare function escapeAttribute(value: string): string;
79
+ /** Strips angle brackets from an id rendered inline in prose, so a crafted model id cannot open a tag of its own. */
80
+ export declare function escapeInline(value: string): string;
81
+ /**
82
+ * Neutralises any sequence that would look like this module's own delimiter.
83
+ *
84
+ * Only the delimiter is touched -- the body is otherwise carried VERBATIM, because the whole value of
85
+ * forwarding complete exposed reasoning (the DeepSeek→OpenAI no-warning case, §8.4 condition 2) is
86
+ * that it is passed unmodified. Escaping every `<` would modify it, and would then have to be
87
+ * declared lossy.
88
+ */
89
+ export declare function neutralizeDelimiters(text: string): string;
@@ -0,0 +1,92 @@
1
+ import type { EvidenceConfidence, ReasoningCapabilities, WinterModelDescriptor } from "@yanlinglabs/winter-provider-catalog";
2
+ import { type ProviderRegistry } from "../registry.js";
3
+ import type { MessageOrigin } from "../types.js";
4
+ /** What a readable-reasoning capability can be. Mirrors `ProviderAdapter.capabilities().readableState` and the descriptor's own evidence. */
5
+ export type ReadableState = "none" | "summary" | "full-exposed";
6
+ /**
7
+ * One side of a transfer: who produced (or will consume) a message, and what its reasoning transport
8
+ * can do. Built from a stamped `MessageOrigin` plus whatever the catalog knows about that model.
9
+ */
10
+ export interface ContinuityEndpoint {
11
+ providerId: string;
12
+ modelKey: string;
13
+ family: string;
14
+ /** The STAMPED continuation-domain id. Absent means "this model's native state is not documented to be replayable anywhere" -- never "any domain". */
15
+ continuationDomain?: string;
16
+ /**
17
+ * The model's reasoning TRANSPORT, straight from its descriptor. `"none"` is the fact that matters
18
+ * here: a model with no reasoning at all has no hidden state to lose, so a switch away from it must
19
+ * not warn about reasoning (review I1 -- `readableState: "none"` alone conflates "no readable
20
+ * summary" with "no reasoning", and a chat model warned about losing state it never had).
21
+ *
22
+ * ABSENT MEANS UNKNOWN, and unknown warns: the fallback endpoint (no catalog row) cannot prove the
23
+ * source was reasoning-free, and silence about a reasoning model is the expensive mistake.
24
+ */
25
+ continuation?: ReasoningCapabilities["continuation"];
26
+ readableState: ReadableState;
27
+ /** Whether this model documents a way to ASK for a readable summary (§9.1's proactive-summary policy). */
28
+ summaryRequest?: {
29
+ field: string;
30
+ values: string[];
31
+ };
32
+ }
33
+ /** The subset of an endpoint the domain test reads. Deliberately tiny: a `nativeState` and a render target are both valid arguments. */
34
+ export interface DomainFacts {
35
+ continuationDomain?: string;
36
+ }
37
+ /**
38
+ * Do these two sides share a continuation domain?
39
+ *
40
+ * TRUE requires BOTH sides to carry a stamped id AND the ids to be equal. Every other shape is false,
41
+ * including the one that looks safest: two sides that both have NO domain id are NOT the same domain,
42
+ * because absence means "undocumented", and two undocumented transports are not thereby the same
43
+ * transport. Reading absence as a wildcard is how an OpenAI reasoning item would be replayed into
44
+ * Anthropic on a session where neither model happened to carry evidence.
45
+ *
46
+ * There is deliberately no `providerId`/`family` argument. Adding one would make it possible to write
47
+ * the forbidden test.
48
+ */
49
+ export declare function sameDomain(a: DomainFacts | undefined, b: DomainFacts | undefined): boolean;
50
+ /** The evidence confidences that CERTIFY a shared continuation domain (§8.4's own word). Anything weaker is a guess, and a guess must not buy a suppressed warning. */
51
+ export declare const CERTIFIED_DOMAIN_CONFIDENCES: ReadonlySet<EvidenceConfidence>;
52
+ /** Whether two sides belong to the same wire family. NEVER a substitute for `sameDomain` -- two Claude models are one family and (absent evidence) two domains. */
53
+ export declare function sameFamily(a: {
54
+ family: string;
55
+ } | undefined, b: {
56
+ family: string;
57
+ } | undefined): boolean;
58
+ /** The descriptor's readable-reasoning evidence, or `"none"` when it declares none. Reading a bare `supported: true` as "has a summary" would be exactly the timeless boolean WS-13 §4 forbids. */
59
+ export declare function readableStateOf(descriptor: WinterModelDescriptor | undefined): ReadableState;
60
+ /** The provider's own documented summary-request field and its accepted values (Anthropic `display`, OpenAI `reasoning.summary`, Gemini `includeThoughts`). */
61
+ export declare function summaryRequestOf(descriptor: WinterModelDescriptor | undefined): {
62
+ field: string;
63
+ values: string[];
64
+ } | undefined;
65
+ /**
66
+ * §9.1's proactive-summary policy: ask for a summary from session start whenever the model documents
67
+ * HOW to ask.
68
+ *
69
+ * THE TEST IS `summaryRequest`, NOT `readableState`, and the difference is a live provider rather
70
+ * than a nicety: DeepSeek's readable state is `full-exposed` (it returns `reasoning_content`) while
71
+ * its Responses compatibility does not generate REQUESTED summaries at all -- so a policy keyed on
72
+ * readable state asks DeepSeek for something it will never produce, on every request of the session.
73
+ * A model with no `summaryRequest` evidence is one Winter does not know how to ask, and asking anyway
74
+ * is the silent assumption §9.1 closes with ("do not silently assume summaries are free or
75
+ * universally available").
76
+ */
77
+ export declare function shouldRequestSummary(descriptor: WinterModelDescriptor | undefined): boolean;
78
+ /**
79
+ * Turns a stamped `MessageOrigin` into the full endpoint facts, through the REGISTRY.
80
+ *
81
+ * The registry is the only capability authority (R6-12: Lane C reads capability facts through T2's
82
+ * registry). `resolve()` returns an ERROR OBJECT rather than throwing, and the fallback for one is
83
+ * the stamped origin itself: a message produced by a model that has since left the catalog still
84
+ * knows its own provider, model and domain, and degrading it to "unknown" would drop a valid native
85
+ * replay because a catalog row was renamed.
86
+ *
87
+ * Cached per `modelKey` for the lifetime of the returned function: one render pass asks about the
88
+ * same handful of models once per message, and `resolve` walks the catalog indexes each time.
89
+ */
90
+ export declare function createEndpointResolver(registry: ProviderRegistry): (origin: MessageOrigin) => ContinuityEndpoint;
91
+ /** The registry-free fallback: everything the stamp itself carries, and `readableState: "none"` because absence of evidence is not evidence of a summary. */
92
+ export declare function endpointFromOrigin(origin: MessageOrigin): ContinuityEndpoint;
@@ -0,0 +1,44 @@
1
+ import type { CapabilityEvidence, EvidenceConfidence, ReasoningCapabilities, ToolCalling, WinterCatalog, WinterModelDescriptor, WinterProviderDescriptor } from "@yanlinglabs/winter-provider-catalog";
2
+ import type { ProviderAdapter, ProviderEvent, ProviderFamily } from "../types.js";
3
+ export declare const evidence: <T>(value: T, confidence?: EvidenceConfidence) => CapabilityEvidence<T>;
4
+ export interface FixtureModelInit {
5
+ key: string;
6
+ providerId: string;
7
+ upstreamId?: string;
8
+ aliases?: string[];
9
+ reasoning?: ReasoningCapabilities;
10
+ toolCalling?: ToolCalling;
11
+ contextWindow?: number;
12
+ }
13
+ export declare function fixtureModel(init: FixtureModelInit): WinterModelDescriptor;
14
+ /**
15
+ * The reasoning evidence a reasoning-capable row carries. `domain` is the MEMBER LIST the registry
16
+ * derives a domain id from -- passing two models the same list is how a fixture certifies a shared
17
+ * domain, and passing different lists is how it certifies two domains that share an HTTP shape.
18
+ */
19
+ export declare function fixtureReasoning(init: {
20
+ readableState: "none" | "summary" | "full-exposed";
21
+ continuation?: ReasoningCapabilities["continuation"];
22
+ domain?: string[];
23
+ summaryRequest?: {
24
+ field: string;
25
+ values: string[];
26
+ };
27
+ efforts?: string[];
28
+ confidence?: EvidenceConfidence;
29
+ }): ReasoningCapabilities;
30
+ export declare function fixtureProvider(init: {
31
+ id: string;
32
+ adapterId?: string;
33
+ family?: string;
34
+ baseUrl?: string;
35
+ }): WinterProviderDescriptor;
36
+ export declare function fixtureCatalog(providers: WinterProviderDescriptor[], models: WinterModelDescriptor[]): WinterCatalog;
37
+ /** A scripted `ProviderAdapter`: no HTTP, no credentials. `events` is what `streamTurn` yields, and every request it is handed is recorded for assertions. */
38
+ export declare function scriptedAdapter(init: {
39
+ id: string;
40
+ family?: ProviderFamily;
41
+ events?: ProviderEvent[];
42
+ }): ProviderAdapter & {
43
+ requests: unknown[];
44
+ };
@@ -0,0 +1,94 @@
1
+ import type { ContinuityEndpoint } from "./domains.js";
2
+ import type { ContinuationChainLike, MaterialKind } from "./renderer.js";
3
+ import type { ProviderMessageLike } from "../types.js";
4
+ /** The delimiter of the handoff block. Its own tag, distinct from a reasoning decoration's: the two carry different classes of content and a reader must not have to guess which. */
5
+ export declare const PRIOR_MODEL_HANDOFF_TAG = "prior_model_handoff";
6
+ /**
7
+ * Instruction-file basenames a tool fact is never allowed to carry (§2.8). Matched case-insensitively
8
+ * on the basename.
9
+ *
10
+ * P7a (D19): the first entry is `brand.instructionsFile`. This is a SAFETY list, so a branded session
11
+ * gets its own name ADDED to this set rather than swapped into it (`PortableHandoffOptions.
12
+ * instructionsFile`) -- a repository can perfectly well contain Winter's own instructions file and a
13
+ * reuser's beside it, and neither belongs in a handoff.
14
+ */
15
+ export declare const INSTRUCTION_FILE_BASENAMES: readonly string[];
16
+ export interface HandoffToolFact {
17
+ name: string;
18
+ ok: boolean;
19
+ /** A BOUNDED excerpt of the factual result. Absent when the call produced nothing quotable. */
20
+ detail?: string;
21
+ }
22
+ export interface PortableHandoffSections {
23
+ source: {
24
+ providerId: string;
25
+ modelKey: string;
26
+ };
27
+ /** The current user objective: the most recent user message's own visible text. Winter does not infer an objective -- it quotes the one the user stated. */
28
+ objective?: string;
29
+ /**
30
+ * The source's readable reasoning, labelled by KIND so a summary is never mistaken for a complete
31
+ * trace (§9.5) -- and by `truncated`, so a 400-character remnant is never labelled "complete".
32
+ */
33
+ reasoning?: {
34
+ kind: MaterialKind;
35
+ text: string;
36
+ truncated: boolean;
37
+ };
38
+ /** The source's visible assistant text, oldest first: the decisions and the rationale it actually stated. */
39
+ visibleRationale: string[];
40
+ toolFacts: HandoffToolFact[];
41
+ artifacts: string[];
42
+ /** Failures, denials and interruptions -- the work that is demonstrably not finished. */
43
+ unresolved: string[];
44
+ finalResponse?: string;
45
+ /** §9.4's optional source-produced continuation brief. Supplied by the caller; never requested from here. */
46
+ brief?: string;
47
+ }
48
+ export interface PortableHandoff {
49
+ sections: PortableHandoffSections;
50
+ /** The rendered block, already delimited and labelled -- drop it into `ProviderMessage.decoration` (door `"tag"`). */
51
+ text: string;
52
+ /** ANY quoted value was bounded -- including a tool-result excerpt, which is not reasoning loss. */
53
+ truncated: boolean;
54
+ /**
55
+ * The REASONING specifically was trimmed. Tracked apart from `truncated` because they mean
56
+ * different things to §9.6: a clipped tool excerpt is a display bound, while a clipped reasoning
57
+ * trace is state the target will not receive -- and the second one, and only the second one, must
58
+ * flip a would-be-lossless transfer to warned-lossy. The engine's switch point folds THIS flag
59
+ * into its `SwitchFacts`, never `truncated`.
60
+ */
61
+ reasoningTruncated: boolean;
62
+ }
63
+ export interface PortableHandoffOptions {
64
+ /** §9.4's brief, when the harness asked the source for one before switching. */
65
+ brief?: string;
66
+ /** How many of the source's most recent visible assistant messages to carry. Default 4. */
67
+ maxVisibleMessages?: number;
68
+ /** How many completed tool facts to carry, most recent first. Default 12. */
69
+ maxToolFacts?: number;
70
+ /** The per-value character bound on any quoted text. Default 400. */
71
+ maxValueChars?: number;
72
+ /** Path fragments whose tool facts are dropped -- pass the session's memory directory here (§2.8). Instruction-file basenames are refused unconditionally. */
73
+ excludedPathFragments?: readonly string[];
74
+ /** P7a (D19): the running brand's `instructionsFile`, ADDED to `INSTRUCTION_FILE_BASENAMES` (never swapped for it). */
75
+ instructionsFile?: string;
76
+ /** Whether the source's exposed reasoning may be forwarded at all (§12.4: only when policy permits). A summary is unaffected. */
77
+ allowExposedForwarding?: boolean;
78
+ }
79
+ /**
80
+ * Builds the handoff.
81
+ *
82
+ * `messages` is the conversation AS THE ENGINE HOLDS IT -- the same array the renderer sees -- and
83
+ * `chain` supplies the source's captured reasoning. Everything is derived MECHANICALLY: the objective
84
+ * is the user's own last message, the rationale is the assistant's own visible text, the tool facts
85
+ * are real completed calls. Nothing here summarises or infers, because a summary Winter invents is a
86
+ * claim about the source's reasoning that the source never made -- that is what §9.4's optional brief
87
+ * is for, and it comes from the source model itself.
88
+ */
89
+ export declare function buildPortableHandoff(messages: readonly ProviderMessageLike[], chain: ContinuationChainLike, from: ContinuityEndpoint, options?: PortableHandoffOptions): PortableHandoff;
90
+ /** The handoff as a `ProviderMessage.decoration`. The TAG door always: a handoff is text, and it must be readable by a family whose reasoning channel validates its input. */
91
+ export declare function handoffDecoration(handoff: PortableHandoff): {
92
+ text: string;
93
+ door: "tag";
94
+ };
@@ -0,0 +1,10 @@
1
+ export { RECOVERED_REASONING_TAG, MIN_DECORATION_BODY_CHARS, buildDecoration, decorationOverhead, doorFor, escapeAttribute, escapeInline, neutralizeDelimiters, trimToBudget } from "./decoration.js";
2
+ export type { Decoration, DecorationDoor, DecorationInput, DecorationSource } from "./decoration.js";
3
+ export { createEndpointResolver, endpointFromOrigin, readableStateOf, sameDomain, sameFamily, shouldRequestSummary, summaryRequestOf } from "./domains.js";
4
+ export type { ContinuityEndpoint, DomainFacts, ReadableState } from "./domains.js";
5
+ export { applyDecorationToContent, createHistoryRenderer } from "./renderer.js";
6
+ export type { ContinuationChainLike, ContinuationLinkLike, HistoryRendererOptions, HistoryTarget, MaterialKind, RenderReport, RenderedDecoration, WinterHistoryRenderer } from "./renderer.js";
7
+ export { classifySwitch } from "./warnings.js";
8
+ export type { LossClass, SwitchClassification, SwitchFacts } from "./warnings.js";
9
+ export { INSTRUCTION_FILE_BASENAMES, PRIOR_MODEL_HANDOFF_TAG, buildPortableHandoff, handoffDecoration } from "./handoff.js";
10
+ export type { HandoffToolFact, PortableHandoff, PortableHandoffOptions, PortableHandoffSections } from "./handoff.js";
@@ -0,0 +1,111 @@
1
+ import type { ProviderRegistry } from "../registry.js";
2
+ import type { ContentBlockLike, MessageOrigin, ProviderMessageLike, ProviderNativeState } from "../types.js";
3
+ import { type DecorationDoor } from "./decoration.js";
4
+ import { type ReadableState } from "./domains.js";
5
+ /** The target of THIS request. Structurally the `target` argument of the runtime's frozen `HistoryRenderer` seam. */
6
+ export interface HistoryTarget {
7
+ family: string;
8
+ continuationDomain?: string;
9
+ readableState: ReadableState;
10
+ }
11
+ /** One assistant entry's folded sidecar records. Structurally `ContinuationLink` from the runtime's `store/provider-state.ts`. */
12
+ export interface ContinuationLinkLike {
13
+ origin?: MessageOrigin;
14
+ nativeState?: ProviderNativeState;
15
+ summary?: string;
16
+ }
17
+ /** What the renderer needs of a chain: a lookup by anchor uuid. `Map<string, ContinuationLink>` -- the runtime's `ContinuationChain` -- satisfies it exactly. */
18
+ export interface ContinuationChainLike {
19
+ get(anchorUuid: string): ContinuationLinkLike | undefined;
20
+ }
21
+ /** Whether a message's readable material is a provider SUMMARY or its own complete exposed reasoning. §9.5: the two are never merged, and never merged with opaque state. */
22
+ export type MaterialKind = "summary" | "exposed";
23
+ export interface RenderedDecoration {
24
+ /** The decorated message's anchor uuid, when it had one. Identity only -- this record never carries the decoration's text, let alone any payload. */
25
+ anchorUuid?: string;
26
+ source: {
27
+ providerId: string;
28
+ modelKey: string;
29
+ };
30
+ kind: MaterialKind;
31
+ door: DecorationDoor;
32
+ truncated: boolean;
33
+ }
34
+ /**
35
+ * What one render did. The seam returns only messages, so this is how a caller learns that a transfer
36
+ * became lossy -- §9.6's "never silently truncate reasoning while still classifying the handoff as
37
+ * lossless" needs somewhere for the truncation to be reported, and this is it.
38
+ */
39
+ export interface RenderReport {
40
+ /** Messages replayed with their native state intact (same domain). */
41
+ replayedNatively: number;
42
+ /** Messages whose native state was dropped because the target is a different domain. */
43
+ droppedNativeState: number;
44
+ /** In-dialect thinking / redacted_thinking blocks stripped from content on a cross-domain leg. */
45
+ strippedInDialectBlocks: number;
46
+ decorations: RenderedDecoration[];
47
+ /** Cross-domain messages that had NO readable material to carry (no summary captured, or forwarding was policy-blocked). */
48
+ withoutMaterial: number;
49
+ /** Material dropped ENTIRELY because the render's total decoration budget was exhausted. */
50
+ budgetDropped: number;
51
+ /** ANY truncation or budget drop. The engine's switch point reads this to flip a would-be-lossless transfer to warned-lossy. */
52
+ truncated: boolean;
53
+ /**
54
+ * Decorations placed on the THINKING-CHANNEL door, which only the target family's own adapter can
55
+ * address. Surfaced so a caller can see that this render depends on adapter-side placement --
56
+ * `applyDecorationToContent` deliberately cannot do it, and a count of zero means every decoration
57
+ * this render produced is placeable as ordinary text.
58
+ */
59
+ thinkingChannelDecorations: number;
60
+ }
61
+ export interface HistoryRendererOptions {
62
+ /** §9.6: the per-decoration character budget. Absent = unbounded. */
63
+ maxDecorationChars?: number;
64
+ /** §9.6: the budget for ALL decorations in one render, spent NEWEST FIRST so the material closest to the current work survives. Absent = unbounded. */
65
+ decorationCharBudget?: number;
66
+ /**
67
+ * §12.4 / §8.4: whether raw exposed reasoning may be forwarded at all. `false` suppresses
68
+ * `exposed` material (a provider or user policy that forbids forwarding) while leaving
69
+ * provider-produced summaries alone; `classifySwitch` turns it into the policy-blocked warning.
70
+ */
71
+ allowExposedForwarding?: boolean;
72
+ /** Called with every render's report. The frozen seam returns only messages; this is the side channel that keeps truncation observable through it. */
73
+ onReport?: (report: RenderReport) => void;
74
+ }
75
+ /**
76
+ * The renderer.
77
+ *
78
+ * `render` is deliberately GENERIC over the message type rather than typed against the engine's
79
+ * `ProviderMessage`: this package must never import the runtime (R6-4's cycle rule), and a generic
80
+ * whose constraint is `ProviderMessageLike` is instantiated at `M = ProviderMessage` when this object
81
+ * is assigned to the runtime's frozen `HistoryRenderer` seam. `corpus/continuity.test.ts` proves that
82
+ * assignment against the REAL `adapterAsProvider` rather than against a hand-copied shape.
83
+ */
84
+ export interface WinterHistoryRenderer {
85
+ render<M extends ProviderMessageLike>(messages: M[], chain: ContinuationChainLike, target: HistoryTarget): M[];
86
+ renderWithReport<M extends ProviderMessageLike>(messages: M[], chain: ContinuationChainLike, target: HistoryTarget): {
87
+ messages: M[];
88
+ report: RenderReport;
89
+ };
90
+ }
91
+ export declare function createHistoryRenderer(registry: ProviderRegistry, options?: HistoryRendererOptions): WinterHistoryRenderer;
92
+ /** What `applyDecorationToContent` did. `applied: false` always says WHY, and always returns the untouched content. */
93
+ export type DecorationPlacement = {
94
+ applied: true;
95
+ content: string | ContentBlockLike[];
96
+ } | {
97
+ applied: false;
98
+ reason: "no-decoration" | "thinking-channel-door";
99
+ content: string | ContentBlockLike[];
100
+ };
101
+ /**
102
+ * Places a TAG-door decoration into a message's own content, as ordinary text.
103
+ *
104
+ * OFFERED TO ADAPTERS, and it is what makes the door real: the renderer annotates, and the adapter
105
+ * that owns a family's wire mapping decides where the annotation goes. For the tag door that is a
106
+ * plain text block appended to the message the reasoning belongs to (text is the ONLY door into a
107
+ * validating family); for the thinking-channel door it is the family's own plain reasoning field,
108
+ * which only that adapter can address, so this helper deliberately refuses that case rather than
109
+ * quietly turning it into text.
110
+ */
111
+ export declare function applyDecorationToContent(message: ProviderMessageLike): DecorationPlacement;
@@ -0,0 +1,46 @@
1
+ import { type ContinuityEndpoint } from "./domains.js";
2
+ /**
3
+ * How much of the source's continuation survives.
4
+ *
5
+ * `lossless-native` -- the target accepts the source's own native continuation object (§8.4's first
6
+ * suppressing condition: a certified shared domain).
7
+ * `lossless-portable` -- the source's COMPLETE readable reasoning crosses unmodified as text (§8.4's
8
+ * second condition; the DeepSeek→OpenAI class).
9
+ * `warned-lossy` -- everything else. Truncation flips either lossless class into this one.
10
+ */
11
+ export type LossClass = "lossless-native" | "lossless-portable" | "warned-lossy";
12
+ /**
13
+ * The per-switch observations. BOOLEANS AND COUNTS ONLY -- there is deliberately no field that could
14
+ * carry reasoning text, a payload, or a credential, which is what makes "warnings never print
15
+ * payloads" a property of the type rather than a rule someone has to remember.
16
+ */
17
+ export interface SwitchFacts {
18
+ /** A provider-produced summary for the source's work exists (captured into the sidecar). */
19
+ summaryAvailable?: boolean;
20
+ /** For a `full-exposed` source: is the readable reasoning COMPLETE? `false` means some of it is missing (§8.4's third trigger). */
21
+ exposedComplete?: boolean;
22
+ /** Reasoning had to be trimmed to fit the target's context (§9.6). Flips a lossless class to lossy, always. */
23
+ truncated?: boolean;
24
+ /** A provider or user policy forbids forwarding the reasoning text (§8.4's fifth trigger, §12.4's "only when policy permits"). */
25
+ policyBlocksForwarding?: boolean;
26
+ /** The switch aborts a RUNNING turn before its final summary/state exists (§8.3's immediate switch). */
27
+ midTurnAbort?: boolean;
28
+ /** How many tool calls completed and are therefore carried as portable FACTS. Zero is a fact about the turn, not a missing input. */
29
+ completedToolResults?: number;
30
+ }
31
+ export interface SwitchClassification {
32
+ lossClass: LossClass;
33
+ /** User-facing prose. Empty for a lossless transfer -- that is what "warn iff lossy" means. */
34
+ warnings: string[];
35
+ /** What survives the switch, named exactly. Never empty: the visible conversation always crosses. */
36
+ portable: string[];
37
+ }
38
+ /**
39
+ * Classifies one requested transition.
40
+ *
41
+ * `from`/`to` carry the CAPABILITY facts (readable state, stamped continuation domain) and `facts`
42
+ * carries what is true of this particular switch. Nothing here reads a provider id to decide
43
+ * portability -- `sameDomain` is the only continuation test, so "same vendor" never buys a suppressed
44
+ * warning and "different vendor" never forces one.
45
+ */
46
+ export declare function classifySwitch(from: ContinuityEndpoint, to: ContinuityEndpoint, facts?: SwitchFacts): SwitchClassification;
@@ -0,0 +1,6 @@
1
+ import type { CredentialStore } from "../types.js";
2
+ export interface EnvCredentialStoreOptions {
3
+ /** The environment this store may read. A host passes `process.env` explicitly at the composition root if that is what it wants — never here. */
4
+ env: Record<string, string | undefined>;
5
+ }
6
+ export declare function createEnvCredentialStore(opts: EnvCredentialStoreOptions): CredentialStore;