@qvac/harness 0.2.10 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/harness.bundle +13 -11
- package/dist-lib/index.d.ts +26 -1
- package/dist-lib/index.js +94 -3
- package/dist-lib/lib/capabilities/completion.d.ts +2 -0
- package/dist-lib/lib/capabilities/detect-language.d.ts +11 -0
- package/dist-lib/lib/capabilities/detect-language.js +13 -0
- package/dist-lib/lib/capabilities/enhance.d.ts +10 -0
- package/dist-lib/lib/capabilities/enhance.js +29 -0
- package/dist-lib/lib/capabilities/invoke.d.ts +24 -0
- package/dist-lib/lib/capabilities/invoke.js +57 -0
- package/dist-lib/lib/connect.d.ts +4 -1
- package/dist-lib/lib/connect.js +50 -21
- package/dist-lib/lib/contract-types.d.ts +21 -0
- package/dist-lib/lib/helpers/clean-answer.d.ts +1 -0
- package/dist-lib/lib/helpers/clean-answer.js +16 -0
- package/dist-lib/lib/helpers/load-stall.d.ts +6 -0
- package/dist-lib/lib/helpers/load-stall.js +24 -0
- package/dist-lib/lib/hooks/built-in/sandbox.js +3 -1
- package/dist-lib/lib/models/model-pool.d.ts +6 -0
- package/dist-lib/lib/models/model-pool.js +12 -0
- package/dist-lib/lib/models/nmt-catalog.d.ts +34 -0
- package/dist-lib/lib/models/nmt-catalog.js +166 -0
- package/dist-lib/lib/serve.js +33 -4
- package/dist-lib/lib/tools/generate-image.js +2 -25
- package/dist-lib/lib/tools/http-request.js +1 -1
- package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.js +92 -1
- package/dist-lib/lib/tools/skills/manifest.d.ts +12 -0
- package/dist-lib/lib/tools/translate.d.ts +78 -0
- package/dist-lib/lib/tools/translate.js +228 -0
- package/dist-lib/lib/tools/web-fetch.js +12 -2
- package/dist-lib/schema/harness.js +53 -3
- package/dist-lib/spec/bind.js +3 -0
- package/dist-lib/spec/calls.js +1 -0
- package/dist-lib/spec/hrpc/hrpc.json +13 -0
- package/dist-lib/spec/hrpc/index.js +15 -3
- package/dist-lib/spec/hrpc/messages.js +206 -10
- package/dist-lib/spec/hyperschema/index.js +206 -10
- package/dist-lib/spec/hyperschema/schema.json +163 -1
- package/dist-lib/spec/hyperschema/types.d.ts +43 -2
- package/package.json +8 -2
package/dist-lib/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
2
2
|
import ReadyResource from 'ready-resource';
|
|
3
3
|
import { type Agent, type ApproveFn, type AskUserFn, type AttachFn, type CompletionEvent, type FetchAttachmentFn, type Message, type ProgressFn, type RagSearchFn, type ResponseFormat, type ToolDefinition, type ToolExecuteContext } from './lib/capabilities/completion.ts';
|
|
4
|
+
import { type DetectLanguageContext, type DetectedLanguage } from './lib/capabilities/detect-language.ts';
|
|
5
|
+
import { type Capability } from './lib/capabilities/invoke.ts';
|
|
6
|
+
import { type EnhanceMode } from './lib/capabilities/enhance.ts';
|
|
4
7
|
import { type DownloadArgs, type DownloadEventStream } from './lib/capabilities/download.ts';
|
|
5
8
|
import { type EmbedContext } from './lib/capabilities/embed.ts';
|
|
6
9
|
import { type KvCacheDeletionResult } from './lib/capabilities/kv-cache.ts';
|
|
@@ -22,6 +25,10 @@ import type { ResolvedSkillCatalogEntry } from './lib/tools/skills/catalog.ts';
|
|
|
22
25
|
import { type SkillSettingEntry } from './lib/tools/skills/skill-settings.ts';
|
|
23
26
|
export * from './lib/capabilities/completion.ts';
|
|
24
27
|
export { attachmentTurn, loadedSkillsFrom, skillLoadedTurn } from './lib/capabilities/tool-turns.ts';
|
|
28
|
+
export { CAPABILITIES, isCapability, type Capability } from './lib/capabilities/invoke.ts';
|
|
29
|
+
export type { EnhanceMode } from './lib/capabilities/enhance.ts';
|
|
30
|
+
export { EURONANO_LANGUAGES, NMT_FROM_ENGLISH, NMT_TO_ENGLISH, SLM_LANGUAGE_CODES, SLM_LANGUAGE_NAMES, translationPlanFor, type TranslationPlan } from './lib/models/nmt-catalog.ts';
|
|
31
|
+
export type { DetectLanguageContext, DetectedLanguage } from './lib/capabilities/detect-language.ts';
|
|
25
32
|
export * from './lib/capabilities/download.ts';
|
|
26
33
|
export type { EmbedContext } from './lib/capabilities/embed.ts';
|
|
27
34
|
export type { SpeakContext, SpeakResult, SpeakStreamContext, TranscribeContext } from './lib/capabilities/voice.ts';
|
|
@@ -37,7 +44,7 @@ export { requiredSettings, settingEnvName, type SkillSettingEntry, type SkillSet
|
|
|
37
44
|
export type { ToolCatalogEntry } from './lib/tools/catalog.ts';
|
|
38
45
|
export { TOOLSETS, toolsetEnabled, type Toolset } from './lib/tools/toolsets.ts';
|
|
39
46
|
export { markCredentialRejected, markCredentialValid, recordCredentialHealth, resetAllCredentialHealth, resetCredentialHealth } from './lib/tools/skills/credential-health.ts';
|
|
40
|
-
export type { InstallStep, SkillRequires, SkillSetup, SkillSetupField, SkillSetupRoute } from './lib/tools/skills/manifest.ts';
|
|
47
|
+
export type { InstallStep, SkillOAuthConfig, SkillRequires, SkillSetup, SkillSetupField, SkillSetupRoute } from './lib/tools/skills/manifest.ts';
|
|
41
48
|
export { readSetup as normalizeSkillSetup } from './lib/tools/skills/manifest-parse.ts';
|
|
42
49
|
export type { CredentialRecord, CredentialStoreValue } from './lib/tools/skills/auth-resolver.ts';
|
|
43
50
|
export { isAvailableState, SKILL_STATE, type SkillState } from './lib/tools/skills/skill-state.ts';
|
|
@@ -106,6 +113,16 @@ export interface CompletionContext {
|
|
|
106
113
|
askUser?: AskUserFn;
|
|
107
114
|
signal?: BareAbortSignal;
|
|
108
115
|
}
|
|
116
|
+
export interface InvokeContext {
|
|
117
|
+
capability: Capability;
|
|
118
|
+
args: Record<string, unknown>;
|
|
119
|
+
enhance?: EnhanceMode;
|
|
120
|
+
agent?: Agent;
|
|
121
|
+
attach?: AttachFn;
|
|
122
|
+
fetchAttachment?: FetchAttachmentFn;
|
|
123
|
+
progress?: ProgressFn;
|
|
124
|
+
signal?: BareAbortSignal;
|
|
125
|
+
}
|
|
109
126
|
export declare class Harness extends ReadyResource {
|
|
110
127
|
private _tools;
|
|
111
128
|
private _sdk;
|
|
@@ -144,7 +161,10 @@ export declare class Harness extends ReadyResource {
|
|
|
144
161
|
private _diffusionSdk;
|
|
145
162
|
private _imageRuntime;
|
|
146
163
|
private _musicSdk;
|
|
164
|
+
private _translationSdk;
|
|
147
165
|
private _musicRuntime;
|
|
166
|
+
private _translationRuntime;
|
|
167
|
+
private _capabilityTools;
|
|
148
168
|
private _capability;
|
|
149
169
|
private _capabilityInflight;
|
|
150
170
|
private readonly _modelFaults;
|
|
@@ -197,6 +217,8 @@ export declare class Harness extends ReadyResource {
|
|
|
197
217
|
private _loadedModels;
|
|
198
218
|
private _loadPluginSdk;
|
|
199
219
|
private _loadDiffusionSdk;
|
|
220
|
+
private _slmTranslate;
|
|
221
|
+
private _loadTranslationSdk;
|
|
200
222
|
private _loadMusicSdk;
|
|
201
223
|
private _deviceConfig;
|
|
202
224
|
private _createPool;
|
|
@@ -224,6 +246,7 @@ export declare class Harness extends ReadyResource {
|
|
|
224
246
|
private _defaultVoiceSrc;
|
|
225
247
|
private _voiceJob;
|
|
226
248
|
transcribe({ model, audio, signal }: TranscribeContext): Promise<string>;
|
|
249
|
+
detectLanguage(context: DetectLanguageContext): DetectedLanguage[];
|
|
227
250
|
speak({ model, text, signal }: SpeakContext): Promise<SpeakResult>;
|
|
228
251
|
speakStream({ model, text, signal }: SpeakStreamContext): AsyncIterable<SpeakResult>;
|
|
229
252
|
list(options?: ListModelsOptions): Promise<ModelInfo[]>;
|
|
@@ -240,7 +263,9 @@ export declare class Harness extends ReadyResource {
|
|
|
240
263
|
agent?: Agent;
|
|
241
264
|
signal?: BareAbortSignal;
|
|
242
265
|
}): Promise<void>;
|
|
266
|
+
invoke({ capability, args, enhance, agent, attach, fetchAttachment, progress, signal }: InvokeContext): AsyncGenerator<CompletionEvent>;
|
|
243
267
|
completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }: CompletionContext): AsyncGenerator<CompletionEvent>;
|
|
268
|
+
private _residentEnhancer;
|
|
244
269
|
private _makeRoomForCompletion;
|
|
245
270
|
private _evictWedgedModel;
|
|
246
271
|
private _evictAfterLeases;
|
package/dist-lib/index.js
CHANGED
|
@@ -5,7 +5,13 @@ import ReadyResource from 'ready-resource';
|
|
|
5
5
|
import { probeGpuStats } from './lib/hardware/gpu-probe.js';
|
|
6
6
|
import { probeCapabilityProfile } from './lib/hardware/capability-probe.js';
|
|
7
7
|
import { closePythonWorker } from './lib/tools/exec/adapters/python/worker/client.js';
|
|
8
|
-
import { completion } from './lib/capabilities/completion.js';
|
|
8
|
+
import { completion, imageSettingsFrom } from './lib/capabilities/completion.js';
|
|
9
|
+
import { detectLanguage } from './lib/capabilities/detect-language.js';
|
|
10
|
+
import { invoke } from './lib/capabilities/invoke.js';
|
|
11
|
+
import { expandImagePrompt } from './lib/capabilities/enhance.js';
|
|
12
|
+
import { cleanAnswer } from './lib/helpers/clean-answer.js';
|
|
13
|
+
import { isTranslationModel } from './lib/models/nmt-catalog.js';
|
|
14
|
+
import { createTranslateTool, createTranslationRuntime } from './lib/tools/translate.js';
|
|
9
15
|
import { download } from './lib/capabilities/download.js';
|
|
10
16
|
import { createEmbedLane } from './lib/capabilities/embed.js';
|
|
11
17
|
import { deleteAllKvCaches, deleteKvCacheKey } from './lib/capabilities/kv-cache.js';
|
|
@@ -17,7 +23,7 @@ import { AbortedError, raceAbort } from './lib/helpers/race-abort.js';
|
|
|
17
23
|
import { audioGenDownloadMetadata, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, infraModelsFor, modelNameToCapabilityProfile, musicModelSet, setCustomModels } from './lib/models/model-catalog.js';
|
|
18
24
|
import { createModelStatusCache, invalidateModelStatusCache, listModels } from './lib/models/model-list.js';
|
|
19
25
|
import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js';
|
|
20
|
-
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictModelWithin, isResident, leaseCount, loadedModels, modelKey, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
|
|
26
|
+
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, modelKey, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
|
|
21
27
|
import { resolveMainGpu } from './lib/models/gpu-selection.js';
|
|
22
28
|
import { openclCacheDir } from './lib/models/opencl-cache.js';
|
|
23
29
|
import { assertEmbedDevice, createEmbedConfig } from './lib/models/embed-config.js';
|
|
@@ -45,6 +51,8 @@ import { isDeviceSharedCredentialKey, scopeKey } from './lib/tools/skills/scope-
|
|
|
45
51
|
import { settingsScopeKey } from './lib/tools/skills/skill-settings.js';
|
|
46
52
|
export * from './lib/capabilities/completion.js';
|
|
47
53
|
export { attachmentTurn, loadedSkillsFrom, skillLoadedTurn } from './lib/capabilities/tool-turns.js';
|
|
54
|
+
export { CAPABILITIES, isCapability } from './lib/capabilities/invoke.js';
|
|
55
|
+
export { EURONANO_LANGUAGES, NMT_FROM_ENGLISH, NMT_TO_ENGLISH, SLM_LANGUAGE_CODES, SLM_LANGUAGE_NAMES, translationPlanFor } from './lib/models/nmt-catalog.js';
|
|
48
56
|
export * from './lib/capabilities/download.js';
|
|
49
57
|
export * from './lib/models/model-catalog.js';
|
|
50
58
|
export * from './lib/models/model-list.js';
|
|
@@ -116,7 +124,10 @@ export class Harness extends ReadyResource {
|
|
|
116
124
|
_diffusionSdk;
|
|
117
125
|
_imageRuntime;
|
|
118
126
|
_musicSdk;
|
|
127
|
+
_translationSdk;
|
|
119
128
|
_musicRuntime;
|
|
129
|
+
_translationRuntime;
|
|
130
|
+
_capabilityTools;
|
|
120
131
|
_capability = null;
|
|
121
132
|
_capabilityInflight = null;
|
|
122
133
|
_modelFaults = new Map();
|
|
@@ -201,6 +212,16 @@ export class Harness extends ReadyResource {
|
|
|
201
212
|
}
|
|
202
213
|
})
|
|
203
214
|
: null;
|
|
215
|
+
this._translationRuntime = createTranslationRuntime({
|
|
216
|
+
loads: this._loads,
|
|
217
|
+
loadSdk: () => this._loadTranslationSdk(),
|
|
218
|
+
onProgress: (model, progress) => this.emit('model', progressState(model, progress))
|
|
219
|
+
});
|
|
220
|
+
this._capabilityTools = [
|
|
221
|
+
createTranslateTool(this._translationRuntime, {
|
|
222
|
+
slm: (...args) => this._slmTranslate(...args)
|
|
223
|
+
})
|
|
224
|
+
];
|
|
204
225
|
this._tools = [
|
|
205
226
|
...tools,
|
|
206
227
|
...(this._imageRuntime
|
|
@@ -220,6 +241,7 @@ export class Harness extends ReadyResource {
|
|
|
220
241
|
this._platform = platform;
|
|
221
242
|
this._diffusionSdk = null;
|
|
222
243
|
this._musicSdk = null;
|
|
244
|
+
this._translationSdk = null;
|
|
223
245
|
}
|
|
224
246
|
async _open() {
|
|
225
247
|
installResolutionDetector(import.meta);
|
|
@@ -513,6 +535,27 @@ export class Harness extends ReadyResource {
|
|
|
513
535
|
this._diffusionSdk = sdk;
|
|
514
536
|
}, () => import('@qvac/sdk/sdcpp-generation/plugin').then((m) => m.diffusionPlugin));
|
|
515
537
|
}
|
|
538
|
+
async _slmTranslate(model, { text, from, to }, signal) {
|
|
539
|
+
const loadOpts = resolveLoadOptions({}, this._deviceConfig());
|
|
540
|
+
const key = modelKey(model, loadOpts);
|
|
541
|
+
const pool = this._models;
|
|
542
|
+
if (!isResident(pool, key))
|
|
543
|
+
await this._makeRoomForCompletion(signal);
|
|
544
|
+
try {
|
|
545
|
+
const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
|
|
546
|
+
const sdk = await this._sdk.load();
|
|
547
|
+
const answer = await raceAbort(sdk.translate({ modelId, text, from, to, stream: false, modelType: 'llm' }).text, signal);
|
|
548
|
+
return cleanAnswer(answer, text);
|
|
549
|
+
}
|
|
550
|
+
finally {
|
|
551
|
+
releaseModel(pool, key);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
_loadTranslationSdk() {
|
|
555
|
+
return this._loadPluginSdk(() => this._translationSdk, (sdk) => {
|
|
556
|
+
this._translationSdk = sdk;
|
|
557
|
+
}, () => import('@qvac/sdk/nmtcpp-translation/plugin').then((m) => m.nmtPlugin));
|
|
558
|
+
}
|
|
516
559
|
_loadMusicSdk() {
|
|
517
560
|
return this._loadPluginSdk(() => this._musicSdk, (sdk) => {
|
|
518
561
|
this._musicSdk = sdk;
|
|
@@ -645,7 +688,8 @@ export class Harness extends ReadyResource {
|
|
|
645
688
|
this._ttsModels.destroy(),
|
|
646
689
|
this._transcriptionModels.destroy(),
|
|
647
690
|
this._imageRuntime?.unload(),
|
|
648
|
-
this._musicRuntime?.unload()
|
|
691
|
+
this._musicRuntime?.unload(),
|
|
692
|
+
this._translationRuntime.unload()
|
|
649
693
|
]);
|
|
650
694
|
for (const outcome of released) {
|
|
651
695
|
if (outcome.status !== 'rejected')
|
|
@@ -809,6 +853,9 @@ export class Harness extends ReadyResource {
|
|
|
809
853
|
return transcribeOnce(sdk, id, pcm ? buildWavBuffer(pcm) : audio);
|
|
810
854
|
});
|
|
811
855
|
}
|
|
856
|
+
detectLanguage(context) {
|
|
857
|
+
return detectLanguage(context);
|
|
858
|
+
}
|
|
812
859
|
async speak({ model, text, signal }) {
|
|
813
860
|
const samples = await this._voiceJob(this._ttsModels, 'tts', model, signal, (sdk, id) => speakOnce(sdk, id, text));
|
|
814
861
|
return { audio: packSamples(samples), sampleRate: SUPERTONIC_SAMPLE_RATE };
|
|
@@ -943,6 +990,32 @@ export class Harness extends ReadyResource {
|
|
|
943
990
|
releaseModel(pool, key);
|
|
944
991
|
}
|
|
945
992
|
}
|
|
993
|
+
async *invoke({ capability, args, enhance, agent, attach, fetchAttachment, progress, signal }) {
|
|
994
|
+
if (!this.opened)
|
|
995
|
+
await this.ready();
|
|
996
|
+
if (this.suspended)
|
|
997
|
+
return yield { type: 'aborted' };
|
|
998
|
+
hlog('invoke start', { capability, enhance });
|
|
999
|
+
try {
|
|
1000
|
+
yield* invoke({
|
|
1001
|
+
capability,
|
|
1002
|
+
args,
|
|
1003
|
+
enhance,
|
|
1004
|
+
enhancer: this._residentEnhancer(),
|
|
1005
|
+
tools: [...this._tools, ...this._capabilityTools],
|
|
1006
|
+
ctx: {
|
|
1007
|
+
image: imageSettingsFrom(agent),
|
|
1008
|
+
attach,
|
|
1009
|
+
fetchAttachment,
|
|
1010
|
+
progress,
|
|
1011
|
+
signal
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
finally {
|
|
1016
|
+
hlog('invoke end', { capability });
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
946
1019
|
async *completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
|
|
947
1020
|
await this.ready();
|
|
948
1021
|
if (this.suspended)
|
|
@@ -1059,6 +1132,24 @@ export class Harness extends ReadyResource {
|
|
|
1059
1132
|
void this._musicRuntime?.unloadIdle().catch(noop);
|
|
1060
1133
|
}
|
|
1061
1134
|
}
|
|
1135
|
+
_residentEnhancer() {
|
|
1136
|
+
return async (prompt, signal) => {
|
|
1137
|
+
const lease = await leaseResidentModel(this._models, (model) => !isTranslationModel(model));
|
|
1138
|
+
if (!lease)
|
|
1139
|
+
return null;
|
|
1140
|
+
try {
|
|
1141
|
+
const sdk = await this._sdk.load();
|
|
1142
|
+
return await expandImagePrompt(sdk, lease.modelId, lease.model, prompt, signal);
|
|
1143
|
+
}
|
|
1144
|
+
catch (err) {
|
|
1145
|
+
hlog('enhance failed', { err: err instanceof Error ? err.message : String(err) });
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
finally {
|
|
1149
|
+
lease.release();
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1062
1153
|
async _makeRoomForCompletion(signal, deadlineMs = MODEL_ROOM_WAIT_MS) {
|
|
1063
1154
|
const roomed = await awaitModelCapacity(this._models, this._completionModelCap, deadlineMs, 250, signal);
|
|
1064
1155
|
if (!roomed) {
|
|
@@ -222,6 +222,8 @@ export type MetricsEvent = {
|
|
|
222
222
|
ctxSize?: number | undefined;
|
|
223
223
|
backends?: RunBackendUse[] | undefined;
|
|
224
224
|
usage?: UsageBreakdown | undefined;
|
|
225
|
+
route?: 'direct' | 'enhanced' | undefined;
|
|
226
|
+
routeModel?: string | undefined;
|
|
225
227
|
stopReason?: string | undefined;
|
|
226
228
|
};
|
|
227
229
|
export interface InferenceCall {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface DetectedLanguage {
|
|
2
|
+
code: string;
|
|
3
|
+
language: string;
|
|
4
|
+
probability?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface DetectLanguageContext {
|
|
7
|
+
text: string;
|
|
8
|
+
topK?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const UNDETERMINED = "und";
|
|
11
|
+
export declare function detectLanguage({ text, topK }: DetectLanguageContext): DetectedLanguage[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { detectMultiple, detectOne } from '@qvac/langdetect-text';
|
|
2
|
+
export const UNDETERMINED = 'und';
|
|
3
|
+
export function detectLanguage({ text, topK }) {
|
|
4
|
+
if (!topK || topK === 1) {
|
|
5
|
+
const { code, language } = detectOne(text);
|
|
6
|
+
return [{ code, language }];
|
|
7
|
+
}
|
|
8
|
+
return detectMultiple(text, topK).map(({ code, language, probability }) => ({
|
|
9
|
+
code,
|
|
10
|
+
language,
|
|
11
|
+
probability
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
2
|
+
import type { CompletionSdk } from './completion.ts';
|
|
3
|
+
export type EnhanceMode = 'never' | 'auto';
|
|
4
|
+
export interface EnhanceResult {
|
|
5
|
+
text: string;
|
|
6
|
+
model: string;
|
|
7
|
+
totalTokens?: number;
|
|
8
|
+
}
|
|
9
|
+
export type Enhancer = (prompt: string, signal?: BareAbortSignal) => Promise<EnhanceResult | null>;
|
|
10
|
+
export declare function expandImagePrompt(sdk: CompletionSdk, modelId: string, model: string, prompt: string, signal?: BareAbortSignal): Promise<EnhanceResult | null>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cleanAnswer } from '../helpers/clean-answer.js';
|
|
2
|
+
import { raceAbort } from '../helpers/race-abort.js';
|
|
3
|
+
const SYSTEM = "Rewrite the user's description as a single vivid prompt for an image generator. " +
|
|
4
|
+
'Answer with the prompt alone: no preamble, no quotes, no explanation.';
|
|
5
|
+
const MAX_PROMPT_CHARS = 400;
|
|
6
|
+
export async function expandImagePrompt(sdk, modelId, model, prompt, signal) {
|
|
7
|
+
const run = sdk.completion({
|
|
8
|
+
modelId,
|
|
9
|
+
history: [
|
|
10
|
+
{ role: 'system', content: SYSTEM },
|
|
11
|
+
{ role: 'user', content: prompt }
|
|
12
|
+
],
|
|
13
|
+
stream: true,
|
|
14
|
+
generationParams: { temp: 0.7, reasoning_budget: 0 }
|
|
15
|
+
});
|
|
16
|
+
let final;
|
|
17
|
+
try {
|
|
18
|
+
final = await raceAbort(run.final, signal);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if (run.requestId)
|
|
22
|
+
void sdk.cancel?.({ requestId: run.requestId }).catch(() => { });
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
const text = cleanAnswer(final.contentText).slice(0, MAX_PROMPT_CHARS);
|
|
26
|
+
if (!text)
|
|
27
|
+
return null;
|
|
28
|
+
return { text, model, totalTokens: final.stats?.generatedTokens };
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CompletionEvent, ToolDefinition, ToolExecuteContext } from './completion.ts';
|
|
2
|
+
import type { EnhanceMode, Enhancer } from './enhance.ts';
|
|
3
|
+
declare const CAPABILITY_TOOLS: {
|
|
4
|
+
readonly image: {
|
|
5
|
+
readonly tool: 'generate_image';
|
|
6
|
+
readonly enhances: 'prompt';
|
|
7
|
+
};
|
|
8
|
+
readonly translate: {
|
|
9
|
+
readonly tool: 'translate';
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export type Capability = keyof typeof CAPABILITY_TOOLS;
|
|
13
|
+
export declare const CAPABILITIES: Capability[];
|
|
14
|
+
export declare function isCapability(name: string): name is Capability;
|
|
15
|
+
export interface InvokeOptions {
|
|
16
|
+
capability: Capability;
|
|
17
|
+
args: Record<string, unknown>;
|
|
18
|
+
tools: readonly ToolDefinition[];
|
|
19
|
+
ctx: ToolExecuteContext;
|
|
20
|
+
enhance?: EnhanceMode;
|
|
21
|
+
enhancer?: Enhancer;
|
|
22
|
+
}
|
|
23
|
+
export declare function invoke({ capability, args, tools, ctx, enhance, enhancer }: InvokeOptions): AsyncGenerator<CompletionEvent>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AbortedError } from '../helpers/race-abort.js';
|
|
2
|
+
const CAPABILITY_TOOLS = {
|
|
3
|
+
image: { tool: 'generate_image', enhances: 'prompt' },
|
|
4
|
+
translate: { tool: 'translate' }
|
|
5
|
+
};
|
|
6
|
+
export const CAPABILITIES = Object.keys(CAPABILITY_TOOLS);
|
|
7
|
+
export function isCapability(name) {
|
|
8
|
+
return name in CAPABILITY_TOOLS;
|
|
9
|
+
}
|
|
10
|
+
export async function* invoke({ capability, args, tools, ctx, enhance = 'never', enhancer }) {
|
|
11
|
+
const entry = CAPABILITY_TOOLS[capability];
|
|
12
|
+
const tool = tools.find((candidate) => candidate.name === entry.tool);
|
|
13
|
+
if (!tool) {
|
|
14
|
+
yield { type: 'error', message: `${capability} is not available on this device` };
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const startedAt = Date.now();
|
|
18
|
+
const field = 'enhances' in entry ? entry.enhances : null;
|
|
19
|
+
let routeModel;
|
|
20
|
+
let totalTokens = 0;
|
|
21
|
+
let called = args;
|
|
22
|
+
if (enhance === 'auto' && field && enhancer) {
|
|
23
|
+
const source = called[field];
|
|
24
|
+
if (typeof source === 'string' && source) {
|
|
25
|
+
const enhanced = await enhancer(source, ctx.signal);
|
|
26
|
+
if (enhanced) {
|
|
27
|
+
called = { ...called, [field]: enhanced.text };
|
|
28
|
+
routeModel = enhanced.model;
|
|
29
|
+
totalTokens = enhanced.totalTokens ?? 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (ctx.signal?.aborted)
|
|
34
|
+
return yield { type: 'aborted' };
|
|
35
|
+
yield { type: 'tool-call', name: entry.tool, args: called, effect: tool.effect(called, ctx) };
|
|
36
|
+
try {
|
|
37
|
+
const result = await tool.execute(called, ctx);
|
|
38
|
+
if (ctx.signal?.aborted)
|
|
39
|
+
return yield { type: 'aborted' };
|
|
40
|
+
yield { type: 'tool-result', name: entry.tool, result };
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err instanceof AbortedError || ctx.signal?.aborted) {
|
|
44
|
+
yield { type: 'aborted' };
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
yield { type: 'error', message: err instanceof Error ? err.message : String(err) };
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
yield {
|
|
51
|
+
type: 'metrics',
|
|
52
|
+
totalTokens,
|
|
53
|
+
durationMs: Date.now() - startedAt,
|
|
54
|
+
route: routeModel ? 'enhanced' : 'direct',
|
|
55
|
+
routeModel
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import { type Transport } from './rpc-session.ts';
|
|
3
3
|
import { type KvCacheDeletionResult } from './capabilities/kv-cache.ts';
|
|
4
|
-
import type { CapabilityInfo, CompletionContext, DownloadContext, EmbedContext, HarnessContract, HarnessDiagnostics, HarnessEvent, HarnessInfo, ListModelsOptions, ModelInfo, ResolvedSkillCatalogEntry, SpeakContext, SpeakResult, SpeakStreamContext, ToolCatalogEntry, TranscribeContext, WarmModelContext } from './contract-types.ts';
|
|
4
|
+
import type { CapabilityInfo, CompletionContext, DetectedLanguage, DetectLanguageContext, DownloadContext, EmbedContext, HarnessContract, HarnessDiagnostics, HarnessEvent, HarnessInfo, InvokeContext, ListModelsOptions, ModelInfo, ResolvedSkillCatalogEntry, SpeakContext, SpeakResult, SpeakStreamContext, ToolCatalogEntry, TranscribeContext, WarmModelContext } from './contract-types.ts';
|
|
5
5
|
import { type EmbedDevice } from './models/embed-config.ts';
|
|
6
6
|
import type { DeleteModelSummary } from './models/model-delete.ts';
|
|
7
7
|
import type { ManagedSkill, SkillFileContent } from './tools/skills/overlay-crud.ts';
|
|
@@ -20,9 +20,12 @@ export declare class RemoteHarness extends EventEmitter implements HarnessContra
|
|
|
20
20
|
private _ensure;
|
|
21
21
|
private _open;
|
|
22
22
|
completion(context: CompletionContext): AsyncIterable<HarnessEvent>;
|
|
23
|
+
invoke(context: InvokeContext): AsyncIterable<HarnessEvent>;
|
|
24
|
+
private _run;
|
|
23
25
|
download(context: DownloadContext): AsyncIterable<HarnessEvent>;
|
|
24
26
|
embed(context: EmbedContext): Promise<number[][]>;
|
|
25
27
|
transcribe(context: TranscribeContext): Promise<string>;
|
|
28
|
+
detectLanguage(context: DetectLanguageContext): Promise<DetectedLanguage[]>;
|
|
26
29
|
speak(context: SpeakContext): Promise<SpeakResult>;
|
|
27
30
|
speakStream(context: SpeakStreamContext): AsyncIterable<SpeakResult>;
|
|
28
31
|
getEmbeddingDevice(): Promise<EmbedDevice>;
|
package/dist-lib/lib/connect.js
CHANGED
|
@@ -91,6 +91,45 @@ export class RemoteHarness extends EventEmitter {
|
|
|
91
91
|
return session;
|
|
92
92
|
}
|
|
93
93
|
async *completion(context) {
|
|
94
|
+
yield* this._run({
|
|
95
|
+
type: 'start',
|
|
96
|
+
model: context.model,
|
|
97
|
+
agent: knobs(context.agent),
|
|
98
|
+
cacheKey: context.cacheKey,
|
|
99
|
+
thinkingDefault: context.thinkingDefault,
|
|
100
|
+
generationParams: context.generationParams,
|
|
101
|
+
responseFormat: context.responseFormat,
|
|
102
|
+
captureInferenceCalls: context.captureInferenceCalls,
|
|
103
|
+
messages: context.messages.map(({ role, content, attachments }) => ({
|
|
104
|
+
role,
|
|
105
|
+
content,
|
|
106
|
+
attachments: attachments?.map(({ path: file }) => ({ path: file }))
|
|
107
|
+
})),
|
|
108
|
+
toolConfigs: context.toolConfigs,
|
|
109
|
+
grantedDirs: context.grantedDirs,
|
|
110
|
+
grantedDrives: context.grantedDrives,
|
|
111
|
+
grantedWriteDirs: context.grantedWriteDirs
|
|
112
|
+
}, context);
|
|
113
|
+
}
|
|
114
|
+
async *invoke(context) {
|
|
115
|
+
const agent = context.agent ?? {};
|
|
116
|
+
yield* this._run({
|
|
117
|
+
type: 'invoke',
|
|
118
|
+
capability: context.capability,
|
|
119
|
+
arguments: context.args,
|
|
120
|
+
enhance: context.enhance,
|
|
121
|
+
agent: knobs(agent)
|
|
122
|
+
}, {
|
|
123
|
+
model: '',
|
|
124
|
+
agent,
|
|
125
|
+
messages: [],
|
|
126
|
+
attach: context.attach,
|
|
127
|
+
fetchAttachment: context.fetchAttachment,
|
|
128
|
+
progress: context.progress,
|
|
129
|
+
signal: context.signal
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async *_run(open, context) {
|
|
94
133
|
const { client } = await this._ensure();
|
|
95
134
|
let duplex = null;
|
|
96
135
|
let emittedAborted = false;
|
|
@@ -104,32 +143,14 @@ export class RemoteHarness extends EventEmitter {
|
|
|
104
143
|
const onAbort = () => duplex?.destroy();
|
|
105
144
|
try {
|
|
106
145
|
duplex = client.completion();
|
|
107
|
-
duplex.write(
|
|
108
|
-
type: 'start',
|
|
109
|
-
model: context.model,
|
|
110
|
-
agent: knobs(context.agent),
|
|
111
|
-
cacheKey: context.cacheKey,
|
|
112
|
-
thinkingDefault: context.thinkingDefault,
|
|
113
|
-
generationParams: context.generationParams,
|
|
114
|
-
responseFormat: context.responseFormat,
|
|
115
|
-
captureInferenceCalls: context.captureInferenceCalls,
|
|
116
|
-
messages: context.messages.map(({ role, content, attachments }) => ({
|
|
117
|
-
role,
|
|
118
|
-
content,
|
|
119
|
-
attachments: attachments?.map(({ path: file }) => ({ path: file }))
|
|
120
|
-
})),
|
|
121
|
-
toolConfigs: context.toolConfigs,
|
|
122
|
-
grantedDirs: context.grantedDirs,
|
|
123
|
-
grantedDrives: context.grantedDrives,
|
|
124
|
-
grantedWriteDirs: context.grantedWriteDirs
|
|
125
|
-
});
|
|
146
|
+
duplex.write(open);
|
|
126
147
|
if (context.signal?.aborted) {
|
|
127
148
|
duplex.destroy();
|
|
128
149
|
yield { type: 'aborted' };
|
|
129
150
|
return;
|
|
130
151
|
}
|
|
131
152
|
context.signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
132
|
-
hlog('client wire open', { model: context.model });
|
|
153
|
+
hlog('client wire open', { model: context.model, capability: open.capability });
|
|
133
154
|
for await (const event of frames(duplex)) {
|
|
134
155
|
received++;
|
|
135
156
|
if (event.type !== 'content' && event.type !== 'thinking') {
|
|
@@ -288,7 +309,7 @@ export class RemoteHarness extends EventEmitter {
|
|
|
288
309
|
throw died(`harness died mid-run: ${err.message}`);
|
|
289
310
|
}
|
|
290
311
|
finally {
|
|
291
|
-
hlog('client
|
|
312
|
+
hlog('client run end', { received, aborted: Boolean(context.signal?.aborted) });
|
|
292
313
|
context.signal?.removeEventListener?.('abort', onAbort);
|
|
293
314
|
duplex?.destroy();
|
|
294
315
|
closed = true;
|
|
@@ -350,6 +371,14 @@ export class RemoteHarness extends EventEmitter {
|
|
|
350
371
|
const response = await abortable(client.transcribe({ model: context.model, audio: context.audio }), context.signal);
|
|
351
372
|
return response.text;
|
|
352
373
|
}
|
|
374
|
+
async detectLanguage(context) {
|
|
375
|
+
const { client } = await this._ensure();
|
|
376
|
+
const response = await client.detectLanguage({
|
|
377
|
+
text: context.text,
|
|
378
|
+
topK: context.topK
|
|
379
|
+
});
|
|
380
|
+
return response.languages.map(({ code, language, probability }) => probability ? { code, language, probability } : { code, language });
|
|
381
|
+
}
|
|
353
382
|
async speak(context) {
|
|
354
383
|
const { client } = await this._ensure();
|
|
355
384
|
const response = await abortable(client.speak({ model: context.model, text: context.text }), context.signal);
|
|
@@ -68,6 +68,25 @@ export interface CompletionContext {
|
|
|
68
68
|
removeEventListener?: (type: 'abort', fn: () => void) => void;
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
+
export interface InvokeContext {
|
|
72
|
+
capability: string;
|
|
73
|
+
args: Record<string, unknown>;
|
|
74
|
+
enhance?: 'never' | 'auto';
|
|
75
|
+
agent?: AgentKnobs;
|
|
76
|
+
attach?: AttachFn;
|
|
77
|
+
fetchAttachment?: FetchAttachmentFn;
|
|
78
|
+
progress?: ProgressFn;
|
|
79
|
+
signal?: CompletionContext['signal'];
|
|
80
|
+
}
|
|
81
|
+
export interface DetectLanguageContext {
|
|
82
|
+
text: string;
|
|
83
|
+
topK?: number;
|
|
84
|
+
}
|
|
85
|
+
export interface DetectedLanguage {
|
|
86
|
+
code: string;
|
|
87
|
+
language: string;
|
|
88
|
+
probability?: number;
|
|
89
|
+
}
|
|
71
90
|
export interface HarnessEvent {
|
|
72
91
|
type: string;
|
|
73
92
|
text?: string;
|
|
@@ -146,10 +165,12 @@ export interface CapabilityInfo {
|
|
|
146
165
|
}
|
|
147
166
|
export interface HarnessContract {
|
|
148
167
|
completion(context: CompletionContext): AsyncIterable<HarnessEvent>;
|
|
168
|
+
invoke?(context: InvokeContext): AsyncIterable<HarnessEvent>;
|
|
149
169
|
download?(context: DownloadContext): AsyncIterable<HarnessEvent>;
|
|
150
170
|
describe?(): Promise<HarnessInfo | null> | HarnessInfo;
|
|
151
171
|
embed?(context: EmbedContext): Promise<number[][]>;
|
|
152
172
|
transcribe?(context: TranscribeContext): Promise<string>;
|
|
173
|
+
detectLanguage?(context: DetectLanguageContext): DetectedLanguage[] | Promise<DetectedLanguage[]>;
|
|
153
174
|
speak?(context: SpeakContext): Promise<SpeakResult>;
|
|
154
175
|
speakStream?(context: SpeakStreamContext): AsyncIterable<SpeakResult>;
|
|
155
176
|
getEmbeddingDevice?(): EmbedDevice | Promise<EmbedDevice>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cleanAnswer(raw: string, source?: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { unwrap } from 'asenion';
|
|
2
|
+
import { firstMarker, markers, truncateAt } from 'asenion/guards';
|
|
3
|
+
const LEAKS = [
|
|
4
|
+
...markers.controlTokens,
|
|
5
|
+
...markers.turnEnd,
|
|
6
|
+
...markers.thinking,
|
|
7
|
+
...markers.toolMarkup,
|
|
8
|
+
...markers.scaffold
|
|
9
|
+
];
|
|
10
|
+
const THINKING_CLOSE = markers.thinking.filter((marker) => marker.startsWith('</'));
|
|
11
|
+
export function cleanAnswer(raw, source = '') {
|
|
12
|
+
const close = firstMarker(raw, THINKING_CLOSE);
|
|
13
|
+
const body = close ? raw.slice(close.index + close.marker.length) : raw;
|
|
14
|
+
const text = truncateAt(body, LEAKS).trim();
|
|
15
|
+
return unwrap(source) === source ? unwrap(text) : text;
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function createLoadStall(ms, message) {
|
|
2
|
+
let timer = null;
|
|
3
|
+
let rejectStall = null;
|
|
4
|
+
const promise = new Promise((_, reject) => {
|
|
5
|
+
rejectStall = reject;
|
|
6
|
+
});
|
|
7
|
+
const arm = () => {
|
|
8
|
+
if (timer !== null)
|
|
9
|
+
clearTimeout(timer);
|
|
10
|
+
timer = setTimeout(() => {
|
|
11
|
+
rejectStall?.(new Error(message));
|
|
12
|
+
}, ms);
|
|
13
|
+
};
|
|
14
|
+
arm();
|
|
15
|
+
return {
|
|
16
|
+
bump: arm,
|
|
17
|
+
clear: () => {
|
|
18
|
+
if (timer !== null)
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
timer = null;
|
|
21
|
+
},
|
|
22
|
+
promise
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { planVendoredInstall } from '../../tools/exec/adapters/python/wheels.js';
|
|
1
2
|
import { bareBin, firstCommandToken } from '../../tools/exec/scope.js';
|
|
2
3
|
const NETWORK_BINS = new Set([
|
|
3
4
|
'curl',
|
|
@@ -32,7 +33,8 @@ export class SandboxHook {
|
|
|
32
33
|
return;
|
|
33
34
|
if (!ctx.sandboxMode || ctx.toolName !== 'exec')
|
|
34
35
|
return;
|
|
35
|
-
|
|
36
|
+
const packages = ctx.args.packages;
|
|
37
|
+
if (Array.isArray(packages) && packages.length && !planVendoredInstall(packages.map(String))) {
|
|
36
38
|
return { error: 'sandbox mode: installing packages reaches the network', guidance: true };
|
|
37
39
|
}
|
|
38
40
|
const bin = bareBin(firstCommandToken(String(ctx.args.command ?? '')).replace(/^.*[/\\]/, ''));
|