@dan-ai-studio/dshopencodego 0.1.55 → 0.1.57

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.
@@ -59,6 +59,14 @@ export interface OpencodeGoAdapterOptions {
59
59
  * discovery per start and nothing else.
60
60
  */
61
61
  readonly cacheDir?: string;
62
+ /**
63
+ * The fetch every gateway request is made through.
64
+ *
65
+ * A factory rather than a function: the pin follows the configuration, and
66
+ * the configuration is live. A request made after a pin was set goes to
67
+ * the pinned address without the adapter being rebuilt.
68
+ */
69
+ readonly fetch?: () => typeof fetch;
62
70
  /**
63
71
  * Observe the provider's own usage for one completed call. This is the only
64
72
  * honest source of token counts: the gateway's `/usage` endpoint reports
@@ -43,6 +43,11 @@ export declare const METADATA_RETRY_DELAY_MS = 1500;
43
43
  * Five minutes is short enough that a service that comes back is picked up
44
44
  * soon, and long enough that a service that stays down is asked once, not
45
45
  * once per request.
46
+ *
47
+ * This is also the ceiling on how long an outage costs. A failed refresh
48
+ * keeps the age of the last reading that succeeded, so the TTL stays expired
49
+ * and the next call after this window tries again — a gateway that comes back
50
+ * is noticed within five minutes, not within a whole TTL.
46
51
  */
47
52
  export declare const BACKGROUND_RETRY_BACKOFF_MS: number;
48
53
  /** Response caps: the listing is tiny, the metadata document is not. */
@@ -11,10 +11,15 @@
11
11
  export declare function readModelIds(body: unknown): readonly string[];
12
12
  /**
13
13
  * Fetch the ids the gateway currently advertises.
14
+ *
15
+ * The request goes through the given fetch, so a gateway pinned to one
16
+ * address is read from that address — and, when that address is gone, from
17
+ * the address the system would have chosen.
14
18
  * @param baseURL - normalized gateway base without a trailing slash.
15
19
  * @param signal - caller cancellation, if any.
20
+ * @param request - the fetch to use; the system's own when none is given.
16
21
  * @returns the advertised ids in endpoint order, deduplicated.
17
22
  * @throws {LlmError} `DISCOVERY_FAILED` when the endpoint is unreachable, not
18
23
  * OK, or does not answer a model listing.
19
24
  */
20
- export declare function fetchModelIds(baseURL: string, signal?: AbortSignal): Promise<readonly string[]>;
25
+ export declare function fetchModelIds(baseURL: string, signal?: AbortSignal, request?: typeof fetch): Promise<readonly string[]>;
@@ -91,6 +91,16 @@ export interface CatalogSnapshot {
91
91
  * one that is true now.
92
92
  */
93
93
  readonly factsCachedAt?: number;
94
+ /**
95
+ * When the content of this snapshot was obtained.
96
+ *
97
+ * It is the age of the *knowledge*, not of the attempt: a snapshot that
98
+ * could not be rebuilt keeps the age of the last reading that succeeded,
99
+ * because that is when the content became true. Stamping it with the time
100
+ * of the failure instead would make a failed refresh look like a fresh one,
101
+ * and the TTL that governs the next attempt would then be measured from
102
+ * the failure rather than from the last thing that was actually known.
103
+ */
94
104
  readonly fetchedAtMs: number;
95
105
  }
96
106
  /** Observers the plugin logs through. */
@@ -132,6 +142,14 @@ export interface CatalogOptions {
132
142
  * nothing else.
133
143
  */
134
144
  readonly cacheDir?: string;
145
+ /**
146
+ * The fetch the gateway listing is read through.
147
+ *
148
+ * A factory rather than a function: the pin follows the configuration, and
149
+ * the configuration is live. A catalogue built before a pin was set must
150
+ * read the next listing from the pinned address without being rebuilt.
151
+ */
152
+ readonly fetch?: () => typeof fetch;
135
153
  }
136
154
  /**
137
155
  * One gateway's catalog. Requests reuse a cached snapshot; explicit discovery
@@ -48,7 +48,7 @@ export interface CatalogReading {
48
48
  * worse than one that admits it.
49
49
  */
50
50
  readonly cached?: boolean;
51
- /** When the underlying snapshot was built. */
51
+ /** When the content behind this reading was obtained. */
52
52
  readonly fetchedAtMs: number;
53
53
  /**
54
54
  * Where the per-model allowances came from: the provider's live document, or
@@ -63,6 +63,12 @@ export interface OpencodeGoConfig {
63
63
  * The escape hatch for a model whose family rule guessed wrong.
64
64
  */
65
65
  modelProtocols: Record<string, string>;
66
+ /**
67
+ * Addresses a name should be sent to instead of the one the system
68
+ * resolves, by host name. A name that is not listed keeps the system's
69
+ * own resolution, and so keeps its own fallback.
70
+ */
71
+ pinnedAddresses: Record<string, string>;
66
72
  /**
67
73
  * Retry policy for this route, owned here and executed by the optional
68
74
  * `dsh-llm-retry` plugin. Absent leaves the host's own default in force.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Wire contract for the network Remote.
3
+ *
4
+ * One reading, answering one question: which address is the gateway sent to
5
+ * right now, and did that address survive its first request?
6
+ *
7
+ * A spent address is not a failure the user has to fix — the requests are
8
+ * being served by the system's own resolution, and this says so.
9
+ *
10
+ * @module @dan-ai-studio/dshopencodego/network/contract
11
+ */
12
+ import type { RemoteResult, TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol';
13
+ /** The gateway's name and the address it is pinned to. */
14
+ export interface NetworkReading {
15
+ /** The gateway's name. */
16
+ readonly host: string;
17
+ /** The address the name is pinned to; absent when nothing is pinned. */
18
+ readonly address?: string;
19
+ /** Whether the pinned address was found to be gone. */
20
+ readonly failed?: boolean;
21
+ }
22
+ /** Validate a network reading crossing the wire. */
23
+ export declare function parseNetworkReading(value: unknown): NetworkReading;
24
+ declare module '@deepseek-ai/dsh-typert-protocol' {
25
+ interface TypertRemoteNamespaceMap {
26
+ opencodeGoNetwork: {
27
+ readNetwork(): Promise<RemoteResult<NetworkReading>>;
28
+ };
29
+ }
30
+ }
31
+ /** Remote methods this package owns, mounted together by the plugin. */
32
+ export declare const networkRemote: TypertRemoteContribution;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Pinning a name to an address.
3
+ *
4
+ * A name that the system resolves may reach an edge that is far away, and
5
+ * a far edge costs every request. Pinning the name to another address of the
6
+ * same network sends it to a different edge instead.
7
+ *
8
+ * The certificate is still checked against the name: only the resolution
9
+ * changes, never the name the server is asked to prove. That is why this
10
+ * is a lookup and not a redirect.
11
+ *
12
+ * Only the gateway's own name is pinned. Every other name — the metadata
13
+ * registry, the provider's documentation — keeps the system's own
14
+ * resolution, including its fallback, its cache, and whatever the host's
15
+ * resolver decides.
16
+ *
17
+ * A pin that cannot be reached is not the end of the request. The address may
18
+ * be gone, and the name may still be served perfectly well by the address the
19
+ * system would have chosen — so the request is sent again, that way, and the
20
+ * answer is the one the caller gets. A wrong address must never cost more
21
+ * than no address at all, so the pin is remembered as spent and the rest of
22
+ * the requests go straight to the system's resolution.
23
+ *
24
+ * @module @dan-ai-studio/dshopencodego/network/pinning
25
+ */
26
+ /** A name and the address it should be sent to. */
27
+ export type PinnedAddresses = Readonly<Record<string, string>>;
28
+ /**
29
+ * The gateway's own name and the address it is pinned to.
30
+ *
31
+ * An absent address is the whole point of the type: nothing is pinned, and
32
+ * every request goes to the system's own resolution, untouched.
33
+ */
34
+ export interface GatewayPin {
35
+ /** The gateway's name, lowercased. */
36
+ readonly host: string;
37
+ /** The address the name is sent to; absent when nothing is pinned. */
38
+ readonly address?: string;
39
+ }
40
+ /**
41
+ * The gateway's own name, as the configured endpoint states it.
42
+ *
43
+ * The endpoint is where every request of this plugin goes, so its name is
44
+ * the only one worth pinning: a name nobody here asks for has no request to
45
+ * redirect.
46
+ * @param baseURL - the configured gateway base.
47
+ * @returns the lowercased host, or an empty string when the endpoint is not
48
+ * a URL this plugin can parse.
49
+ */
50
+ export declare function gatewayHostOf(baseURL: string): string;
51
+ /**
52
+ * The pin that applies to the configured gateway.
53
+ *
54
+ * A pin for another name is not this plugin's business: the endpoint is what
55
+ * this plugin asks for, and a name that is not the endpoint's is never on
56
+ * the path of a request. An address that is not an address is a typo worth
57
+ * ignoring rather than a crash in the middle of a request, and ignoring it
58
+ * means the request goes to the system's resolution — which is exactly what
59
+ * an unconfigured plugin does.
60
+ * @param pins - the configured pins.
61
+ * @param baseURL - the configured gateway base.
62
+ * @returns the gateway's name and, when one is configured and usable, the
63
+ * address it is pinned to.
64
+ */
65
+ export declare function resolveGatewayPin(pins: PinnedAddresses, baseURL: string): GatewayPin;
66
+ /**
67
+ * Close the dispatcher, if one is open.
68
+ *
69
+ * The plugin's own lifetime is the host's, so this is only for a test or
70
+ * a shutdown that wants the sockets gone now.
71
+ */
72
+ export declare function closeDispatcher(): Promise<void>;
73
+ /**
74
+ * Whether the pinned address was already found to be gone.
75
+ *
76
+ * A spent address is not tried again: the request that proved it gone is
77
+ * the one that paid for it, and every later request would pay the same price
78
+ * for the same answer.
79
+ * @param pin - the pin.
80
+ * @returns true when the address was found to be gone.
81
+ */
82
+ export declare function isPinSpent(pin: GatewayPin): boolean;
83
+ /**
84
+ * A fetch that sends the gateway's name to the pinned address.
85
+ *
86
+ * A request for another name is sent exactly as it would have been: the
87
+ * dispatcher only answers the name it was given, and the rest keep the
88
+ * system's own resolution.
89
+ *
90
+ * A pinned address that cannot be reached is not the end of the request.
91
+ * The address may be gone, and the name may still be served perfectly well
92
+ * by the address the system would have chosen — so the request is sent
93
+ * again, that way, and the answer is the one the caller gets.
94
+ * @param pin - the gateway's name and its pinned address.
95
+ * @returns the fetch to use.
96
+ */
97
+ export declare function gatewayFetch(pin: GatewayPin): typeof fetch;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Host half of the network Remote.
3
+ *
4
+ * The page needs to know which address the gateway is sent to, and whether
5
+ * that address is still working: a pin that was found to be gone leaves the
6
+ * requests on the system's own resolution, and a page that kept showing the
7
+ * old address would claim a speed the plugin is no longer delivering.
8
+ *
9
+ * @module @dan-ai-studio/dshopencodego/network/service
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
13
+ import type { NetworkReading } from './contract.ts';
14
+ /** Host inputs for the network service. */
15
+ export interface NetworkServiceOptions {
16
+ /** Current gateway base URL. */
17
+ readonly baseURL: () => string;
18
+ /** The configured pins, by host name. */
19
+ readonly pinnedAddresses: () => Readonly<Record<string, string>>;
20
+ }
21
+ /** The network Remote: the gateway's name, its pin, and that pin's health. */
22
+ export declare class OpencodeGoNetworkService extends TypertRemoteService {
23
+ private readonly options;
24
+ constructor(ctx: Context, options: NetworkServiceOptions);
25
+ /** The gateway's name and its pin, as the configuration states them. */
26
+ readNetwork(): Promise<NetworkReading>;
27
+ }
@@ -22,6 +22,14 @@ export interface UsageServiceOptions {
22
22
  readonly resolveApiKey: () => Promise<string | undefined>;
23
23
  /** The process-lifetime meter fed by the adapter. */
24
24
  readonly meter: UsageMeter;
25
+ /**
26
+ * The fetch the usage read is made through.
27
+ *
28
+ * A factory rather than a function: the pin follows the configuration, and
29
+ * the configuration is live. A read made after a pin was set goes to the
30
+ * pinned address without the service being rebuilt.
31
+ */
32
+ readonly fetch?: () => typeof fetch;
25
33
  }
26
34
  /**
27
35
  * Decide what one failed read means for the client.
@@ -40,6 +40,14 @@ interface UsageReadOptions {
40
40
  /** Opaque identity of the endpoint and credential pair, when known. */
41
41
  readonly source?: string;
42
42
  readonly signal?: AbortSignal;
43
+ /**
44
+ * The fetch to use.
45
+ *
46
+ * The read goes to the gateway, so a gateway pinned to one address is read
47
+ * from that address — and, when that address is gone, from the address the
48
+ * system would have chosen.
49
+ */
50
+ readonly request?: typeof fetch;
43
51
  }
44
52
  /**
45
53
  * Read the account's usage windows.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "OpenCode Go provider for DeepSeek Harness: live gateway catalog, per-conversation session header, usage",
5
5
  "type": "module",
6
6
  "license": "MIT",