@qvac/harness 0.2.12 → 0.3.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/CHANGELOG.md +213 -0
- package/dist/harness.bundle +16 -13
- package/dist-lib/index.d.ts +12 -2
- package/dist-lib/index.js +66 -5
- package/dist-lib/lib/capabilities/completion.d.ts +5 -0
- package/dist-lib/lib/capabilities/completion.js +17 -5
- 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 +11 -2
- package/dist-lib/lib/capabilities/invoke.js +32 -8
- package/dist-lib/lib/connect.js +1 -0
- package/dist-lib/lib/contract-types.d.ts +1 -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/helpers/tool-call-markup.js +15 -7
- 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/no-redirect-fetch-impl.d.ts +3 -2
- package/dist-lib/lib/no-redirect-fetch-impl.js +4 -1
- package/dist-lib/lib/serve.js +1 -0
- package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +2 -0
- package/dist-lib/lib/tools/exec/adapters/python/distributions.d.ts +1 -0
- package/dist-lib/lib/tools/exec/adapters/python/distributions.js +4 -0
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +67 -0
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +196 -0
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +2 -1
- package/dist-lib/lib/tools/exec/adapters/python/module-distributions.d.ts +1 -0
- package/dist-lib/lib/tools/exec/adapters/python/module-distributions.js +52 -0
- package/dist-lib/lib/tools/exec/adapters/python/net-shim.d.ts +2 -0
- package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +148 -0
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +13 -1
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +1 -0
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +1 -0
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +9 -3
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +130 -20
- package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +3 -0
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +25 -8
- package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +6 -0
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +17 -3
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +7 -1
- package/dist-lib/lib/tools/exec/shell-guidance.d.ts +10 -0
- package/dist-lib/lib/tools/exec/shell-guidance.js +123 -0
- package/dist-lib/lib/tools/exec/video-tools.d.ts +5 -0
- package/dist-lib/lib/tools/exec/video-tools.js +40 -0
- package/dist-lib/lib/tools/exec.d.ts +2 -1
- package/dist-lib/lib/tools/exec.js +59 -32
- package/dist-lib/lib/tools/generate-image.js +2 -25
- package/dist-lib/lib/tools/http-request/truncate-response.d.ts +1 -0
- package/dist-lib/lib/tools/http-request/truncate-response.js +1 -1
- package/dist-lib/lib/tools/http-request.js +4 -1
- package/dist-lib/lib/tools/operations/builders.d.ts +1 -1
- package/dist-lib/lib/tools/operations/builders.js +9 -0
- package/dist-lib/lib/tools/operations/compile.js +41 -18
- package/dist-lib/lib/tools/operations/decoders.d.ts +6 -0
- package/dist-lib/lib/tools/operations/decoders.js +82 -0
- package/dist-lib/lib/tools/operations/manifest.d.ts +16 -2
- package/dist-lib/lib/tools/operations/manifest.js +6 -1
- package/dist-lib/lib/tools/skill.d.ts +3 -0
- package/dist-lib/lib/tools/skill.js +38 -5
- package/dist-lib/lib/tools/skills/manifest-parse.js +43 -1
- package/dist-lib/lib/tools/skills/manifest.d.ts +13 -0
- package/dist-lib/lib/tools/translate.d.ts +78 -0
- package/dist-lib/lib/tools/translate.js +228 -0
- package/dist-lib/schema/harness.js +22 -2
- package/dist-lib/spec/hrpc/messages.js +101 -8
- package/dist-lib/spec/hyperschema/index.js +101 -8
- package/dist-lib/spec/hyperschema/schema.json +71 -1
- package/dist-lib/spec/hyperschema/types.d.ts +19 -1
- package/package.json +35 -2
- package/wheels/manifest.json +15 -0
package/dist-lib/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ 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
4
|
import { type DetectLanguageContext, type DetectedLanguage } from './lib/capabilities/detect-language.ts';
|
|
5
5
|
import { type Capability } from './lib/capabilities/invoke.ts';
|
|
6
|
+
import { type EnhanceMode } from './lib/capabilities/enhance.ts';
|
|
6
7
|
import { type DownloadArgs, type DownloadEventStream } from './lib/capabilities/download.ts';
|
|
7
8
|
import { type EmbedContext } from './lib/capabilities/embed.ts';
|
|
8
9
|
import { type KvCacheDeletionResult } from './lib/capabilities/kv-cache.ts';
|
|
@@ -25,6 +26,8 @@ import { type SkillSettingEntry } from './lib/tools/skills/skill-settings.ts';
|
|
|
25
26
|
export * from './lib/capabilities/completion.ts';
|
|
26
27
|
export { attachmentTurn, loadedSkillsFrom, skillLoadedTurn } from './lib/capabilities/tool-turns.ts';
|
|
27
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';
|
|
28
31
|
export type { DetectLanguageContext, DetectedLanguage } from './lib/capabilities/detect-language.ts';
|
|
29
32
|
export * from './lib/capabilities/download.ts';
|
|
30
33
|
export type { EmbedContext } from './lib/capabilities/embed.ts';
|
|
@@ -41,7 +44,7 @@ export { requiredSettings, settingEnvName, type SkillSettingEntry, type SkillSet
|
|
|
41
44
|
export type { ToolCatalogEntry } from './lib/tools/catalog.ts';
|
|
42
45
|
export { TOOLSETS, toolsetEnabled, type Toolset } from './lib/tools/toolsets.ts';
|
|
43
46
|
export { markCredentialRejected, markCredentialValid, recordCredentialHealth, resetAllCredentialHealth, resetCredentialHealth } from './lib/tools/skills/credential-health.ts';
|
|
44
|
-
export type { InstallStep, SkillOAuthConfig, SkillRequires, SkillSetup, SkillSetupField, SkillSetupRoute } from './lib/tools/skills/manifest.ts';
|
|
47
|
+
export type { InstallStep, SkillOAuthConfig, SkillOAuthVerify, SkillOAuthVerifyRule, SkillRequires, SkillSetup, SkillSetupField, SkillSetupRoute } from './lib/tools/skills/manifest.ts';
|
|
45
48
|
export { readSetup as normalizeSkillSetup } from './lib/tools/skills/manifest-parse.ts';
|
|
46
49
|
export type { CredentialRecord, CredentialStoreValue } from './lib/tools/skills/auth-resolver.ts';
|
|
47
50
|
export { isAvailableState, SKILL_STATE, type SkillState } from './lib/tools/skills/skill-state.ts';
|
|
@@ -113,6 +116,7 @@ export interface CompletionContext {
|
|
|
113
116
|
export interface InvokeContext {
|
|
114
117
|
capability: Capability;
|
|
115
118
|
args: Record<string, unknown>;
|
|
119
|
+
enhance?: EnhanceMode;
|
|
116
120
|
agent?: Agent;
|
|
117
121
|
attach?: AttachFn;
|
|
118
122
|
fetchAttachment?: FetchAttachmentFn;
|
|
@@ -157,7 +161,10 @@ export declare class Harness extends ReadyResource {
|
|
|
157
161
|
private _diffusionSdk;
|
|
158
162
|
private _imageRuntime;
|
|
159
163
|
private _musicSdk;
|
|
164
|
+
private _translationSdk;
|
|
160
165
|
private _musicRuntime;
|
|
166
|
+
private _translationRuntime;
|
|
167
|
+
private _capabilityTools;
|
|
161
168
|
private _capability;
|
|
162
169
|
private _capabilityInflight;
|
|
163
170
|
private readonly _modelFaults;
|
|
@@ -210,6 +217,8 @@ export declare class Harness extends ReadyResource {
|
|
|
210
217
|
private _loadedModels;
|
|
211
218
|
private _loadPluginSdk;
|
|
212
219
|
private _loadDiffusionSdk;
|
|
220
|
+
private _slmTranslate;
|
|
221
|
+
private _loadTranslationSdk;
|
|
213
222
|
private _loadMusicSdk;
|
|
214
223
|
private _deviceConfig;
|
|
215
224
|
private _createPool;
|
|
@@ -254,8 +263,9 @@ export declare class Harness extends ReadyResource {
|
|
|
254
263
|
agent?: Agent;
|
|
255
264
|
signal?: BareAbortSignal;
|
|
256
265
|
}): Promise<void>;
|
|
257
|
-
invoke({ capability, args, agent, attach, fetchAttachment, progress, signal }: InvokeContext): AsyncGenerator<CompletionEvent>;
|
|
266
|
+
invoke({ capability, args, enhance, agent, attach, fetchAttachment, progress, signal }: InvokeContext): AsyncGenerator<CompletionEvent>;
|
|
258
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;
|
|
259
269
|
private _makeRoomForCompletion;
|
|
260
270
|
private _evictWedgedModel;
|
|
261
271
|
private _evictAfterLeases;
|
package/dist-lib/index.js
CHANGED
|
@@ -8,6 +8,10 @@ import { closePythonWorker } from './lib/tools/exec/adapters/python/worker/clien
|
|
|
8
8
|
import { completion, imageSettingsFrom } from './lib/capabilities/completion.js';
|
|
9
9
|
import { detectLanguage } from './lib/capabilities/detect-language.js';
|
|
10
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';
|
|
11
15
|
import { download } from './lib/capabilities/download.js';
|
|
12
16
|
import { createEmbedLane } from './lib/capabilities/embed.js';
|
|
13
17
|
import { deleteAllKvCaches, deleteKvCacheKey } from './lib/capabilities/kv-cache.js';
|
|
@@ -19,7 +23,7 @@ import { AbortedError, raceAbort } from './lib/helpers/race-abort.js';
|
|
|
19
23
|
import { audioGenDownloadMetadata, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, infraModelsFor, modelNameToCapabilityProfile, musicModelSet, setCustomModels } from './lib/models/model-catalog.js';
|
|
20
24
|
import { createModelStatusCache, invalidateModelStatusCache, listModels } from './lib/models/model-list.js';
|
|
21
25
|
import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js';
|
|
22
|
-
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';
|
|
23
27
|
import { resolveMainGpu } from './lib/models/gpu-selection.js';
|
|
24
28
|
import { openclCacheDir } from './lib/models/opencl-cache.js';
|
|
25
29
|
import { assertEmbedDevice, createEmbedConfig } from './lib/models/embed-config.js';
|
|
@@ -48,6 +52,7 @@ import { settingsScopeKey } from './lib/tools/skills/skill-settings.js';
|
|
|
48
52
|
export * from './lib/capabilities/completion.js';
|
|
49
53
|
export { attachmentTurn, loadedSkillsFrom, skillLoadedTurn } from './lib/capabilities/tool-turns.js';
|
|
50
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';
|
|
51
56
|
export * from './lib/capabilities/download.js';
|
|
52
57
|
export * from './lib/models/model-catalog.js';
|
|
53
58
|
export * from './lib/models/model-list.js';
|
|
@@ -119,7 +124,10 @@ export class Harness extends ReadyResource {
|
|
|
119
124
|
_diffusionSdk;
|
|
120
125
|
_imageRuntime;
|
|
121
126
|
_musicSdk;
|
|
127
|
+
_translationSdk;
|
|
122
128
|
_musicRuntime;
|
|
129
|
+
_translationRuntime;
|
|
130
|
+
_capabilityTools;
|
|
123
131
|
_capability = null;
|
|
124
132
|
_capabilityInflight = null;
|
|
125
133
|
_modelFaults = new Map();
|
|
@@ -204,6 +212,16 @@ export class Harness extends ReadyResource {
|
|
|
204
212
|
}
|
|
205
213
|
})
|
|
206
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
|
+
];
|
|
207
225
|
this._tools = [
|
|
208
226
|
...tools,
|
|
209
227
|
...(this._imageRuntime
|
|
@@ -223,6 +241,7 @@ export class Harness extends ReadyResource {
|
|
|
223
241
|
this._platform = platform;
|
|
224
242
|
this._diffusionSdk = null;
|
|
225
243
|
this._musicSdk = null;
|
|
244
|
+
this._translationSdk = null;
|
|
226
245
|
}
|
|
227
246
|
async _open() {
|
|
228
247
|
installResolutionDetector(import.meta);
|
|
@@ -516,6 +535,27 @@ export class Harness extends ReadyResource {
|
|
|
516
535
|
this._diffusionSdk = sdk;
|
|
517
536
|
}, () => import('@qvac/sdk/sdcpp-generation/plugin').then((m) => m.diffusionPlugin));
|
|
518
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
|
+
}
|
|
519
559
|
_loadMusicSdk() {
|
|
520
560
|
return this._loadPluginSdk(() => this._musicSdk, (sdk) => {
|
|
521
561
|
this._musicSdk = sdk;
|
|
@@ -648,7 +688,8 @@ export class Harness extends ReadyResource {
|
|
|
648
688
|
this._ttsModels.destroy(),
|
|
649
689
|
this._transcriptionModels.destroy(),
|
|
650
690
|
this._imageRuntime?.unload(),
|
|
651
|
-
this._musicRuntime?.unload()
|
|
691
|
+
this._musicRuntime?.unload(),
|
|
692
|
+
this._translationRuntime.unload()
|
|
652
693
|
]);
|
|
653
694
|
for (const outcome of released) {
|
|
654
695
|
if (outcome.status !== 'rejected')
|
|
@@ -949,17 +990,19 @@ export class Harness extends ReadyResource {
|
|
|
949
990
|
releaseModel(pool, key);
|
|
950
991
|
}
|
|
951
992
|
}
|
|
952
|
-
async *invoke({ capability, args, agent, attach, fetchAttachment, progress, signal }) {
|
|
993
|
+
async *invoke({ capability, args, enhance, agent, attach, fetchAttachment, progress, signal }) {
|
|
953
994
|
if (!this.opened)
|
|
954
995
|
await this.ready();
|
|
955
996
|
if (this.suspended)
|
|
956
997
|
return yield { type: 'aborted' };
|
|
957
|
-
hlog('invoke start', { capability });
|
|
998
|
+
hlog('invoke start', { capability, enhance });
|
|
958
999
|
try {
|
|
959
1000
|
yield* invoke({
|
|
960
1001
|
capability,
|
|
961
1002
|
args,
|
|
962
|
-
|
|
1003
|
+
enhance,
|
|
1004
|
+
enhancer: this._residentEnhancer(),
|
|
1005
|
+
tools: [...this._tools, ...this._capabilityTools],
|
|
963
1006
|
ctx: {
|
|
964
1007
|
image: imageSettingsFrom(agent),
|
|
965
1008
|
attach,
|
|
@@ -1089,6 +1132,24 @@ export class Harness extends ReadyResource {
|
|
|
1089
1132
|
void this._musicRuntime?.unloadIdle().catch(noop);
|
|
1090
1133
|
}
|
|
1091
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
|
+
}
|
|
1092
1153
|
async _makeRoomForCompletion(signal, deadlineMs = MODEL_ROOM_WAIT_MS) {
|
|
1093
1154
|
const roomed = await awaitModelCapacity(this._models, this._completionModelCap, deadlineMs, 250, signal);
|
|
1094
1155
|
if (!roomed) {
|
|
@@ -139,6 +139,9 @@ export interface ToolExecuteContext {
|
|
|
139
139
|
toolOps?: ReadonlyMap<string, readonly CliValidator[]> | undefined;
|
|
140
140
|
runBudget?: RunBudget | undefined;
|
|
141
141
|
reportBackend?: ((use: RunBackendUse) => void) | undefined;
|
|
142
|
+
uncappedResponse?: boolean | undefined;
|
|
143
|
+
sandboxMode?: boolean | undefined;
|
|
144
|
+
toolNames?: readonly string[] | undefined;
|
|
142
145
|
}
|
|
143
146
|
export interface RunBackendUse {
|
|
144
147
|
tool: string;
|
|
@@ -222,6 +225,8 @@ export type MetricsEvent = {
|
|
|
222
225
|
ctxSize?: number | undefined;
|
|
223
226
|
backends?: RunBackendUse[] | undefined;
|
|
224
227
|
usage?: UsageBreakdown | undefined;
|
|
228
|
+
route?: 'direct' | 'enhanced' | undefined;
|
|
229
|
+
routeModel?: string | undefined;
|
|
225
230
|
stopReason?: string | undefined;
|
|
226
231
|
};
|
|
227
232
|
export interface InferenceCall {
|
|
@@ -19,6 +19,7 @@ import { annotateLastUserTime, skillsPromptSection, systemPromptParts } from '..
|
|
|
19
19
|
import { isMetadataMethod } from '../tools/mcp/utils.js';
|
|
20
20
|
import { unquoteArg } from '../tools/model-args.js';
|
|
21
21
|
import { SKILL_TOOL_NAME, WEB_SEARCH_TOOL_NAME } from './tool-names.js';
|
|
22
|
+
import { mainFileRequested, skillFileArg, skillFileStem } from '../tools/skill.js';
|
|
22
23
|
import { promptUsageParts, usageBreakdown } from './usage-breakdown.js';
|
|
23
24
|
import { collectToolGrants, enabledTools, isSelected, skillIsActive, skillsAreSelected } from './tool-selection.js';
|
|
24
25
|
import { guardToolCall } from '../tools/outbound-guard.js';
|
|
@@ -709,6 +710,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
709
710
|
? { ...resolvedGenerationParams, ...generationParamsOverride }
|
|
710
711
|
: resolvedGenerationParams;
|
|
711
712
|
const loadedThisTurn = new Set();
|
|
713
|
+
const loadedFilesThisTurn = new Set();
|
|
714
|
+
const fileKey = (skill, file) => `${skill}\0${skillFileStem(file)}`;
|
|
712
715
|
let skillIcon = null;
|
|
713
716
|
const skillEmoji = (name) => skillCatalog.find((entry) => entry.name === name)?.emoji ?? null;
|
|
714
717
|
const nudgedSkills = new Set();
|
|
@@ -1004,13 +1007,20 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1004
1007
|
toolOps: grantedOps,
|
|
1005
1008
|
fsRoots,
|
|
1006
1009
|
fsWriteRoots: writableRoots,
|
|
1007
|
-
reportBackend: (use) => stats.backends.push(use)
|
|
1010
|
+
reportBackend: (use) => stats.backends.push(use),
|
|
1011
|
+
sandboxMode: agent?.sandboxMode === true,
|
|
1012
|
+
toolNames: enabled.map((tool) => tool.name)
|
|
1008
1013
|
};
|
|
1009
1014
|
const skillCall = call.name === SKILL_TOOL_NAME;
|
|
1010
1015
|
const effect = tool?.effect(args, callCtx) ?? (skillCall ? 'read' : undefined);
|
|
1011
1016
|
const resource = tool?.resource?.(args) ?? null;
|
|
1012
1017
|
const skillName = unquoteArg(args?.name ?? args?.skill_name ?? args?.skill);
|
|
1013
|
-
const
|
|
1018
|
+
const askedFile = skillFileArg(args);
|
|
1019
|
+
const mainRequested = mainFileRequested(skillName, askedFile);
|
|
1020
|
+
const skillReloaded = tool?.name === SKILL_TOOL_NAME &&
|
|
1021
|
+
(mainRequested
|
|
1022
|
+
? loadedThisTurn.has(skillName)
|
|
1023
|
+
: loadedFilesThisTurn.has(fileKey(skillName, unquoteArg(askedFile))));
|
|
1014
1024
|
const silent = searchCapped || skillReloaded || priorFailure !== undefined;
|
|
1015
1025
|
const display = silent
|
|
1016
1026
|
? undefined
|
|
@@ -1034,9 +1044,10 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1034
1044
|
execution = terminalCall(priorFailure.result, priorFailure.outcome, { executed: false });
|
|
1035
1045
|
}
|
|
1036
1046
|
else if (skillReloaded) {
|
|
1047
|
+
const loadedWhat = mainRequested ? skillName : `${skillName} ${unquoteArg(askedFile)}`;
|
|
1037
1048
|
execution = terminalCall({
|
|
1038
1049
|
name: skillName,
|
|
1039
|
-
content: `${
|
|
1050
|
+
content: `${loadedWhat} is already loaded. Follow the instructions you were given and do not load it again.`
|
|
1040
1051
|
}, { kind: 'guidance', message: `${skillName} is already loaded.` }, { executed: false });
|
|
1041
1052
|
}
|
|
1042
1053
|
else if (skillBlocked) {
|
|
@@ -1105,9 +1116,10 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1105
1116
|
files.add(output.file);
|
|
1106
1117
|
loadedSkillFiles.set(skillName, files);
|
|
1107
1118
|
}
|
|
1108
|
-
|
|
1119
|
+
if (mainRequested)
|
|
1109
1120
|
loadedThisTurn.add(skillName);
|
|
1110
|
-
|
|
1121
|
+
else
|
|
1122
|
+
loadedFilesThisTurn.add(fileKey(skillName, unquoteArg(askedFile)));
|
|
1111
1123
|
skillIcon = skillEmoji(skillName) ?? skillIcon;
|
|
1112
1124
|
}
|
|
1113
1125
|
if (!activity &&
|
|
@@ -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
|
+
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { CompletionEvent, ToolDefinition, ToolExecuteContext } from './completion.ts';
|
|
2
|
+
import type { EnhanceMode, Enhancer } from './enhance.ts';
|
|
2
3
|
declare const CAPABILITY_TOOLS: {
|
|
3
|
-
readonly image:
|
|
4
|
+
readonly image: {
|
|
5
|
+
readonly tool: 'generate_image';
|
|
6
|
+
readonly enhances: 'prompt';
|
|
7
|
+
};
|
|
8
|
+
readonly translate: {
|
|
9
|
+
readonly tool: 'translate';
|
|
10
|
+
};
|
|
4
11
|
};
|
|
5
12
|
export type Capability = keyof typeof CAPABILITY_TOOLS;
|
|
6
13
|
export declare const CAPABILITIES: Capability[];
|
|
@@ -10,6 +17,8 @@ export interface InvokeOptions {
|
|
|
10
17
|
args: Record<string, unknown>;
|
|
11
18
|
tools: readonly ToolDefinition[];
|
|
12
19
|
ctx: ToolExecuteContext;
|
|
20
|
+
enhance?: EnhanceMode;
|
|
21
|
+
enhancer?: Enhancer;
|
|
13
22
|
}
|
|
14
|
-
export declare function invoke({ capability, args, tools, ctx }: InvokeOptions): AsyncGenerator<CompletionEvent>;
|
|
23
|
+
export declare function invoke({ capability, args, tools, ctx, enhance, enhancer }: InvokeOptions): AsyncGenerator<CompletionEvent>;
|
|
15
24
|
export {};
|
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
import { AbortedError } from '../helpers/race-abort.js';
|
|
2
2
|
const CAPABILITY_TOOLS = {
|
|
3
|
-
image: 'generate_image'
|
|
3
|
+
image: { tool: 'generate_image', enhances: 'prompt' },
|
|
4
|
+
translate: { tool: 'translate' }
|
|
4
5
|
};
|
|
5
6
|
export const CAPABILITIES = Object.keys(CAPABILITY_TOOLS);
|
|
6
7
|
export function isCapability(name) {
|
|
7
8
|
return name in CAPABILITY_TOOLS;
|
|
8
9
|
}
|
|
9
|
-
export async function* invoke({ capability, args, tools, ctx }) {
|
|
10
|
-
const
|
|
11
|
-
const tool = tools.find((candidate) => candidate.name ===
|
|
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);
|
|
12
13
|
if (!tool) {
|
|
13
14
|
yield { type: 'error', message: `${capability} is not available on this device` };
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
const startedAt = Date.now();
|
|
17
|
-
|
|
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) };
|
|
18
36
|
try {
|
|
19
|
-
const result = await tool.execute(
|
|
37
|
+
const result = await tool.execute(called, ctx);
|
|
20
38
|
if (ctx.signal?.aborted)
|
|
21
39
|
return yield { type: 'aborted' };
|
|
22
|
-
yield { type: 'tool-result', name, result };
|
|
40
|
+
yield { type: 'tool-result', name: entry.tool, result };
|
|
23
41
|
}
|
|
24
42
|
catch (err) {
|
|
25
43
|
if (err instanceof AbortedError || ctx.signal?.aborted) {
|
|
@@ -29,5 +47,11 @@ export async function* invoke({ capability, args, tools, ctx }) {
|
|
|
29
47
|
yield { type: 'error', message: err instanceof Error ? err.message : String(err) };
|
|
30
48
|
return;
|
|
31
49
|
}
|
|
32
|
-
yield {
|
|
50
|
+
yield {
|
|
51
|
+
type: 'metrics',
|
|
52
|
+
totalTokens,
|
|
53
|
+
durationMs: Date.now() - startedAt,
|
|
54
|
+
route: routeModel ? 'enhanced' : 'direct',
|
|
55
|
+
routeModel
|
|
56
|
+
};
|
|
33
57
|
}
|
package/dist-lib/lib/connect.js
CHANGED
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
const FUNCTION_OPEN = '<function=';
|
|
2
2
|
const FUNCTION_CLOSE = '</function>';
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const TOOL_RESPONSE = { open: '<tool_response>', close: '</tool_response>' };
|
|
4
|
+
const TOOL_CALL = { open: '<tool_call>', close: '</tool_call>' };
|
|
5
5
|
const PARAMETER_OPEN = '<parameter=';
|
|
6
6
|
const PARAMETER_CLOSE = '</parameter>';
|
|
7
7
|
const MAX_NAME_LENGTH = 64;
|
|
@@ -66,26 +66,34 @@ export function stripToolCallMarkup(text) {
|
|
|
66
66
|
out += text.slice(cursor);
|
|
67
67
|
const dangling = danglingIndex(out);
|
|
68
68
|
const withoutCalls = dangling === -1 ? out : out.slice(0, dangling);
|
|
69
|
-
|
|
69
|
+
const withoutSpans = stripSpans(stripToolResponseBlocks(withoutCalls), TOOL_CALL);
|
|
70
|
+
return withoutSpans
|
|
70
71
|
.replace(/<\/?(tool_call|tool_response)>/gi, '')
|
|
71
72
|
.replace(/\n{3,}/g, '\n\n')
|
|
72
73
|
.trim();
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
function stripSpans(text, span) {
|
|
76
|
+
const hay = text.replace(/[A-Z]/g, lowerAscii);
|
|
75
77
|
let out = '';
|
|
76
78
|
let cursor = 0;
|
|
77
79
|
for (;;) {
|
|
78
|
-
const start =
|
|
80
|
+
const start = hay.indexOf(span.open, cursor);
|
|
79
81
|
if (start === -1)
|
|
80
82
|
break;
|
|
81
83
|
out += text.slice(cursor, start);
|
|
82
|
-
const end =
|
|
84
|
+
const end = hay.indexOf(span.close, start + span.open.length);
|
|
83
85
|
if (end === -1)
|
|
84
86
|
return out;
|
|
85
|
-
cursor = end +
|
|
87
|
+
cursor = end + span.close.length;
|
|
86
88
|
}
|
|
87
89
|
return out + text.slice(cursor);
|
|
88
90
|
}
|
|
91
|
+
function lowerAscii(char) {
|
|
92
|
+
return char.toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
export function stripToolResponseBlocks(text) {
|
|
95
|
+
return stripSpans(text, TOOL_RESPONSE);
|
|
96
|
+
}
|
|
89
97
|
function danglingIndex(text) {
|
|
90
98
|
let cursor = 0;
|
|
91
99
|
for (;;) {
|
|
@@ -69,6 +69,12 @@ export interface LoadedModel {
|
|
|
69
69
|
type PoolLike = ReturnType<typeof createModelPool>;
|
|
70
70
|
export declare function releaseModel(pool: PoolLike, key: string): void;
|
|
71
71
|
export declare function loadedModels(pool: PoolLike): LoadedModel[];
|
|
72
|
+
export interface ResidentLease {
|
|
73
|
+
model: string;
|
|
74
|
+
modelId: string;
|
|
75
|
+
release: () => void;
|
|
76
|
+
}
|
|
77
|
+
export declare function leaseResidentModel(pool: PoolLike, wanted?: (model: string) => boolean): Promise<ResidentLease | null>;
|
|
72
78
|
export declare function isResident(pool: PoolLike, key: string): boolean;
|
|
73
79
|
export declare function leaseCount(pool: PoolLike, key: string): number;
|
|
74
80
|
export declare function evictIdle(pool: PoolLike): Promise<void>;
|
|
@@ -91,6 +91,18 @@ export function loadedModels(pool) {
|
|
|
91
91
|
}
|
|
92
92
|
return out;
|
|
93
93
|
}
|
|
94
|
+
export async function leaseResidentModel(pool, wanted = () => true) {
|
|
95
|
+
for (const entry of pool.resources.values()) {
|
|
96
|
+
const key = entry.id;
|
|
97
|
+
const pending = entry.value.peek();
|
|
98
|
+
if (!pending || entry.value.loading || !wanted(key.split('\0')[0]))
|
|
99
|
+
continue;
|
|
100
|
+
pool.open(key);
|
|
101
|
+
const loaded = await pending;
|
|
102
|
+
return { model: loaded.model, modelId: loaded.modelId, release: () => releaseModel(pool, key) };
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
94
106
|
export function isResident(pool, key) {
|
|
95
107
|
const handle = entryOf(pool, key)?.value;
|
|
96
108
|
return handle !== undefined && handle.peek() !== null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const ENGLISH = "en";
|
|
2
|
+
export declare const NMT_TO_ENGLISH: readonly string[];
|
|
3
|
+
export declare const NMT_FROM_ENGLISH: readonly string[];
|
|
4
|
+
export declare const SLM_MODEL = "TranslatePsy-AfriSLM-0.8B-Q4_K_M-imat";
|
|
5
|
+
export declare function isTranslationModel(model: string): boolean;
|
|
6
|
+
export declare const EURONANO_LANGUAGES: readonly string[];
|
|
7
|
+
export declare const SLM_LANGUAGE_CODES: readonly string[];
|
|
8
|
+
export declare function languageLabel(code: string): string | undefined;
|
|
9
|
+
export declare const SLM_LANGUAGE_NAMES: ReadonlyArray<{
|
|
10
|
+
code: string;
|
|
11
|
+
name: string;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function slmLanguage(code: string): string | undefined;
|
|
14
|
+
export interface NmtPlan {
|
|
15
|
+
route: 'nmt';
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
model: string;
|
|
19
|
+
pivot?: string;
|
|
20
|
+
srcVocab?: string;
|
|
21
|
+
dstVocab?: string;
|
|
22
|
+
tag?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SlmPlan {
|
|
25
|
+
route: 'slm';
|
|
26
|
+
from: string;
|
|
27
|
+
to: string;
|
|
28
|
+
model: string;
|
|
29
|
+
}
|
|
30
|
+
export type TranslationPlan = NmtPlan | SlmPlan;
|
|
31
|
+
export declare function sameLanguage(from: string, to: string): boolean;
|
|
32
|
+
export declare function translationPlanFor(requestedFrom: string, requestedTo: string): TranslationPlan | {
|
|
33
|
+
error: string;
|
|
34
|
+
};
|