@vibedev-si/dsh-vibedev 0.2.1
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 +155 -0
- package/THIRD-PARTY-NOTICES.txt +566 -0
- package/client/client.js +1368 -0
- package/cordis.patch.yml +5 -0
- package/icon.svg +7 -0
- package/lib/account/index.d.ts +118 -0
- package/lib/anthropic-messages-KOS0z8Fy.js +13472 -0
- package/lib/auth/credentials.d.ts +47 -0
- package/lib/auth/login.d.ts +154 -0
- package/lib/gateway/assets.d.ts +61 -0
- package/lib/gateway/catalog.d.ts +126 -0
- package/lib/gateway/errors.d.ts +76 -0
- package/lib/gateway/http.d.ts +94 -0
- package/lib/index.d.ts +105 -0
- package/lib/index.js +7301 -0
- package/lib/llm/adapter.d.ts +129 -0
- package/lib/llm/catalog.d.ts +75 -0
- package/lib/llm/context.d.ts +54 -0
- package/lib/llm/index.d.ts +69 -0
- package/lib/llm/limits.d.ts +19 -0
- package/lib/llm/models.d.ts +67 -0
- package/lib/llm/replay.d.ts +66 -0
- package/lib/llm/stream.d.ts +46 -0
- package/lib/media/chat.d.ts +49 -0
- package/lib/media/sources.d.ts +46 -0
- package/lib/models-CAcLevEN.js +1045 -0
- package/lib/node-Cujw_a4_.js +2403 -0
- package/lib/node-vkDR3GtK.js +25 -0
- package/lib/openai-completions-DqTCiqpS.js +983 -0
- package/lib/openai-prompt-cache-kzRZVokq.js +9567 -0
- package/lib/openai-responses-DygWnsye.js +832 -0
- package/lib/pricing.d.ts +49 -0
- package/lib/runtime.d.ts +150 -0
- package/lib/search/provider.d.ts +86 -0
- package/lib/service.d.ts +99 -0
- package/lib/tasks/remote.d.ts +47 -0
- package/lib/tasks/store.d.ts +132 -0
- package/lib/tasks/tracker.d.ts +127 -0
- package/lib/tools/account.d.ts +21 -0
- package/lib/tools/audio.d.ts +13 -0
- package/lib/tools/common.d.ts +77 -0
- package/lib/tools/image.d.ts +50 -0
- package/lib/tools/models.d.ts +13 -0
- package/lib/tools/tasks.d.ts +13 -0
- package/lib/tools/transcribe.d.ts +19 -0
- package/lib/tools/video.d.ts +48 -0
- package/lib/transform-messages-Csvf2ItW.js +1023 -0
- package/lib/util/files.d.ts +80 -0
- package/lib/video/plan.d.ts +96 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +127 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The VibeDev gateway behind the Harness LLM seam.
|
|
3
|
+
*
|
|
4
|
+
* Shaped after `@deepseek-ai/dsh-llm-pi-ai`'s adapter (src/adapter.ts,
|
|
5
|
+
* dsh-v0.2.0-rc.2): one immutable snapshot per catalog, captured before an
|
|
6
|
+
* operation's first await, so a catalog refresh mid-reply only affects the next
|
|
7
|
+
* step. What differs is what a gateway needs:
|
|
8
|
+
*
|
|
9
|
+
* - one route serves every protocol, each model naming its own (see models.ts);
|
|
10
|
+
* - the credential is the account's access token (or a development key), sent
|
|
11
|
+
* as `Authorization: Bearer` on every protocol;
|
|
12
|
+
* - Anthropic requests carry `metadata.user_id = {device_id, session_id}` and
|
|
13
|
+
* OpenAI requests `session_id` and `prompt_cache_key`, which is how the gateway
|
|
14
|
+
* keeps one session on one upstream account so its prompt cache keeps hitting;
|
|
15
|
+
* - "off" is spelled out where the catalog offers `none`, and Responses drops
|
|
16
|
+
* `temperature` while reasoning;
|
|
17
|
+
* - a 401 hands the credential back to its owner and, when the owner renewed it,
|
|
18
|
+
* sends the request once more; failures carry this plugin's own codes
|
|
19
|
+
* (`VIBEDEV_SIGN_IN_REQUIRED`, `VIBEDEV_INSUFFICIENT_BALANCE`) because the
|
|
20
|
+
* Harness's account codes raise the DeepSeek account's prompts.
|
|
21
|
+
*
|
|
22
|
+
* Ported from the VibeDev app's `@vibedev/dsh-llm-gateway` (vibedev-app
|
|
23
|
+
* f08333ee7f), where the same models sit on the app's own account channel.
|
|
24
|
+
*
|
|
25
|
+
* @module dsh-vibedev/llm/adapter
|
|
26
|
+
*/
|
|
27
|
+
import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
28
|
+
import type { GenerateOptions, ImageAttachmentAccess, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, PreparedAdapterCall, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
29
|
+
import type { AttachmentStore, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
30
|
+
import type { GatewayModel } from './catalog.js';
|
|
31
|
+
/** The credential for one request. */
|
|
32
|
+
export interface GatewayCredential {
|
|
33
|
+
readonly token: string;
|
|
34
|
+
/**
|
|
35
|
+
* `account`: the host app's VibeDev account (the VibeDev app); `plugin`: this
|
|
36
|
+
* plugin's own sign-in; `key`: a development key. Account and plugin tokens
|
|
37
|
+
* go back to their owner on a 401; a development key does not.
|
|
38
|
+
*/
|
|
39
|
+
readonly kind: 'account' | 'plugin' | 'key';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Failure code for "no VibeDev sign-in": deliberately not the Harness's
|
|
43
|
+
* `ACCOUNT_SIGN_IN_REQUIRED`, which belongs to the DeepSeek account and would
|
|
44
|
+
* raise its sign-in prompt; the chat shows this failure's own message.
|
|
45
|
+
*/
|
|
46
|
+
export declare const SIGN_IN_REQUIRED_CODE = "VIBEDEV_SIGN_IN_REQUIRED";
|
|
47
|
+
/** Failure code for a used-up balance; not the Harness's `ACCOUNT_QUOTA`, which opens the DeepSeek top-up. */
|
|
48
|
+
export declare const INSUFFICIENT_BALANCE_CODE = "VIBEDEV_INSUFFICIENT_BALANCE";
|
|
49
|
+
/** What a person reads when a VibeDev model is used before signing in. */
|
|
50
|
+
export declare const SIGN_IN_MESSAGE: string;
|
|
51
|
+
/** What a person reads when the gateway ended the sign-in and a refresh could not renew it. */
|
|
52
|
+
export declare const SESSION_ENDED_MESSAGE: string;
|
|
53
|
+
/**
|
|
54
|
+
* What a person reads on a used-up balance.
|
|
55
|
+
* @param origin - the gateway origin, whose `/purchase` page tops up.
|
|
56
|
+
* @returns the message.
|
|
57
|
+
*/
|
|
58
|
+
export declare function balanceMessage(origin: string): string;
|
|
59
|
+
/** Constructor options for {@link GatewayAdapter}. */
|
|
60
|
+
export interface GatewayAdapterOptions {
|
|
61
|
+
/** The route key the adapter registers under. */
|
|
62
|
+
readonly provider: string;
|
|
63
|
+
/** The name selectors show for the route. */
|
|
64
|
+
readonly displayName: string;
|
|
65
|
+
/** The gateway origin, read per snapshot. */
|
|
66
|
+
readonly origin: () => string;
|
|
67
|
+
/** The current catalog; a new array identity means a new catalog. */
|
|
68
|
+
readonly catalog: () => readonly GatewayModel[];
|
|
69
|
+
/**
|
|
70
|
+
* Read the catalog again because it is empty when models are listed, so a catalog read that missed a
|
|
71
|
+
* fresh sign-in, or failed on a flaky network, recovers on the next list instead of the next timer.
|
|
72
|
+
*/
|
|
73
|
+
readonly refreshCatalog?: () => Promise<void>;
|
|
74
|
+
/** The credential for one request, or undefined when nobody is signed in and no key is configured. */
|
|
75
|
+
readonly resolveCredential: () => Promise<GatewayCredential | undefined>;
|
|
76
|
+
/** Hand a credential the gateway rejected back to its owner, which refreshes it or signs out. */
|
|
77
|
+
readonly rejectCredential?: (credential: GatewayCredential) => Promise<void>;
|
|
78
|
+
/** A stable device id for Anthropic session affinity. */
|
|
79
|
+
readonly deviceId?: () => Promise<string | undefined>;
|
|
80
|
+
readonly retryPolicy?: () => ResolvedRetryPolicy | undefined;
|
|
81
|
+
readonly streamIdleTimeoutMs?: number;
|
|
82
|
+
readonly cacheRetention?: 'none' | 'short' | 'long';
|
|
83
|
+
readonly resolveAttachments?: () => AttachmentStore | undefined;
|
|
84
|
+
readonly resolveImageAccess?: (attachments: AttachmentStore, ref: ImageAttachmentRef) => ImageAttachmentAccess | undefined;
|
|
85
|
+
readonly onReplayDegrade?: (detail: {
|
|
86
|
+
provider: string;
|
|
87
|
+
model: string;
|
|
88
|
+
reason: string;
|
|
89
|
+
}) => void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The gateway as an LLM adapter. Each operation reads the current catalog, so a refresh reaches
|
|
93
|
+
* the next request without a restart, while a request in flight finishes on the catalog it began with.
|
|
94
|
+
*/
|
|
95
|
+
export declare class GatewayAdapter extends LlmAdapter {
|
|
96
|
+
private readonly config;
|
|
97
|
+
private snapshot;
|
|
98
|
+
constructor(config: GatewayAdapterOptions);
|
|
99
|
+
private current;
|
|
100
|
+
private assertOwned;
|
|
101
|
+
private modelOf;
|
|
102
|
+
providerInfo(provider: string): LlmProviderInfo;
|
|
103
|
+
providerRetryPolicy(provider: string): ResolvedRetryPolicy | undefined;
|
|
104
|
+
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
105
|
+
resolveModel(provider: string, model: string, _signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
106
|
+
private modelInfo;
|
|
107
|
+
prepareCall(provider: string, model: string, _signal?: AbortSignal): Promise<PreparedAdapterCall>;
|
|
108
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
109
|
+
private streamWithSnapshot;
|
|
110
|
+
/**
|
|
111
|
+
* Fit pi-ai's payload to the gateway: Chat Completions gets the session's cache key (pi-ai sends it
|
|
112
|
+
* only to api.openai.com), "off" is spelled out where the catalog offers `none`, and Responses
|
|
113
|
+
* drops `temperature` while reasoning, which reasoning models refuse.
|
|
114
|
+
*/
|
|
115
|
+
private shapePayload;
|
|
116
|
+
/**
|
|
117
|
+
* Hand a refused credential back to its owner (which refreshes it, or signs out when the gateway
|
|
118
|
+
* ended the session) and return the credential that replaced it, if one did.
|
|
119
|
+
* @param credential - the credential the gateway answered 401 to.
|
|
120
|
+
* @returns a credential with a different token, or undefined when there is none to try.
|
|
121
|
+
*/
|
|
122
|
+
private renew;
|
|
123
|
+
/**
|
|
124
|
+
* Turn a failed finish into the VibeDev failure the gateway status names: 402 is the balance
|
|
125
|
+
* (with the top-up page), 401 a sign-in that has ended. Both carry this plugin's own codes and
|
|
126
|
+
* messages, so the chat shows them instead of the DeepSeek account's prompts.
|
|
127
|
+
*/
|
|
128
|
+
private accountFailure;
|
|
129
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The VibeDev gateway's model catalog (`GET /v1/models`), narrowed to the chat
|
|
3
|
+
* models this channel can serve and annotated with the wire protocol each one
|
|
4
|
+
* is called over.
|
|
5
|
+
*
|
|
6
|
+
* The gateway answers with its VibeDev projection for signed, signature-exempt,
|
|
7
|
+
* and app-token requests: every entry carries `protocol`, `capabilities` (context,
|
|
8
|
+
* output, thinking, `reasoning_levels`, `default_reasoning`, modalities,
|
|
9
|
+
* `allowed_endpoints`), and prices. Media generators, transcription models, and
|
|
10
|
+
* the models the client drives itself (`internal_role`) are not chat models and
|
|
11
|
+
* never reach the picker.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-vibedev/llm/catalog
|
|
14
|
+
*/
|
|
15
|
+
/** The wire protocols the gateway serves chat models over, named as pi-ai names them. */
|
|
16
|
+
export type GatewayApi = 'anthropic-messages' | 'openai-responses' | 'openai-completions';
|
|
17
|
+
/** A reasoning level as the gateway catalog spells it. */
|
|
18
|
+
export type GatewayReasoningLevel = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
19
|
+
/** One chat model of the catalog with everything the adapter needs to describe and call it. */
|
|
20
|
+
export interface GatewayModel {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
/** The protocol requests for this model use. */
|
|
24
|
+
readonly api: GatewayApi;
|
|
25
|
+
readonly contextWindow: number;
|
|
26
|
+
readonly maxTokens: number;
|
|
27
|
+
readonly input: readonly ('text' | 'image')[];
|
|
28
|
+
/**
|
|
29
|
+
* Levels the user may pick, in escalation order; `undefined` when the catalog
|
|
30
|
+
* states none (no level control), `[]` when the model thinks with no levels.
|
|
31
|
+
*/
|
|
32
|
+
readonly reasoningLevels?: readonly GatewayReasoningLevel[];
|
|
33
|
+
/** The level a new session starts on, when the catalog names one it offers. */
|
|
34
|
+
readonly defaultReasoning?: GatewayReasoningLevel;
|
|
35
|
+
/** Claude's effort-driven thinking, as opposed to a token budget. */
|
|
36
|
+
readonly adaptiveThinking: boolean;
|
|
37
|
+
/** Catalog prices per million tokens, in the catalog's currency (yuan). */
|
|
38
|
+
readonly cost: {
|
|
39
|
+
readonly input: number;
|
|
40
|
+
readonly output: number;
|
|
41
|
+
readonly cacheRead: number;
|
|
42
|
+
readonly cacheWrite: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The protocol a model family is called over best, before the catalog's endpoint list has a say:
|
|
47
|
+
* Claude speaks Messages natively; GPT, the o-series, Codex, and Grok keep their reasoning state
|
|
48
|
+
* on Responses; everything else (DeepSeek, GLM, Kimi, Doubao, ...) passes through Chat Completions.
|
|
49
|
+
* @param id - the catalog model id.
|
|
50
|
+
* @returns the preferred protocol.
|
|
51
|
+
*/
|
|
52
|
+
export declare function preferredApi(id: string): GatewayApi;
|
|
53
|
+
/**
|
|
54
|
+
* The protocol to call one catalog entry over: the family's preferred protocol when the
|
|
55
|
+
* entry allows it, otherwise the first protocol it does allow. An entry listing no endpoint
|
|
56
|
+
* this channel speaks (a native-only Gemini, say) has none.
|
|
57
|
+
* @param id - the catalog model id.
|
|
58
|
+
* @param allowed - the entry's `allowed_endpoints`, when it lists them.
|
|
59
|
+
* @param protocol - the entry's `protocol`, used when it lists no endpoints.
|
|
60
|
+
* @returns the protocol, or undefined when this channel cannot call the model.
|
|
61
|
+
*/
|
|
62
|
+
export declare function chooseApi(id: string, allowed: readonly string[] | undefined, protocol: string | undefined): GatewayApi | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Parse one `/v1/models` body into the chat models this channel serves.
|
|
65
|
+
* @param body - the decoded response: `{object, data: [...]}` or a bare array.
|
|
66
|
+
* @returns the chat models, in catalog order; entries that are not chat models or cannot be called are left out.
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseCatalog(body: unknown): GatewayModel[];
|
|
69
|
+
/**
|
|
70
|
+
* The level a new session starts on: the catalog's default, else `high`, else the highest level
|
|
71
|
+
* not above `high`, else the lowest level offered.
|
|
72
|
+
* @param model - the catalog model.
|
|
73
|
+
* @returns the starting level, or undefined when the model offers no levels.
|
|
74
|
+
*/
|
|
75
|
+
export declare function startingReasoning(model: GatewayModel): GatewayReasoningLevel | undefined;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copied (by way of the VibeDev app) from DeepSeek Harness `@deepseek-ai/dsh-llm-pi-ai` (src/context.ts,
|
|
3
|
+
* dsh-v0.2.0-rc.2; MIT, Copyright (c) 2026 DeepSeek — see THIRD-PARTY-NOTICES.txt), only the
|
|
4
|
+
* image-limit import moved to ./limits.ts,
|
|
5
|
+
* so the gateway adapter can choose a wire protocol per model. Keep it in step with upstream.
|
|
6
|
+
*
|
|
7
|
+
* Harness request-history conversion into pi-ai's Context vocabulary.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-llm-pi-ai/context
|
|
10
|
+
*/
|
|
11
|
+
import type { GenerateOptions, ImageAttachmentAccessResolver } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
13
|
+
import type { Context as PiContext } from '@earendil-works/pi-ai';
|
|
14
|
+
/** Inputs that bind deterministic request images to one current tool execution world. */
|
|
15
|
+
export interface PiImageRequestContext {
|
|
16
|
+
/** Durable provider that resolves request-image bytes and provider-owned host objects. */
|
|
17
|
+
attachments: AttachmentStore;
|
|
18
|
+
/** Resolve current tool access separately from deterministic request-image versions. */
|
|
19
|
+
resolveImageAccess: ImageAttachmentAccessResolver;
|
|
20
|
+
/** Request-level bound on the base64-encoded payload of retained images; omission leaves the bound unchecked. */
|
|
21
|
+
maxRequestImageBytes?: number;
|
|
22
|
+
/** Route pixel and raw encoded-byte budgets. */
|
|
23
|
+
requestImagePolicy?: PiImageRequestBudget;
|
|
24
|
+
}
|
|
25
|
+
/** Per-route budgets from which each request image's target is derived. */
|
|
26
|
+
export interface PiImageRequestBudget {
|
|
27
|
+
/** Total-pixel budget; larger sources are downscaled proportionally. */
|
|
28
|
+
maxPixels: number;
|
|
29
|
+
/** Encoded-byte target for one request image. */
|
|
30
|
+
maxBytes: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Convert text-only harness history to a synchronous pi-ai Context. Tool
|
|
34
|
+
* result names are recovered from preceding assistant tool calls.
|
|
35
|
+
* @param options - the harness request; `options.system`, else a leading `system` message, maps to pi-ai's single `systemPrompt` slot.
|
|
36
|
+
* @param images - absent; selects the synchronous conversion.
|
|
37
|
+
* @param onReplayDegrade - forwarded to {@link toPiAssistant} for each assistant message.
|
|
38
|
+
* @returns the pi-ai context; `tools` is omitted when the request declares none.
|
|
39
|
+
* @throws {LlmError} `UNSUPPORTED_CONTENT` for images in any history role, including a leading system message.
|
|
40
|
+
*/
|
|
41
|
+
export declare function toPiContext(options: GenerateOptions, images?: undefined, onReplayDegrade?: (reason: string) => void): PiContext;
|
|
42
|
+
/**
|
|
43
|
+
* Convert harness history to a pi-ai Context while resolving durable images.
|
|
44
|
+
* Tool result names are recovered from preceding assistant tool calls. Image
|
|
45
|
+
* occurrences the surface marks offloaded become text placeholders; when the
|
|
46
|
+
* retained occurrences' exact base64 payload still exceeds
|
|
47
|
+
* `maxRequestImageBytes`, the call fails with `IMAGE_OFFLOAD_REQUIRED` naming
|
|
48
|
+
* how many more oldest occurrences must be offloaded.
|
|
49
|
+
* @param options - the harness request; `options.system`, else a leading `system` message, maps to pi-ai's single `systemPrompt` slot.
|
|
50
|
+
* @param images - attachment provider, current path resolver, and request limits.
|
|
51
|
+
* @param onReplayDegrade - forwarded to {@link toPiAssistant} for each assistant message.
|
|
52
|
+
* @returns the asynchronously resolved pi-ai context.
|
|
53
|
+
*/
|
|
54
|
+
export declare function toPiContext(options: GenerateOptions, images: PiImageRequestContext, onReplayDegrade?: (reason: string) => void): Promise<PiContext>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The VibeDev gateway's chat models on their own route, `vibedev-gateway`, so
|
|
3
|
+
* they sit in the model pickers next to whatever else the Harness offers — the
|
|
4
|
+
* DeepSeek account's models included — and are paid from the VibeDev balance.
|
|
5
|
+
*
|
|
6
|
+
* The catalog comes from the gateway (`GET /v1/models`) with the credential
|
|
7
|
+
* requests use, so nothing is listed before a sign-in: the catalog is per
|
|
8
|
+
* account. It is read at start, on every sign-in, sign-out or account change,
|
|
9
|
+
* and every `catalogRefreshMinutes`; a changed catalog republishes the route
|
|
10
|
+
* so pickers update without a restart.
|
|
11
|
+
*
|
|
12
|
+
* Inside the VibeDev app the host account already serves these models on the
|
|
13
|
+
* app's own account channel; when the credential comes from the host account
|
|
14
|
+
* the route stays empty, so the pickers do not list every model twice.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-vibedev/llm
|
|
17
|
+
*/
|
|
18
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
19
|
+
import type { GatewayCredential } from './adapter.js';
|
|
20
|
+
import type { GatewayModel } from './catalog.js';
|
|
21
|
+
export { GatewayAdapter, INSUFFICIENT_BALANCE_CODE, SIGN_IN_REQUIRED_CODE } from './adapter.js';
|
|
22
|
+
export type { GatewayAdapterOptions, GatewayCredential } from './adapter.js';
|
|
23
|
+
export { parseCatalog } from './catalog.js';
|
|
24
|
+
export type { GatewayModel } from './catalog.js';
|
|
25
|
+
/** The route the gateway's chat models are listed under. */
|
|
26
|
+
export declare const VIBEDEV_ROUTE = "vibedev-gateway";
|
|
27
|
+
/** What the route needs from the rest of the plugin. */
|
|
28
|
+
export interface GatewayModelsOptions {
|
|
29
|
+
/** Gateway origin, without a trailing slash. */
|
|
30
|
+
readonly origin: string;
|
|
31
|
+
/** The name pickers and the Models page show for the route. */
|
|
32
|
+
readonly displayName: string;
|
|
33
|
+
/** The credential for the next request, from the plugin's credential chain. */
|
|
34
|
+
readonly resolveCredential: () => Promise<GatewayCredential | undefined>;
|
|
35
|
+
/** Hand a credential the gateway refused back to its owner. */
|
|
36
|
+
readonly rejectCredential: (credential: GatewayCredential) => Promise<void>;
|
|
37
|
+
/** A stable id for this installation (Anthropic session affinity). */
|
|
38
|
+
readonly deviceId: () => Promise<string>;
|
|
39
|
+
/** Model ids listed first, in this order, when the catalog offers them. Read at every catalog read. */
|
|
40
|
+
readonly preferredModels: () => readonly string[];
|
|
41
|
+
/** Subscribe to sign-in changes; returns unsubscribe. */
|
|
42
|
+
readonly onCredentialChange: (listener: () => void) => () => void;
|
|
43
|
+
readonly catalogRefreshMinutes?: number;
|
|
44
|
+
readonly catalogTimeoutMs?: number;
|
|
45
|
+
readonly cacheRetention?: 'none' | 'short' | 'long';
|
|
46
|
+
}
|
|
47
|
+
/** The route's live state, for status lines and tests. */
|
|
48
|
+
export interface GatewayModels {
|
|
49
|
+
/** Chat models listed now. */
|
|
50
|
+
models(): readonly GatewayModel[];
|
|
51
|
+
/** Why the route is empty, when it is. */
|
|
52
|
+
hidden(): 'signed-out' | 'host-account' | undefined;
|
|
53
|
+
/** Read the catalog now (after a sign-in, say); resolves when the read ends. */
|
|
54
|
+
refresh(): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The catalog with the preferred models first, in their configured order, and the rest in catalog order.
|
|
58
|
+
* @param models - the parsed catalog.
|
|
59
|
+
* @param preferred - model ids to list first.
|
|
60
|
+
* @returns the reordered catalog.
|
|
61
|
+
*/
|
|
62
|
+
export declare function preferredFirst(models: readonly GatewayModel[], preferred: readonly string[]): GatewayModel[];
|
|
63
|
+
/**
|
|
64
|
+
* Register the route and keep its catalog current.
|
|
65
|
+
* @param ctx - a context with the `llm` service.
|
|
66
|
+
* @param options - origin, credentials and catalog policy.
|
|
67
|
+
* @returns the route's live state.
|
|
68
|
+
*/
|
|
69
|
+
export declare function installGatewayModels(ctx: Context, options: GatewayModelsOptions): GatewayModels;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request limits the gateway adapter and its copied conversion modules read.
|
|
3
|
+
* The values are `@deepseek-ai/dsh-llm-pi-ai`'s defaults, so a request through
|
|
4
|
+
* the gateway is sized exactly as one through the pi-ai channel.
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-vibedev/llm/limits
|
|
7
|
+
*/
|
|
8
|
+
/** Maximum base64-encoded image payload per request before the oldest images become placeholders. */
|
|
9
|
+
export declare const DEFAULT_MAX_REQUEST_IMAGE_BYTES: number;
|
|
10
|
+
/** Total-pixel budget that preserves the complete 2048px normalized attachment. */
|
|
11
|
+
export declare const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET: number;
|
|
12
|
+
/** Raw encoded-byte target before inline base64 expansion. */
|
|
13
|
+
export declare const DEFAULT_REQUEST_IMAGE_MAX_BYTES: number;
|
|
14
|
+
/** Maximum provider idle time while one stream read is outstanding. */
|
|
15
|
+
export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300000;
|
|
16
|
+
/** Context capacity for a catalog model that does not state one. */
|
|
17
|
+
export declare const DEFAULT_CONTEXT_WINDOW = 200000;
|
|
18
|
+
/** Output capability for a catalog model that does not state one. */
|
|
19
|
+
export declare const DEFAULT_MAX_TOKENS = 32768;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-ai model descriptors and the multi-protocol provider for the gateway.
|
|
3
|
+
*
|
|
4
|
+
* A configured pi-ai route speaks one protocol, but one gateway serves Claude
|
|
5
|
+
* over Messages, GPT and Grok over Responses, and DeepSeek and the rest over Chat
|
|
6
|
+
* Completions. Every pi-ai `Model` carries its own `api`, so this provider
|
|
7
|
+
* dispatches each request to the protocol module its model names, and the whole
|
|
8
|
+
* catalog lives on one channel.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-vibedev/llm/models
|
|
11
|
+
*/
|
|
12
|
+
import type { Api, CreateModelsOptions, Model, ModelThinkingLevel, MutableModels, Provider, ThinkingLevelMap } from '@earendil-works/pi-ai';
|
|
13
|
+
import type { GatewayApi, GatewayModel, GatewayReasoningLevel } from './catalog.js';
|
|
14
|
+
/** Every pi-ai thinking level, in pi-ai's escalation order. */
|
|
15
|
+
export declare const THINKING_LEVELS: readonly ModelThinkingLevel[];
|
|
16
|
+
/**
|
|
17
|
+
* pi-ai's level map for the catalog's levels. pi-ai offers `low`/`medium`/`high`
|
|
18
|
+
* (and `minimal`) unless mapped to null, and `xhigh`/`max` only when mapped, so
|
|
19
|
+
* every level the catalog leaves out is closed explicitly. `off` stays open only
|
|
20
|
+
* when the catalog offers `none`; a model that always thinks never shows "off".
|
|
21
|
+
* @param levels - the catalog's levels for one model.
|
|
22
|
+
* @returns the map pi-ai reads to list and spell levels.
|
|
23
|
+
*/
|
|
24
|
+
export declare function thinkingLevelMap(levels: readonly GatewayReasoningLevel[]): ThinkingLevelMap;
|
|
25
|
+
/**
|
|
26
|
+
* The endpoint root the protocol's SDK appends its path to: the Anthropic SDK adds
|
|
27
|
+
* `/v1/messages` itself, the OpenAI SDK expects the `/v1` root.
|
|
28
|
+
* @param origin - the gateway origin.
|
|
29
|
+
* @param api - the model's protocol.
|
|
30
|
+
* @returns the base URL for that protocol.
|
|
31
|
+
*/
|
|
32
|
+
export declare function baseUrlFor(origin: string, api: GatewayApi): string;
|
|
33
|
+
/**
|
|
34
|
+
* Build pi-ai's descriptor for one catalog model on the given provider route.
|
|
35
|
+
* @param model - the catalog model.
|
|
36
|
+
* @param provider - the route the model is registered under.
|
|
37
|
+
* @param origin - the gateway origin.
|
|
38
|
+
* @returns the pi-ai model.
|
|
39
|
+
*/
|
|
40
|
+
export declare function toPiModel(model: GatewayModel, provider: string, origin: string): Model<Api>;
|
|
41
|
+
/**
|
|
42
|
+
* The provider for one catalog snapshot: each request goes to the protocol module its model names.
|
|
43
|
+
* @param input - route id, display name, and the snapshot's models.
|
|
44
|
+
* @param input.id - the provider route key.
|
|
45
|
+
* @param input.name - the display name.
|
|
46
|
+
* @param input.models - pi-ai descriptors for the snapshot's models.
|
|
47
|
+
* @returns the pi-ai provider.
|
|
48
|
+
*/
|
|
49
|
+
export declare function gatewayProvider(input: {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
models: readonly Model<Api>[];
|
|
53
|
+
}): Provider;
|
|
54
|
+
/**
|
|
55
|
+
* Create an empty pi-ai collection. pi-ai's main entry builds one with no
|
|
56
|
+
* providers; its `providers/all` entry would pull every provider's SDK (AWS,
|
|
57
|
+
* Google, Mistral…) into this plugin's bundle for nothing.
|
|
58
|
+
* @param options - credential storage and ambient authentication integrations.
|
|
59
|
+
* @returns a mutable collection with no registered providers.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createModels(options?: CreateModelsOptions): MutableModels;
|
|
62
|
+
/**
|
|
63
|
+
* Selectable levels from pi-ai's view of one model, in escalation order.
|
|
64
|
+
* @param model - descriptor carrying reasoning support and wire mappings.
|
|
65
|
+
* @returns the supported levels.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getSupportedThinkingLevels(model: Model<Api>): ModelThinkingLevel[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copied unchanged (by way of the VibeDev app) from DeepSeek Harness `@deepseek-ai/dsh-llm-pi-ai` (src/replay.ts,
|
|
3
|
+
* dsh-v0.2.0-rc.2; MIT, Copyright (c) 2026 DeepSeek — see THIRD-PARTY-NOTICES.txt)
|
|
4
|
+
* so the gateway adapter can choose a wire protocol per model. Keep it in step with upstream.
|
|
5
|
+
*
|
|
6
|
+
* Durable pi-ai replay metadata and assistant-history reconstruction.
|
|
7
|
+
*
|
|
8
|
+
* Harness content remains the durable source for text and tool calls. This
|
|
9
|
+
* module stores only the provider-native metadata needed to reconstruct a
|
|
10
|
+
* pi-ai assistant message on a later request.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-llm-pi-ai/replay
|
|
13
|
+
*/
|
|
14
|
+
import type { AssistantMessage as HarnessAssistantMessage, ReplayEnvelope } from '@deepseek-ai/dsh-llm';
|
|
15
|
+
import type { Api, AssistantMessage } from '@earendil-works/pi-ai';
|
|
16
|
+
/** Per-block half of the pi-ai replay envelope, one entry per content block. */
|
|
17
|
+
export type PiAiReplayBlock = {
|
|
18
|
+
type: 'text';
|
|
19
|
+
textSignature?: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'reasoning';
|
|
22
|
+
thinkingSignature?: string;
|
|
23
|
+
redacted?: boolean;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'tool-call';
|
|
26
|
+
thoughtSignature?: string;
|
|
27
|
+
};
|
|
28
|
+
/** Versioned response-level half of the pi-ai replay envelope. */
|
|
29
|
+
export interface PiAiReplayResponse {
|
|
30
|
+
kind: 'pi-ai';
|
|
31
|
+
version: 2;
|
|
32
|
+
api: Api;
|
|
33
|
+
provider: string;
|
|
34
|
+
/** Requested model identity, matching the durable assistant source. */
|
|
35
|
+
model: string;
|
|
36
|
+
/** Provider-reported model; replay retains the requested model for signature matching. */
|
|
37
|
+
responseModel?: string;
|
|
38
|
+
responseId?: string;
|
|
39
|
+
/** Provider-native effort for historical replay; absence is preserved. */
|
|
40
|
+
providerThinkingLevel?: string;
|
|
41
|
+
stopReason: AssistantMessage['stopReason'];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Project a successful pi-ai response into the minimal durable replay state.
|
|
45
|
+
* The per-block half is index-aligned with the streamed blocks (pi-ai content
|
|
46
|
+
* order), so `BlockAssembler` prunes an entry with its block whenever assembly
|
|
47
|
+
* removes one.
|
|
48
|
+
* @param message - completed native pi-ai assistant response.
|
|
49
|
+
* @param requestedModel - request identity stored in the assistant source; defaults to the native model.
|
|
50
|
+
* @returns the versioned lossless-JSON replay projection.
|
|
51
|
+
*/
|
|
52
|
+
export declare function toPiReplayState(message: AssistantMessage, requestedModel?: string): ReplayEnvelope;
|
|
53
|
+
/**
|
|
54
|
+
* Convert one durable Harness assistant message into pi-ai history.
|
|
55
|
+
*
|
|
56
|
+
* Durable content is the authoritative record; replay metadata only restores
|
|
57
|
+
* native fidelity (ids, signatures). A replay state this build cannot use —
|
|
58
|
+
* another adapter's kind, another version, a malformed value, or metadata that
|
|
59
|
+
* no longer matches the content — therefore degrades the one message to
|
|
60
|
+
* provider-neutral history instead of failing the request.
|
|
61
|
+
* @param message - model-produced assistant content with provider, model, and optional adapter-owned replay metadata.
|
|
62
|
+
* @param onDegrade - called with the diagnostic reason when an unusable replay
|
|
63
|
+
* state falls back to provider-neutral conversion.
|
|
64
|
+
* @returns a native pi-ai assistant message reconstructed from durable content.
|
|
65
|
+
*/
|
|
66
|
+
export declare function toPiAssistant(message: HarnessAssistantMessage, onDegrade?: (reason: string) => void): AssistantMessage;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copied unchanged (by way of the VibeDev app) from DeepSeek Harness `@deepseek-ai/dsh-llm-pi-ai` (src/stream.ts,
|
|
3
|
+
* dsh-v0.2.0-rc.2; MIT, Copyright (c) 2026 DeepSeek — see THIRD-PARTY-NOTICES.txt)
|
|
4
|
+
* so the gateway adapter can choose a wire protocol per model. Keep it in step with upstream.
|
|
5
|
+
*
|
|
6
|
+
* pi-ai assistant event translation into the Harness streaming protocol.
|
|
7
|
+
*
|
|
8
|
+
* pi-ai tool-call arguments are parsed objects while the Harness keeps their
|
|
9
|
+
* raw JSON representation. pi-ai also reports failures as terminal stream
|
|
10
|
+
* events, which this module maps into Harness finish chunks.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-llm-pi-ai/stream
|
|
13
|
+
*/
|
|
14
|
+
import type { FinishReason, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm';
|
|
15
|
+
import type { AssistantMessage, AssistantMessageEvent, Usage as PiUsage } from '@earendil-works/pi-ai';
|
|
16
|
+
/**
|
|
17
|
+
* Map pi-ai usage (reasoning folded into output by pi-ai).
|
|
18
|
+
* @param usage - cumulative usage from the terminal pi-ai event.
|
|
19
|
+
* @returns harness counts with pi-ai's exact total; cache fields appear only
|
|
20
|
+
* when non-zero (pi-ai reports zeros, not absence).
|
|
21
|
+
*/
|
|
22
|
+
export declare function mapUsage(usage: PiUsage): TokenUsage;
|
|
23
|
+
/**
|
|
24
|
+
* Map a terminal pi-ai event to the harness finish reason.
|
|
25
|
+
* @param message - the assistant message carried by the `done` or `error` event.
|
|
26
|
+
* @param contextWindow - resolved catalog capacity for usage-based overflow detection.
|
|
27
|
+
* @returns the mapped harness reason. Recognized error text, `stop` usage above
|
|
28
|
+
* `contextWindow`, and zero-output `length` usage that fills the window map
|
|
29
|
+
* to `CONTEXT_WINDOW_EXCEEDED`; a `stop` with no content blocks maps to an
|
|
30
|
+
* `EMPTY_RESPONSE` error, while terminal `pending` and `deferred` states map
|
|
31
|
+
* to non-retryable `PI_AI_ERROR` failures.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mapStopReason(message: AssistantMessage, contextWindow?: number): FinishReason;
|
|
34
|
+
/**
|
|
35
|
+
* Translate the pi-ai event stream into StreamChunks. pi-ai never throws
|
|
36
|
+
* mid-stream — failures arrive as `error` events, which become error/aborted
|
|
37
|
+
* `finish` chunks (the harness protocol's other error-delivery style).
|
|
38
|
+
* @param events - one assistant turn's pi-ai event stream.
|
|
39
|
+
* @param contextWindow - resolved catalog capacity for usage-based overflow detection.
|
|
40
|
+
* @param callerSignal - caller cancellation state; an aborted caller makes any
|
|
41
|
+
* in-band terminal error an aborted finish.
|
|
42
|
+
* @param requestedModel - request model identity recorded for durable replay.
|
|
43
|
+
* @returns the harness chunks, ending with `usage` then `finish`; throws
|
|
44
|
+
* `LlmError` (`STREAM_CLOSED`) if the source ends without a terminal event.
|
|
45
|
+
*/
|
|
46
|
+
export declare function toStreamChunks(events: AsyncIterable<AssistantMessageEvent>, contextWindow?: number, callerSignal?: AbortSignal, requestedModel?: string): AsyncGenerator<StreamChunk>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media the user attached in the conversation, addressed as `chat:N` (the Nth
|
|
3
|
+
* most recent attachment of the kind the input needs) or `chat:<kind>:N`
|
|
4
|
+
* (`chat:image:1`, `chat:video:2`, `chat:audio:1`). The model sees attached
|
|
5
|
+
* images but no path to them; this is how it can pass one on as a reference.
|
|
6
|
+
* @module dsh-vibedev/media/chat
|
|
7
|
+
*/
|
|
8
|
+
import type { AttachmentStore, FileAttachmentRef, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
9
|
+
import type { Message } from '@deepseek-ai/dsh-llm';
|
|
10
|
+
import type { LoadedMedia } from './sources.js';
|
|
11
|
+
/** The media kinds a chat reference can name. */
|
|
12
|
+
export type ChatMediaKind = 'image' | 'video' | 'audio';
|
|
13
|
+
/** A parsed `chat:` reference. */
|
|
14
|
+
export interface ChatReference {
|
|
15
|
+
/** The kind named in the reference, or undefined for `chat:N`. */
|
|
16
|
+
readonly kind?: ChatMediaKind;
|
|
17
|
+
/** 1 for the most recent. */
|
|
18
|
+
readonly index: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse a `chat:` reference.
|
|
22
|
+
* @param source - what the model passed.
|
|
23
|
+
* @returns the reference, or undefined when the source is not one.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseChatReference(source: string): ChatReference | undefined;
|
|
26
|
+
interface ChatItem {
|
|
27
|
+
readonly kind: ChatMediaKind | 'other';
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly image?: ImageAttachmentRef;
|
|
30
|
+
readonly file?: FileAttachmentRef;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The user's attachments, most recent first.
|
|
34
|
+
* @param messages - the conversation, oldest first.
|
|
35
|
+
* @returns images and files from user messages.
|
|
36
|
+
*/
|
|
37
|
+
export declare function chatAttachments(messages: readonly Message[]): ChatItem[];
|
|
38
|
+
/**
|
|
39
|
+
* Load one `chat:` reference.
|
|
40
|
+
* @param reference - the parsed reference.
|
|
41
|
+
* @param want - the kind the input needs; `chat:N` counts only attachments of this kind.
|
|
42
|
+
* @param messages - the conversation, oldest first.
|
|
43
|
+
* @param store - the attachment store.
|
|
44
|
+
* @param maxBytes - the largest accepted file.
|
|
45
|
+
* @param signal - cancels the read.
|
|
46
|
+
* @returns the loaded media.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadChatReference(reference: ChatReference, want: ChatMediaKind, messages: readonly Message[], store: AttachmentStore, maxBytes: number, signal?: AbortSignal): Promise<LoadedMedia>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading media inputs: a workspace path, an absolute path, an HTTP(S) link or a
|
|
3
|
+
* `data:` URL. The type is read from the file's own header first, so a file
|
|
4
|
+
* named `.png` that is really a JPEG is treated as a JPEG.
|
|
5
|
+
* @module dsh-vibedev/media/sources
|
|
6
|
+
*/
|
|
7
|
+
/** One loaded input. */
|
|
8
|
+
export interface LoadedMedia {
|
|
9
|
+
/** What the caller passed. */
|
|
10
|
+
readonly source: string;
|
|
11
|
+
/** A file name for uploads. */
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly mime: string;
|
|
14
|
+
readonly data: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
/** How paths are read; the tool layer backs it with the host's filesystem service. */
|
|
17
|
+
export interface SourceReader {
|
|
18
|
+
readPath(path: string, maxBytes: number, signal?: AbortSignal): Promise<{
|
|
19
|
+
data: Uint8Array;
|
|
20
|
+
name: string;
|
|
21
|
+
}>;
|
|
22
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The file extension for a MIME type.
|
|
26
|
+
* @param mime - the type.
|
|
27
|
+
* @param fallback - extension when the type is unknown.
|
|
28
|
+
* @returns the extension, without a dot.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extensionFor(mime: string, fallback?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* The MIME type a file's leading bytes identify, falling back to its name.
|
|
33
|
+
* @param data - the file bytes (the first 64 suffice).
|
|
34
|
+
* @param name - the file name or path, for the extension fallback.
|
|
35
|
+
* @returns the type, or `application/octet-stream` when unknown.
|
|
36
|
+
*/
|
|
37
|
+
export declare function sniffMime(data: Uint8Array, name?: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Load one input.
|
|
40
|
+
* @param source - path, URL or data URL.
|
|
41
|
+
* @param reader - how paths are read, and the fetch to use for links.
|
|
42
|
+
* @param maxBytes - the largest accepted size.
|
|
43
|
+
* @param signal - cancels the load.
|
|
44
|
+
* @returns the bytes, a name and the sniffed type.
|
|
45
|
+
*/
|
|
46
|
+
export declare function loadMedia(source: string, reader: SourceReader, maxBytes: number, signal?: AbortSignal): Promise<LoadedMedia>;
|