@huaweidevtools/aishell-dsh-llm 0.0.1-beta.2 → 0.0.1-beta.3

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.
@@ -10,13 +10,43 @@
10
10
  */
11
11
  import { LlmAdapter } from '@deepseek-ai/dsh-llm';
12
12
  import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
13
+ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
13
14
  import type { CredentialsStore } from '@huaweidevtools/aishell-dsh-credentials-store';
15
+ import { type ImageRequestBudget } from './images.ts';
14
16
  /** Adapter construction options. */
15
17
  export interface GatewayAdapterOptions {
16
- /** Chat API base URL (e.g. `https://api.aishell.openbuildlab.com`). */
17
- readonly baseUrl: string;
18
+ /**
19
+ * Resolve the current chat API base URL (e.g. `https://openai.aishell.openbuildlab.com/v1`).
20
+ *
21
+ * A reader, not a value, for the same reason `resolveAttachments` is: this
22
+ * plugin's whole Config is `Volatile`, so a settings edit is a
23
+ * volatile-only update. The loader commits such an update into the existing
24
+ * references and returns without re-running `apply()`
25
+ * (`cordis-plugin-loader/lib/index.js:381-388`), which means the adapter
26
+ * instance outlives the value it was built with. A captured string would
27
+ * leave the settings surface showing the new endpoint while requests kept
28
+ * going to the old one — silently, and for the life of the process.
29
+ */
30
+ readonly resolveBaseUrl: () => string;
18
31
  /** Credential store for reading AK/SK. */
19
32
  readonly keyring: CredentialsStore;
33
+ /**
34
+ * Resolve the mounted attachment service, when the deployment has one.
35
+ *
36
+ * Resolved per request rather than captured at construction: the service is
37
+ * provided by a sibling plugin whose fiber may mount after this one, and a
38
+ * captured `undefined` would disable image input for the whole session.
39
+ */
40
+ readonly resolveAttachments?: () => AttachmentStore | undefined;
41
+ /** Mounted filesystem provider, mapped into the tool execution world for image handle paths. */
42
+ readonly resolveHostPath?: (hostPath: string) => string | undefined;
43
+ /**
44
+ * Resolve the per-image projection policy, read per request so a budget edit
45
+ * takes effect without a reload — same reasoning as `resolveBaseUrl`.
46
+ */
47
+ readonly resolveImageBudget: () => ImageRequestBudget;
48
+ /** Resolve accumulated represented base64 bytes one request may carry before images must be offloaded. */
49
+ readonly resolveMaxRequestImageBytes: () => number;
20
50
  }
21
51
  export declare class GatewayAdapter extends LlmAdapter {
22
52
  private readonly opts;
@@ -34,15 +64,81 @@ export declare class GatewayAdapter extends LlmAdapter {
34
64
  private fetchWireModels;
35
65
  listModels(provider: string): Promise<readonly LlmModelInfo[]>;
36
66
  resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
37
- /** Read AK/SK (and optional SecurityToken) from the credential store. */
67
+ /**
68
+ * Whether a usable AK/SK pair is stored.
69
+ *
70
+ * The one predicate behind every "can this route do anything" question:
71
+ * `fetchWireModels` returns an empty catalog without it, and the Models-page
72
+ * card reports readiness from it. Kept in one place so the catalog and the
73
+ * card cannot disagree about whether this route is usable.
74
+ *
75
+ * @returns whether both halves of the signing credential are present.
76
+ */
77
+ hasCredentials(): boolean;
78
+ /**
79
+ * Read the AK/SK pair from the credential store.
80
+ *
81
+ * Delegates to the shared reader: this adapter, `aishell-e2b`,
82
+ * `aishell-credentials` and `aishell-deploy` all read the same record, and a
83
+ * local copy of that rule is how two of them end up disagreeing about what
84
+ * "credentials are configured" means.
85
+ *
86
+ * Read per request, never captured — that is what makes a rotation in the
87
+ * credential form apply to the very next call.
88
+ */
38
89
  private readCredentials;
39
- /** Read credentials or throw MISSING_CREDENTIAL — every signing code path goes through here. */
90
+ /**
91
+ * Read credentials or throw `MISSING_CREDENTIAL` — every signing code path
92
+ * goes through here.
93
+ */
40
94
  private requireCredentials;
41
95
  /**
42
96
  * Sign a request with AK/SK and fetch. Encapsulates the SDK-HMAC-SHA256
43
97
  * signing flow: read credentials → sign → attach headers → fetch.
44
98
  */
45
99
  private signedFetch;
100
+ /**
101
+ * Diagnose an "unsupported model" rejection and append what to do about it.
102
+ *
103
+ * The gateway answers a model id it does not carry with `AISHELL.1004`
104
+ * (`model_not_found`, HTTP 404 — see {@link isModelNotFound}). The default
105
+ * selection is a pinned literal (it must be readable before any credential or
106
+ * network call could enumerate the catalog), so the day the catalog drops
107
+ * that id, every fresh session fails here. Rather than leave the user to
108
+ * reverse-engineer the supported set, the failure itself asks the gateway
109
+ * (the catalog cache usually makes this a second signed GET already in
110
+ * flight) and names the models it currently accepts.
111
+ *
112
+ * Returns '' for any other failure, so the message stays untouched there.
113
+ */
114
+ private unsupportedModelHint;
115
+ /**
116
+ * Resolve one image occurrence's execution-world read path, when the
117
+ * deployment exposes one. Only enriches the handle text written beside an
118
+ * image; a deployment without a filesystem provider simply yields the
119
+ * shorter form.
120
+ *
121
+ * Deliberately unguarded, like the official adapters: this throws only for
122
+ * an invalid durable reference, and every reference reaching here was
123
+ * already validated by the `readImageRequest` that loaded its bytes — so a
124
+ * throw means the attachment record is inconsistent, which should surface
125
+ * rather than be absorbed into less informative handle text.
126
+ */
127
+ private readonly imageAccess;
128
+ /**
129
+ * Turn the harness history into the wire body, loading image bytes on the
130
+ * way.
131
+ *
132
+ * Deliberately the whole image pipeline and nothing else, so `stream` stays
133
+ * about transport. Ordering matters and mirrors the official adapters:
134
+ * resolve the attachment service, prepare one request version per retained
135
+ * image, fail the request if the budget is exceeded (handing
136
+ * `dsh-compaction-image-offload` its decision), and only then project the
137
+ * offloaded occurrences to placeholder text — a mark set by an earlier
138
+ * request's offload decision, which serialization must replace rather than
139
+ * look up bytes for.
140
+ */
141
+ private buildBody;
46
142
  stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
47
143
  }
48
144
  //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * aishell LLM browser half — shadows the Models page's provider row.
3
+ *
4
+ * The page's row for the aishell family is read-only: its profile has no
5
+ * fields the host editor knows how to write, and its credentials are entered
6
+ * through the credential form the `aishell-credentials` chips open. What the
7
+ * row should tell the user is the one fact the old card extension distilled —
8
+ * ready or not — so the browser half paints it as a status dot beside the
9
+ * row's name and hides the header's Edit button (see
10
+ * `client/provider-row-shadow.ts`).
11
+ *
12
+ * The readiness fact is read from the `aishell-client-credentials` settings
13
+ * namespace (`aishell-credentials` owns it; the shadow never names accounts
14
+ * or manages anything). The shadow follows that namespace through
15
+ * `whileServed`, so a deployment without that plugin (this LLM package
16
+ * installed standalone) shows no trace of the dot either — the fact is
17
+ * unknown, not false.
18
+ *
19
+ * The dot's hover copy rides this package's own locale namespace. The shadow
20
+ * is a DOM patch that does not re-render on a locale switch, so the install
21
+ * is redone on `locale/change` — the repaint is attribute writes over rows
22
+ * the sweep re-finds, not a page mutation.
23
+ *
24
+ * @module @huaweidevtools/aishell-dsh-llm/client
25
+ */
26
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
27
+ export type { ProviderRowShadowDisposer, ProviderRowTitles, } from './provider-row-shadow.ts';
28
+ export { installProviderRowShadow, ROW_DOT_MARKER, ROW_EDIT_HIDDEN, } from './provider-row-shadow.ts';
29
+ /** Required services. */
30
+ export declare const inject: string[];
31
+ export declare function apply(ctx: ClientContext): void;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * aishell-llm locale dictionaries — the Models-page provider row's copy.
3
+ *
4
+ * The row shadow is a DOM patch, not a React render, so its strings do not
5
+ * ride the framework's locale seat: the install reads them through `t` at
6
+ * install time and repaints on a locale switch (the client half re-installs
7
+ * on `locale/change`). zh is the key-set source of truth; en mirrors it.
8
+ *
9
+ * @module @huaweidevtools/aishell-dsh-llm/client/locales
10
+ */
11
+ /** Dictionary namespace owned by this plugin. */
12
+ export declare const NS: "aishell-llm";
13
+ /** Simplified Chinese dictionary (the key-set source of truth). */
14
+ export declare const zh: {
15
+ readonly 'provider.ready': "已就绪";
16
+ readonly 'provider.warn': "未就绪 — 凭据未配置(点击对话输入框上方的凭据按钮填写 AK/SK)";
17
+ };
18
+ /** English dictionary. */
19
+ export declare const en: {
20
+ readonly 'provider.ready': "Ready";
21
+ readonly 'provider.warn': "Not ready — credentials missing (click the credentials button above the chat input to enter AK/SK)";
22
+ };
23
+ /** All keys (derived from the zh dictionary, the source of truth). */
24
+ export type AishellLlmLocaleKey = keyof typeof zh;
25
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
26
+ interface LocaleNamespaceMap {
27
+ 'aishell-llm': AishellLlmLocaleKey;
28
+ }
29
+ }
30
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Runtime shadow of the aishell provider's Models-page row.
3
+ *
4
+ * The Models page renders each configured provider as a row whose header
5
+ * (`rowHead`) shows the display name followed by an Edit button the aishell
6
+ * family has no use for: the profile has no fields the host editor knows how
7
+ * to write (the old card extension said so itself), and the row's directory
8
+ * entry names no api-key credential, so the host header also paints no status
9
+ * dot. What the user actually needs from this row is the one fact the old
10
+ * card distilled — ready or not — and that reads better beside the name than
11
+ * in a card below the header.
12
+ *
13
+ * The header lives in ui-settings-models, digest-protected with no slot for
14
+ * the identity half (the `settings.models.provider-card` seat extends the
15
+ * area BELOW the header, which is exactly the part this family no longer
16
+ * wants). The shadow therefore works the way aishell-brand's host residues
17
+ * do: find the row, patch the DOM. The row is located by its rendered name
18
+ * (the directory entry's displayName, `PROVIDER_DISPLAY_NAME` — a literal
19
+ * both halves share); the class names beside it are CSS-module hashes, so
20
+ * they are matched by the same `[class*="…"]` substring selectors the hero
21
+ * patch already relies on.
22
+ *
23
+ * What the shadow does to a found row:
24
+ *
25
+ * 1. a status dot after the name — the host credential dot's own 8px
26
+ * geometry and success/warn colors, green when AK/SK are stored, warn
27
+ * when they are not, with the readiness copy (installer-supplied, so it
28
+ * follows the active locale) as its title;
29
+ * 2. the row's Edit button is hidden — the row is read-only, and the only
30
+ * control the header keeps is whatever the host itself decides (Remove,
31
+ * for user-added rows; this family's row is not removable). The hide is
32
+ * a marker-keyed `display: none !important` rule, not the `hidden`
33
+ * attribute: the button's own class sets `display: inline-flex`, and an
34
+ * author declaration beats the UA sheet's `[hidden] { display: none }`,
35
+ * so the attribute alone leaves the button visible.
36
+ *
37
+ * The Models page re-renders the rows (credential saves, pushed
38
+ * invalidations), so a MutationObserver keeps the shadow applied for as long
39
+ * as the page exists. Shadowing is a default, not a force: install() and the
40
+ * disposer it returns are symmetric, so disabling the plugin restores the
41
+ * official row exactly — dot removed, Edit button back.
42
+ *
43
+ * @module @huaweidevtools/aishell-dsh-llm/client/provider-row-shadow
44
+ */
45
+ /**
46
+ * The disposer stops the observer, removes every dot the install added, and
47
+ * restores every hidden Edit button; a no-op install returns this.
48
+ */
49
+ export type ProviderRowShadowDisposer = () => void;
50
+ /** Marker attribute the dot carries (exposed for the spec). */
51
+ export declare const ROW_DOT_MARKER = "data-aishell-provider-dot";
52
+ /** Marker attribute a hidden Edit button carries (exposed for the spec). */
53
+ export declare const ROW_EDIT_HIDDEN = "data-aishell-edit-hidden";
54
+ /** The dot's own CSS: the host credential dot's geometry and palette. */
55
+ export declare const ROW_DOT_CSS: string;
56
+ /** Title copy for the dot, by readiness — supplied by the installer so it
57
+ * follows the active locale (this module is a DOM patch with no locale seat
58
+ * of its own). The warn state keeps the actionable half — where to enter
59
+ * AK/SK — because that is the one thing a fresh user cannot guess; the ready
60
+ * state needs nothing beyond itself. */
61
+ export interface ProviderRowTitles {
62
+ /** Hover copy when AK/SK are stored. */
63
+ readonly ok: string;
64
+ /** Hover copy when they are not. */
65
+ readonly warn: string;
66
+ }
67
+ /**
68
+ * Install the provider-row shadow on a document.
69
+ *
70
+ * `ready` is the credential fact as the browser half derives it from the
71
+ * settings namespace `aishell-credentials` owns (see `client/index.ts`);
72
+ * `titles` is the dot's hover copy in the active locale (the installer
73
+ * translates; this module holds only the DOM work). Neither is read from any
74
+ * store here, so a later credential save or locale switch is a reinstall with
75
+ * the new values, not a re-derivation buried in here. Sweeps the page once,
76
+ * then observes the body so re-rendered rows are covered too. Returns the
77
+ * disposer that stops the observer and restores every row.
78
+ */
79
+ export declare function installProviderRowShadow(target: Document, ready: boolean, titles: ProviderRowTitles): ProviderRowShadowDisposer;
80
+ //# sourceMappingURL=provider-row-shadow.d.ts.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Request-image preparation for the aishell gateway adapter.
3
+ *
4
+ * The harness hands an adapter durable *references*, never bytes: an
5
+ * {@link ImageBlock} carries an opaque `attachmentId` that is explicitly
6
+ * "never a filesystem path or bearer URL". Turning one into the base64 payload
7
+ * an OpenAI `image_url` part needs is therefore an asynchronous read against
8
+ * the mounted attachment service — which is why this runs as a step ahead of
9
+ * serialization rather than inside it.
10
+ *
11
+ * Mirrors the official adapters' shape (`dsh-llm-pi-ai`'s `collectImageRefs` /
12
+ * `prepareRequestImages`, `dsh-llm-deepseek`'s `prepareImages`): collect the
13
+ * retained references, ask the attachment service for one deterministic
14
+ * request version each, and let the route's budget decide whether the request
15
+ * fits.
16
+ *
17
+ * @module @huaweidevtools/aishell-dsh-llm/images
18
+ */
19
+ import type { AttachmentStore, ImageAttachmentRef, RequestImageAttachment } from '@deepseek-ai/dsh-attachment';
20
+ import type { ImageAttachmentAccessResolver, RequestMessage } from '@deepseek-ai/dsh-llm';
21
+ /** Key under which one durable image's request version is held. */
22
+ export type ImageKey = ImageAttachmentRef['attachmentId'];
23
+ /** Request versions by durable attachment id, as {@link prepareRequestImages} returns them. */
24
+ export type RequestImages = ReadonlyMap<ImageKey, RequestImageAttachment>;
25
+ /**
26
+ * The route's per-image projection policy: the total-pixel cap one image is
27
+ * scaled into, and the encoded-byte target the encoder aims for. Both are
28
+ * config fields on this plugin's Config, sharing the pi-ai defaults.
29
+ */
30
+ export interface ImageRequestBudget {
31
+ /** Positive width-times-height cap one request image is projected into. */
32
+ readonly maxPixels: number;
33
+ /** Encoded-byte target per request image; the smallest ladder output wins when none fits. */
34
+ readonly maxBytes: number;
35
+ }
36
+ /**
37
+ * Execution-world path resolver, re-exported under this module's vocabulary.
38
+ * The official type rather than a local restatement: it is what
39
+ * `requestImageHandleText` / `offloadedImageText` accept, so an ad-hoc copy
40
+ * could drift from the contract it is passed into.
41
+ */
42
+ export type ImageAccessResolver = ImageAttachmentAccessResolver;
43
+ /**
44
+ * True when any message carries an image block — the guard that decides
45
+ * whether this adapter needs the attachment service at all.
46
+ *
47
+ * @param messages - complete request history.
48
+ * @returns whether any message content holds an image.
49
+ */
50
+ export declare function historyHasImage(messages: readonly RequestMessage[]): boolean;
51
+ /**
52
+ * Reject image blocks in roles the wire format cannot carry them in.
53
+ *
54
+ * A user or tool-result message maps onto content parts, so an image there is
55
+ * representable. Every other role serializes to a plain string (assistant
56
+ * text plus tool calls, system/developer prose), where an image would be
57
+ * silently dropped — the exact failure this module exists to remove, so it
58
+ * fails loud instead.
59
+ *
60
+ * @param messages - complete request history.
61
+ * @throws LlmError `UNSUPPORTED_CONTENT` naming the offending role.
62
+ */
63
+ export declare function assertImageRoles(messages: readonly RequestMessage[]): void;
64
+ /**
65
+ * Resolve one request version per retained image, keyed by durable attachment
66
+ * id so a reference shared by several occurrences is read once.
67
+ *
68
+ * Occurrences already marked `offloaded` are skipped: their bytes are not sent
69
+ * (serialization replaces them with placeholder text), so reading them would
70
+ * be work whose result nothing consumes.
71
+ *
72
+ * @param messages - complete request history.
73
+ * @param attachments - mounted attachment service.
74
+ * @param budget - projection policy for this route.
75
+ * @param signal - request cancellation, honoured by the attachment read.
76
+ * @returns request versions by durable attachment id.
77
+ */
78
+ export declare function prepareRequestImages(messages: readonly RequestMessage[], attachments: AttachmentStore, budget: ImageRequestBudget, signal?: AbortSignal): Promise<RequestImages>;
79
+ /**
80
+ * Request version for one retained occurrence, or a loud failure.
81
+ *
82
+ * Every image block reaching serialization must have been prepared, so a
83
+ * miss is a bug in this adapter rather than bad input — but the alternative
84
+ * (letting the lookup produce `undefined` and reading `.bytes` off it) turns
85
+ * it into a `TypeError` from deep inside the request body, so it is named.
86
+ *
87
+ * @param images - prepared request versions.
88
+ * @param ref - durable reference of one retained occurrence.
89
+ * @returns the request version carrying that image's bytes.
90
+ * @throws LlmError `INVALID_REQUEST` when the reference was never prepared.
91
+ */
92
+ export declare function requireRequestImage(images: RequestImages, ref: ImageAttachmentRef): RequestImageAttachment;
93
+ /**
94
+ * Replace every offloaded occurrence with its placeholder text.
95
+ *
96
+ * The durable `offloaded` mark is a surface fact shared by all routes; only
97
+ * the placeholder wording is route-owned. Request assembly does not do this
98
+ * for adapters, so an adapter that skips it would send an image whose bytes
99
+ * it deliberately never loaded.
100
+ *
101
+ * @param messages - complete request history.
102
+ * @param resolveAccess - execution-world path resolver, when one is available.
103
+ * @returns the history with offloaded occurrences projected to text.
104
+ */
105
+ export declare function projectOffloaded(messages: readonly RequestMessage[], resolveAccess?: ImageAccessResolver): readonly RequestMessage[];
106
+ /**
107
+ * Fail the request when its retained images exceed the route's byte budget.
108
+ *
109
+ * This adapter does not offload on its own: the durable decision belongs to
110
+ * the surface, and `dsh-compaction-image-offload` (mounted by `dsh-base`) is
111
+ * the component that records it and retries the step. Throwing the canonical
112
+ * code with the required count is what hands it that decision.
113
+ *
114
+ * @param messages - complete request history, carrying the surface's offload marks.
115
+ * @param images - prepared request versions.
116
+ * @param maxBytes - accumulated represented base64 bytes this route accepts.
117
+ * @throws LlmError `IMAGE_OFFLOAD_REQUIRED` carrying how many more occurrences must be offloaded.
118
+ */
119
+ export declare function assertRequestImageBudget(messages: readonly RequestMessage[], images: RequestImages, maxBytes: number): void;
120
+ //# sourceMappingURL=images.d.ts.map
@@ -10,20 +10,54 @@
10
10
  * Credential management (settings namespace + watch → store transfer) lives
11
11
  * in `aishell-credentials`; this plugin only consumes the stored credentials.
12
12
  *
13
+ * This plugin also declares the `aishell` route in the configurable-provider
14
+ * directory (`registerConfigurableProviders`), which is what the Models page
15
+ * joins with live routes — and it carries a Config schema, without which the
16
+ * settings mirror (and therefore the Models row) never lists this namespace.
17
+ * The row itself carries no card: the browser half replaces the page's
18
+ * Edit-card extension with a status dot beside the row's name (see
19
+ * `client/provider-row-shadow.ts`).
20
+ *
13
21
  * @module @huaweidevtools/aishell-dsh-llm
14
22
  */
15
- import type { Context } from '@deepseek-ai/cordis';
23
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
24
+ import z from '@deepseek-ai/schemastery';
16
25
  export type { GatewayAdapter, GatewayAdapterOptions } from './adapter.ts';
17
26
  /** Plugin config. */
18
27
  export interface Config {
19
28
  /** Chat API base URL. Default: the public aishell gateway. */
20
- readonly baseUrl?: string;
21
- /** Credential store service name; defaults to `'aishell-dsh'`. */
22
- readonly keyringService?: string;
29
+ readonly baseUrl: Volatile<string>;
30
+ /** Total-pixel cap one request image is projected into. */
31
+ readonly requestImagePixelBudget: Volatile<number>;
32
+ /** Encoded-byte target for one request image. */
33
+ readonly requestImageMaxBytes: Volatile<number>;
34
+ /** Accumulated base64 image bytes one request accepts before offload is required. */
35
+ readonly maxRequestImageBytes: Volatile<number>;
23
36
  }
37
+ /**
38
+ * Settings schema for this entry.
39
+ *
40
+ * The schema exists for two reasons beyond configurability: the settings
41
+ * mirror only lists namespaces whose plugin declares a Config, and the Models
42
+ * page derives a provider row's existence from that listing — without a
43
+ * schema, the `aishell` route registers its adapter but never shows up as a
44
+ * configurable provider.
45
+ */
46
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
47
+ baseUrl: z<string, string, "volatile-defined">;
48
+ requestImagePixelBudget: z<number, number, "volatile-defined">;
49
+ requestImageMaxBytes: z<number, number, "volatile-defined">;
50
+ maxRequestImageBytes: z<number, number, "volatile-defined">;
51
+ }>>, Schemastery.ObjectT<NoInfer<{
52
+ baseUrl: z<string, string, "volatile-defined">;
53
+ requestImagePixelBudget: z<number, number, "volatile-defined">;
54
+ requestImageMaxBytes: z<number, number, "volatile-defined">;
55
+ maxRequestImageBytes: z<number, number, "volatile-defined">;
56
+ }>>, "plain">;
24
57
  export declare const name = "aishell-llm";
25
58
  export declare const inject: string[];
26
59
  /** The provider route this plugin owns. */
27
60
  export declare const PROVIDER = "aishell";
28
- export declare function apply(ctx: Context, config?: Config): void;
61
+ export { PROVIDER_DISPLAY_NAME } from './provider-name.ts';
62
+ export declare function apply(ctx: Context, config: Config): void;
29
63
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The aishell provider's display name, shared by both plugin halves.
3
+ *
4
+ * The Host half registers it on the configurable-provider directory (the
5
+ * Models page renders it as the provider row's name), and the browser half's
6
+ * row shadow finds that row by matching the rendered name against this
7
+ * literal (see `client/provider-row-shadow.ts`). It must stay a single
8
+ * literal string — the shadow compares `textContent` with it.
9
+ *
10
+ * It is also the name `providerInfo()` reports, so the picker and the Models
11
+ * row call the route the same thing: this package's whole surface is one
12
+ * product, and qualifying it by cloud ("AI Shell (Huawei Cloud)") added a
13
+ * distinction no reader needed — there is no second AI Shell to tell it
14
+ * apart from.
15
+ */
16
+ export declare const PROVIDER_DISPLAY_NAME = "AI Shell";
17
+ //# sourceMappingURL=provider-name.d.ts.map
@@ -1,13 +1,30 @@
1
1
  /**
2
2
  * Serialize harness {@link GenerateOptions} into an OpenAI-compatible wire request.
3
3
  *
4
- * Text-only: image blocks are not mapped (the gateway adapter is for text
5
- * models; image support can be added later by extending `blocksToText`).
4
+ * Messages without images serialize to a plain string, exactly as before image
5
+ * support existed. A message carrying an image serializes to an ordered part
6
+ * list instead — a handle-text part naming the image, then the image itself as
7
+ * a base64 data URL — which is the only shape an OpenAI-compatible endpoint
8
+ * accepts image input in.
9
+ *
10
+ * Image bytes are not read here. The harness hands over durable references,
11
+ * not bytes, so `prepareRequestImages` resolves them one step ahead of this
12
+ * call and passes the result in; that keeps this function synchronous and
13
+ * keeps the one await in the adapter, where the attachment service is
14
+ * reachable.
6
15
  *
7
16
  * @module @huaweidevtools/aishell-dsh-llm/serialize
8
17
  */
9
18
  import type { GenerateOptions } from '@deepseek-ai/dsh-llm';
19
+ import { type ImageAccessResolver, type RequestImages } from './images.ts';
10
20
  import type { WireRequest } from './types.ts';
11
- /** Build the POST body for `/chat/completions`. */
12
- export declare function serializeRequest(options: GenerateOptions): WireRequest;
21
+ /**
22
+ * Build the POST body for `/chat/completions`.
23
+ *
24
+ * @param options - provider-neutral request.
25
+ * @param images - prepared request versions, required only when the history carries a retained image.
26
+ * @param resolveAccess - execution-world path resolver for image handle text, when a provider exposes one.
27
+ * @returns the OpenAI-compatible request body.
28
+ */
29
+ export declare function serializeRequest(options: GenerateOptions, images?: RequestImages, resolveAccess?: ImageAccessResolver): WireRequest;
13
30
  //# sourceMappingURL=serialize.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * OpenAI-compatible chat-completions wire format (text-only).
2
+ * OpenAI-compatible chat-completions wire format.
3
3
  *
4
4
  * @module @huaweidevtools/aishell-dsh-llm/types
5
5
  */
@@ -16,6 +16,25 @@ export interface WireRequest {
16
16
  max_tokens?: number;
17
17
  stop?: string[];
18
18
  }
19
+ /** One message's content: plain text, or an ordered part list when it carries an image. */
20
+ export type WireContent = string | WireContentPart[];
21
+ /**
22
+ * One content part. Only the two shapes this adapter emits are declared: text,
23
+ * and the base64 data URL an OpenAI-compatible endpoint reads an image from.
24
+ * The harness's `reasoning` blocks fold into text; `file` blocks are projected
25
+ * to handle text by request assembly before any adapter sees them.
26
+ */
27
+ export type WireContentPart = WireTextPart | WireImageUrlPart;
28
+ export interface WireTextPart {
29
+ type: 'text';
30
+ text: string;
31
+ }
32
+ export interface WireImageUrlPart {
33
+ type: 'image_url';
34
+ image_url: {
35
+ url: string;
36
+ };
37
+ }
19
38
  /** One wire message. */
20
39
  export type WireMessage = WireSystemMessage | WireUserMessage | WireAssistantMessage | WireToolMessage;
21
40
  export interface WireSystemMessage {
@@ -24,7 +43,7 @@ export interface WireSystemMessage {
24
43
  }
25
44
  export interface WireUserMessage {
26
45
  role: 'user';
27
- content: string;
46
+ content: WireContent;
28
47
  }
29
48
  export interface WireAssistantMessage {
30
49
  role: 'assistant';
@@ -34,6 +53,13 @@ export interface WireAssistantMessage {
34
53
  export interface WireToolMessage {
35
54
  role: 'tool';
36
55
  tool_call_id: string;
56
+ /**
57
+ * Always a string, even when the tool result carried an image. OpenAI's
58
+ * tool message takes text only; an image from a tool result is hoisted into
59
+ * a separate `user` turn instead, which is what the one OpenAI-path
60
+ * serializer in the ecosystem does. Widening this field would produce a
61
+ * request that some gateways reject with 400 and others silently drop.
62
+ */
37
63
  content: string;
38
64
  }
39
65
  export interface WireAssistantToolCall {
@@ -60,25 +86,33 @@ export interface WireChunk {
60
86
  /** One model entry from the GET /models response (OpenAI-compatible). */
61
87
  export interface WireModel {
62
88
  id: string;
89
+ /**
90
+ * Display name, when the gateway sends one. Outside OpenAI's own model
91
+ * object (which carries `id` alone), so it is an extension like the
92
+ * metadata below, and the gateway omits it for a model that declares none.
93
+ *
94
+ * This is the field that lets an id stay a durable routing key while the
95
+ * label a person reads stays free to change — the split every official
96
+ * adapter's `name ?? id` expresses. Without it the two roles collapse onto
97
+ * the id, and a reserved name like `trial` has to read well in a picker.
98
+ */
99
+ name?: string;
63
100
  object?: string;
64
101
  owned_by?: string;
65
102
  created?: number;
66
- capabilities?: WireModelCapabilities;
103
+ /**
104
+ * The gateway's request-modality list (`["text","image"]`) — the catalog's
105
+ * own answer to "what does this model accept", and the only field this
106
+ * adapter reads for capability. A sibling `capabilities` object is not
107
+ * declared: the gateway may send one, and its `image: boolean` reads like a
108
+ * fallback for an absent `modalities`, but nothing here consumes it, so
109
+ * declaring it would only suggest a fallback that does not exist.
110
+ */
67
111
  modalities?: string[];
68
112
  reasoning_effort?: string[];
69
113
  limits?: WireModelLimits;
70
114
  pricing?: WireModelPricing;
71
115
  }
72
- export interface WireModelCapabilities {
73
- text?: boolean;
74
- image?: boolean;
75
- video?: boolean;
76
- audio?: boolean;
77
- streaming?: boolean;
78
- thinking?: boolean;
79
- tool_call?: boolean;
80
- embedding?: boolean;
81
- }
82
116
  export interface WireModelLimits {
83
117
  context_window?: number;
84
118
  max_prompt_tokens?: number;