dsh-opencode-go 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/cordis.patch.yml +3 -0
- package/docs/independent-package.md +13 -0
- package/docs/verification.md +50 -0
- package/examples/headless.patch.yml +4 -0
- package/examples/migrate-from-fork.patch.yml +3 -0
- package/lib/build-info.json +10 -0
- package/lib/client.js +853 -0
- package/lib/index.js +1158 -0
- package/lib/types/adapter.d.ts +101 -0
- package/lib/types/catalog.d.ts +95 -0
- package/lib/types/client/Section.d.ts +26 -0
- package/lib/types/client/index.d.ts +34 -0
- package/lib/types/client/locales.d.ts +50 -0
- package/lib/types/client/section-controller.d.ts +175 -0
- package/lib/types/client/staged-form.d.ts +184 -0
- package/lib/types/config.d.ts +52 -0
- package/lib/types/conversion/config.d.ts +4 -0
- package/lib/types/conversion/context.d.ts +49 -0
- package/lib/types/conversion/index.d.ts +5 -0
- package/lib/types/conversion/replay.d.ts +62 -0
- package/lib/types/conversion/stream.d.ts +42 -0
- package/lib/types/index.d.ts +51 -0
- package/package.json +174 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenCode Go adapter: one route, one catalog, per-request routing header.
|
|
3
|
+
*
|
|
4
|
+
* Every request to the gateway carries two Harness-owned headers: the
|
|
5
|
+
* attribution User-Agent (`deepseek-harness/<version>`), which pi-ai's client
|
|
6
|
+
* lets request headers override, and `x-opencode-session`, which the gateway
|
|
7
|
+
* requires and uses to route a conversation and share its prompt cache. The
|
|
8
|
+
* header value is the request's session id — stable per conversation, so
|
|
9
|
+
* caching and billing attribution stay correct; a request arriving with no
|
|
10
|
+
* session id gets a fresh random value rather than a shared constant, because
|
|
11
|
+
* a constant would merge unrelated traffic into one cache bucket.
|
|
12
|
+
*
|
|
13
|
+
* Multi-turn correctness rides on the shared pi-ai conversion machinery
|
|
14
|
+
* (`toPiContext` reconstructs provider-native replay state from the session
|
|
15
|
+
* log; `toStreamChunks` maps events to seam chunks), so assistant history,
|
|
16
|
+
* tool calls, and usage land in the session log exactly as the generic pi-ai
|
|
17
|
+
* adapter records them. Image content rides the same machinery: models whose
|
|
18
|
+
* catalog entry declares the image modality convert attachments through the
|
|
19
|
+
* durable attachment service, and every other model refuses image content
|
|
20
|
+
* before any provider I/O.
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-llm-opencode-go/adapter
|
|
23
|
+
*/
|
|
24
|
+
import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
25
|
+
import type { GenerateOptions, ImageAttachmentAccess, LlmModelInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
26
|
+
import type { AttachmentStore, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
27
|
+
import { OpencodeGoCatalog } from './catalog.ts';
|
|
28
|
+
import type { OpencodeGoConfig } from './config.ts';
|
|
29
|
+
/**
|
|
30
|
+
* The attachment-service bridges one image request reads. Construction-time
|
|
31
|
+
* (context-dependent); the config-dependent policy numbers are merged per
|
|
32
|
+
* request from the current configuration.
|
|
33
|
+
*/
|
|
34
|
+
export interface OpencodeGoImageAccess {
|
|
35
|
+
/** Resolve the optional durable attachment service at request time. */
|
|
36
|
+
resolveAttachments: () => AttachmentStore | undefined;
|
|
37
|
+
/** Bridge one attachment reference into the current model-tool execution world. */
|
|
38
|
+
resolveImageAccess: (attachments: AttachmentStore, ref: ImageAttachmentRef) => ImageAttachmentAccess | undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Constructor inputs for {@link OpencodeGoAdapter}. */
|
|
41
|
+
export interface OpencodeGoAdapterOptions {
|
|
42
|
+
/**
|
|
43
|
+
* The current configuration, re-read at every operation: a settings write
|
|
44
|
+
* reaches the next request without a restart, and one operation never mixes
|
|
45
|
+
* two configuration generations.
|
|
46
|
+
*/
|
|
47
|
+
config: () => OpencodeGoConfig;
|
|
48
|
+
/** Resolve the route's credential per call; missing must fail loud. */
|
|
49
|
+
resolveApiKey: () => Promise<string | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* Image input machinery; absent refuses image content, which is the posture
|
|
52
|
+
* for direct construction without a durable attachment service behind it.
|
|
53
|
+
*/
|
|
54
|
+
imageAccess?: OpencodeGoImageAccess;
|
|
55
|
+
/** Observe the catalog falling back to the curated table. */
|
|
56
|
+
onFallback?: (detail: {
|
|
57
|
+
url: string;
|
|
58
|
+
error: unknown;
|
|
59
|
+
kept: number;
|
|
60
|
+
}) => void;
|
|
61
|
+
/** Observe live ids the curated table cannot route. */
|
|
62
|
+
onOmitted?: (ids: readonly string[]) => void;
|
|
63
|
+
/** Observe assistant history degrading to provider-neutral conversion. */
|
|
64
|
+
onReplayDegrade?: (reason: string) => void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The single route's adapter. The catalog snapshot freezes at each operation,
|
|
68
|
+
* so a refresh between two requests never mixes model generations inside one
|
|
69
|
+
* call.
|
|
70
|
+
*/
|
|
71
|
+
export declare class OpencodeGoAdapter extends LlmAdapter {
|
|
72
|
+
private readonly options;
|
|
73
|
+
/**
|
|
74
|
+
* One catalog instance per endpoint/refresh pair. A settings write that
|
|
75
|
+
* changes either gets a fresh resolver (and a fresh live-listing fetch) on
|
|
76
|
+
* the next operation; an unchanged configuration keeps its cached snapshot
|
|
77
|
+
* for the whole refresh interval.
|
|
78
|
+
*/
|
|
79
|
+
private catalogCache;
|
|
80
|
+
constructor(options: OpencodeGoAdapterOptions);
|
|
81
|
+
/**
|
|
82
|
+
* The catalog resolver for one configuration, rebuilding on the facts it
|
|
83
|
+
* owns. Public for the plugin's discovery registration, which resolves the
|
|
84
|
+
* current configuration the same way the adapter does.
|
|
85
|
+
* @param config - the configuration whose endpoint and refresh interval the
|
|
86
|
+
* resolver serves; a change to either yields a fresh resolver.
|
|
87
|
+
* @returns the resolver caching one snapshot per endpoint/refresh pair.
|
|
88
|
+
*/
|
|
89
|
+
catalogOf(config: OpencodeGoConfig): OpencodeGoCatalog;
|
|
90
|
+
providerInfo(provider: string): {
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
};
|
|
94
|
+
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
95
|
+
resolveModel(_provider: string, model: string, _signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
96
|
+
/** Describe one model: capacities plus the reasoning levels it actually offers. */
|
|
97
|
+
private modelInfo;
|
|
98
|
+
/** Validate an explicit effort against the model's own levels, without clamping. */
|
|
99
|
+
private resolveReasoningLevel;
|
|
100
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
101
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenCode Go model catalog: a curated table intersected with the gateway's
|
|
3
|
+
* live model list.
|
|
4
|
+
*
|
|
5
|
+
* The curated table is seeded from pi-ai's installed `opencode-go` catalog —
|
|
6
|
+
* its entries carry the wire protocol, compat quirks, thinking-level spellings,
|
|
7
|
+
* and capacities that the gateway's own listing endpoint does not disclose —
|
|
8
|
+
* plus this package's additions for models the installed catalog has not
|
|
9
|
+
* caught up with. The gateway rotates its list faster than either pi-ai or
|
|
10
|
+
* this package releases, so the served catalog is the curated table **minus**
|
|
11
|
+
* models the live listing no longer advertises: a model the gateway retired
|
|
12
|
+
* must disappear rather than error mid-session. Live ids the curated table
|
|
13
|
+
* does not describe are omitted too, because without a protocol mapping they
|
|
14
|
+
* cannot be routed correctly.
|
|
15
|
+
*
|
|
16
|
+
* The live listing at `{baseURL}/models` is public and answers ids only. A
|
|
17
|
+
* fetch failure never takes the route down: the curated table is served as-is
|
|
18
|
+
* and the failure is reported, on the grounds that a network blip should not
|
|
19
|
+
* deny service, while model discovery — whose whole purpose is the live
|
|
20
|
+
* answer — fails loud instead.
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-llm-opencode-go/catalog
|
|
23
|
+
*/
|
|
24
|
+
import type { Api, Model, Provider } from '@earendil-works/pi-ai';
|
|
25
|
+
import type { LlmDiscoveredModel } from '@deepseek-ai/dsh-llm';
|
|
26
|
+
/** Provider route key this plugin registers and every materialized model carries. */
|
|
27
|
+
export declare const PROVIDER_ID = "opencode-go";
|
|
28
|
+
/** Display name for selectors and status labels. */
|
|
29
|
+
export declare const DISPLAY_NAME = "OpenCode Go";
|
|
30
|
+
/** Endpoint serving both model requests and the model listing. */
|
|
31
|
+
export declare const DEFAULT_BASE_URL = "https://opencode.ai/zen/go/v1";
|
|
32
|
+
/** One immutable resolution of the served catalog. */
|
|
33
|
+
export interface CatalogSnapshot {
|
|
34
|
+
/** Served models by id: the curated table minus models absent from the live listing. */
|
|
35
|
+
readonly models: ReadonlyMap<string, Model<Api>>;
|
|
36
|
+
/** The provider serving exactly these models. */
|
|
37
|
+
readonly provider: Provider;
|
|
38
|
+
/** Whether the live listing answered this resolution; `false` on the fallback path. */
|
|
39
|
+
readonly live: boolean;
|
|
40
|
+
/** When this resolution completed, for the refresh interval. */
|
|
41
|
+
readonly fetchedAtMs: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read one live listing reply. The endpoint answers the standard OpenAI
|
|
45
|
+
* `data` array with entries carrying ids only; anything else is a fault the
|
|
46
|
+
* caller treats as "live unavailable" and falls back. An honest empty array
|
|
47
|
+
* is a valid answer: the gateway says it serves nothing.
|
|
48
|
+
* @param body - the decoded JSON reply.
|
|
49
|
+
* @returns the non-empty ids it advertises, in reply order.
|
|
50
|
+
* @throws {Error} when the reply carries no `data` array.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readLiveModelIds(body: unknown): readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* TTL-cached resolution of the served catalog. Concurrent callers share one
|
|
55
|
+
* in-flight fetch; a completed snapshot serves unchanged until the refresh
|
|
56
|
+
* interval elapses, so one stream call never observes two catalog generations.
|
|
57
|
+
*/
|
|
58
|
+
export declare class OpencodeGoCatalog {
|
|
59
|
+
private readonly baseURL;
|
|
60
|
+
private readonly refreshMs;
|
|
61
|
+
private readonly onFallback;
|
|
62
|
+
private readonly onOmitted;
|
|
63
|
+
private served;
|
|
64
|
+
private pending;
|
|
65
|
+
/**
|
|
66
|
+
* @param baseURL - The endpoint the gateway serves; also the listing base.
|
|
67
|
+
* @param refreshMs - How long one live resolution stays authoritative.
|
|
68
|
+
* @param onFallback - Observes a failed live listing and how many curated
|
|
69
|
+
* models kept serving because of it.
|
|
70
|
+
* @param onOmitted - Observes live ids the curated table cannot route.
|
|
71
|
+
*/
|
|
72
|
+
constructor(baseURL: string, refreshMs: number, onFallback: (detail: {
|
|
73
|
+
url: string;
|
|
74
|
+
error: unknown;
|
|
75
|
+
kept: number;
|
|
76
|
+
}) => void, onOmitted: (ids: readonly string[]) => void);
|
|
77
|
+
/**
|
|
78
|
+
* The current catalog, fetching when expired or never fetched.
|
|
79
|
+
* @returns the snapshot now serving, shared by concurrent callers.
|
|
80
|
+
*/
|
|
81
|
+
snapshot(): Promise<CatalogSnapshot>;
|
|
82
|
+
/** One resolution: intersect the curated table with the live listing. */
|
|
83
|
+
private build;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Candidate models for the configuration surface's "fetch available models"
|
|
87
|
+
* action: the same live intersection the route would serve, with the curated
|
|
88
|
+
* capacities the listing endpoint does not disclose. Unlike route resolution,
|
|
89
|
+
* discovery fails when the live listing is unreachable: its whole purpose is
|
|
90
|
+
* the live answer, and a stale answer would offer models the gateway retired.
|
|
91
|
+
* @param catalog - The route's catalog resolver.
|
|
92
|
+
* @returns the served models in curated order.
|
|
93
|
+
* @throws LlmError `DISCOVERY_FAILED` when the live listing is unreachable.
|
|
94
|
+
*/
|
|
95
|
+
export declare function discoverCatalogModels(catalog: OpencodeGoCatalog): Promise<readonly LlmDiscoveredModel[]>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenCode Go settings section. It leads with the one value a user has to
|
|
3
|
+
* supply — the API key, stored write-only through the credentials domain — and
|
|
4
|
+
* the models the gateway currently serves, then keeps the credential
|
|
5
|
+
* reference, the endpoint, and the adapter tuning fields in the
|
|
6
|
+
* `llm-opencode-go` namespace behind a collapsed disclosure.
|
|
7
|
+
*/
|
|
8
|
+
import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
|
|
9
|
+
import type { OpencodeGoSectionFace } from './section-controller.ts';
|
|
10
|
+
import type { en } from './locales.ts';
|
|
11
|
+
export type { OpencodeGoSectionState } from './section-controller.ts';
|
|
12
|
+
/** Section copy lookup, including the optional `{name}` template params. */
|
|
13
|
+
type SectionTranslate = (key: keyof typeof en, params?: Record<string, unknown>) => string;
|
|
14
|
+
/** Injected dependencies of {@link OpencodeGoSection} (slot `inject`). */
|
|
15
|
+
export interface OpencodeGoSectionInjected extends OpencodeGoSectionFace {
|
|
16
|
+
/** Section copy. */
|
|
17
|
+
t: SectionTranslate;
|
|
18
|
+
}
|
|
19
|
+
/** Props delivered by the slot outlet: the inject face spread flat. */
|
|
20
|
+
export type OpencodeGoSectionProps = Partial<InjectFace<OpencodeGoSectionInjected>>;
|
|
21
|
+
/**
|
|
22
|
+
* Render the OpenCode Go settings page.
|
|
23
|
+
* @param props - locale copy, the page snapshot, and its form actions.
|
|
24
|
+
* @returns the section.
|
|
25
|
+
*/
|
|
26
|
+
export declare function OpencodeGoSection(props: OpencodeGoSectionProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Go settings plugin, browser half. Registers the "OpenCode Go"
|
|
3
|
+
* settings page over the `llm-opencode-go` namespace: the API key (stored
|
|
4
|
+
* write-only through the credentials domain), the gateway's current model
|
|
5
|
+
* listing, and the adapter knobs behind the page's advanced disclosure. The
|
|
6
|
+
* Host settings and credential contracts stay behind their existing wire APIs.
|
|
7
|
+
*/
|
|
8
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
9
|
+
import { en } from './locales.ts';
|
|
10
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
11
|
+
interface LocaleNamespaceMap {
|
|
12
|
+
/** The OpenCode Go settings page copy. */
|
|
13
|
+
'settings.opencode-go': keyof typeof en;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export type { OpencodeGoSectionProps } from './Section.tsx';
|
|
17
|
+
export type { OpencodeGoSectionState, OpencodeGoSettings } from './section-controller.ts';
|
|
18
|
+
export { OPENCODE_GO_NS } from './section-controller.ts';
|
|
19
|
+
export type { OpencodeGoKey } from './locales.ts';
|
|
20
|
+
/**
|
|
21
|
+
* Required services (cordis fiber inject). The target slot is declared by
|
|
22
|
+
* ui-settings' apply, whose activation order relative to this one is NOT
|
|
23
|
+
* constrained; registration depends on each slot through `slots.inject()`.
|
|
24
|
+
* `remote.llm` is the discovery namespace the page reads the model listing
|
|
25
|
+
* through.
|
|
26
|
+
*/
|
|
27
|
+
export declare const inject: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Register the section once the `settings.section` declaration is on the
|
|
30
|
+
* ledger, and keep the credential badge fresh on Host-reported key changes
|
|
31
|
+
* written from any surface.
|
|
32
|
+
* @param ctx - client root context.
|
|
33
|
+
*/
|
|
34
|
+
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Copy dictionaries for the OpenCode Go settings page. English is the key-set source of truth. */
|
|
2
|
+
export declare const en: {
|
|
3
|
+
nav: string;
|
|
4
|
+
title: string;
|
|
5
|
+
intro: string;
|
|
6
|
+
enabledLabel: string;
|
|
7
|
+
enabledHint: string;
|
|
8
|
+
enabledOff: string;
|
|
9
|
+
enabledUnavailable: string;
|
|
10
|
+
keyLabel: string;
|
|
11
|
+
keyHint: string;
|
|
12
|
+
keyConfigured: string;
|
|
13
|
+
keyMissing: string;
|
|
14
|
+
keyNotWritable: string;
|
|
15
|
+
modelsLabel: string;
|
|
16
|
+
modelsRefresh: string;
|
|
17
|
+
modelsLoading: string;
|
|
18
|
+
modelsFailed: string;
|
|
19
|
+
modelsEmpty: string;
|
|
20
|
+
modelsCount: string;
|
|
21
|
+
advancedLabel: string;
|
|
22
|
+
advancedHint: string;
|
|
23
|
+
apiKeyEnvLabel: string;
|
|
24
|
+
apiKeyEnvHint: string;
|
|
25
|
+
baseURLLabel: string;
|
|
26
|
+
baseURLHint: string;
|
|
27
|
+
refreshMinutesLabel: string;
|
|
28
|
+
refreshMinutesHint: string;
|
|
29
|
+
streamIdleTimeoutMsLabel: string;
|
|
30
|
+
streamIdleTimeoutMsHint: string;
|
|
31
|
+
maxRequestImageBytesLabel: string;
|
|
32
|
+
maxRequestImageBytesHint: string;
|
|
33
|
+
requestImagePixelBudgetLabel: string;
|
|
34
|
+
requestImagePixelBudgetHint: string;
|
|
35
|
+
requestImageMaxBytesLabel: string;
|
|
36
|
+
requestImageMaxBytesHint: string;
|
|
37
|
+
overridden: string;
|
|
38
|
+
reset: string;
|
|
39
|
+
invalidValue: string;
|
|
40
|
+
save: string;
|
|
41
|
+
discard: string;
|
|
42
|
+
saving: string;
|
|
43
|
+
savedFailed: string;
|
|
44
|
+
readOnly: string;
|
|
45
|
+
unavailable: string;
|
|
46
|
+
};
|
|
47
|
+
/** Chinese copy; the key set mirrors {@link en}. */
|
|
48
|
+
export declare const zh: typeof en;
|
|
49
|
+
/** Every copy key this page's dictionaries must supply. */
|
|
50
|
+
export type OpencodeGoKey = keyof typeof en;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenCode Go settings page's staged form over the `llm-opencode-go`
|
|
3
|
+
* settings namespace, plus the gateway model listing the page reports.
|
|
4
|
+
*
|
|
5
|
+
* The key is the one control that does not live in the section: its literal
|
|
6
|
+
* never rides a response, so the page learns only whether one is configured
|
|
7
|
+
* and writes it through the credentials domain, addressed by the reference the
|
|
8
|
+
* section names. It is still staged with the rest of the form, so one save
|
|
9
|
+
* covers everything the page shows.
|
|
10
|
+
*/
|
|
11
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
13
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
14
|
+
import { type FieldState, type FormActions, type FormShell } from './staged-form.ts';
|
|
15
|
+
/** Namespace of the OpenCode Go adapter. Spelled here rather than imported: a client package must not depend on a Host package. */
|
|
16
|
+
export declare const OPENCODE_GO_NS = "llm-opencode-go";
|
|
17
|
+
/** The adapter fields this page edits. */
|
|
18
|
+
export interface OpencodeGoSettings {
|
|
19
|
+
/** Whether the adapter serves its route; false withdraws it from every picker. */
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
/** Credential reference naming the environment key. */
|
|
22
|
+
apiKeyEnv?: string;
|
|
23
|
+
/** The gateway endpoint; also the live listing base. */
|
|
24
|
+
baseURL?: string;
|
|
25
|
+
/** Live catalog re-resolution interval, in minutes. */
|
|
26
|
+
refreshMinutes?: number;
|
|
27
|
+
/** Largest idle gap between stream events, in milliseconds. */
|
|
28
|
+
streamIdleTimeoutMs?: number;
|
|
29
|
+
/** Accumulated base64 image payload bound for one request. */
|
|
30
|
+
maxRequestImageBytes?: number;
|
|
31
|
+
/** Total-pixel budget for one request image. */
|
|
32
|
+
requestImagePixelBudget?: number;
|
|
33
|
+
/** Raw encoded-byte target for one request image. */
|
|
34
|
+
requestImageMaxBytes?: number;
|
|
35
|
+
}
|
|
36
|
+
/** The gateway's model listing as the page reports it. */
|
|
37
|
+
export type OpencodeGoModels =
|
|
38
|
+
/** Not asked for yet; the page asks once it mounts. */
|
|
39
|
+
{
|
|
40
|
+
readonly status: 'idle';
|
|
41
|
+
}
|
|
42
|
+
/** A listing request is outstanding. */
|
|
43
|
+
| {
|
|
44
|
+
readonly status: 'loading';
|
|
45
|
+
}
|
|
46
|
+
/** The gateway answered: how many models it serves, and a preview of their names. */
|
|
47
|
+
| {
|
|
48
|
+
readonly status: 'ready';
|
|
49
|
+
readonly count: number;
|
|
50
|
+
readonly preview: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/** The listing could not be read; `message` is the Host's own diagnostic. */
|
|
53
|
+
| {
|
|
54
|
+
readonly status: 'failed';
|
|
55
|
+
readonly message: string;
|
|
56
|
+
};
|
|
57
|
+
/** What the settings page renders. */
|
|
58
|
+
export interface OpencodeGoSectionState extends FormShell {
|
|
59
|
+
/**
|
|
60
|
+
* Whether the adapter currently serves its route. Resolved from the section
|
|
61
|
+
* rather than staged: the switch writes on the click that flips it, because
|
|
62
|
+
* a withdrawn route is what the user is trying to observe.
|
|
63
|
+
*/
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
/** Credential reference naming the environment key. */
|
|
66
|
+
apiKeyEnv: FieldState;
|
|
67
|
+
/** The gateway endpoint. */
|
|
68
|
+
baseURL: FieldState;
|
|
69
|
+
/** Live catalog re-resolution interval, in minutes. */
|
|
70
|
+
refreshMinutes: FieldState;
|
|
71
|
+
/** Largest idle gap between stream events, in milliseconds. */
|
|
72
|
+
streamIdleTimeoutMs: FieldState;
|
|
73
|
+
/** Accumulated base64 image payload bound for one request. */
|
|
74
|
+
maxRequestImageBytes: FieldState;
|
|
75
|
+
/** Total-pixel budget for one request image. */
|
|
76
|
+
requestImagePixelBudget: FieldState;
|
|
77
|
+
/** Raw encoded-byte target for one request image. */
|
|
78
|
+
requestImageMaxBytes: FieldState;
|
|
79
|
+
/** The staged credential, which starts blank on every load. */
|
|
80
|
+
apiKey: FieldState;
|
|
81
|
+
/** Whether the Host reports a credential configured for the referenced key. */
|
|
82
|
+
apiKeyConfigured: boolean;
|
|
83
|
+
/** Whether the credentials domain accepts a write for it; false disables the control. */
|
|
84
|
+
apiKeyWritable: boolean;
|
|
85
|
+
/** The gateway's current model listing. */
|
|
86
|
+
models: OpencodeGoModels;
|
|
87
|
+
}
|
|
88
|
+
/** The registration-side face the page's slot entry injects. */
|
|
89
|
+
export interface OpencodeGoSectionFace extends FormActions {
|
|
90
|
+
hooks: {
|
|
91
|
+
/** Page snapshot bound by the UI renderer as useOpencodeGo. */
|
|
92
|
+
opencodeGo: SnapshotStore<OpencodeGoSectionState>;
|
|
93
|
+
};
|
|
94
|
+
/** Read the gateway's model listing, now or again after a failure. */
|
|
95
|
+
loadModels: () => void;
|
|
96
|
+
/**
|
|
97
|
+
* Turn the adapter's route on or off, writing immediately.
|
|
98
|
+
* @param next - the state the switch asks for.
|
|
99
|
+
*/
|
|
100
|
+
setEnabled: (next: boolean) => void;
|
|
101
|
+
}
|
|
102
|
+
/** Bridges the `llm-opencode-go` scope and the credentials domain onto the page. */
|
|
103
|
+
export declare class OpencodeGoSectionController {
|
|
104
|
+
private readonly scope;
|
|
105
|
+
private readonly ctx;
|
|
106
|
+
private readonly form;
|
|
107
|
+
private readonly store;
|
|
108
|
+
private credential;
|
|
109
|
+
private models;
|
|
110
|
+
private modelsRequest;
|
|
111
|
+
private face;
|
|
112
|
+
/**
|
|
113
|
+
* @param scope - the bound settings scope for the `llm-opencode-go` namespace.
|
|
114
|
+
* @param ctx - the page plugin's context, whose `remote.credentials` namespace
|
|
115
|
+
* answers for the credential the section references.
|
|
116
|
+
*/
|
|
117
|
+
constructor(scope: SettingsScope<OpencodeGoSettings>, ctx: ClientContext);
|
|
118
|
+
private projection;
|
|
119
|
+
/**
|
|
120
|
+
* The adapter's effective switch state: the resolved section's value, over
|
|
121
|
+
* the Host's own default when the section carries none.
|
|
122
|
+
* @returns whether the route is currently served.
|
|
123
|
+
*/
|
|
124
|
+
private enabled;
|
|
125
|
+
/**
|
|
126
|
+
* Flip the switch by writing the field on the click itself.
|
|
127
|
+
*
|
|
128
|
+
* This is the one control on the page that does not wait for Save: the point
|
|
129
|
+
* of turning it off is to watch the models leave the pickers, and the point
|
|
130
|
+
* of turning it back on is to use the route again — staging either behind a
|
|
131
|
+
* second gesture would report a state the Host does not hold. The write is
|
|
132
|
+
* revision-fenced by the scope like every other, and a refusal surfaces as a
|
|
133
|
+
* failed save through the shared shell rather than a silent revert.
|
|
134
|
+
* @param next - the state the switch asks for.
|
|
135
|
+
*/
|
|
136
|
+
setEnabled(next: boolean): void;
|
|
137
|
+
/**
|
|
138
|
+
* Read the gateway's model listing through the Host's discovery for this
|
|
139
|
+
* adapter. Called when the page mounts and again from its refresh control.
|
|
140
|
+
* A rejection settles as a failure too: the refresh control is disabled
|
|
141
|
+
* while loading and the next read starts only from `idle`, so leaving the
|
|
142
|
+
* state loading would strand the page with no way to ask the gateway again.
|
|
143
|
+
*/
|
|
144
|
+
loadModels(): void;
|
|
145
|
+
/**
|
|
146
|
+
* Ask the credentials domain about the reference the section currently names.
|
|
147
|
+
*
|
|
148
|
+
* The answer is stored with the reference it describes: `apiKeyEnv` can
|
|
149
|
+
* change between the request and its response, and two reads can settle out
|
|
150
|
+
* of order, so a response is published only while it still answers for the
|
|
151
|
+
* reference in force.
|
|
152
|
+
*/
|
|
153
|
+
private readCredential;
|
|
154
|
+
/**
|
|
155
|
+
* Re-read after the Host reports a change to the reference this page watches.
|
|
156
|
+
*
|
|
157
|
+
* A key can be written from somewhere else — the Models page addresses the
|
|
158
|
+
* same reference — and the settings section does not change when it is, so
|
|
159
|
+
* without this the badge keeps reporting a state the Host already replaced.
|
|
160
|
+
* @param ref - the reference the Host reports as changed.
|
|
161
|
+
*/
|
|
162
|
+
refreshCredential(ref: string): void;
|
|
163
|
+
/**
|
|
164
|
+
* Build the face the page's slot registration injects. Built once: the store
|
|
165
|
+
* is what changes, and the renderer binds the same callbacks across renders.
|
|
166
|
+
* @returns the page snapshot and its form actions.
|
|
167
|
+
*/
|
|
168
|
+
inject(): OpencodeGoSectionFace;
|
|
169
|
+
/**
|
|
170
|
+
* Write the staged key, then re-read whether the Host now holds one.
|
|
171
|
+
* @param value - the staged credential literal.
|
|
172
|
+
* @returns whether the Host reports a configured credential afterwards.
|
|
173
|
+
*/
|
|
174
|
+
private writeKey;
|
|
175
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The staged form behind the OpenCode Go settings page.
|
|
3
|
+
*
|
|
4
|
+
* The page stages what the user types and writes it only when they save. Each
|
|
5
|
+
* settings write is a durable, revision-fenced document mutation, so a control
|
|
6
|
+
* that committed as it settled turned one edit into a write the user never
|
|
7
|
+
* asked for and could not preview; staged text makes what is on screen exactly
|
|
8
|
+
* what a save would store.
|
|
9
|
+
*
|
|
10
|
+
* A field shows its effective value — the user layer over the composition
|
|
11
|
+
* layer over the schema default — and whether the user layer carries it. That
|
|
12
|
+
* presence, not a value comparison, is what marks a field overridden: an
|
|
13
|
+
* override equal to the composition default is still an override.
|
|
14
|
+
*
|
|
15
|
+
* Copied from the plugin-configuration card form (`ui-settings-plugins`): the
|
|
16
|
+
* client bundle purity gate forbids importing it across packages, and the two
|
|
17
|
+
* forms evolve independently anyway.
|
|
18
|
+
*/
|
|
19
|
+
import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
20
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
21
|
+
/** The write one field's staged text performs when the page is saved. */
|
|
22
|
+
export type FieldWrite = {
|
|
23
|
+
kind: 'set';
|
|
24
|
+
value: unknown;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'clear';
|
|
27
|
+
};
|
|
28
|
+
/** How one section field converts between its stored value and its draft text. */
|
|
29
|
+
export interface FieldSpec {
|
|
30
|
+
/** Field name inside the namespace section. */
|
|
31
|
+
field: string;
|
|
32
|
+
/** Render a stored value as draft text; the empty string when the section carries none. */
|
|
33
|
+
format: (value: unknown) => string;
|
|
34
|
+
/**
|
|
35
|
+
* The write this draft text stages, or undefined when the text is not a
|
|
36
|
+
* value this field accepts — which blocks the save rather than discarding it.
|
|
37
|
+
*/
|
|
38
|
+
parse: (text: string) => FieldWrite | undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A control whose value is written outside the settings section. A credential
|
|
42
|
+
* literal never rides a response, so its draft has nothing to seed from: it is
|
|
43
|
+
* blank until typed, and a blank draft writes nothing.
|
|
44
|
+
*/
|
|
45
|
+
export interface SecretSpec {
|
|
46
|
+
/** Field name addressing this control inside the page's form. */
|
|
47
|
+
field: string;
|
|
48
|
+
/** Write the staged text; resolves to whether the Host accepted it. */
|
|
49
|
+
write: (text: string) => Promise<boolean>;
|
|
50
|
+
}
|
|
51
|
+
/** One field as the page's control renders it. */
|
|
52
|
+
export interface FieldState {
|
|
53
|
+
/** Draft text the control renders. */
|
|
54
|
+
text: string;
|
|
55
|
+
/**
|
|
56
|
+
* Whether saving would leave a user-layer entry for this field. A staged
|
|
57
|
+
* edit answers for itself, so the badge previews the save rather than
|
|
58
|
+
* reporting a state the pending edit already contradicts.
|
|
59
|
+
*/
|
|
60
|
+
overridden: boolean;
|
|
61
|
+
/** Whether the draft is not a value this field accepts, which blocks saving. */
|
|
62
|
+
invalid: boolean;
|
|
63
|
+
}
|
|
64
|
+
/** Form state the page shell shares across its controls. */
|
|
65
|
+
export interface FormShell {
|
|
66
|
+
/** False while the namespace is not served to this client; the page renders nothing. */
|
|
67
|
+
available: boolean;
|
|
68
|
+
/** Whether the Host document accepts writes. */
|
|
69
|
+
writable: boolean;
|
|
70
|
+
/** Whether the form holds edits that a save would write. */
|
|
71
|
+
dirty: boolean;
|
|
72
|
+
/** Whether any staged draft is invalid, which blocks the save. */
|
|
73
|
+
invalid: boolean;
|
|
74
|
+
/** Whether a save is crossing the wire. */
|
|
75
|
+
saving: boolean;
|
|
76
|
+
/** Whether the last save did not land as staged; cleared by the next edit or save. */
|
|
77
|
+
failed: boolean;
|
|
78
|
+
}
|
|
79
|
+
/** The write actions the page's slot entry injects. */
|
|
80
|
+
export interface FormActions {
|
|
81
|
+
/** Stage draft text for one field. */
|
|
82
|
+
edit: (field: string, text: string) => void;
|
|
83
|
+
/** Stage a clear, so saving lets the field re-inherit the composition layer. */
|
|
84
|
+
resetField: (field: string) => void;
|
|
85
|
+
/** Write every staged edit, then re-seed from what the Host accepted. */
|
|
86
|
+
save: () => void;
|
|
87
|
+
/** Drop every staged edit. */
|
|
88
|
+
discard: () => void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A whole-number field. An empty draft clears the field; any other draft that
|
|
92
|
+
* is not a finite number blocks the save.
|
|
93
|
+
* @param field - field name inside the namespace section.
|
|
94
|
+
* @returns the field's conversion spec.
|
|
95
|
+
*/
|
|
96
|
+
export declare function numberField(field: string): FieldSpec;
|
|
97
|
+
/**
|
|
98
|
+
* A two-state field. Unlike the text fields, the draft is the boolean itself
|
|
99
|
+
* rather than text: the control that renders it reports the state the user
|
|
100
|
+
* asked for, and an unset section resolves to the composition default the
|
|
101
|
+
* schema supplied. Its draft text is the state's string form so the shared
|
|
102
|
+
* override and invalid tracking needs no second branch.
|
|
103
|
+
* @param field - field name inside the namespace section.
|
|
104
|
+
* @returns the field's conversion spec.
|
|
105
|
+
*/
|
|
106
|
+
export declare function booleanField(field: string): FieldSpec;
|
|
107
|
+
/**
|
|
108
|
+
* A free-text field. An empty draft clears the field, so emptying the control
|
|
109
|
+
* and saving is the same gesture as resetting it.
|
|
110
|
+
* @param field - field name inside the namespace section.
|
|
111
|
+
* @returns the field's conversion spec.
|
|
112
|
+
*/
|
|
113
|
+
export declare function textField(field: string): FieldSpec;
|
|
114
|
+
/**
|
|
115
|
+
* Stages the page's edits over the `llm-opencode-go` namespace and writes them
|
|
116
|
+
* on save.
|
|
117
|
+
*
|
|
118
|
+
* The form publishes through a snapshot store because the slot component reads
|
|
119
|
+
* through a snapshot selector, while both the scope and the local drafts
|
|
120
|
+
* change underneath; every projection is rebuilt from the two together.
|
|
121
|
+
*/
|
|
122
|
+
export declare class StagedForm {
|
|
123
|
+
private readonly scope;
|
|
124
|
+
private readonly specs;
|
|
125
|
+
private readonly secretSpecs;
|
|
126
|
+
private readonly staged;
|
|
127
|
+
private readonly listeners;
|
|
128
|
+
private saving;
|
|
129
|
+
private failed;
|
|
130
|
+
/**
|
|
131
|
+
* @param scope - the bound settings scope for this page's namespace.
|
|
132
|
+
* @param specs - the section fields this page edits.
|
|
133
|
+
* @param secrets - the page's write-only controls, written outside the section.
|
|
134
|
+
*/
|
|
135
|
+
constructor(scope: SettingsScope<Record<string, unknown>>, specs: FieldSpec[], secrets?: SecretSpec[]);
|
|
136
|
+
/**
|
|
137
|
+
* Publish a projection of this form, rebuilt whenever the scope or a draft changes.
|
|
138
|
+
* @param project - build the page state from the form's current reads.
|
|
139
|
+
* @returns the store the component reads through its bound selector.
|
|
140
|
+
*/
|
|
141
|
+
bind<S>(project: () => S): SnapshotStore<S>;
|
|
142
|
+
/**
|
|
143
|
+
* Read the page-level state: what the Host serves, and what a save would do.
|
|
144
|
+
* @returns the form state the page shell shares.
|
|
145
|
+
*/
|
|
146
|
+
shell(): FormShell;
|
|
147
|
+
/**
|
|
148
|
+
* Read one control's state.
|
|
149
|
+
* @param field - field name of a section field or of a write-only control.
|
|
150
|
+
* @returns the draft text, whether a save would leave an override, and whether it is invalid.
|
|
151
|
+
*/
|
|
152
|
+
field(field: string): FieldState;
|
|
153
|
+
/**
|
|
154
|
+
* Build the edit, reset, save, and discard actions bound to this form.
|
|
155
|
+
* @returns the actions the page's slot entry injects.
|
|
156
|
+
*/
|
|
157
|
+
actions(): FormActions;
|
|
158
|
+
/**
|
|
159
|
+
* Write every staged edit, then re-seed from what the Host accepted.
|
|
160
|
+
*
|
|
161
|
+
* The Host is the only authority on whether a value was accepted — its
|
|
162
|
+
* validators own the constraints no schema can express — so the outcome is
|
|
163
|
+
* read back from the section rather than predicted here. A save that did not
|
|
164
|
+
* land keeps its drafts, so the user can correct them instead of retyping.
|
|
165
|
+
*/
|
|
166
|
+
save(): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Every staged edit a save would write. An entry whose draft is not a value
|
|
169
|
+
* its field accepts carries no write: the form is still dirty, and the save
|
|
170
|
+
* refuses rather than dropping the edit.
|
|
171
|
+
* @returns the planned writes, in the order the fields were staged.
|
|
172
|
+
*/
|
|
173
|
+
private plan;
|
|
174
|
+
private clear;
|
|
175
|
+
private store;
|
|
176
|
+
private stage;
|
|
177
|
+
private spec;
|
|
178
|
+
private snapshotOf;
|
|
179
|
+
private sectionValue;
|
|
180
|
+
private baseValue;
|
|
181
|
+
private userLayer;
|
|
182
|
+
private stored;
|
|
183
|
+
private publish;
|
|
184
|
+
}
|