@qvac/harness 0.7.3 → 0.8.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/CHANGELOG.md +45 -1
- package/NOTICE.md +16 -0
- package/dist-lib/lib/capabilities/completion.d.ts +1 -0
- package/dist-lib/lib/capabilities/completion.js +28 -8
- package/dist-lib/lib/capabilities/tool-turns.d.ts +3 -0
- package/dist-lib/lib/capabilities/tool-turns.js +7 -0
- package/dist-lib/lib/context-window.js +2 -2
- package/dist-lib/lib/helpers/run-outcome.d.ts +4 -1
- package/dist-lib/lib/helpers/run-outcome.js +14 -8
- package/dist-lib/lib/helpers/tool-call-markup.js +4 -4
- package/dist-lib/lib/hooks/built-in/arg-repair.js +7 -0
- package/dist-lib/lib/office/engine/codes.d.ts +62 -0
- package/dist-lib/lib/office/engine/codes.js +61 -0
- package/dist-lib/lib/office/engine/docx/document.d.ts +216 -0
- package/dist-lib/lib/office/engine/docx/document.js +1152 -0
- package/dist-lib/lib/office/engine/docx/index.d.ts +7 -0
- package/dist-lib/lib/office/engine/docx/index.js +8 -0
- package/dist-lib/lib/office/engine/docx/styles.d.ts +31 -0
- package/dist-lib/lib/office/engine/docx/styles.js +173 -0
- package/dist-lib/lib/office/engine/docx/template.d.ts +18 -0
- package/dist-lib/lib/office/engine/docx/template.js +133 -0
- package/dist-lib/lib/office/engine/docx/text.d.ts +55 -0
- package/dist-lib/lib/office/engine/docx/text.js +538 -0
- package/dist-lib/lib/office/engine/drawingml/chart.d.ts +63 -0
- package/dist-lib/lib/office/engine/drawingml/chart.js +539 -0
- package/dist-lib/lib/office/engine/drawingml/text.d.ts +35 -0
- package/dist-lib/lib/office/engine/drawingml/text.js +352 -0
- package/dist-lib/lib/office/engine/errors.d.ts +6 -0
- package/dist-lib/lib/office/engine/errors.js +12 -0
- package/dist-lib/lib/office/engine/image.d.ts +8 -0
- package/dist-lib/lib/office/engine/image.js +48 -0
- package/dist-lib/lib/office/engine/index.d.ts +12 -0
- package/dist-lib/lib/office/engine/index.js +7 -0
- package/dist-lib/lib/office/engine/opc/content-types.d.ts +17 -0
- package/dist-lib/lib/office/engine/opc/content-types.js +74 -0
- package/dist-lib/lib/office/engine/opc/media.d.ts +13 -0
- package/dist-lib/lib/office/engine/opc/media.js +11 -0
- package/dist-lib/lib/office/engine/opc/package.d.ts +51 -0
- package/dist-lib/lib/office/engine/opc/package.js +324 -0
- package/dist-lib/lib/office/engine/opc/paths.d.ts +7 -0
- package/dist-lib/lib/office/engine/opc/paths.js +46 -0
- package/dist-lib/lib/office/engine/opc/properties.d.ts +7 -0
- package/dist-lib/lib/office/engine/opc/properties.js +75 -0
- package/dist-lib/lib/office/engine/opc/relationships.d.ts +23 -0
- package/dist-lib/lib/office/engine/opc/relationships.js +65 -0
- package/dist-lib/lib/office/engine/opc/zip.d.ts +15 -0
- package/dist-lib/lib/office/engine/opc/zip.js +264 -0
- package/dist-lib/lib/office/engine/pptx/fit.d.ts +12 -0
- package/dist-lib/lib/office/engine/pptx/fit.js +37 -0
- package/dist-lib/lib/office/engine/pptx/index.d.ts +8 -0
- package/dist-lib/lib/office/engine/pptx/index.js +8 -0
- package/dist-lib/lib/office/engine/pptx/inherit.d.ts +29 -0
- package/dist-lib/lib/office/engine/pptx/inherit.js +155 -0
- package/dist-lib/lib/office/engine/pptx/notes.d.ts +5 -0
- package/dist-lib/lib/office/engine/pptx/notes.js +85 -0
- package/dist-lib/lib/office/engine/pptx/presentation.d.ts +54 -0
- package/dist-lib/lib/office/engine/pptx/presentation.js +749 -0
- package/dist-lib/lib/office/engine/pptx/read.d.ts +9 -0
- package/dist-lib/lib/office/engine/pptx/read.js +66 -0
- package/dist-lib/lib/office/engine/pptx/shapes.d.ts +52 -0
- package/dist-lib/lib/office/engine/pptx/shapes.js +276 -0
- package/dist-lib/lib/office/engine/pptx/slides.d.ts +27 -0
- package/dist-lib/lib/office/engine/pptx/slides.js +123 -0
- package/dist-lib/lib/office/engine/pptx/template.d.ts +20 -0
- package/dist-lib/lib/office/engine/pptx/template.js +391 -0
- package/dist-lib/lib/office/engine/pptx/types.d.ts +120 -0
- package/dist-lib/lib/office/engine/pptx/types.js +1 -0
- package/dist-lib/lib/office/engine/units.d.ts +3 -0
- package/dist-lib/lib/office/engine/units.js +12 -0
- package/dist-lib/lib/office/engine/utf8.d.ts +2 -0
- package/dist-lib/lib/office/engine/utf8.js +103 -0
- package/dist-lib/lib/office/engine/xlsx/formula.d.ts +49 -0
- package/dist-lib/lib/office/engine/xlsx/formula.js +381 -0
- package/dist-lib/lib/office/engine/xlsx/index.d.ts +8 -0
- package/dist-lib/lib/office/engine/xlsx/index.js +8 -0
- package/dist-lib/lib/office/engine/xlsx/refs.d.ts +22 -0
- package/dist-lib/lib/office/engine/xlsx/refs.js +82 -0
- package/dist-lib/lib/office/engine/xlsx/reshape.d.ts +21 -0
- package/dist-lib/lib/office/engine/xlsx/reshape.js +424 -0
- package/dist-lib/lib/office/engine/xlsx/sheet.d.ts +108 -0
- package/dist-lib/lib/office/engine/xlsx/sheet.js +662 -0
- package/dist-lib/lib/office/engine/xlsx/strings.d.ts +15 -0
- package/dist-lib/lib/office/engine/xlsx/strings.js +93 -0
- package/dist-lib/lib/office/engine/xlsx/styles.d.ts +38 -0
- package/dist-lib/lib/office/engine/xlsx/styles.js +369 -0
- package/dist-lib/lib/office/engine/xlsx/template.d.ts +7 -0
- package/dist-lib/lib/office/engine/xlsx/template.js +54 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +161 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.js +850 -0
- package/dist-lib/lib/office/engine/xlsx/write.d.ts +2 -0
- package/dist-lib/lib/office/engine/xlsx/write.js +42 -0
- package/dist-lib/lib/office/engine/xml.d.ts +45 -0
- package/dist-lib/lib/office/engine/xml.js +258 -0
- package/dist-lib/lib/tools/exec/scope.d.ts +2 -0
- package/dist-lib/lib/tools/exec/scope.js +33 -2
- package/dist-lib/lib/tools/pdf/build.d.ts +43 -0
- package/dist-lib/lib/tools/pdf/build.js +150 -0
- package/dist-lib/lib/tools/pdf/images.d.ts +7 -0
- package/dist-lib/lib/tools/pdf/images.js +82 -0
- package/dist-lib/lib/tools/pdf/request.d.ts +63 -0
- package/dist-lib/lib/tools/pdf/request.js +335 -0
- package/dist-lib/lib/tools/pdf.d.ts +6 -0
- package/dist-lib/lib/tools/pdf.js +302 -0
- package/dist-lib/lib/tools/skill.js +14 -0
- package/package.json +25 -1
- package/worker/harness-sidecar.mjs +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,49 @@ While the major version is `0`, a breaking change bumps the minor version.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.8.0] - 2026-10-05
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- A skill-gated `pdf` tool on `bare-pdfium` 1.6.0. With an `attachmentId` and
|
|
16
|
+
no `action` it reads a PDF: the page count, each page's text and the form
|
|
17
|
+
fields. With an `action` it saves a new file named `output` and attaches it:
|
|
18
|
+
`create` from Markdown (chat images placed with ``),
|
|
19
|
+
`merge`, `split`, `rotate`, `watermark`, `fill`, `encrypt`, `decrypt` and
|
|
20
|
+
`text`. Errors name what exists, a locked PDF takes its password on any
|
|
21
|
+
action, and the one-line answer comes back as `reply`. On Node it reports
|
|
22
|
+
that the device cannot open PDFs. The pdf skill in the next `@qvac/skills`
|
|
23
|
+
release uses it instead of Python. (#256)
|
|
24
|
+
- An office engine in `lib/office/engine/` that reads, edits and creates `.pptx`,
|
|
25
|
+
`.docx` and `.xlsx` files in TypeScript on Bare and Node, so office editing no
|
|
26
|
+
longer needs Python and can run on mobile. It keeps untouched parts byte for
|
|
27
|
+
byte, fails with a `DocsError` whose code names the problem, and moves Excel
|
|
28
|
+
references when rows and columns change. It adds `@xmldom/xmldom`, makes
|
|
29
|
+
`bare-zlib` a direct dependency and maps `#zlib` to `bare-zlib` or `zlib`.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Qwen3.5-family models: a tool call that ran goes back into the chat history
|
|
34
|
+
in the Qwen3.5 template's own format (`<tool_call>` wrapper, one line per tag,
|
|
35
|
+
string values as written) instead of a compact `<function=…>` line. A model
|
|
36
|
+
several calls into a turn copied the compact form, the tool grammar rejected
|
|
37
|
+
it, and the run ended in "This didn't finish". Models with another tool format
|
|
38
|
+
(e.g. MedPsy) still get a format that is not their own, until the SDK accepts
|
|
39
|
+
structured tool calls in history. (QVAC-23092, #146)
|
|
40
|
+
- A skill loaded with a `file` it does not have, before its instructions were
|
|
41
|
+
loaded, now returns those instructions with a line naming the files it has,
|
|
42
|
+
so the skill counts as loaded and its skill-gated tools appear. Before, a
|
|
43
|
+
one-file skill answered that the model already had its instructions, and its
|
|
44
|
+
tools stayed hidden. (#257)
|
|
45
|
+
- After a tool attaches a file, the loop tells the model the file is shown and
|
|
46
|
+
to answer, allows one more tool round, then writes the answer in a tool-less
|
|
47
|
+
round. Small models no longer keep calling tools on their own output until
|
|
48
|
+
the round cap. (#258)
|
|
49
|
+
- `exec`: a `$` before a digit, a space or punctuation in a shell command is
|
|
50
|
+
escaped instead of rejected, so text like `$1,299` runs on the first try. Real
|
|
51
|
+
shell expansion is still refused, now with a hint to single-quote the text.
|
|
52
|
+
(#208)
|
|
53
|
+
|
|
11
54
|
## [0.7.3] - 2026-10-01
|
|
12
55
|
|
|
13
56
|
### Added
|
|
@@ -1065,7 +1108,8 @@ Tagged but never published to npm; these changes first shipped in 0.2.12.
|
|
|
1065
1108
|
- GPU stats are read through `bare-gpu-info`, so every dependency installs from
|
|
1066
1109
|
the public npm registry. (#12)
|
|
1067
1110
|
|
|
1068
|
-
[Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.
|
|
1111
|
+
[Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.8.0...HEAD
|
|
1112
|
+
[0.8.0]: https://github.com/tetherto/qvac-harness/compare/v0.7.3...v0.8.0
|
|
1069
1113
|
[0.7.3]: https://github.com/tetherto/qvac-harness/compare/v0.7.2...v0.7.3
|
|
1070
1114
|
[0.7.2]: https://github.com/tetherto/qvac-harness/compare/v0.7.1...v0.7.2
|
|
1071
1115
|
[0.7.1]: https://github.com/tetherto/qvac-harness/compare/v0.7.0...v0.7.1
|
package/NOTICE.md
CHANGED
|
@@ -11,3 +11,19 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
11
11
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
See the License for the specific language governing permissions and
|
|
13
13
|
limitations under the License.
|
|
14
|
+
|
|
15
|
+
## Third-party components
|
|
16
|
+
|
|
17
|
+
### @xmldom/xmldom
|
|
18
|
+
|
|
19
|
+
XML parsing and serialization for the office engine. Copyright 2019 - present
|
|
20
|
+
Christopher J. Brody and other contributors; Copyright 2012 - 2017 @jindw and
|
|
21
|
+
other contributors. Licensed under the MIT License.
|
|
22
|
+
<https://github.com/xmldom/xmldom>
|
|
23
|
+
|
|
24
|
+
## Test data
|
|
25
|
+
|
|
26
|
+
The files under `test/fixtures/office-corpus` are taken from the Apache POI test
|
|
27
|
+
data. Copyright The Apache Software Foundation. Licensed under the Apache
|
|
28
|
+
License, Version 2.0. <https://github.com/apache/poi>. They are used by the test
|
|
29
|
+
suite only and are not part of the published package.
|
|
@@ -123,6 +123,7 @@ export interface RunBudget {
|
|
|
123
123
|
export interface ToolExecuteContext {
|
|
124
124
|
agentId?: string | null | undefined;
|
|
125
125
|
skills?: readonly string[] | undefined;
|
|
126
|
+
skillsLoadedThisTurn?: readonly string[] | undefined;
|
|
126
127
|
ctxSize?: number | undefined;
|
|
127
128
|
image?: ImageSettings | undefined;
|
|
128
129
|
settings?: Record<string, unknown> | undefined;
|
|
@@ -29,7 +29,7 @@ import { resolveImplicitSkill, resolveSkillMention, skillPreloadMessage } from '
|
|
|
29
29
|
import { NETWORK_TOOLSETS, permittedTools } from '../tools/toolsets.js';
|
|
30
30
|
import { promptPrefixHash } from './prompt-prefix.js';
|
|
31
31
|
import { logToolPrompt } from './tool-prompt-log.js';
|
|
32
|
-
import { DIRECT_CALL_ID, loadedSkillFilesFrom, loadedSkillsFrom, TOOL_CONTINUATION_NUDGE, toolResultTurn, withoutSkillLoadTurns } from './tool-turns.js';
|
|
32
|
+
import { DIRECT_CALL_ID, deliversFile, FILE_DELIVERED_NUDGE, loadedSkillFilesFrom, loadedSkillsFrom, TOOL_CONTINUATION_NUDGE, toolResultTurn, withoutSkillLoadTurns } from './tool-turns.js';
|
|
33
33
|
export function imageSettingsFrom(agent) {
|
|
34
34
|
if (!agent)
|
|
35
35
|
return undefined;
|
|
@@ -153,7 +153,7 @@ function assistantToolTurn(contentText, calls) {
|
|
|
153
153
|
if (!calls.length || contentText.includes('<function='))
|
|
154
154
|
return contentText;
|
|
155
155
|
const markup = toolCallMarkup(calls);
|
|
156
|
-
return contentText ? `${contentText}\n${markup}` : markup;
|
|
156
|
+
return contentText ? `${contentText}\n\n${markup}` : markup;
|
|
157
157
|
}
|
|
158
158
|
function preloadLastUser(messages, name, body) {
|
|
159
159
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -734,17 +734,18 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
734
734
|
let skillIcon = null;
|
|
735
735
|
const skillEmoji = (name) => skillCatalog.find((entry) => entry.name === name)?.emoji ?? null;
|
|
736
736
|
const nudgedSkills = new Set();
|
|
737
|
-
function recordSkillLoad(skillName, output, { mainRequested, askedFile }) {
|
|
737
|
+
function recordSkillLoad(skillName, output, { mainRequested, askedFile, executed }) {
|
|
738
738
|
if (!output || typeof output !== 'object' || !('content' in output)) {
|
|
739
739
|
return;
|
|
740
740
|
}
|
|
741
741
|
loadedSkills.add(skillName);
|
|
742
|
-
|
|
742
|
+
const loadedFile = 'file' in output && typeof output.file === 'string' ? output.file : null;
|
|
743
|
+
if (loadedFile !== null) {
|
|
743
744
|
const files = loadedSkillFiles.get(skillName) ?? new Set();
|
|
744
|
-
files.add(
|
|
745
|
+
files.add(loadedFile);
|
|
745
746
|
loadedSkillFiles.set(skillName, files);
|
|
746
747
|
}
|
|
747
|
-
if (mainRequested) {
|
|
748
|
+
if (mainRequested || (executed && loadedFile === null)) {
|
|
748
749
|
loadedThisTurn.add(skillName);
|
|
749
750
|
}
|
|
750
751
|
else {
|
|
@@ -928,6 +929,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
928
929
|
for (; round < MAX_TOOL_ROUNDS; round++) {
|
|
929
930
|
if (signal?.aborted)
|
|
930
931
|
throw new AbortedError();
|
|
932
|
+
let deliveredThisRound = false;
|
|
931
933
|
if (Date.now() >= runBudget.deadlineMs) {
|
|
932
934
|
outcome = 'time-capped';
|
|
933
935
|
break;
|
|
@@ -1059,6 +1061,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1059
1061
|
skills: [...loadedSkills].filter((name) => !skillsSelected ||
|
|
1060
1062
|
(agent?.enabledSkills ?? []).includes(name) ||
|
|
1061
1063
|
name === mention?.name),
|
|
1064
|
+
skillsLoadedThisTurn: [...loadedThisTurn],
|
|
1062
1065
|
settings: toolConfigs?.[call.name],
|
|
1063
1066
|
toolOps: grantedOps,
|
|
1064
1067
|
fsRoots,
|
|
@@ -1165,7 +1168,11 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1165
1168
|
webSearches++;
|
|
1166
1169
|
}
|
|
1167
1170
|
if (call.name === SKILL_TOOL_NAME) {
|
|
1168
|
-
recordSkillLoad(skillName, output, {
|
|
1171
|
+
recordSkillLoad(skillName, output, {
|
|
1172
|
+
mainRequested,
|
|
1173
|
+
askedFile,
|
|
1174
|
+
executed: execution.executed
|
|
1175
|
+
});
|
|
1169
1176
|
}
|
|
1170
1177
|
if (!activity &&
|
|
1171
1178
|
!silent &&
|
|
@@ -1196,6 +1203,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1196
1203
|
if (isMutation && executionOutcome.kind === 'success') {
|
|
1197
1204
|
runOutcomes.confirmWrite(output);
|
|
1198
1205
|
}
|
|
1206
|
+
if (executionOutcome.kind === 'success' && deliversFile(output)) {
|
|
1207
|
+
deliveredThisRound = true;
|
|
1208
|
+
}
|
|
1199
1209
|
const turn = toolResultTurn(call.name, call.id, output);
|
|
1200
1210
|
sources.push({
|
|
1201
1211
|
origin: call.name === SKILL_TOOL_NAME ? 'skill' : 'tool-result',
|
|
@@ -1215,7 +1225,17 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1215
1225
|
outcome = 'answered';
|
|
1216
1226
|
break;
|
|
1217
1227
|
}
|
|
1218
|
-
history.push({
|
|
1228
|
+
history.push({
|
|
1229
|
+
role: 'user',
|
|
1230
|
+
content: deliveredThisRound ? FILE_DELIVERED_NUDGE : TOOL_CONTINUATION_NUDGE
|
|
1231
|
+
});
|
|
1232
|
+
if (runOutcomes.facts().delivered) {
|
|
1233
|
+
outcome = 'delivered';
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
if (deliveredThisRound) {
|
|
1237
|
+
runOutcomes.confirmDelivery();
|
|
1238
|
+
}
|
|
1219
1239
|
if (blockedRepeats >= MAX_BLOCKED_REPEATS) {
|
|
1220
1240
|
break;
|
|
1221
1241
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Message } from './completion.ts';
|
|
2
2
|
export declare const DIRECT_CALL_ID = "call-direct";
|
|
3
3
|
export declare const TOOL_CONTINUATION_NUDGE = "[Tool results above. Continue the user's request: call the next tool, or answer.]";
|
|
4
|
+
export declare const FILE_DELIVERED_NUDGE = "[Tool results above. The file is attached to the chat and shown to the user. Answer now, unless the request still needs a file that is not made yet.]";
|
|
5
|
+
export declare function isContinuationNudge(content: string): boolean;
|
|
6
|
+
export declare function deliversFile(output: unknown): boolean;
|
|
4
7
|
export declare function toolResultTurn(name: string, id: string, output: unknown): string;
|
|
5
8
|
export declare function skillLoadedTurn(name: string): string;
|
|
6
9
|
export declare function attachmentTurn(name: string, result: unknown): string | null;
|
|
@@ -2,6 +2,13 @@ import { isRecord } from '../guards.js';
|
|
|
2
2
|
import { SKILL_TOOL_NAME } from './tool-names.js';
|
|
3
3
|
export const DIRECT_CALL_ID = 'call-direct';
|
|
4
4
|
export const TOOL_CONTINUATION_NUDGE = "[Tool results above. Continue the user's request: call the next tool, or answer.]";
|
|
5
|
+
export const FILE_DELIVERED_NUDGE = '[Tool results above. The file is attached to the chat and shown to the user. Answer now, unless the request still needs a file that is not made yet.]';
|
|
6
|
+
export function isContinuationNudge(content) {
|
|
7
|
+
return content === TOOL_CONTINUATION_NUDGE || content === FILE_DELIVERED_NUDGE;
|
|
8
|
+
}
|
|
9
|
+
export function deliversFile(output) {
|
|
10
|
+
return isRecord(output) && Array.isArray(output.attachments) && output.attachments.length > 0;
|
|
11
|
+
}
|
|
5
12
|
export function toolResultTurn(name, id, output) {
|
|
6
13
|
return `[Tool: ${name} (${id})]\n${JSON.stringify(historyPayload(output))}`;
|
|
7
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isContinuationNudge } from './capabilities/tool-turns.js';
|
|
2
2
|
export const TOOL_RESULT_TRUNCATION_MARKER = '\n[tool result truncated for context]\n';
|
|
3
3
|
function isTurnBoundary(message) {
|
|
4
|
-
return message.role === 'user' && message.content
|
|
4
|
+
return message.role === 'user' && !isContinuationNudge(message.content);
|
|
5
5
|
}
|
|
6
6
|
export function contextCandidates(history) {
|
|
7
7
|
const { prefix, turns } = splitTurns(history);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { ToolExecutionOutcome } from '../tool-call-display.ts';
|
|
2
|
-
export type RunOutcome = 'answered' | 'mute' | 'exhausted' | 'search-capped' | 'time-capped';
|
|
2
|
+
export type RunOutcome = 'answered' | 'mute' | 'exhausted' | 'search-capped' | 'time-capped' | 'delivered';
|
|
3
3
|
export declare const DEGENERATE_ANSWER_FALLBACK = "I couldn't complete that step and don't have a useful result to share.";
|
|
4
4
|
export declare const ROUND_CAP_ANSWER = "I ran out of steps before I could finish that. Ask me to continue and I'll pick up where I left off.";
|
|
5
5
|
export declare const SEARCH_CAPPED_ANSWER = "I searched the web but couldn't find the answer in the results I got.";
|
|
6
|
+
export declare const DELIVERED_ANSWER = "Your file is attached to the chat.";
|
|
6
7
|
export declare const WRITE_RESULT_LEAD = "Here's what came back:";
|
|
7
8
|
export declare const TIME_CAPPED_ANSWER = "I hit this run's time budget before I could finish that. Ask me to continue and I'll pick up where I left off.";
|
|
8
9
|
export interface RunOutcomeFacts {
|
|
9
10
|
readonly latestFailure: string | null;
|
|
10
11
|
readonly confirmedWrites: ReadonlyArray<string>;
|
|
12
|
+
readonly delivered: boolean;
|
|
11
13
|
}
|
|
12
14
|
export interface HistoryTurn {
|
|
13
15
|
readonly role: string;
|
|
@@ -19,6 +21,7 @@ export interface RunOutcomeTracker {
|
|
|
19
21
|
record(result: unknown, outcome: ToolExecutionOutcome): void;
|
|
20
22
|
finishRound(): void;
|
|
21
23
|
confirmWrite(result: unknown): void;
|
|
24
|
+
confirmDelivery(): void;
|
|
22
25
|
facts(): RunOutcomeFacts;
|
|
23
26
|
}
|
|
24
27
|
export declare function createRunOutcomeTracker(): RunOutcomeTracker;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isContinuationNudge } from '../capabilities/tool-turns.js';
|
|
2
2
|
import { isRecord } from '../guards.js';
|
|
3
3
|
export const DEGENERATE_ANSWER_FALLBACK = "I couldn't complete that step and don't have a useful result to share.";
|
|
4
4
|
export const ROUND_CAP_ANSWER = "I ran out of steps before I could finish that. Ask me to continue and I'll pick up where I left off.";
|
|
5
5
|
export const SEARCH_CAPPED_ANSWER = "I searched the web but couldn't find the answer in the results I got.";
|
|
6
|
+
export const DELIVERED_ANSWER = 'Your file is attached to the chat.';
|
|
6
7
|
export const WRITE_RESULT_LEAD = "Here's what came back:";
|
|
7
8
|
export const TIME_CAPPED_ANSWER = "I hit this run's time budget before I could finish that. Ask me to continue and I'll pick up where I left off.";
|
|
8
9
|
const MAX_FAILURE_CHARS = 300;
|
|
@@ -18,6 +19,7 @@ export function createRunOutcomeTracker() {
|
|
|
18
19
|
let roundFailure = null;
|
|
19
20
|
let latestFailure = null;
|
|
20
21
|
let hasRoundResult = false;
|
|
22
|
+
let delivered = false;
|
|
21
23
|
return {
|
|
22
24
|
confirmedWrites,
|
|
23
25
|
beginRound() {
|
|
@@ -42,8 +44,11 @@ export function createRunOutcomeTracker() {
|
|
|
42
44
|
confirmedWrites.push(write);
|
|
43
45
|
}
|
|
44
46
|
},
|
|
47
|
+
confirmDelivery() {
|
|
48
|
+
delivered = true;
|
|
49
|
+
},
|
|
45
50
|
facts() {
|
|
46
|
-
return { latestFailure, confirmedWrites };
|
|
51
|
+
return { latestFailure, confirmedWrites, delivered };
|
|
47
52
|
}
|
|
48
53
|
};
|
|
49
54
|
}
|
|
@@ -64,11 +69,12 @@ export function failureFromToolExecution(result, outcome) {
|
|
|
64
69
|
}
|
|
65
70
|
export function degenerateAnswer(outcome, history, facts) {
|
|
66
71
|
const rest = (facts ? facts.latestFailure : lastFailedToolAnswer(history)) ?? cappedAnswer(outcome, history);
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
const lines = [
|
|
73
|
+
writeReport(facts?.confirmedWrites ?? []),
|
|
74
|
+
facts?.delivered ? DELIVERED_ANSWER : null,
|
|
75
|
+
rest
|
|
76
|
+
].filter((line) => line !== null);
|
|
77
|
+
return lines.length ? lines.join('\n\n') : DEGENERATE_ANSWER_FALLBACK;
|
|
72
78
|
}
|
|
73
79
|
function cappedAnswer(outcome, history) {
|
|
74
80
|
if (outcome === 'search-capped') {
|
|
@@ -108,7 +114,7 @@ function searchCappedAnswer(history) {
|
|
|
108
114
|
export function lastFailedToolAnswer(history) {
|
|
109
115
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
110
116
|
const turn = history[i];
|
|
111
|
-
if (turn?.role === 'user' && turn.content
|
|
117
|
+
if (turn?.role === 'user' && isContinuationNudge(turn.content)) {
|
|
112
118
|
continue;
|
|
113
119
|
}
|
|
114
120
|
if (turn?.role !== 'tool') {
|
|
@@ -120,12 +120,12 @@ export function toolCallMarkup(calls) {
|
|
|
120
120
|
}
|
|
121
121
|
function markupForCall(call) {
|
|
122
122
|
const params = Object.entries(call.arguments ?? {})
|
|
123
|
-
.map(([key, value]) => `<parameter=${key}
|
|
123
|
+
.map(([key, value]) => `<parameter=${key}>\n${markupValue(value)}\n</parameter>\n`)
|
|
124
124
|
.join('');
|
|
125
|
-
return `<function=${call.name}
|
|
125
|
+
return `<tool_call>\n<function=${call.name}>\n${params}</function>\n</tool_call>`;
|
|
126
126
|
}
|
|
127
|
-
function
|
|
128
|
-
return JSON.stringify(value ?? null)
|
|
127
|
+
function markupValue(value) {
|
|
128
|
+
return typeof value === 'string' ? value : JSON.stringify(value ?? null);
|
|
129
129
|
}
|
|
130
130
|
const ARG_WRAPPER_KEYS = new Set(['parameters', 'arguments', 'args', 'input', 'params']);
|
|
131
131
|
function isPlainObject(value) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsonrepair } from 'jsonrepair';
|
|
2
2
|
import { isRecord } from '../../guards.js';
|
|
3
3
|
import { hlog } from '../../log.js';
|
|
4
|
+
import { escapeLiteralDollars } from '../../tools/exec/scope.js';
|
|
4
5
|
const STRUCTURED_TYPES = new Set(['object', 'array']);
|
|
5
6
|
export class ArgRepairHook {
|
|
6
7
|
phase = 'preToolCall';
|
|
@@ -24,6 +25,12 @@ export class ArgRepairHook {
|
|
|
24
25
|
}
|
|
25
26
|
args = { ...args, [key]: parsed };
|
|
26
27
|
}
|
|
28
|
+
if (ctx.toolName === 'exec' && args.language !== 'python' && typeof args.command === 'string') {
|
|
29
|
+
const command = escapeLiteralDollars(args.command);
|
|
30
|
+
if (command !== args.command) {
|
|
31
|
+
args = { ...args, command };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
27
34
|
if (args !== ctx.args) {
|
|
28
35
|
return { args };
|
|
29
36
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export declare const CODES: {
|
|
2
|
+
readonly INVALID_INPUT: 'the input is not a Uint8Array or Buffer';
|
|
3
|
+
readonly NOT_OOXML: 'the file is not a zip archive with [Content_Types].xml';
|
|
4
|
+
readonly NOT_PPTX: 'the file is not a PowerPoint presentation';
|
|
5
|
+
readonly NOT_DOCX: 'the file is not a Word document';
|
|
6
|
+
readonly NOT_XLSX: 'the file is not an Excel workbook';
|
|
7
|
+
readonly UNSUPPORTED_PPTX: 'the presentation uses Strict Open XML';
|
|
8
|
+
readonly UNSUPPORTED_DOCX: 'the document uses Strict Open XML';
|
|
9
|
+
readonly UNSUPPORTED_XLSX: 'the workbook uses Strict Open XML';
|
|
10
|
+
readonly BROKEN_PPTX: 'the presentation points at parts that do not exist';
|
|
11
|
+
readonly BROKEN_DOCX: 'the document is missing a required part';
|
|
12
|
+
readonly BROKEN_XLSX: 'the workbook points at parts that do not exist';
|
|
13
|
+
readonly TOO_LARGE: 'the file exceeds the entry, size or compression-ratio limits';
|
|
14
|
+
readonly UNSAFE_XML: 'an XML part declares a DOCTYPE or nests too deeply';
|
|
15
|
+
readonly INVALID_XML: 'an XML part is not well-formed';
|
|
16
|
+
readonly PART_EXISTS: 'a part with that name already exists';
|
|
17
|
+
readonly PART_MISSING: 'the part is not in the package';
|
|
18
|
+
readonly UNTOUCHED_EDIT: 'a part was changed without touch() (strict mode only)';
|
|
19
|
+
readonly INVALID_OPTIONS: 'an option is unknown or out of range';
|
|
20
|
+
readonly INVALID_TEXT: 'text or paragraphs are not in an accepted shape';
|
|
21
|
+
readonly INVALID_STYLE: 'a style or format value is unknown or out of range';
|
|
22
|
+
readonly INVALID_BOX: 'a position or size is not a valid whole number of EMU';
|
|
23
|
+
readonly INVALID_IMAGE: 'the image is empty, truncated or has no size';
|
|
24
|
+
readonly UNSUPPORTED_IMAGE: 'the image is not PNG, JPEG or GIF';
|
|
25
|
+
readonly INVALID_TABLE: 'table rows or cells are not in an accepted shape';
|
|
26
|
+
readonly INVALID_CHART: 'a chart option is not valid';
|
|
27
|
+
readonly INVALID_CHART_DATA: 'chart categories or series are not valid';
|
|
28
|
+
readonly UNSUPPORTED_CHART: 'that chart type cannot be created or edited';
|
|
29
|
+
readonly CHART_COMBO: 'a combo chart must keep its number of series';
|
|
30
|
+
readonly NOT_CHART: 'the target holds no chart';
|
|
31
|
+
readonly SLIDE_NOT_FOUND: 'no slide at that index';
|
|
32
|
+
readonly SHAPE_NOT_FOUND: 'no shape with that id or role on the slide';
|
|
33
|
+
readonly LAYOUT_NOT_FOUND: 'no layout with that name';
|
|
34
|
+
readonly PARAGRAPH_NOT_FOUND: 'no paragraph at that index';
|
|
35
|
+
readonly NOT_TEXT: 'the shape or block holds no text';
|
|
36
|
+
readonly NOT_PICTURE: 'the shape is not a picture';
|
|
37
|
+
readonly NOT_TABLE: 'the shape or block is not a table';
|
|
38
|
+
readonly CELL_NOT_FOUND: 'no table cell at that row and column';
|
|
39
|
+
readonly CELL_MERGED: 'the cell is covered by a merged cell';
|
|
40
|
+
readonly SLIDE_IS_LINKED: 'another slide links to the slide';
|
|
41
|
+
readonly TOO_MANY_SLIDES: 'the deck has run out of slide ids';
|
|
42
|
+
readonly BLOCK_NOT_FOUND: 'no block at that index';
|
|
43
|
+
readonly NOT_PARAGRAPH: 'the block is not a paragraph';
|
|
44
|
+
readonly NOT_HEADING: 'the block is not a heading';
|
|
45
|
+
readonly STYLE_NOT_FOUND: 'no style with that name';
|
|
46
|
+
readonly TRACKED_CHANGES: 'the paragraph has tracked changes that an edit would accept silently';
|
|
47
|
+
readonly ROW_NOT_FOUND: 'no table row at that index';
|
|
48
|
+
readonly ROW_MERGED: 'the row is part of a vertically merged cell';
|
|
49
|
+
readonly LAST_ROW: 'the table would be left without rows';
|
|
50
|
+
readonly SHEET_NOT_FOUND: 'no sheet with that name or index';
|
|
51
|
+
readonly NOT_WORKSHEET: 'the sheet is a chart sheet or dialog sheet';
|
|
52
|
+
readonly INVALID_NAME: 'the sheet name is not allowed';
|
|
53
|
+
readonly INVALID_CELL: 'not a valid cell, row or column reference';
|
|
54
|
+
readonly INVALID_RANGE: 'not a valid range or limit';
|
|
55
|
+
readonly INVALID_VALUE: 'a cell value, formula or date is not valid';
|
|
56
|
+
readonly INVALID_FORMULA: 'text starting with = does not parse as a formula';
|
|
57
|
+
readonly SHARED_FORMULA: 'the cell belongs to a shared or array formula range';
|
|
58
|
+
readonly SHEET_HAS_DEPENDENTS: 'the sheet has pivot tables, comments or other parts that rows or columns cannot move through yet';
|
|
59
|
+
readonly SHEET_IS_REFERENCED: 'other formulas, names or charts refer to the sheet';
|
|
60
|
+
readonly LAST_SHEET: 'a workbook needs at least one visible sheet';
|
|
61
|
+
};
|
|
62
|
+
export type DocsErrorCode = keyof typeof CODES;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const CODES = {
|
|
2
|
+
INVALID_INPUT: 'the input is not a Uint8Array or Buffer',
|
|
3
|
+
NOT_OOXML: 'the file is not a zip archive with [Content_Types].xml',
|
|
4
|
+
NOT_PPTX: 'the file is not a PowerPoint presentation',
|
|
5
|
+
NOT_DOCX: 'the file is not a Word document',
|
|
6
|
+
NOT_XLSX: 'the file is not an Excel workbook',
|
|
7
|
+
UNSUPPORTED_PPTX: 'the presentation uses Strict Open XML',
|
|
8
|
+
UNSUPPORTED_DOCX: 'the document uses Strict Open XML',
|
|
9
|
+
UNSUPPORTED_XLSX: 'the workbook uses Strict Open XML',
|
|
10
|
+
BROKEN_PPTX: 'the presentation points at parts that do not exist',
|
|
11
|
+
BROKEN_DOCX: 'the document is missing a required part',
|
|
12
|
+
BROKEN_XLSX: 'the workbook points at parts that do not exist',
|
|
13
|
+
TOO_LARGE: 'the file exceeds the entry, size or compression-ratio limits',
|
|
14
|
+
UNSAFE_XML: 'an XML part declares a DOCTYPE or nests too deeply',
|
|
15
|
+
INVALID_XML: 'an XML part is not well-formed',
|
|
16
|
+
PART_EXISTS: 'a part with that name already exists',
|
|
17
|
+
PART_MISSING: 'the part is not in the package',
|
|
18
|
+
UNTOUCHED_EDIT: 'a part was changed without touch() (strict mode only)',
|
|
19
|
+
INVALID_OPTIONS: 'an option is unknown or out of range',
|
|
20
|
+
INVALID_TEXT: 'text or paragraphs are not in an accepted shape',
|
|
21
|
+
INVALID_STYLE: 'a style or format value is unknown or out of range',
|
|
22
|
+
INVALID_BOX: 'a position or size is not a valid whole number of EMU',
|
|
23
|
+
INVALID_IMAGE: 'the image is empty, truncated or has no size',
|
|
24
|
+
UNSUPPORTED_IMAGE: 'the image is not PNG, JPEG or GIF',
|
|
25
|
+
INVALID_TABLE: 'table rows or cells are not in an accepted shape',
|
|
26
|
+
INVALID_CHART: 'a chart option is not valid',
|
|
27
|
+
INVALID_CHART_DATA: 'chart categories or series are not valid',
|
|
28
|
+
UNSUPPORTED_CHART: 'that chart type cannot be created or edited',
|
|
29
|
+
CHART_COMBO: 'a combo chart must keep its number of series',
|
|
30
|
+
NOT_CHART: 'the target holds no chart',
|
|
31
|
+
SLIDE_NOT_FOUND: 'no slide at that index',
|
|
32
|
+
SHAPE_NOT_FOUND: 'no shape with that id or role on the slide',
|
|
33
|
+
LAYOUT_NOT_FOUND: 'no layout with that name',
|
|
34
|
+
PARAGRAPH_NOT_FOUND: 'no paragraph at that index',
|
|
35
|
+
NOT_TEXT: 'the shape or block holds no text',
|
|
36
|
+
NOT_PICTURE: 'the shape is not a picture',
|
|
37
|
+
NOT_TABLE: 'the shape or block is not a table',
|
|
38
|
+
CELL_NOT_FOUND: 'no table cell at that row and column',
|
|
39
|
+
CELL_MERGED: 'the cell is covered by a merged cell',
|
|
40
|
+
SLIDE_IS_LINKED: 'another slide links to the slide',
|
|
41
|
+
TOO_MANY_SLIDES: 'the deck has run out of slide ids',
|
|
42
|
+
BLOCK_NOT_FOUND: 'no block at that index',
|
|
43
|
+
NOT_PARAGRAPH: 'the block is not a paragraph',
|
|
44
|
+
NOT_HEADING: 'the block is not a heading',
|
|
45
|
+
STYLE_NOT_FOUND: 'no style with that name',
|
|
46
|
+
TRACKED_CHANGES: 'the paragraph has tracked changes that an edit would accept silently',
|
|
47
|
+
ROW_NOT_FOUND: 'no table row at that index',
|
|
48
|
+
ROW_MERGED: 'the row is part of a vertically merged cell',
|
|
49
|
+
LAST_ROW: 'the table would be left without rows',
|
|
50
|
+
SHEET_NOT_FOUND: 'no sheet with that name or index',
|
|
51
|
+
NOT_WORKSHEET: 'the sheet is a chart sheet or dialog sheet',
|
|
52
|
+
INVALID_NAME: 'the sheet name is not allowed',
|
|
53
|
+
INVALID_CELL: 'not a valid cell, row or column reference',
|
|
54
|
+
INVALID_RANGE: 'not a valid range or limit',
|
|
55
|
+
INVALID_VALUE: 'a cell value, formula or date is not valid',
|
|
56
|
+
INVALID_FORMULA: 'text starting with = does not parse as a formula',
|
|
57
|
+
SHARED_FORMULA: 'the cell belongs to a shared or array formula range',
|
|
58
|
+
SHEET_HAS_DEPENDENTS: 'the sheet has pivot tables, comments or other parts that rows or columns cannot move through yet',
|
|
59
|
+
SHEET_IS_REFERENCED: 'other formulas, names or charts refer to the sheet',
|
|
60
|
+
LAST_SHEET: 'a workbook needs at least one visible sheet'
|
|
61
|
+
};
|