@dan-ai-studio/dshopencodego 0.1.54 → 0.1.56

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
@@ -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[]>;
@@ -24,6 +24,16 @@
24
24
  */
25
25
  /** A repository listing is large; the cap keeps a hostile response bounded. */
26
26
  export declare const DISCOVERY_MAX_BYTES: number;
27
+ /**
28
+ * How long the repository listing waits before it is asked again.
29
+ *
30
+ * The listing is unauthenticated, and an unauthenticated GitHub API allows
31
+ * sixty requests an hour. The listing is also large — the provider's whole
32
+ * tree, thousands of entries. Asking it on every build would spend that
33
+ * budget on a provider that is merely unreachable, and would make every
34
+ * build slower for a question with the same answer.
35
+ */
36
+ export declare const DISCOVERY_COOLDOWN_MS: number;
27
37
  /**
28
38
  * The repository paths a discovery run found.
29
39
  *
@@ -76,3 +86,21 @@ export declare function readCachedPaths(cacheDir: string | undefined): readonly
76
86
  * @returns the merged set, in the order it should be tried.
77
87
  */
78
88
  export declare function rememberPaths(cacheDir: string | undefined, discovered: readonly string[]): readonly string[];
89
+ /**
90
+ * Whether the repository may be asked again.
91
+ *
92
+ * The question is worth at most an hour: a provider whose document has
93
+ * moved is found within the hour, and one that is merely down is not asked
94
+ * again every minute while it is down.
95
+ * @param cacheDir - the plugin's cache directory.
96
+ * @returns true when the repository may be asked.
97
+ */
98
+ export declare function discoveryAllowed(cacheDir: string | undefined): boolean;
99
+ /**
100
+ * Record that the repository was asked, whether or not it answered.
101
+ *
102
+ * The attempt is what the budget is spent on, so a question that came back
103
+ * empty counts as a question.
104
+ * @param cacheDir - the plugin's cache directory.
105
+ */
106
+ export declare function rememberAttempt(cacheDir: string | undefined): void;
@@ -132,6 +132,14 @@ export interface CatalogOptions {
132
132
  * nothing else.
133
133
  */
134
134
  readonly cacheDir?: string;
135
+ /**
136
+ * The fetch the gateway listing is read through.
137
+ *
138
+ * A factory rather than a function: the pin follows the configuration, and
139
+ * the configuration is live. A catalogue built before a pin was set must
140
+ * read the next listing from the pinned address without being rebuilt.
141
+ */
142
+ readonly fetch?: () => typeof fetch;
135
143
  }
136
144
  /**
137
145
  * One gateway's catalog. Requests reuse a cached snapshot; explicit discovery
@@ -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.54",
3
+ "version": "0.1.56",
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",