@stackstackstack/dsh-llm-pi-ai 0.1.5

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.
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@stackstackstack/dsh-llm-pi-ai`.
4
+ * @module @stackstackstack/dsh-llm-pi-ai/invariant
5
+ */
6
+ const PACKAGE_NAME = "@stackstackstack/dsh-llm-pi-ai";
7
+ /** Cordis companion plugin name. */
8
+ const name = "llm-pi-ai-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: this package exposes no independent event sequence or mutable data relation
13
+ * beyond contracts enforced at its owning seam.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Generic pi-ai-backed implementation of the Harness LLM seam.
3
+ *
4
+ * Each resolution produces one **immutable** snapshot — the profiles plus a
5
+ * `Models` collection holding the `Provider` each route built — and an
6
+ * operation captures a whole snapshot before its first `await`. A
7
+ * configuration change builds a *new* collection rather than mutating the one
8
+ * in use, because `Models.streamSimple()` is lazy: it resolves the provider
9
+ * when the stream is first consumed, which is after the credential await, so a
10
+ * mutated collection would let a request that started under one configuration
11
+ * finish under another — or fail with a provider that no longer exists. This is
12
+ * what makes the seam's per-step call freeze (`llm.prepareCall()`) hold all the
13
+ * way down: switching models mid-reply takes effect on the next step, never
14
+ * inside the one in flight.
15
+ *
16
+ * Credentials stay outside that collection. The harness resolves a route's key
17
+ * through its own seam and passes it as the request's `apiKey` option, which
18
+ * pi-ai treats as the highest-priority auth override — so `Models` never holds
19
+ * a credential store and the harness keeps its fail-loud reference semantics.
20
+ *
21
+ * @module dsh-llm-pi-ai/adapter
22
+ */
23
+ import { LlmAdapter } from '@stackstackstack/dsh-llm';
24
+ import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk } from '@stackstackstack/dsh-llm';
25
+ import type { AttachmentStore } from '@stackstackstack/dsh-attachment';
26
+ import type { ResolvedPiAiProviderProfile } from './config.ts';
27
+ /** Constructor options for {@link PiAiAdapter}: the two resolution hooks the plugin owns. */
28
+ export interface PiAiAdapterOptions {
29
+ /** Current validated profiles by provider route; called once per operation. */
30
+ profiles: () => ReadonlyMap<string, ResolvedPiAiProviderProfile>;
31
+ /**
32
+ * Resolve the credential for one already-resolved profile; called once per
33
+ * stream call and frozen for that call. `undefined` defers to the route's own
34
+ * pi-ai auth, which for an installed catalog route is its provider-native
35
+ * ambient discovery; the plugin allows that only for a profile naming no
36
+ * credential at all, because a named reference that misses throws `LlmError`
37
+ * `MISSING_CREDENTIAL` rather than falling back.
38
+ */
39
+ resolveApiKey: (provider: string, profile: ResolvedPiAiProviderProfile) => Promise<string | undefined>;
40
+ /** Resolve the optional durable attachment service at request time. */
41
+ resolveAttachments?: () => AttachmentStore | undefined;
42
+ }
43
+ /**
44
+ * pi-ai-backed multi-provider adapter. Each operation reads the current
45
+ * profiles, so a configuration change reaches the next request without a
46
+ * restart; model descriptors come from the collection those profiles built.
47
+ */
48
+ export declare class PiAiAdapter extends LlmAdapter {
49
+ private readonly config;
50
+ private snapshot;
51
+ constructor(config: PiAiAdapterOptions);
52
+ /**
53
+ * The snapshot for the current profiles. Resolution memoizes its result, so
54
+ * an unchanged configuration is recognized by identity; a changed one gets a
55
+ * brand-new collection, leaving any snapshot an operation already captured
56
+ * untouched for as long as that operation holds it.
57
+ */
58
+ private current;
59
+ /** The profile for one route within one snapshot, or the not-owned failure. */
60
+ private profileOf;
61
+ /** The configured descriptor for one exact route/model pair within one snapshot. */
62
+ private modelOf;
63
+ providerInfo(provider: string): LlmProviderInfo;
64
+ providerRetryPolicy(provider: string): ResolvedRetryPolicy | undefined;
65
+ listModels(provider: string): Promise<readonly LlmModelInfo[]>;
66
+ resolveModel(provider: string, model: string, _signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
67
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
68
+ }
69
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Materialization of one provider route's model catalog. The installed pi-ai
3
+ * catalog supplies defaults keyed by model id, and a profile's own model
4
+ * entries override them field by field, so a route naming a catalog provider
5
+ * stays configuration-free while a route pi-ai has never heard of is fully
6
+ * describable from `settings.yaml`.
7
+ *
8
+ * Every pi-ai `Model` field the harness cannot default is required here rather
9
+ * than at request time: an unserviceable route fails while its configuration is
10
+ * being resolved, which is the earliest point that can name the offending key.
11
+ *
12
+ * @module dsh-llm-pi-ai/catalog
13
+ */
14
+ import type { Api, Model, ModelThinkingLevel, OpenAICompletionsCompat, Provider } from '@earendil-works/pi-ai';
15
+ /** One request modality a pi-ai model may accept. */
16
+ export type PiAiModality = Model<Api>['input'][number];
17
+ /** Every request modality a profile may declare. */
18
+ export declare const MODALITIES: readonly PiAiModality[];
19
+ /** Every pi-ai thinking level a profile may declare, in escalation order. */
20
+ export declare const THINKING_LEVELS: readonly ModelThinkingLevel[];
21
+ /** The `compat.thinkingFormat` spellings pi-ai accepts on an `openai-completions` model. */
22
+ type PiThinkingFormat = NonNullable<OpenAICompletionsCompat['thinkingFormat']>;
23
+ /**
24
+ * pi-ai thinking formats a profile cannot name: both drive the request through
25
+ * `chatTemplateKwargs`, which this configuration does not expose.
26
+ */
27
+ type WithheldThinkingFormat = 'chat-template' | 'qwen-chat-template';
28
+ /** One reasoning-dispatch wire format a profile may name. */
29
+ export type PiAiThinkingFormat = Exclude<PiThinkingFormat, WithheldThinkingFormat>;
30
+ /** Reasoning-dispatch wire formats a profile may name, most-reached first. */
31
+ export declare const SUPPORTED_THINKING_FORMATS: readonly PiAiThinkingFormat[];
32
+ /**
33
+ * The installed catalog provider for one route, when pi-ai ships one.
34
+ * @param provider - provider route key.
35
+ * @returns the catalog provider, or `undefined` for a route pi-ai does not ship.
36
+ */
37
+ export declare function catalogProvider(provider: string): Provider | undefined;
38
+ /**
39
+ * Every provider route the installed pi-ai catalog ships.
40
+ * @returns the catalog provider ids.
41
+ */
42
+ export declare function catalogProviderIds(): readonly string[];
43
+ /**
44
+ * Whether the installed catalog provider for one route declares an api-key
45
+ * method — the only authentication this adapter obtains on its own.
46
+ *
47
+ * A key is what the harness resolves through its own credential seam and hands
48
+ * pi-ai per request. pi-ai's other method, OAuth, resolves from a *stored*
49
+ * OAuth credential alone: `resolveProviderAuth` has no ambient path for it,
50
+ * this adapter builds its `Models` collection with no credential store, and
51
+ * nothing here runs a login flow. So a provider offering OAuth by itself
52
+ * leaves nothing for this adapter to authenticate with, and the posture such a
53
+ * provider invites — no key configured, credentials discovered by the provider
54
+ * — fails every request with `Provider is not configured`.
55
+ * @param provider - provider route key.
56
+ * @returns whether the catalog provider takes an api key; false for a route
57
+ * pi-ai does not ship, which the caller answers for separately.
58
+ */
59
+ export declare function catalogProviderTakesApiKey(provider: string): boolean;
60
+ /**
61
+ * The installed catalog models for one route, indexed by model id.
62
+ * @param provider - provider route key.
63
+ * @returns catalog models by id; empty for a route pi-ai does not ship.
64
+ */
65
+ export declare function catalogModels(provider: string): Map<string, Model<Api>>;
66
+ /**
67
+ * Selectable reasoning efforts for one model: each key is a level the model
68
+ * offers (and selectors show), and its value is the wire spelling dispatch
69
+ * sends for it. `off` alone may leave its value empty — "supported, send
70
+ * nothing" — because for most providers not thinking is the parameter's
71
+ * absence; every other declared level must name a wire value. A level absent
72
+ * from the dict is not offered.
73
+ */
74
+ export type PiAiReasoningEfforts = Partial<Record<ModelThinkingLevel, string | null>>;
75
+ /**
76
+ * Reasoning-dispatch compatibility switches, set on the route (its models'
77
+ * default) or per model (winning over the route). Only the switches pi-ai's
78
+ * reasoning dispatch reads are offered; the rest of pi-ai's compat surface
79
+ * keeps its baseURL-derived auto-detection. pi-ai types both fields only on
80
+ * `OpenAICompletionsCompat` — the other wire protocols define their reasoning
81
+ * fields in the protocol itself — so resolution rejects a model-level switch
82
+ * anywhere else, while a route-level default skips past models it cannot fit.
83
+ */
84
+ export interface PiAiCompatProfile {
85
+ /** Reasoning parameter format the endpoint expects; absent keeps the catalog entry's, then pi-ai's baseURL-derived guess. */
86
+ thinkingFormat?: PiAiThinkingFormat;
87
+ /** Whether the endpoint accepts `reasoning_effort`; absent keeps the catalog entry's, then pi-ai's baseURL-derived guess. */
88
+ supportsReasoningEffort?: boolean;
89
+ }
90
+ /** One configured model entry: an id plus the catalog fields it overrides. */
91
+ export interface PiAiModelProfile {
92
+ /** Model id sent to the provider and accepted by {@link GenerateOptions.model}. */
93
+ id: string;
94
+ /** Display name for selectors; defaults to the catalog name, then the id. */
95
+ name?: string;
96
+ /** Maximum combined request and response context in tokens. */
97
+ contextWindow?: number;
98
+ /**
99
+ * Maximum output tokens. Configuring one also makes it this model's
100
+ * per-request default; a value inherited from the installed catalog, or the
101
+ * route's fallback, is the model's capability and never becomes a request
102
+ * default on its own.
103
+ */
104
+ maxTokens?: number;
105
+ /**
106
+ * Request modalities this model accepts. Absent — or empty, which describes
107
+ * a model that accepts nothing and so states no answer either — keeps the
108
+ * installed catalog entry's modalities, then the route's `defaultInput`.
109
+ * Declaring images is what makes a hand-declared vision model usable, and
110
+ * declaring text alone corrects a catalog model whose gateway does not serve
111
+ * what the catalog records. This is a claim about the endpoint, not a check
112
+ * of it: nothing interrogates a gateway for what it accepts, so a model
113
+ * claiming images its endpoint refuses is refused by the provider instead,
114
+ * mid-turn.
115
+ */
116
+ input?: PiAiModality[];
117
+ /**
118
+ * Selectable reasoning efforts. Absent inherits the installed catalog
119
+ * entry's capability (a hand-declared model has none and does not reason);
120
+ * `false` declares a non-reasoning model, which is how a profile strips
121
+ * reasoning from a catalog model its gateway cannot serve; a non-empty dict
122
+ * declares the offered levels and their wire spellings.
123
+ */
124
+ reasoningEfforts?: false | PiAiReasoningEfforts;
125
+ /** Reasoning-dispatch switches for this model, winning over the route's. */
126
+ compat?: PiAiCompatProfile;
127
+ }
128
+ /**
129
+ * Customization of one installed catalog model, keyed by its id in the
130
+ * route's `modelOverrides` dict — the same fields a `models` entry may set,
131
+ * with the id living in the key. Unlike a `models` list, overrides leave the
132
+ * rest of the catalog serving untouched, which is what makes "correct one
133
+ * model, keep the other thirty-seven" a three-line edit.
134
+ */
135
+ export type PiAiModelOverride = Omit<PiAiModelProfile, 'id'>;
136
+ /** The route-level facts model materialization reads. */
137
+ export interface RouteCatalogRequest {
138
+ /** Provider route key, stamped onto every materialized model. */
139
+ provider: string;
140
+ /** Wire protocol override; absent defers to each catalog model's own API. */
141
+ api?: string;
142
+ /** Endpoint override; absent defers to the catalog model, then the catalog provider. */
143
+ baseURL?: string;
144
+ /** Configured catalog; absent means the whole installed catalog for this route. */
145
+ models?: readonly PiAiModelProfile[];
146
+ /** Installed-catalog customizations by model id; only meaningful while `models` is absent. */
147
+ modelOverrides?: Readonly<Record<string, PiAiModelOverride>>;
148
+ /** Reasoning-dispatch switches for every `openai-completions` model on the route; entries override per field. */
149
+ compat?: PiAiCompatProfile;
150
+ /** Context capacity for a model neither the entry nor the catalog sizes. */
151
+ defaultContextWindow: number;
152
+ /** Output capability for a model neither the entry nor the catalog sizes. */
153
+ defaultMaxTokens: number;
154
+ /** Modalities for a model neither the entry nor the catalog declares. */
155
+ defaultInput: Model<Api>['input'];
156
+ }
157
+ /** One route's materialized catalog, plus the request caps its profile chose. */
158
+ export interface RouteCatalog {
159
+ /** The materialized models in configuration order. */
160
+ models: readonly Model<Api>[];
161
+ /**
162
+ * Per-request output caps this profile explicitly configured, by model id.
163
+ *
164
+ * Separate from `Model.maxTokens` because the two answer different
165
+ * questions: pi-ai requires `maxTokens` as the model's output *capability*,
166
+ * while the harness seam's `defaultMaxTokens` is a cap the deployment chose
167
+ * to send on requests that name none. Materializing a catalog capability as
168
+ * a request default would start capping every request at a number nobody
169
+ * picked, so only an explicit configuration lands here.
170
+ */
171
+ configuredMaxTokens: ReadonlyMap<string, number>;
172
+ }
173
+ /**
174
+ * Materialize one route's catalog by merging the installed catalog defaults
175
+ * under the configured entries. A route with no configured `models` serves the
176
+ * installed catalog unchanged, which is what keeps an existing
177
+ * `providers: { deepseek: { apiKeyEnv: … } }` profile working untouched.
178
+ * @param request - the route-level catalog facts.
179
+ * @returns the materialized models and the explicitly configured request caps.
180
+ */
181
+ export declare function resolveRouteModels(request: RouteCatalogRequest): RouteCatalog;
182
+ export {};
183
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Configuration schema and provider-profile validation for the pi-ai adapter.
3
+ * Profiles are a dict keyed by provider route, so the composition base and a
4
+ * user-settings layer merge per provider and the route set is structural.
5
+ *
6
+ * A route key is not required to name an installed pi-ai provider. When it does,
7
+ * that provider's endpoint, protocol, display name, and model catalog are the
8
+ * profile's defaults and the profile overrides them field by field; when it does
9
+ * not, the profile is the whole provider declaration. Resolution therefore ends
10
+ * in a built pi-ai `Provider` per route: everything a request needs is decided
11
+ * once, while the configuration key that made a route unserviceable can still be
12
+ * named in the failure.
13
+ *
14
+ * @module dsh-llm-pi-ai/config
15
+ */
16
+ import type { CacheRetention, ModelThinkingLevel, Provider, ThinkingBudgets, Transport } from '@earendil-works/pi-ai';
17
+ import z from '@deepseek-ai/schemastery';
18
+ import type { CredentialRef } from '@stackstackstack/dsh-credentials';
19
+ import type { ResolvedRetryPolicy, RetryPolicyConfig } from '@stackstackstack/dsh-llm';
20
+ import type { PiAiCompatProfile, PiAiModality, PiAiModelOverride, PiAiModelProfile } from './catalog.ts';
21
+ /** Default maximum idle interval while an adapter stream read is outstanding. */
22
+ export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300000;
23
+ /** Context capacity assumed for a model neither configuration nor the catalog sizes. */
24
+ export declare const DEFAULT_CONTEXT_WINDOW = 262144;
25
+ /** Output capability assumed for a model neither configuration nor the catalog sizes. */
26
+ export declare const DEFAULT_MAX_TOKENS = 32768;
27
+ /**
28
+ * Modalities assumed for a model neither configuration nor the catalog
29
+ * declares. Text is the floor every supported protocol certainly carries, so
30
+ * this is the absence of a declaration rather than a guess at the endpoint:
31
+ * nothing can interrogate a gateway for its modalities, and the two wrong
32
+ * answers do not cost the same. Under-claiming refuses the image before it is
33
+ * attached, naming the model. Over-claiming admits one the provider then
34
+ * rejects mid-turn, after the message is durable, leaving the session
35
+ * repeating a request that cannot succeed.
36
+ */
37
+ export declare const DEFAULT_INPUT: readonly PiAiModality[];
38
+ export type { PiAiCompatProfile, PiAiModality, PiAiModelOverride, PiAiModelProfile, PiAiReasoningEfforts, PiAiThinkingFormat, } from './catalog.ts';
39
+ /** Configuration for one pi-ai provider route; the `providers` dict key IS the route. */
40
+ export interface PiAiProviderProfile {
41
+ /** Credential reference (environment-variable name) resolved per request through `ctx.credentials`. */
42
+ apiKeyEnv?: string;
43
+ /** Name shown by configuration surfaces; defaults to the route key. */
44
+ displayName?: string;
45
+ /**
46
+ * Wire protocol every model on this route speaks. Omission keeps each
47
+ * installed catalog model's own protocol, which is why a catalog route needs
48
+ * no protocol at all; a route the catalog does not ship must name one.
49
+ */
50
+ api?: string;
51
+ /** Endpoint for this route's models; defaults to the installed catalog's endpoint. */
52
+ baseURL?: string;
53
+ /**
54
+ * This route's model catalog. Omission serves the installed catalog for the
55
+ * route unchanged; an explicit list replaces it, each entry defaulting its
56
+ * unset fields from the installed model of the same id.
57
+ */
58
+ models?: PiAiModelProfile[];
59
+ /**
60
+ * Installed-catalog customizations by model id: each entry reshapes that
61
+ * one model with the same fields a {@link models} entry takes, while the
62
+ * rest of the catalog keeps serving untouched. Only meaningful on a catalog
63
+ * route with no `models` list — `models` already replaces the catalog, so
64
+ * an override beside it, on a route the catalog does not ship, or naming a
65
+ * model the catalog does not describe is refused rather than skipped.
66
+ */
67
+ modelOverrides?: Record<string, PiAiModelOverride>;
68
+ /**
69
+ * Reasoning-dispatch switches for every `openai-completions` model on this
70
+ * route; each model's own `compat` overrides per field. What neither sets
71
+ * keeps the installed catalog entry's value, then pi-ai's baseURL-derived
72
+ * detection.
73
+ */
74
+ compat?: PiAiCompatProfile;
75
+ /**
76
+ * Context capacity for a model this route lists that neither the entry nor
77
+ * the installed catalog sizes (default 262,144). A guess by construction, so
78
+ * a deployment whose gateway serves smaller models corrects it here.
79
+ */
80
+ defaultContextWindow?: number;
81
+ /**
82
+ * Output capability for a model this route lists that neither the entry nor
83
+ * the installed catalog sizes (default 32,768). This sizes the model; it
84
+ * never becomes a per-request cap on its own.
85
+ */
86
+ defaultMaxTokens?: number;
87
+ /**
88
+ * Request modalities for a model this route lists that neither its entry's
89
+ * {@link PiAiModelProfile.input} nor the installed catalog declares (default
90
+ * `[text]`). A fallback like the capacities above, not an override: a
91
+ * catalog model keeps the modalities the catalog records for it, and this
92
+ * value never narrows one. A gateway serving vision models the catalog does
93
+ * not describe declares `[text, image]` once here instead of on every entry.
94
+ * Unlike an entry's list, this one may not be empty — nothing sits below it
95
+ * to answer instead.
96
+ */
97
+ defaultInput?: PiAiModality[];
98
+ /** Provider request headers; Harness attribution wins reserved names. */
99
+ headers?: Record<string, string>;
100
+ /** Provider-neutral pi-ai reasoning level. */
101
+ reasoning?: ModelThinkingLevel;
102
+ /** Token budgets used by reasoning providers that support them. */
103
+ thinkingBudgets?: ThinkingBudgets;
104
+ /** Prompt-cache retention preference. */
105
+ cacheRetention?: CacheRetention;
106
+ /** Streaming transport preference. */
107
+ transport?: Transport;
108
+ /** HTTP/provider SDK timeout in milliseconds. */
109
+ timeoutMs?: number;
110
+ /** WebSocket connection timeout in milliseconds. */
111
+ websocketConnectTimeoutMs?: number;
112
+ /** Maximum provider idle time while one stream read is outstanding. */
113
+ streamIdleTimeoutMs?: number;
114
+ /** Provider-owned model-request retry policy; omission uses normal defaults. */
115
+ retryPolicy?: RetryPolicyConfig;
116
+ }
117
+ /** Validated profile with its route stamped and every adapter-owned default resolved. */
118
+ export interface ResolvedPiAiProviderProfile extends Omit<PiAiProviderProfile, 'apiKeyEnv' | 'retryPolicy' | 'models' | 'displayName'> {
119
+ /** Harness route key and the `Models` collection key (the configuration dict key). */
120
+ provider: string;
121
+ /** Resolved display name for selectors and configuration surfaces. */
122
+ displayName: string;
123
+ /** Validated credential reference, when one is configured. */
124
+ apiKeyEnv?: CredentialRef;
125
+ /** Positive finite provider-idle interval after defaulting. */
126
+ streamIdleTimeoutMs: number;
127
+ /** Immutable retry policy captured with this provider route. */
128
+ retryPolicy: ResolvedRetryPolicy;
129
+ /**
130
+ * The pi-ai provider this route registers, built from the resolved models.
131
+ * Construction happens here so an unserviceable protocol or an underspecified
132
+ * model fails with the rest of resolution, leaving the last good route set
133
+ * serving requests.
134
+ */
135
+ piProvider: Provider;
136
+ /**
137
+ * Per-request output caps this profile explicitly configured, by model id.
138
+ * The seam materializes one only into a request that names no cap of its
139
+ * own, so a catalog capability must not appear here.
140
+ */
141
+ configuredMaxTokens: ReadonlyMap<string, number>;
142
+ }
143
+ /** Plugin configuration: the provider routes this instance owns. */
144
+ export interface Config {
145
+ /**
146
+ * pi-ai provider routes, keyed by provider. An empty (or omitted) dict is
147
+ * the dormant settings-driven posture: the adapter mounts with no routes
148
+ * and registers them the moment a settings section supplies profiles.
149
+ */
150
+ providers?: Record<string, PiAiProviderProfile>;
151
+ }
152
+ /** Runtime schema for {@link Config}. */
153
+ export declare const Config: z<Config>;
154
+ /**
155
+ * Reject a section this adapter could not serve. Registered as the settings
156
+ * namespace's validator, so an unserviceable profile is refused where it is
157
+ * *written* — `settings.mutate` answers `settings-rejected` with the offending
158
+ * route and model named — instead of being stored and then quietly disabling
159
+ * every route in the namespace. It stays a validator rather than a schema
160
+ * transform because the schema is also the shape a configuration surface
161
+ * renders and the value an absent section resolves to; wrapping it would break
162
+ * both.
163
+ * @param config - the resolved section to check.
164
+ * @throws Error naming the route and model that cannot be served.
165
+ */
166
+ export declare function assertServiceable(config: Config): void;
167
+ /**
168
+ * Validate profiles and return a detached route-keyed map suitable for
169
+ * per-request reads. This is the one explicit resolve step, so an omitted dict
170
+ * resolves to the empty (dormant) route set here rather than through a hidden
171
+ * fallback, and each route's models and pi-ai provider are materialized once.
172
+ * @param providers - configured provider profiles keyed by route.
173
+ * @returns validated profiles in configuration order.
174
+ */
175
+ export declare function resolveProfiles(providers: Readonly<Record<string, PiAiProviderProfile>> | undefined): Map<string, ResolvedPiAiProviderProfile>;
176
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Harness request-history conversion into pi-ai's Context vocabulary.
3
+ *
4
+ * @module dsh-llm-pi-ai/context
5
+ */
6
+ import type { GenerateOptions } from '@stackstackstack/dsh-llm';
7
+ import type { AttachmentStore } from '@stackstackstack/dsh-attachment';
8
+ import type { Context as PiContext } from '@earendil-works/pi-ai';
9
+ /**
10
+ * Convert text-only harness history to a synchronous pi-ai Context. Tool
11
+ * result names are recovered from preceding assistant tool calls.
12
+ * @param options - the harness request; `options.system` maps to pi-ai's single `systemPrompt` slot.
13
+ * @returns the pi-ai context; `tools` is omitted when the request declares none.
14
+ */
15
+ export declare function toPiContext(options: GenerateOptions): PiContext;
16
+ /**
17
+ * Convert harness history to a pi-ai Context while resolving durable images.
18
+ * Tool result names are recovered from preceding assistant tool calls.
19
+ * @param options - the harness request; `options.system` maps to pi-ai's single `systemPrompt` slot.
20
+ * @param attachments - durable byte resolver for image references.
21
+ * @returns the asynchronously resolved pi-ai context.
22
+ */
23
+ export declare function toPiContext(options: GenerateOptions, attachments: AttachmentStore): Promise<PiContext>;
24
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Answering "which models can this provider serve?" for the configuration
3
+ * surface's "fetch available models" action.
4
+ *
5
+ * A route the installed pi-ai catalog ships is answered **from that catalog**,
6
+ * with no network call at all: pi-ai's registry is the authoritative list for
7
+ * its own providers, and it carries the capacities a listing endpoint would
8
+ * not disclose. Only a route the catalog does not describe — a gateway, a
9
+ * self-hosted server — is interrogated over the wire.
10
+ *
11
+ * Neither path is a catalog refresh. Nothing here is stored: the request
12
+ * carries a draft the user is still editing, and the reply is candidate
13
+ * metadata the surface offers for adoption. `settings.yaml` remains the only
14
+ * thing that decides what a route serves.
15
+ *
16
+ * Only OpenAI-compatible protocols are interrogated. Their listing is the one
17
+ * shape a gateway, a self-hosted server, and the official endpoints all agree
18
+ * on, which is the case this action exists for; every other protocol reports
19
+ * that it cannot be interrogated so the surface falls back to hand-entry
20
+ * rather than guessing a response shape.
21
+ *
22
+ * @module dsh-llm-pi-ai/discovery
23
+ */
24
+ import type { LlmDiscoveredModel, LlmModelDiscoveryRequest } from '@stackstackstack/dsh-llm';
25
+ /**
26
+ * Interrogate one draft provider endpoint for the models it advertises.
27
+ * @param request - the endpoint, protocol, and one-shot credential to use.
28
+ * @param storedApiKey - the credential the named route already stored, asked
29
+ * for only when the draft carries none and only on the path that reaches the
30
+ * network. A configuration surface never holds a stored secret — it edits a
31
+ * redacted descriptor — so without this an already-configured route would be
32
+ * interrogated unauthenticated and answer 401.
33
+ * @returns the advertised models in endpoint order.
34
+ * @throws LlmError when the protocol has no readable listing, the endpoint
35
+ * refuses or fails the request, or the reply is not a model listing.
36
+ */
37
+ export declare function discoverModels(request: LlmModelDiscoveryRequest, storedApiKey?: () => Promise<string | undefined>): Promise<readonly LlmDiscoveredModel[]>;
38
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Generic pi-ai-backed LLM adapter plugin. One plugin instance owns a dict of
3
+ * provider routes; a route naming an installed pi-ai provider inherits that
4
+ * provider's endpoint, protocol, and model catalog as defaults, and a route
5
+ * pi-ai does not ship is declared outright. Profile facts resolve per request
6
+ * over the optional `llm-pi-ai` user-settings section and the optional
7
+ * credential seam, so a changed key, endpoint, model, or knob reaches the next
8
+ * request without a restart; a changed *route set* (or a route's
9
+ * registration-captured retry policy) re-registers the same adapter instance
10
+ * in place.
11
+ *
12
+ * ```yaml
13
+ * - id: llm
14
+ * name: '@stackstackstack/dsh-llm-pi-ai'
15
+ * config:
16
+ * providers:
17
+ * # Catalog route: everything but the credential comes from pi-ai.
18
+ * openai:
19
+ * apiKeyEnv: OPENAI_API_KEY
20
+ * retryPolicy:
21
+ * mode: normal
22
+ * maxRetries: 2
23
+ * # Catalog route with the catalog narrowed and one capacity corrected.
24
+ * anthropic:
25
+ * apiKeyEnv: ANTHROPIC_API_KEY
26
+ * models:
27
+ * - id: claude-sonnet-4-5
28
+ * contextWindow: 200000
29
+ * # Hand-declared route: pi-ai ships nothing under this key.
30
+ * acme-gateway:
31
+ * displayName: Acme Gateway
32
+ * apiKeyEnv: ACME_GATEWAY_API_KEY
33
+ * api: openai-completions
34
+ * baseURL: https://gateway.acme.example/v1
35
+ * # Reasoning dialect for a URL pi-ai cannot recognize.
36
+ * compat:
37
+ * thinkingFormat: deepseek
38
+ * models:
39
+ * - id: acme-large
40
+ * name: Acme Large
41
+ * contextWindow: 65536
42
+ * maxTokens: 4096
43
+ * - id: acme-think
44
+ * name: Acme Think
45
+ * contextWindow: 262144
46
+ * maxTokens: 32768
47
+ * # key = selectable level, value = wire spelling; only off may
48
+ * # leave the value empty (supported, send nothing).
49
+ * reasoningEfforts:
50
+ * off:
51
+ * high: high
52
+ * max: ultra
53
+ * ```
54
+ *
55
+ * @module @stackstackstack/dsh-llm-pi-ai
56
+ */
57
+ import type { Context } from '@deepseek-ai/cordis';
58
+ import { Config } from './config.ts';
59
+ export { PiAiAdapter } from './adapter.ts';
60
+ export type { PiAiAdapterOptions } from './adapter.ts';
61
+ export { Config } from './config.ts';
62
+ export type { PiAiCompatProfile, PiAiModality, PiAiModelOverride, PiAiModelProfile, PiAiProviderProfile, PiAiReasoningEfforts, PiAiThinkingFormat, ResolvedPiAiProviderProfile, } from './config.ts';
63
+ export { supportedProtocols } from './provider.ts';
64
+ export declare const name = "llm-pi-ai";
65
+ export declare const inject: string[];
66
+ /** Register one generic pi-ai adapter for all configured provider routes. */
67
+ export declare function apply(ctx: Context, config: Config): void;
68
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@stackstackstack/dsh-llm-pi-ai`.
3
+ * @module @stackstackstack/dsh-llm-pi-ai/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "llm-pi-ai-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Construction of the pi-ai `Provider` that one configured route registers into
3
+ * the adapter's `Models` collection.
4
+ *
5
+ * Two constructions, one decision: a route the installed catalog ships, whose
6
+ * profile does not override the wire protocol, **reuses that catalog provider**
7
+ * with its models replaced — the catalog provider owns API implementations this
8
+ * package cannot reconstruct (Bedrock loads its Smithy module through a
9
+ * separate entry point), so rebuilding it from parts would silently narrow
10
+ * which providers work. Every other route — one pi-ai has never heard of, or a
11
+ * catalog route pointed at a different protocol — is built by `createProvider`
12
+ * over the protocol table below.
13
+ *
14
+ * Credentials never reach this module's storage: the harness resolves a route's
15
+ * key through `ctx.credentials` before the request enters pi-ai and hands it
16
+ * over as a stream option, which `Models` presents to `resolve()` as the
17
+ * credential key.
18
+ *
19
+ * @module dsh-llm-pi-ai/provider
20
+ */
21
+ import type { Api, Model, Provider } from '@earendil-works/pi-ai';
22
+ /**
23
+ * Every wire protocol a configured route may name, most-reached first. The
24
+ * order is the table's and therefore stable; a configuration surface offering
25
+ * a choice presents the first as its default, which is why the protocol a
26
+ * hand-declared gateway most often speaks — and the one endpoint interrogation
27
+ * can read — leads.
28
+ * @returns the supported protocol identifiers.
29
+ */
30
+ export declare function supportedProtocols(): readonly string[];
31
+ /** The resolved route facts provider construction reads. */
32
+ export interface ProviderSpec {
33
+ /** Provider route key; also the `Models` collection key and each model's `provider`. */
34
+ provider: string;
35
+ /** Display name for selectors and status labels. */
36
+ displayName: string;
37
+ /** Wire protocol override; absent means each model keeps its catalog protocol. */
38
+ api?: string;
39
+ /** Endpoint override already applied to {@link models}; kept for provider-level display. */
40
+ baseURL?: string;
41
+ /** The route's materialized models, in configuration order. */
42
+ models: readonly Model<Api>[];
43
+ /**
44
+ * Whether the profile names a credential, which it does through `apiKeyEnv`
45
+ * alone: configuration carries the reference, never the secret. Only that
46
+ * decides whether {@link routeAuth} adds the harness's own api-key method to
47
+ * a catalog provider that offers none; the key itself still arrives per
48
+ * request, never at construction.
49
+ */
50
+ namesCredential: boolean;
51
+ }
52
+ /**
53
+ * Build the pi-ai provider for one resolved route.
54
+ * @param spec - the resolved route facts.
55
+ * @returns the provider to register in the adapter's `Models` collection.
56
+ * @throws Error when the route names a wire protocol this build cannot serve.
57
+ */
58
+ export declare function buildProvider(spec: ProviderSpec): Provider;
59
+ //# sourceMappingURL=provider.d.ts.map