@qvac/harness 0.8.6 → 0.8.7
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 +1259 -507
- package/dist-lib/index.d.ts +11 -0
- package/dist-lib/index.js +102 -25
- package/dist-lib/lib/capabilities/completion.d.ts +4 -4
- package/dist-lib/lib/capabilities/completion.js +86 -57
- package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
- package/dist-lib/lib/capabilities/tool-selection.js +2 -4
- package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
- package/dist-lib/lib/capabilities/tool-turns.js +0 -6
- package/dist-lib/lib/connect.js +2 -0
- package/dist-lib/lib/contract-types.d.ts +2 -0
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
- package/dist-lib/lib/models/context-choice.d.ts +15 -0
- package/dist-lib/lib/models/context-choice.js +55 -0
- package/dist-lib/lib/models/model-pool.d.ts +3 -1
- package/dist-lib/lib/models/model-pool.js +8 -11
- package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
- package/dist-lib/lib/models/sdk-faults.js +6 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
- package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
- package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
- package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
- package/dist-lib/lib/serve.js +5 -1
- package/dist-lib/lib/spawn.js +4 -10
- package/dist-lib/lib/tools/exec.d.ts +4 -1
- package/dist-lib/lib/tools/exec.js +55 -11
- package/dist-lib/lib/tools/office/ops.js +1 -1
- package/dist-lib/lib/tools/office.js +29 -6
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +2 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
- package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
- package/dist-lib/lib/tools/skills/mention.d.ts +0 -1
- package/dist-lib/lib/tools/skills/mention.js +0 -3
- package/dist-lib/schema/harness.js +2 -1
- package/dist-lib/spec/hrpc/messages.js +10 -5
- package/dist-lib/spec/hyperschema/index.js +10 -5
- package/dist-lib/spec/hyperschema/schema.json +6 -1
- package/dist-lib/spec/hyperschema/types.d.ts +2 -1
- package/package.json +5 -7
- package/worker/harness-sidecar.mjs +2 -1
|
@@ -18,11 +18,9 @@ export declare function collectToolGrants<T extends {
|
|
|
18
18
|
export interface SkillActivation {
|
|
19
19
|
skillsSelected: boolean;
|
|
20
20
|
enabledSkills: ReadonlyArray<string>;
|
|
21
|
-
mention: string | null;
|
|
22
|
-
loadedSkills: ReadonlySet<string>;
|
|
23
21
|
}
|
|
24
22
|
export declare function skillsAreSelected(scope: 'all' | 'selected' | null | undefined, hasAgent: boolean): boolean;
|
|
25
|
-
export declare function skillIsActive(name: string, { skillsSelected, enabledSkills
|
|
23
|
+
export declare function skillIsActive(name: string, { skillsSelected, enabledSkills }: SkillActivation): boolean;
|
|
26
24
|
export interface SelectableTool {
|
|
27
25
|
name: string;
|
|
28
26
|
skillGated?: boolean | undefined;
|
|
@@ -25,10 +25,8 @@ export function collectToolGrants(skills) {
|
|
|
25
25
|
export function skillsAreSelected(scope, hasAgent) {
|
|
26
26
|
return hasAgent && scope !== 'all';
|
|
27
27
|
}
|
|
28
|
-
export function skillIsActive(name, { skillsSelected, enabledSkills
|
|
29
|
-
|
|
30
|
-
return true;
|
|
31
|
-
return enabledSkills.includes(name) || name === mention || loadedSkills.has(name);
|
|
28
|
+
export function skillIsActive(name, { skillsSelected, enabledSkills }) {
|
|
29
|
+
return !skillsSelected || enabledSkills.includes(name);
|
|
32
30
|
}
|
|
33
31
|
export function isSelected(scope, list) {
|
|
34
32
|
return (scope ?? (list?.length ? 'selected' : 'all')) === 'selected';
|
|
@@ -9,4 +9,3 @@ export declare function skillLoadedTurn(name: string): string;
|
|
|
9
9
|
export declare function attachmentTurn(name: string, result: unknown): string | null;
|
|
10
10
|
export declare function loadedSkillsFrom(messages: readonly Message[]): Set<string>;
|
|
11
11
|
export declare function loadedSkillFilesFrom(messages: readonly Message[]): Map<string, Set<string>>;
|
|
12
|
-
export declare function withoutSkillLoadTurns(messages: readonly Message[], name: string): Message[];
|
|
@@ -71,12 +71,6 @@ export function loadedSkillFilesFrom(messages) {
|
|
|
71
71
|
}
|
|
72
72
|
return loaded;
|
|
73
73
|
}
|
|
74
|
-
export function withoutSkillLoadTurns(messages, name) {
|
|
75
|
-
return messages.filter((turn) => {
|
|
76
|
-
const payload = skillLoadPayload(turn);
|
|
77
|
-
return !payload || payload.name !== name || payload.file !== undefined;
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
74
|
function* skillLoadPayloads(messages) {
|
|
81
75
|
for (const turn of messages) {
|
|
82
76
|
const payload = skillLoadPayload(turn);
|
package/dist-lib/lib/connect.js
CHANGED
|
@@ -102,6 +102,7 @@ export class RemoteHarness extends EventEmitter {
|
|
|
102
102
|
model: context.model,
|
|
103
103
|
agent: knobs(context.agent),
|
|
104
104
|
cacheKey: context.cacheKey,
|
|
105
|
+
session: context.session,
|
|
105
106
|
thinkingDefault: context.thinkingDefault,
|
|
106
107
|
generationParams: context.generationParams,
|
|
107
108
|
responseFormat: context.responseFormat,
|
|
@@ -111,6 +112,7 @@ export class RemoteHarness extends EventEmitter {
|
|
|
111
112
|
content,
|
|
112
113
|
attachments: attachments?.map(({ path: file }) => ({ path: file }))
|
|
113
114
|
})),
|
|
115
|
+
payload: { typedText: context.typedText },
|
|
114
116
|
toolConfigs: context.toolConfigs,
|
|
115
117
|
grantedDirs: context.grantedDirs,
|
|
116
118
|
grantedDrives: context.grantedDrives,
|
|
@@ -46,7 +46,9 @@ export interface CompletionContext {
|
|
|
46
46
|
model: string;
|
|
47
47
|
agent: AgentKnobs;
|
|
48
48
|
messages: ChatMessage[];
|
|
49
|
+
typedText?: string;
|
|
49
50
|
cacheKey?: string;
|
|
51
|
+
session?: string;
|
|
50
52
|
thinkingDefault?: string;
|
|
51
53
|
toolConfigs?: Record<string, Record<string, unknown>>;
|
|
52
54
|
generationParams?: LlmGenerationParams;
|
|
@@ -9,6 +9,7 @@ export declare function clipRawCall(raw: string): string;
|
|
|
9
9
|
export interface KnownTool {
|
|
10
10
|
readonly name: string;
|
|
11
11
|
}
|
|
12
|
+
export declare function hasMarkupCall(text: string): boolean;
|
|
12
13
|
export declare function recoverMarkupToolCalls(text: string, knownTools: ReadonlyArray<KnownTool>): RecoveredToolCall[];
|
|
13
14
|
export declare function stripToolCallMarkup(text: string): string;
|
|
14
15
|
export declare function stripToolResponseBlocks(text: string): string;
|
|
@@ -46,6 +46,9 @@ export const MAX_RAW_CALL_CHARS = 4096;
|
|
|
46
46
|
export function clipRawCall(raw) {
|
|
47
47
|
return raw.length > MAX_RAW_CALL_CHARS ? raw.slice(0, MAX_RAW_CALL_CHARS) : raw;
|
|
48
48
|
}
|
|
49
|
+
export function hasMarkupCall(text) {
|
|
50
|
+
return !markupBlocks(text, FUNCTION_OPEN, FUNCTION_CLOSE).next().done;
|
|
51
|
+
}
|
|
49
52
|
export function recoverMarkupToolCalls(text, knownTools) {
|
|
50
53
|
const known = new Set(knownTools.map((tool) => tool.name));
|
|
51
54
|
const calls = [];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CapabilityProfile } from './model-catalog-constants.ts';
|
|
2
|
+
export interface ContextAsk {
|
|
3
|
+
trained(): Promise<number | undefined>;
|
|
4
|
+
fits(ctxSize: number): Promise<boolean>;
|
|
5
|
+
}
|
|
6
|
+
export declare class ContextChoice {
|
|
7
|
+
private readonly _profile;
|
|
8
|
+
private readonly _pending;
|
|
9
|
+
private readonly _chosen;
|
|
10
|
+
constructor(profile: () => Promise<CapabilityProfile | undefined>);
|
|
11
|
+
static ceiling(profile: CapabilityProfile | undefined): number | undefined;
|
|
12
|
+
chosen(key: string): number | undefined;
|
|
13
|
+
choose(key: string, tier: number, ask: ContextAsk): Promise<number>;
|
|
14
|
+
private _climb;
|
|
15
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const RUNGS = [262_144, 131_072, 65_536];
|
|
2
|
+
const CEILINGS = {
|
|
3
|
+
mid: 65_536,
|
|
4
|
+
high: 131_072,
|
|
5
|
+
xhigh: 262_144
|
|
6
|
+
};
|
|
7
|
+
export class ContextChoice {
|
|
8
|
+
_profile;
|
|
9
|
+
_pending = new Map();
|
|
10
|
+
_chosen = new Map();
|
|
11
|
+
constructor(profile) {
|
|
12
|
+
this._profile = profile;
|
|
13
|
+
}
|
|
14
|
+
static ceiling(profile) {
|
|
15
|
+
return profile && CEILINGS[profile];
|
|
16
|
+
}
|
|
17
|
+
chosen(key) {
|
|
18
|
+
return this._chosen.get(key);
|
|
19
|
+
}
|
|
20
|
+
choose(key, tier, ask) {
|
|
21
|
+
const pending = this._pending.get(key);
|
|
22
|
+
if (pending) {
|
|
23
|
+
return pending;
|
|
24
|
+
}
|
|
25
|
+
const choice = this._climb(tier, ask).then((ctxSize) => {
|
|
26
|
+
if (ctxSize === undefined) {
|
|
27
|
+
this._pending.delete(key);
|
|
28
|
+
return tier;
|
|
29
|
+
}
|
|
30
|
+
this._chosen.set(key, ctxSize);
|
|
31
|
+
return ctxSize;
|
|
32
|
+
});
|
|
33
|
+
this._pending.set(key, choice);
|
|
34
|
+
return choice;
|
|
35
|
+
}
|
|
36
|
+
async _climb(tier, ask) {
|
|
37
|
+
const ceiling = ContextChoice.ceiling(await this._profile());
|
|
38
|
+
if (!ceiling || ceiling <= tier) {
|
|
39
|
+
return tier;
|
|
40
|
+
}
|
|
41
|
+
const trained = await ask.trained();
|
|
42
|
+
if (!trained) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
for (const rung of RUNGS) {
|
|
46
|
+
if (rung > ceiling || rung > trained || rung <= tier) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (await ask.fits(rung)) {
|
|
50
|
+
return rung;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return tier;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -25,6 +25,7 @@ export interface Admission {
|
|
|
25
25
|
release(): void;
|
|
26
26
|
}
|
|
27
27
|
export type BeforeLoad = (load: PendingLoad) => Promise<Admission | undefined>;
|
|
28
|
+
export type ChooseContext = (load: PendingLoad, tier: number) => Promise<number>;
|
|
28
29
|
export interface LlmDeviceConfig {
|
|
29
30
|
device?: 'cpu' | 'gpu';
|
|
30
31
|
gpuLayers?: number;
|
|
@@ -157,9 +158,10 @@ export interface ModelPoolOptions {
|
|
|
157
158
|
deviceConfig?: LlmDeviceConfig;
|
|
158
159
|
loads?: LoadTracker;
|
|
159
160
|
beforeLoad?: BeforeLoad;
|
|
161
|
+
chooseContext?: ChooseContext;
|
|
160
162
|
hold?: HoldFn;
|
|
161
163
|
}
|
|
162
|
-
export declare function createModelPool(loadSdk: LoadSdk, notify: NotifyFn, { deviceConfig, loads, beforeLoad, hold }?: ModelPoolOptions): ModelPuddle;
|
|
164
|
+
export declare function createModelPool(loadSdk: LoadSdk, notify: NotifyFn, { deviceConfig, loads, beforeLoad, chooseContext, hold }?: ModelPoolOptions): ModelPuddle;
|
|
163
165
|
export interface EmbedPoolOptions {
|
|
164
166
|
faults: GpuFaultState;
|
|
165
167
|
batchSize?: number;
|
|
@@ -357,12 +357,12 @@ export function loadClockGuard(model, sdk, { now = () => Date.now(), monoMs = mo
|
|
|
357
357
|
};
|
|
358
358
|
return { race };
|
|
359
359
|
}
|
|
360
|
-
export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = createLoadTracker(), beforeLoad, hold } = {}) {
|
|
360
|
+
export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = createLoadTracker(), beforeLoad, chooseContext, hold } = {}) {
|
|
361
361
|
const load = async (key, model, opts) => {
|
|
362
362
|
const sdk = await loadSdk();
|
|
363
363
|
const modelSrc = await resolveModelSrc(sdk, model);
|
|
364
364
|
const resolved = resolveLoadOptions(opts, deviceConfig);
|
|
365
|
-
const
|
|
365
|
+
const tier = defaultContextSize(modelSrc, customContextLength(model));
|
|
366
366
|
const projectionModelSrc = resolved.projectionModel
|
|
367
367
|
? await resolveModelSrc(sdk, resolved.projectionModel)
|
|
368
368
|
: undefined;
|
|
@@ -376,11 +376,10 @@ export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = cr
|
|
|
376
376
|
const openclCache = deviceConfig.device !== 'cpu' && deviceConfig.openclCacheDir
|
|
377
377
|
? deviceConfig.openclCacheDir()
|
|
378
378
|
: undefined;
|
|
379
|
-
const
|
|
379
|
+
const config = definedFields({
|
|
380
380
|
verbosity: llmVerbosity(),
|
|
381
381
|
tools: true,
|
|
382
382
|
stop_sequences: RUNAWAY_STOP_SEQUENCES,
|
|
383
|
-
ctx_size: ctxSize,
|
|
384
383
|
'cache-type-k': kvCache.k,
|
|
385
384
|
'cache-type-v': kvCache.v,
|
|
386
385
|
projectionModelSrc,
|
|
@@ -389,15 +388,13 @@ export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = cr
|
|
|
389
388
|
'main-gpu': mainGpu,
|
|
390
389
|
openclCacheDir: openclCache || undefined
|
|
391
390
|
});
|
|
391
|
+
const pending = { key, model, modelSrc, modelType: 'llamacpp-completion' };
|
|
392
|
+
const ctxSize = resolved.ctxSize ||
|
|
393
|
+
(chooseContext ? await chooseContext({ ...pending, modelConfig: config }, tier) : tier);
|
|
394
|
+
const modelConfig = { ...config, ctx_size: ctxSize };
|
|
392
395
|
const { projectionModelSrc: projection, ...loggedConfig } = modelConfig;
|
|
393
396
|
hlog('llm load config', { model, projection: Boolean(projection), ...loggedConfig });
|
|
394
|
-
const admission = await beforeLoad?.({
|
|
395
|
-
key,
|
|
396
|
-
model,
|
|
397
|
-
modelSrc,
|
|
398
|
-
modelType: 'llamacpp-completion',
|
|
399
|
-
modelConfig
|
|
400
|
-
});
|
|
397
|
+
const admission = await beforeLoad?.({ ...pending, modelConfig });
|
|
401
398
|
const loadStarted = Date.now();
|
|
402
399
|
const modelId = await loadClockGuard(model, sdk)
|
|
403
400
|
.race(loads.track(sdk.loadModel({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EmbedDevice } from './embed-config.ts';
|
|
2
2
|
export declare function isSdkGpuDeviceLostError(err: unknown): boolean;
|
|
3
3
|
export declare function isSdkModelWedgedError(err: unknown): boolean;
|
|
4
|
+
export declare function isGrammarOverrunError(err: unknown): boolean;
|
|
4
5
|
export declare function isContextOverflowError(err: unknown): boolean;
|
|
5
6
|
export type GpuFaultDecision = 'reload' | 'fallback';
|
|
6
7
|
interface GpuFaultEscalation {
|
|
@@ -16,6 +16,12 @@ export function isSdkModelWedgedError(err) {
|
|
|
16
16
|
}
|
|
17
17
|
return /failed to (decode next token|eval chunk)/i.test(message);
|
|
18
18
|
}
|
|
19
|
+
export function isGrammarOverrunError(err) {
|
|
20
|
+
if (typeof err !== 'object' || err === null)
|
|
21
|
+
return false;
|
|
22
|
+
const message = 'message' in err ? err.message : undefined;
|
|
23
|
+
return typeof message === 'string' && /empty grammar stack/i.test(message);
|
|
24
|
+
}
|
|
19
25
|
export function isContextOverflowError(err) {
|
|
20
26
|
if (typeof err !== 'object' || err === null)
|
|
21
27
|
return false;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Box } from './inherit.ts';
|
|
2
|
+
import type { SlideSize } from './template.ts';
|
|
3
|
+
export type Role = 'slot' | 'blank' | 'body' | 'picture' | 'other';
|
|
4
|
+
export interface Size {
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Placed {
|
|
9
|
+
readonly box: Box;
|
|
10
|
+
readonly role: Role;
|
|
11
|
+
}
|
|
12
|
+
export interface Beside<T> {
|
|
13
|
+
readonly shape: T;
|
|
14
|
+
readonly box: Box;
|
|
15
|
+
}
|
|
16
|
+
export interface Area<T> {
|
|
17
|
+
readonly box: Box;
|
|
18
|
+
readonly cleared: readonly T[];
|
|
19
|
+
readonly beside: Beside<T> | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function framed(slide: SlideSize): Box;
|
|
22
|
+
export declare function fitted(size: Size, area: Box): Box;
|
|
23
|
+
export declare function pictureArea<T extends Placed>(slide: SlideSize, shapes: readonly T[]): Area<T>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const FRAME = 0.8;
|
|
2
|
+
const MARGIN = 0.03;
|
|
3
|
+
const MIN_BAND = 0.15;
|
|
4
|
+
const MIN_SPLIT = 0.5;
|
|
5
|
+
export function framed(slide) {
|
|
6
|
+
return {
|
|
7
|
+
x: Math.round((slide.width * (1 - FRAME)) / 2),
|
|
8
|
+
y: Math.round((slide.height * (1 - FRAME)) / 2),
|
|
9
|
+
width: Math.round(slide.width * FRAME),
|
|
10
|
+
height: Math.round(slide.height * FRAME)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function fitted(size, area) {
|
|
14
|
+
const scale = Math.min(area.width / size.width, area.height / size.height);
|
|
15
|
+
const width = Math.round(size.width * scale);
|
|
16
|
+
const height = Math.round(size.height * scale);
|
|
17
|
+
return {
|
|
18
|
+
x: area.x + Math.round((area.width - width) / 2),
|
|
19
|
+
y: area.y + Math.round((area.height - height) / 2),
|
|
20
|
+
width,
|
|
21
|
+
height
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function bandBelow(slide, shapes, margin) {
|
|
25
|
+
const top = Math.max(...shapes.map(({ box }) => box.y + box.height)) + margin;
|
|
26
|
+
const band = {
|
|
27
|
+
x: margin,
|
|
28
|
+
y: top,
|
|
29
|
+
width: slide.width - 2 * margin,
|
|
30
|
+
height: slide.height - top - margin
|
|
31
|
+
};
|
|
32
|
+
return band.height >= slide.height * MIN_BAND ? band : null;
|
|
33
|
+
}
|
|
34
|
+
function halves(box, gap) {
|
|
35
|
+
if (box.width >= box.height) {
|
|
36
|
+
const half = Math.round((box.width - gap) / 2);
|
|
37
|
+
const rest = box.width - half - gap;
|
|
38
|
+
return [
|
|
39
|
+
{ ...box, width: half },
|
|
40
|
+
{ ...box, x: box.x + half + gap, width: rest }
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
const half = Math.round((box.height - gap) / 2);
|
|
44
|
+
const rest = box.height - half - gap;
|
|
45
|
+
return [
|
|
46
|
+
{ ...box, height: half },
|
|
47
|
+
{ ...box, y: box.y + half + gap, height: rest }
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function split(shape, gap) {
|
|
51
|
+
const [kept, freed] = halves(shape.box, gap);
|
|
52
|
+
const box = shape.role === 'picture' ? fitted(shape.box, kept) : kept;
|
|
53
|
+
return { box: freed, cleared: [], beside: { shape, box } };
|
|
54
|
+
}
|
|
55
|
+
function largest(shapes) {
|
|
56
|
+
return shapes.reduce((best, shape) => !best || shape.box.width * shape.box.height > best.box.width * best.box.height ? shape : best, undefined);
|
|
57
|
+
}
|
|
58
|
+
function overlaps(a, b) {
|
|
59
|
+
return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
60
|
+
}
|
|
61
|
+
function place(slide, shapes) {
|
|
62
|
+
const slot = shapes.find((shape) => shape.role === 'slot');
|
|
63
|
+
if (slot) {
|
|
64
|
+
return { box: slot.box, cleared: [slot], beside: null };
|
|
65
|
+
}
|
|
66
|
+
const whole = { box: framed(slide), cleared: [], beside: null };
|
|
67
|
+
if (!shapes.length) {
|
|
68
|
+
return whole;
|
|
69
|
+
}
|
|
70
|
+
const margin = Math.round(slide.height * MARGIN);
|
|
71
|
+
const band = bandBelow(slide, shapes, margin);
|
|
72
|
+
if (band) {
|
|
73
|
+
return { box: band, cleared: [], beside: null };
|
|
74
|
+
}
|
|
75
|
+
const bodies = shapes.filter((shape) => shape.role === 'body');
|
|
76
|
+
const wide = bodies.filter((shape) => shape.box.width >= slide.width * MIN_SPLIT);
|
|
77
|
+
const shared = largest(wide) ?? largest(shapes.filter((shape) => shape.role === 'picture')) ?? largest(bodies);
|
|
78
|
+
return shared ? split(shared, margin) : whole;
|
|
79
|
+
}
|
|
80
|
+
export function pictureArea(slide, shapes) {
|
|
81
|
+
const area = place(slide, shapes.filter((shape) => shape.role !== 'blank'));
|
|
82
|
+
const covered = shapes.filter((shape) => shape.role === 'blank' && overlaps(shape.box, area.box));
|
|
83
|
+
return { ...area, cleared: [...area.cleared, ...covered] };
|
|
84
|
+
}
|
|
@@ -12,6 +12,7 @@ import * as fit from './fit.js';
|
|
|
12
12
|
import * as template from './template.js';
|
|
13
13
|
import * as slides from './slides.js';
|
|
14
14
|
import * as notes from './notes.js';
|
|
15
|
+
import { fitted, framed, pictureArea } from './picture-area.js';
|
|
15
16
|
import { pick, describe, layoutName, layoutType, BODY_TYPES } from './read.js';
|
|
16
17
|
import { SHAPE_ORDER, shapesOf, placeholderFrom, pictureElement, textBoxElement, tableElement, chartFrameElement, setBackground, nextShapeId } from './shapes.js';
|
|
17
18
|
const { a: A, p: P, r: R, p14: P14 } = xml.ns;
|
|
@@ -154,16 +155,19 @@ function checkSlideText(doc, at, opts) {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
|
-
function
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
158
|
+
function roleOf(shape) {
|
|
159
|
+
const { el, kind, placeholder } = shape;
|
|
160
|
+
if (kind === 'picture') {
|
|
161
|
+
return 'picture';
|
|
162
|
+
}
|
|
163
|
+
if (el.localName !== 'sp' || !placeholder || !BODY_TYPES.has(placeholder)) {
|
|
164
|
+
return 'other';
|
|
165
|
+
}
|
|
166
|
+
const txBody = xml.child(el, P, 'txBody');
|
|
167
|
+
if (txBody && text.readParagraphs(txBody).some((p) => p.text.trim())) {
|
|
168
|
+
return placeholder === 'subTitle' ? 'other' : 'body';
|
|
169
|
+
}
|
|
170
|
+
return placeholder === 'obj' ? 'slot' : 'blank';
|
|
167
171
|
}
|
|
168
172
|
function tableBody(tc) {
|
|
169
173
|
const existing = xml.child(tc, A, 'txBody');
|
|
@@ -366,6 +370,9 @@ export class Presentation {
|
|
|
366
370
|
const layoutDoc = this.package.xml(layout);
|
|
367
371
|
const doc = slideFromLayout(layoutDoc);
|
|
368
372
|
checkSlideText(doc, at, opts);
|
|
373
|
+
if (opts.image !== undefined) {
|
|
374
|
+
probe(opts.image);
|
|
375
|
+
}
|
|
369
376
|
this.insertSlide({ doc, layout, at, ids: all.map((e) => e.id), before: all[at]?.el ?? null });
|
|
370
377
|
if (opts.title !== undefined) {
|
|
371
378
|
this.setText({ slide: at, shape: 'title' }, opts.title);
|
|
@@ -373,14 +380,25 @@ export class Presentation {
|
|
|
373
380
|
if (opts.body !== undefined) {
|
|
374
381
|
this.setText({ slide: at, shape: 'body' }, opts.body);
|
|
375
382
|
}
|
|
376
|
-
|
|
383
|
+
if (opts.image !== undefined) {
|
|
384
|
+
this.addPicture(at, opts.image);
|
|
385
|
+
}
|
|
386
|
+
const shapes = shapesOf(this.package.xml(slides.slideAt(this.deck, at).part));
|
|
387
|
+
return { index: at, layout: layoutName(layoutDoc), shapes: shapes.map(describe) };
|
|
377
388
|
}
|
|
378
389
|
addPicture(slide, bytes, opts = {}) {
|
|
379
390
|
const entry = slides.slideAt(this.deck, slide);
|
|
380
|
-
const
|
|
391
|
+
const area = this.pictureArea(entry.part, opts);
|
|
392
|
+
const box = this.pictureBox(probe(bytes), opts, area.box);
|
|
381
393
|
const { rId } = media.addImage(this.package, bytes, { source: entry.part, dir: '../media' });
|
|
382
394
|
const doc = this.package.xml(entry.part);
|
|
383
395
|
const id = nextShapeId(doc);
|
|
396
|
+
for (const shape of area.cleared) {
|
|
397
|
+
xml.remove(shape.el);
|
|
398
|
+
}
|
|
399
|
+
if (area.beside) {
|
|
400
|
+
this.narrow(entry.part, area.beside);
|
|
401
|
+
}
|
|
384
402
|
this.spTree(entry.part).appendChild(pictureElement(doc, { id, rId, box, name: opts.name, description: opts.description }));
|
|
385
403
|
this.package.touch(entry.part);
|
|
386
404
|
return { id, ...box };
|
|
@@ -722,14 +740,34 @@ export class Presentation {
|
|
|
722
740
|
}
|
|
723
741
|
return box;
|
|
724
742
|
}
|
|
725
|
-
|
|
743
|
+
narrow(part, { shape, box }) {
|
|
744
|
+
writeXfrm(shape.el, box);
|
|
745
|
+
const txBody = xml.child(shape.el, P, 'txBody');
|
|
746
|
+
const estimate = txBody ? this.estimate(part, shape.el, txBody) : null;
|
|
747
|
+
if (txBody && estimate?.overflow) {
|
|
748
|
+
applyAutofit(txBody, estimate);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
pictureArea(part, opts) {
|
|
752
|
+
if (opts.x !== undefined || opts.y !== undefined) {
|
|
753
|
+
return { box: framed(this.slideSize), cleared: [], beside: null };
|
|
754
|
+
}
|
|
755
|
+
const shapes = shapesOf(this.package.xml(part)).flatMap((shape) => {
|
|
756
|
+
if (shape.group !== null || SKIPPED_PLACEHOLDERS.has(shape.placeholder ?? '')) {
|
|
757
|
+
return [];
|
|
758
|
+
}
|
|
759
|
+
const box = inherit.box({ pkg: this.package, slide: part, el: shape.el });
|
|
760
|
+
return box ? [{ el: shape.el, box, role: roleOf(shape) }] : [];
|
|
761
|
+
});
|
|
762
|
+
return pictureArea(this.slideSize, shapes);
|
|
763
|
+
}
|
|
764
|
+
pictureBox(info, opts, area) {
|
|
726
765
|
checkPlacement(opts);
|
|
727
|
-
const slide = this.slideSize;
|
|
728
766
|
const ratio = info.height / info.width;
|
|
729
767
|
let { width, height } = opts;
|
|
730
768
|
if (width === undefined && height === undefined) {
|
|
731
769
|
const natural = { width: info.width * EMU_PER_PIXEL, height: info.height * EMU_PER_PIXEL };
|
|
732
|
-
const scale = Math.min(1,
|
|
770
|
+
const scale = Math.min(1, area.width / natural.width, area.height / natural.height);
|
|
733
771
|
width = Math.round(natural.width * scale);
|
|
734
772
|
height = Math.round(natural.height * scale);
|
|
735
773
|
}
|
|
@@ -742,8 +780,8 @@ export class Presentation {
|
|
|
742
780
|
if (!width || !height) {
|
|
743
781
|
throw new DocsError('INVALID_BOX', 'the picture would have no visible size');
|
|
744
782
|
}
|
|
745
|
-
const x = opts.x ?? Math.max(0, Math.round((
|
|
746
|
-
const y = opts.y ?? Math.max(0, Math.round((
|
|
783
|
+
const x = opts.x ?? Math.max(0, area.x + Math.round((area.width - width) / 2));
|
|
784
|
+
const y = opts.y ?? Math.max(0, area.y + Math.round((area.height - height) / 2));
|
|
747
785
|
return { x, y, width, height };
|
|
748
786
|
}
|
|
749
787
|
}
|
|
@@ -2,6 +2,7 @@ import { Package } from '../opc/package.js';
|
|
|
2
2
|
import * as xml from '../xml.js';
|
|
3
3
|
import * as media from '../opc/media.js';
|
|
4
4
|
import { probe } from '../image.js';
|
|
5
|
+
import { points } from '../units.js';
|
|
5
6
|
import * as refs from './refs.js';
|
|
6
7
|
import * as formula from './formula.js';
|
|
7
8
|
import { Sheet, SHEET_ORDER } from './sheet.js';
|
|
@@ -170,6 +171,12 @@ function pictureAnchor(doc, anchor, spec) {
|
|
|
170
171
|
root.appendChild(d('clientData'));
|
|
171
172
|
return root;
|
|
172
173
|
}
|
|
174
|
+
function markerCell(marker) {
|
|
175
|
+
return {
|
|
176
|
+
row: Number(xml.text(xml.child(marker, XDR, 'row'))),
|
|
177
|
+
col: Number(xml.text(xml.child(marker, XDR, 'col')))
|
|
178
|
+
};
|
|
179
|
+
}
|
|
173
180
|
function nextDrawingId(doc) {
|
|
174
181
|
return (Math.max(0, ...xml.descendants(doc, XDR, 'cNvPr').map((el) => Number(el.getAttribute('id')) || 0)) + 1);
|
|
175
182
|
}
|
|
@@ -421,7 +428,7 @@ export class Workbook {
|
|
|
421
428
|
const target = this.sheet(sheet);
|
|
422
429
|
const info = probe(bytes);
|
|
423
430
|
const o = isRecord(opts) ? opts : {};
|
|
424
|
-
const at =
|
|
431
|
+
const at = o.cell === undefined ? this.besideData(target) : refs.parse(o.cell);
|
|
425
432
|
const size = imageSize(o, info);
|
|
426
433
|
const drawing = this.ensureDrawing(target);
|
|
427
434
|
const { rId } = media.addImage(this.package, bytes, { source: drawing, dir: '../media' });
|
|
@@ -791,7 +798,8 @@ export class Workbook {
|
|
|
791
798
|
const from = xml.child(anchor, XDR, 'from');
|
|
792
799
|
const info = chart.readChart(this.package.xml(rel.part));
|
|
793
800
|
if (from) {
|
|
794
|
-
|
|
801
|
+
const { row, col } = markerCell(from);
|
|
802
|
+
info.cell = refs.cell(row, col);
|
|
795
803
|
}
|
|
796
804
|
out.push(info);
|
|
797
805
|
}
|
|
@@ -823,7 +831,30 @@ export class Workbook {
|
|
|
823
831
|
}
|
|
824
832
|
besideData(sheet) {
|
|
825
833
|
const used = sheet.usedRange();
|
|
826
|
-
|
|
834
|
+
const col = used ? used.right + 2 : 0;
|
|
835
|
+
return { row: Math.max(used ? used.top : 0, ...this.rowsBelowDrawings(sheet, col)), col };
|
|
836
|
+
}
|
|
837
|
+
rowsBelowDrawings(sheet, col) {
|
|
838
|
+
const drawing = this.drawingOf(sheet);
|
|
839
|
+
if (!drawing) {
|
|
840
|
+
return [];
|
|
841
|
+
}
|
|
842
|
+
const format = xml.child(sheet.root, X, 'sheetFormatPr');
|
|
843
|
+
const rowHeight = points(Number(format?.getAttribute('defaultRowHeight') ?? 15));
|
|
844
|
+
return xml.children(this.package.root(drawing), XDR).flatMap((anchor) => {
|
|
845
|
+
const from = xml.child(anchor, XDR, 'from');
|
|
846
|
+
if (!from || markerCell(from).col !== col) {
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
const to = xml.child(anchor, XDR, 'to');
|
|
850
|
+
if (to) {
|
|
851
|
+
return [markerCell(to).row + 2];
|
|
852
|
+
}
|
|
853
|
+
const ext = xml.child(anchor, XDR, 'ext');
|
|
854
|
+
return ext
|
|
855
|
+
? [markerCell(from).row + Math.ceil(Number(ext.getAttribute('cy')) / rowHeight) + 1]
|
|
856
|
+
: [];
|
|
857
|
+
});
|
|
827
858
|
}
|
|
828
859
|
drawingOf(sheet) {
|
|
829
860
|
const ref = xml.child(sheet.root, X, 'drawing');
|
package/dist-lib/lib/serve.js
CHANGED
|
@@ -8,7 +8,7 @@ import HRPC from '../spec/hrpc/index.js';
|
|
|
8
8
|
import { isCapability } from './capabilities/invoke.js';
|
|
9
9
|
import { CUSTOM_TASK_TYPES } from './models/model-catalog.js';
|
|
10
10
|
import { unsupportedKvCacheDeletion } from './capabilities/kv-cache.js';
|
|
11
|
-
import { errorMessage } from './guards.js';
|
|
11
|
+
import { errorMessage, isRecord } from './guards.js';
|
|
12
12
|
import { hlog } from './log.js';
|
|
13
13
|
import { pushQueue } from './queue.js';
|
|
14
14
|
import { TextCoalescer } from './helpers/text-coalescer.js';
|
|
@@ -525,10 +525,14 @@ function completion(harness) {
|
|
|
525
525
|
model: frame.model ?? '',
|
|
526
526
|
agent: frame.agent ?? {},
|
|
527
527
|
cacheKey: frame.cacheKey ?? undefined,
|
|
528
|
+
session: frame.session ?? undefined,
|
|
528
529
|
thinkingDefault: frame.thinkingDefault ?? undefined,
|
|
529
530
|
generationParams: frame.generationParams ?? undefined,
|
|
530
531
|
responseFormat: frame.responseFormat ?? undefined,
|
|
531
532
|
captureInferenceCalls: frame.captureInferenceCalls ?? undefined,
|
|
533
|
+
typedText: isRecord(frame.payload) && typeof frame.payload.typedText === 'string'
|
|
534
|
+
? frame.payload.typedText
|
|
535
|
+
: undefined,
|
|
532
536
|
messages: (frame.messages ?? []).map(({ role, content, attachments }) => ({
|
|
533
537
|
role,
|
|
534
538
|
content,
|
package/dist-lib/lib/spawn.js
CHANGED
|
@@ -46,9 +46,8 @@ export function spawnHarness(opts) {
|
|
|
46
46
|
batchSize: opts.embedBatchSize
|
|
47
47
|
});
|
|
48
48
|
return connectHarness(async () => {
|
|
49
|
-
const { default:
|
|
50
|
-
const {
|
|
51
|
-
const sidecar = new Sidecar(opts.entry, [
|
|
49
|
+
const { default: launch } = await import('bare-supervisor/runner/sidecar');
|
|
50
|
+
const { ipc, exit } = launch(opts.entry, [
|
|
52
51
|
opts.skillsDir ?? '',
|
|
53
52
|
JSON.stringify(opts.sdkStorage),
|
|
54
53
|
JSON.stringify(embedConfig),
|
|
@@ -60,13 +59,8 @@ export function spawnHarness(opts) {
|
|
|
60
59
|
opts.musicClamps ? JSON.stringify(opts.musicClamps) : '',
|
|
61
60
|
opts.wheelsDir ?? '',
|
|
62
61
|
opts.gpuStats === undefined ? '' : JSON.stringify(opts.gpuStats)
|
|
63
|
-
]);
|
|
64
|
-
|
|
65
|
-
sidecar.stderr.on('data', (data) => logger?.warn('harness', data.toString().trimEnd()));
|
|
66
|
-
const ipc = wrap(sidecar);
|
|
67
|
-
const died = new Promise((resolve) => {
|
|
68
|
-
sidecar.once('exit', (code) => resolve(`harness exited (${code}) before ready`));
|
|
69
|
-
});
|
|
62
|
+
], logger);
|
|
63
|
+
const died = exit.then((code) => `harness exited (${code}) before ready`);
|
|
70
64
|
const failure = await Promise.race([ipc.ready.then(() => null), died]);
|
|
71
65
|
if (failure) {
|
|
72
66
|
throw new Error(failure);
|