@huaweidevtools/aishell-dsh-llm 0.0.1-beta.1 → 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.
- package/LICENSE +1 -1
- package/README.md +45 -9
- package/lib/client.js +234 -0
- package/lib/index.js +1344 -47
- package/lib/types/adapter.d.ts +100 -4
- package/lib/types/client/index.d.ts +32 -0
- package/lib/types/client/locales.d.ts +30 -0
- package/lib/types/client/provider-row-shadow.d.ts +80 -0
- package/lib/types/images.d.ts +120 -0
- package/lib/types/index.d.ts +39 -5
- package/lib/types/provider-name.d.ts +17 -0
- package/lib/types/serialize.d.ts +21 -4
- package/lib/types/sse.d.ts +1 -1
- package/lib/types/types.d.ts +47 -13
- package/package.json +29 -7
package/lib/types/adapter.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
17
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
|
21
|
-
/**
|
|
22
|
-
readonly
|
|
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
|
|
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
|
package/lib/types/serialize.d.ts
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Serialize harness {@link GenerateOptions} into an OpenAI-compatible wire request.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
/**
|
|
12
|
-
|
|
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
|
package/lib/types/sse.d.ts
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
* @module @huaweidevtools/aishell-dsh-llm/sse
|
|
9
9
|
*/
|
|
10
10
|
export declare const DONE = "[DONE]";
|
|
11
|
-
export declare function parseSse(stream: ReadableStream<
|
|
11
|
+
export declare function parseSse(stream: ReadableStream<Uint8Array>): AsyncGenerator<string>;
|
|
12
12
|
//# sourceMappingURL=sse.d.ts.map
|
package/lib/types/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenAI-compatible chat-completions wire format
|
|
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:
|
|
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
|
-
|
|
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;
|