@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
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import ScopeLock from 'scope-lock';
|
|
2
|
+
import { getLangName } from '@qvac/langdetect-text';
|
|
3
|
+
import { detectLanguage, UNDETERMINED } from '../capabilities/detect-language.js';
|
|
4
|
+
import { AbortedError, raceAbort } from '../helpers/race-abort.js';
|
|
5
|
+
import { ENGLISH, NMT_FROM_ENGLISH, NMT_TO_ENGLISH, languageLabel, SLM_LANGUAGE_CODES, sameLanguage, translationPlanFor } from '../models/nmt-catalog.js';
|
|
6
|
+
import { createLoadStall } from '../helpers/load-stall.js';
|
|
7
|
+
import { createLoadTracker } from '../models/model-pool.js';
|
|
8
|
+
import { resolveModelSrc } from '../models/model-source.js';
|
|
9
|
+
export const TRANSLATION_LOAD_STALL_MS = 15 * 60_000;
|
|
10
|
+
export const TRANSLATION_LOAD_STALL_ERROR = 'translation model download stalled';
|
|
11
|
+
function packName(plan) {
|
|
12
|
+
const pair = plan.model.match(/model\.([a-z]+)\.intgemm/)?.[1];
|
|
13
|
+
if (pair)
|
|
14
|
+
return `Bergamot ${pair}`;
|
|
15
|
+
return plan.model.includes('/en-xx/') ? 'EuroNano en-xx' : 'EuroNano xx-en';
|
|
16
|
+
}
|
|
17
|
+
const fingerprintOf = (plan) => `${plan.model}|${plan.pivot ?? ''}`;
|
|
18
|
+
function bergamotConfig(plan) {
|
|
19
|
+
const config = { engine: 'Bergamot', from: plan.from, to: plan.to };
|
|
20
|
+
if (plan.srcVocab)
|
|
21
|
+
config.srcVocabSrc = plan.srcVocab;
|
|
22
|
+
if (plan.dstVocab)
|
|
23
|
+
config.dstVocabSrc = plan.dstVocab;
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(), onProgress, stallMs = TRANSLATION_LOAD_STALL_MS }) {
|
|
27
|
+
let loading = null;
|
|
28
|
+
let residentFingerprint = null;
|
|
29
|
+
const lock = new ScopeLock();
|
|
30
|
+
const turn = new ScopeLock();
|
|
31
|
+
const loadPack = async (sdk, plan) => {
|
|
32
|
+
const modelSrc = await resolveModelSrc(sdk, plan.model, 'nmt');
|
|
33
|
+
const modelConfig = bergamotConfig(plan);
|
|
34
|
+
if (plan.pivot) {
|
|
35
|
+
modelConfig.pivotModel = { modelSrc: await resolveModelSrc(sdk, plan.pivot, 'nmt') };
|
|
36
|
+
}
|
|
37
|
+
const name = packName(plan);
|
|
38
|
+
const stall = createLoadStall(stallMs, TRANSLATION_LOAD_STALL_ERROR);
|
|
39
|
+
onProgress?.(name, {});
|
|
40
|
+
const pending = sdk.loadModel({
|
|
41
|
+
modelSrc,
|
|
42
|
+
modelType: 'nmtcpp-translation',
|
|
43
|
+
modelConfig,
|
|
44
|
+
onProgress: (progress) => {
|
|
45
|
+
stall.bump();
|
|
46
|
+
onProgress?.(name, progress);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
const modelId = await Promise.race([loads.track(pending), stall.promise]);
|
|
51
|
+
return { modelId };
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err instanceof Error &&
|
|
55
|
+
err.message === TRANSLATION_LOAD_STALL_ERROR &&
|
|
56
|
+
pending.requestId) {
|
|
57
|
+
void sdk.cancel?.({ requestId: pending.requestId }).catch(() => { });
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
stall.clear();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const doUnload = async () => {
|
|
66
|
+
const pending = loading;
|
|
67
|
+
if (!pending) {
|
|
68
|
+
residentFingerprint = null;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const loaded = await pending.catch(() => null);
|
|
72
|
+
if (!loaded) {
|
|
73
|
+
loading = null;
|
|
74
|
+
residentFingerprint = null;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const sdk = await loadSdk();
|
|
78
|
+
await sdk.unloadModel({ modelId: loaded.modelId });
|
|
79
|
+
loading = null;
|
|
80
|
+
residentFingerprint = null;
|
|
81
|
+
};
|
|
82
|
+
const unload = async () => {
|
|
83
|
+
await lock.lock();
|
|
84
|
+
try {
|
|
85
|
+
await doUnload();
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
lock.unlock();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
loadSdk,
|
|
93
|
+
ensureLoaded: async (sdk, plan, signal) => {
|
|
94
|
+
const fingerprint = fingerprintOf(plan);
|
|
95
|
+
await lock.lock();
|
|
96
|
+
try {
|
|
97
|
+
if (loading && residentFingerprint !== fingerprint)
|
|
98
|
+
await doUnload();
|
|
99
|
+
residentFingerprint = fingerprint;
|
|
100
|
+
loading ??= loadPack(sdk, plan).catch((err) => {
|
|
101
|
+
loading = null;
|
|
102
|
+
residentFingerprint = null;
|
|
103
|
+
throw err;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
lock.unlock();
|
|
108
|
+
}
|
|
109
|
+
return raceAbort(loading, signal);
|
|
110
|
+
},
|
|
111
|
+
unload,
|
|
112
|
+
enter: async (signal) => {
|
|
113
|
+
const pending = turn.lock();
|
|
114
|
+
try {
|
|
115
|
+
await raceAbort(pending, signal);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
void pending.then(() => turn.unlock());
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
exit: () => turn.unlock()
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function stripLanguageLabel(text, ...codes) {
|
|
126
|
+
const at = text.indexOf(':');
|
|
127
|
+
if (at === -1)
|
|
128
|
+
return text;
|
|
129
|
+
const head = text.slice(0, at).trim();
|
|
130
|
+
const labels = codes.map((code) => languageLabel(code) ?? getLangName(code));
|
|
131
|
+
return labels.includes(head) ? text.slice(at + 1).trim() : text;
|
|
132
|
+
}
|
|
133
|
+
export function parseTranslateArgs(args) {
|
|
134
|
+
const text = typeof args.text === 'string' ? args.text.trim() : '';
|
|
135
|
+
if (!text)
|
|
136
|
+
return { error: 'text must be a non-empty string' };
|
|
137
|
+
const to = typeof args.to === 'string' ? args.to.trim() : '';
|
|
138
|
+
if (!to)
|
|
139
|
+
return { error: 'to must be a language code' };
|
|
140
|
+
const from = typeof args.from === 'string' ? args.from.trim() : undefined;
|
|
141
|
+
return { text, to, from };
|
|
142
|
+
}
|
|
143
|
+
export function sourceLanguage(text, from) {
|
|
144
|
+
if (from)
|
|
145
|
+
return from;
|
|
146
|
+
const [{ code }] = detectLanguage({ text });
|
|
147
|
+
if (code === UNDETERMINED)
|
|
148
|
+
return { error: NEEDS_SOURCE };
|
|
149
|
+
return code;
|
|
150
|
+
}
|
|
151
|
+
const NEEDS_SOURCE = 'could not detect the source language; pass `from` explicitly';
|
|
152
|
+
export function createTranslateTool(runtime, { slm } = {}) {
|
|
153
|
+
return {
|
|
154
|
+
name: 'translate',
|
|
155
|
+
toolset: null,
|
|
156
|
+
description: 'Translate text between languages using the local neural translation models. ' +
|
|
157
|
+
`\`to\` and \`from\` are ISO 639-1 codes. Translating into ${ENGLISH} supports ` +
|
|
158
|
+
`${NMT_TO_ENGLISH.length} languages and out of it ${NMT_FROM_ENGLISH.length}; a pair ` +
|
|
159
|
+
'with English on neither side is translated through English, except between the ' +
|
|
160
|
+
'languages below. ' +
|
|
161
|
+
`${SLM_LANGUAGE_CODES.length} further languages, mostly African, may also be named by ` +
|
|
162
|
+
'their FLORES-200 code and always need an explicit `from`, which the detector cannot ' +
|
|
163
|
+
'name. Elsewhere `from` is detected from the text when omitted; pass it when known.',
|
|
164
|
+
parameters: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
text: { type: 'string', description: 'The text to translate' },
|
|
168
|
+
to: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
description: 'Target language as an ISO 639-1 code, e.g. `es`, or a FLORES-200 code'
|
|
171
|
+
},
|
|
172
|
+
from: {
|
|
173
|
+
type: 'string',
|
|
174
|
+
description: 'Source language as an ISO 639-1 or FLORES-200 code; detected from the text when omitted'
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
required: ['text', 'to']
|
|
178
|
+
},
|
|
179
|
+
effect: () => 'read',
|
|
180
|
+
resource: (args) => ({ kind: 'translation', label: String(args.to ?? '') }),
|
|
181
|
+
execute: async (args, ctx) => {
|
|
182
|
+
const parsed = parseTranslateArgs(args);
|
|
183
|
+
if ('error' in parsed)
|
|
184
|
+
return parsed;
|
|
185
|
+
const from = sourceLanguage(parsed.text, parsed.from);
|
|
186
|
+
if (typeof from === 'object')
|
|
187
|
+
return from;
|
|
188
|
+
if (sameLanguage(from, parsed.to)) {
|
|
189
|
+
return { text: parsed.text, from, to: parsed.to, engine: 'none' };
|
|
190
|
+
}
|
|
191
|
+
const plan = translationPlanFor(from, parsed.to);
|
|
192
|
+
if ('error' in plan)
|
|
193
|
+
return plan;
|
|
194
|
+
if (plan.route === 'slm' && !slm) {
|
|
195
|
+
return { error: `this device cannot translate ${from} to ${parsed.to}` };
|
|
196
|
+
}
|
|
197
|
+
let entered = false;
|
|
198
|
+
try {
|
|
199
|
+
await runtime.enter(ctx?.signal);
|
|
200
|
+
entered = true;
|
|
201
|
+
if (plan.route === 'slm') {
|
|
202
|
+
const text = await slm(plan.model, { text: parsed.text, from: plan.from, to: plan.to }, ctx?.signal);
|
|
203
|
+
return {
|
|
204
|
+
text: stripLanguageLabel(text, plan.from, plan.to),
|
|
205
|
+
from,
|
|
206
|
+
to: parsed.to,
|
|
207
|
+
engine: 'slm',
|
|
208
|
+
model: plan.model
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const sdk = await runtime.loadSdk();
|
|
212
|
+
const { modelId } = await runtime.ensureLoaded(sdk, plan, ctx?.signal);
|
|
213
|
+
const source = plan.tag ? `${plan.tag} ${parsed.text}` : parsed.text;
|
|
214
|
+
const text = await raceAbort(sdk.translate({ modelId, text: source, stream: false, modelType: 'nmt' }).text, ctx?.signal);
|
|
215
|
+
return { text, from, to: parsed.to, engine: 'nmt', pivoted: Boolean(plan.pivot) };
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (err instanceof AbortedError)
|
|
219
|
+
return { error: 'translation canceled' };
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
if (entered)
|
|
224
|
+
runtime.exit();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
@@ -28,7 +28,11 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
|
|
|
28
28
|
toolset: 'web-search',
|
|
29
29
|
description: 'Fetch a public web page and return its readable text. ' +
|
|
30
30
|
'Use this to read the full content behind a web_search result URL, or a page the user ' +
|
|
31
|
-
'shares, when a search snippet is not enough to answer.'
|
|
31
|
+
'shares, when a search snippet is not enough to answer. ' +
|
|
32
|
+
'When the URL serves a file rather than a page — PDF, .docx, .xlsx, .pptx, an image, any ' +
|
|
33
|
+
'binary — this is also the tool that downloads it: the bytes become a chat attachment and ' +
|
|
34
|
+
'the result carries its attachmentId. It is the only way to get a remote file into the ' +
|
|
35
|
+
'chat; exec has no network and http_request truncates binary bodies.',
|
|
32
36
|
parameters: {
|
|
33
37
|
type: 'object',
|
|
34
38
|
properties: {
|
|
@@ -178,8 +182,14 @@ async function attachDocument(response, url, mimeType, attach) {
|
|
|
178
182
|
raw: !mime.startsWith('image/')
|
|
179
183
|
});
|
|
180
184
|
return {
|
|
185
|
+
attachmentId: record.id,
|
|
186
|
+
fileName: record.fileName,
|
|
187
|
+
byteLength: record.byteLength,
|
|
181
188
|
content: `Attached the file "${record.fileName}" (${Math.round(record.byteLength / 1024)}KB, ${mime}) from ${url.toString()} — ` +
|
|
182
|
-
'the user can open or save it. Its contents were NOT read
|
|
189
|
+
'the user can open or save it. Its contents were NOT read, so do not describe what is inside ' +
|
|
190
|
+
'yet. To read it, stage it with exec as ' +
|
|
191
|
+
`inputs [{ "attachmentId": "${record.id}", "path": "${record.fileName}" }] — never ask the ` +
|
|
192
|
+
'user to attach a file this call already attached.'
|
|
183
193
|
};
|
|
184
194
|
}
|
|
185
195
|
const MIME_EXTENSIONS = {
|
|
@@ -69,7 +69,8 @@ export function registerHarnessTypes(ns) {
|
|
|
69
69
|
'fetch-result',
|
|
70
70
|
'rag-result',
|
|
71
71
|
'question-result',
|
|
72
|
-
'drive-result'
|
|
72
|
+
'drive-result',
|
|
73
|
+
'invoke'
|
|
73
74
|
],
|
|
74
75
|
strings: true
|
|
75
76
|
});
|
|
@@ -109,7 +110,10 @@ export function registerHarnessTypes(ns) {
|
|
|
109
110
|
{ name: 'grantedDrives', type: '@harness/granted-drive', array: true },
|
|
110
111
|
{ name: 'payload', type: 'json' },
|
|
111
112
|
{ name: 'grantedWriteDirs', type: 'string', array: true },
|
|
112
|
-
{ name: 'captureInferenceCalls', type: 'bool' }
|
|
113
|
+
{ name: 'captureInferenceCalls', type: 'bool' },
|
|
114
|
+
{ name: 'capability', type: 'string' },
|
|
115
|
+
{ name: 'arguments', type: 'json' },
|
|
116
|
+
{ name: 'enhance', type: 'string' }
|
|
113
117
|
]
|
|
114
118
|
});
|
|
115
119
|
ns.register({
|
|
@@ -459,6 +463,25 @@ export function registerHarnessTypes(ns) {
|
|
|
459
463
|
name: 'transcribe-response',
|
|
460
464
|
fields: [{ name: 'text', type: 'string', required: true }]
|
|
461
465
|
});
|
|
466
|
+
ns.register({
|
|
467
|
+
name: 'detect-language-request',
|
|
468
|
+
fields: [
|
|
469
|
+
{ name: 'text', type: 'string', required: true },
|
|
470
|
+
{ name: 'topK', type: 'uint' }
|
|
471
|
+
]
|
|
472
|
+
});
|
|
473
|
+
ns.register({
|
|
474
|
+
name: 'detected-language',
|
|
475
|
+
fields: [
|
|
476
|
+
{ name: 'code', type: 'string', required: true },
|
|
477
|
+
{ name: 'language', type: 'string', required: true },
|
|
478
|
+
{ name: 'probability', type: 'float64' }
|
|
479
|
+
]
|
|
480
|
+
});
|
|
481
|
+
ns.register({
|
|
482
|
+
name: 'detect-language-response',
|
|
483
|
+
fields: [{ name: 'languages', type: '@harness/detected-language', array: true, required: true }]
|
|
484
|
+
});
|
|
462
485
|
ns.register({
|
|
463
486
|
name: 'speak-request',
|
|
464
487
|
fields: [
|
|
@@ -507,7 +530,8 @@ export function registerHarnessTypes(ns) {
|
|
|
507
530
|
{ name: 'steps', type: 'string', array: true },
|
|
508
531
|
{ name: 'configSteps', type: 'string', array: true },
|
|
509
532
|
{ name: 'fields', type: '@harness/skill-setup-field', array: true },
|
|
510
|
-
{ name: 'satisfied', type: 'bool' }
|
|
533
|
+
{ name: 'satisfied', type: 'bool' },
|
|
534
|
+
{ name: 'oauth', type: '@harness/skill-oauth-config' }
|
|
511
535
|
]
|
|
512
536
|
});
|
|
513
537
|
ns.register({
|
|
@@ -614,6 +638,27 @@ export function registerHarnessTypes(ns) {
|
|
|
614
638
|
name: 'kv-cache-delete-request',
|
|
615
639
|
fields: [{ name: 'kvCacheKey', type: 'string', required: true }]
|
|
616
640
|
});
|
|
641
|
+
ns.register({
|
|
642
|
+
name: 'skill-oauth-param',
|
|
643
|
+
fields: [
|
|
644
|
+
{ name: 'key', type: 'string', required: true },
|
|
645
|
+
{ name: 'value', type: 'string', required: true }
|
|
646
|
+
]
|
|
647
|
+
});
|
|
648
|
+
ns.register({
|
|
649
|
+
name: 'skill-oauth-config',
|
|
650
|
+
fields: [
|
|
651
|
+
{ name: 'stateKey', type: 'string', required: true },
|
|
652
|
+
{ name: 'authUrl', type: 'string', required: true },
|
|
653
|
+
{ name: 'tokenUrl', type: 'string', required: true },
|
|
654
|
+
{ name: 'tokenAuth', type: 'string', required: true },
|
|
655
|
+
{ name: 'scopes', type: 'string', array: true },
|
|
656
|
+
{ name: 'extraAuthParams', type: '@harness/skill-oauth-param', array: true },
|
|
657
|
+
{ name: 'registrationEndpoint', type: 'string' },
|
|
658
|
+
{ name: 'port', type: 'uint' },
|
|
659
|
+
{ name: 'sharedIosSession', type: 'bool' }
|
|
660
|
+
]
|
|
661
|
+
});
|
|
617
662
|
}
|
|
618
663
|
export function registerHarnessApi(api) {
|
|
619
664
|
api.register({
|
|
@@ -756,4 +801,9 @@ export function registerHarnessApi(api) {
|
|
|
756
801
|
request: { name: '@harness/kv-cache-delete-request', stream: false },
|
|
757
802
|
response: { name: '@harness/kv-cache-deletion-response', stream: false }
|
|
758
803
|
});
|
|
804
|
+
api.register({
|
|
805
|
+
name: 'detect-language',
|
|
806
|
+
request: { name: '@harness/detect-language-request', stream: false },
|
|
807
|
+
response: { name: '@harness/detect-language-response', stream: false }
|
|
808
|
+
});
|
|
759
809
|
}
|
package/dist-lib/spec/bind.js
CHANGED
package/dist-lib/spec/calls.js
CHANGED
|
@@ -38,5 +38,6 @@ export function harnessClient(rpc) {
|
|
|
38
38
|
copySkillForEdit: (input) => rpc.copySkillForEdit(input),
|
|
39
39
|
deleteOverlaySkill: (input) => rpc.deleteOverlaySkill(input),
|
|
40
40
|
deleteKvCache: (input) => rpc.deleteKvCache(input),
|
|
41
|
+
detectLanguage: (input) => rpc.detectLanguage(input),
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -364,6 +364,19 @@
|
|
|
364
364
|
"stream": false
|
|
365
365
|
},
|
|
366
366
|
"version": 1
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"id": 28,
|
|
370
|
+
"name": "@harness/detect-language",
|
|
371
|
+
"request": {
|
|
372
|
+
"name": "@harness/detect-language-request",
|
|
373
|
+
"stream": false
|
|
374
|
+
},
|
|
375
|
+
"response": {
|
|
376
|
+
"name": "@harness/detect-language-response",
|
|
377
|
+
"stream": false
|
|
378
|
+
},
|
|
379
|
+
"version": 1
|
|
367
380
|
}
|
|
368
381
|
]
|
|
369
382
|
}
|
|
@@ -61,7 +61,9 @@ const methods = new Map([
|
|
|
61
61
|
['@harness/delete-overlay-skill', 26],
|
|
62
62
|
[26, '@harness/delete-overlay-skill'],
|
|
63
63
|
['@harness/delete-kv-cache', 27],
|
|
64
|
-
[27, '@harness/delete-kv-cache']
|
|
64
|
+
[27, '@harness/delete-kv-cache'],
|
|
65
|
+
['@harness/detect-language', 28],
|
|
66
|
+
[28, '@harness/detect-language']
|
|
65
67
|
])
|
|
66
68
|
|
|
67
69
|
class HRPC {
|
|
@@ -96,7 +98,8 @@ class HRPC {
|
|
|
96
98
|
['@harness/write-skill-file', getEncoding('@harness/write-skill-request')],
|
|
97
99
|
['@harness/copy-skill-for-edit', getEncoding('@harness/skill-name-request')],
|
|
98
100
|
['@harness/delete-overlay-skill', getEncoding('@harness/skill-name-request')],
|
|
99
|
-
['@harness/delete-kv-cache', getEncoding('@harness/kv-cache-delete-request')]
|
|
101
|
+
['@harness/delete-kv-cache', getEncoding('@harness/kv-cache-delete-request')],
|
|
102
|
+
['@harness/detect-language', getEncoding('@harness/detect-language-request')]
|
|
100
103
|
])
|
|
101
104
|
this._responseEncodings = new Map([
|
|
102
105
|
['@harness/completion', getEncoding('@harness/completion-event')],
|
|
@@ -126,7 +129,8 @@ class HRPC {
|
|
|
126
129
|
['@harness/write-skill-file', getEncoding('@harness/ok-response')],
|
|
127
130
|
['@harness/copy-skill-for-edit', getEncoding('@harness/copy-skill-response')],
|
|
128
131
|
['@harness/delete-overlay-skill', getEncoding('@harness/delete-skill-response')],
|
|
129
|
-
['@harness/delete-kv-cache', getEncoding('@harness/kv-cache-deletion-response')]
|
|
132
|
+
['@harness/delete-kv-cache', getEncoding('@harness/kv-cache-deletion-response')],
|
|
133
|
+
['@harness/detect-language', getEncoding('@harness/detect-language-response')]
|
|
130
134
|
])
|
|
131
135
|
this._rpc = new RPC(stream, async (req) => {
|
|
132
136
|
const command = methods.get(req.command)
|
|
@@ -336,6 +340,10 @@ class HRPC {
|
|
|
336
340
|
return this._call('@harness/delete-kv-cache', args)
|
|
337
341
|
}
|
|
338
342
|
|
|
343
|
+
async detectLanguage(args) {
|
|
344
|
+
return this._call('@harness/detect-language', args)
|
|
345
|
+
}
|
|
346
|
+
|
|
339
347
|
onCompletion(responseFn) {
|
|
340
348
|
this._handlers['@harness/completion'] = responseFn
|
|
341
349
|
}
|
|
@@ -448,6 +456,10 @@ class HRPC {
|
|
|
448
456
|
this._handlers['@harness/delete-kv-cache'] = responseFn
|
|
449
457
|
}
|
|
450
458
|
|
|
459
|
+
onDetectLanguage(responseFn) {
|
|
460
|
+
this._handlers['@harness/detect-language'] = responseFn
|
|
461
|
+
}
|
|
462
|
+
|
|
451
463
|
_requestIsStream(command) {
|
|
452
464
|
return [
|
|
453
465
|
'@harness/completion',
|