@ossclip/core 0.1.29 → 0.1.30
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/package.json +1 -1
- package/src/browser.ts +1 -0
- package/src/producer/antigravity.ts +47 -1
- package/src/producer/beats.ts +17 -4
- package/src/scene-props-controls.ts +63 -0
package/package.json
CHANGED
package/src/browser.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export * from "./scene-schema";
|
|
8
8
|
export * from "./scene-registry";
|
|
9
|
+
export * from "./scene-props-controls";
|
|
9
10
|
export * from "./overrides";
|
|
10
11
|
// The editor derives its plain takes with the SAME function the pipeline
|
|
11
12
|
// uses — a copy would drift and the two timelines would disagree.
|
|
@@ -36,6 +36,50 @@ export const AGY_PRINT_TIMEOUT = "90s";
|
|
|
36
36
|
*/
|
|
37
37
|
export const MAX_AGY_PROMPT_BYTES = 700_000;
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Remove the constraints agy would REJECT a whole generation over and we can
|
|
41
|
+
* absorb ourselves (§151).
|
|
42
|
+
*
|
|
43
|
+
* agy does not constrain decoding — it generates, validates server-side
|
|
44
|
+
* against the schema we hand it, and on failure regenerates. Captured from a
|
|
45
|
+
* real call:
|
|
46
|
+
*
|
|
47
|
+
* "error": "invalid arguments:\n- at '/hook': maxLength: got 136, want 120"
|
|
48
|
+
*
|
|
49
|
+
* Sixteen characters over, and the whole attempt is discarded. `maxLength`
|
|
50
|
+
* buys nothing there, because `cappedText` already truncates an overshoot at a
|
|
51
|
+
* word boundary — so the cap on the wire could only ever cost a generation,
|
|
52
|
+
* never save one.
|
|
53
|
+
*
|
|
54
|
+
* SCOPE, stated because the obvious guess is wrong: this is NOT why agy times
|
|
55
|
+
* out. That was the theory this function was written under, and it was
|
|
56
|
+
* refuted by replaying the exact failing beat-sheet request standalone — with
|
|
57
|
+
* maxLength stripped it still timed out, and with `--json-schema` dropped
|
|
58
|
+
* ENTIRELY it still timed out, agy's own error being "timeout waiting for
|
|
59
|
+
* response" with an empty body. The hang is upstream and prompt-triggered
|
|
60
|
+
* (§143, §149); this only removes one real-but-separate way a generation gets
|
|
61
|
+
* thrown away.
|
|
62
|
+
*
|
|
63
|
+
* `maxItems`, `enum`, `const`, `type` and `required` all stay: a 25th moment
|
|
64
|
+
* or an invented sceneKind is not something truncation can quietly repair, and
|
|
65
|
+
* that is the part of the contract worth paying a retry for.
|
|
66
|
+
*
|
|
67
|
+
* Only agy needs this. claude-cli receives the schema as prompt text, and
|
|
68
|
+
* gemini constrains during decoding, so neither turns a long string into a
|
|
69
|
+
* discarded generation.
|
|
70
|
+
*/
|
|
71
|
+
export function stripAbsorbableCaps(schema: unknown): unknown {
|
|
72
|
+
if (Array.isArray(schema)) return schema.map(stripAbsorbableCaps);
|
|
73
|
+
if (schema && typeof schema === "object") {
|
|
74
|
+
return Object.fromEntries(
|
|
75
|
+
Object.entries(schema as Record<string, unknown>)
|
|
76
|
+
.filter(([k]) => k !== "maxLength")
|
|
77
|
+
.map(([k, v]) => [k, stripAbsorbableCaps(v)]),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return schema;
|
|
81
|
+
}
|
|
82
|
+
|
|
39
83
|
/**
|
|
40
84
|
* agy's `--effort` levels. Exposed after the §143 hang incident (2026-08-22):
|
|
41
85
|
* untested at real scale whether a lower effort moves the hang, but the knob
|
|
@@ -316,7 +360,9 @@ export class AntigravityProvider implements LlmProvider {
|
|
|
316
360
|
schema: z.ZodType<T>;
|
|
317
361
|
schemaName: string;
|
|
318
362
|
}): Promise<T> {
|
|
319
|
-
|
|
363
|
+
// Stripped before it goes on the wire (§151) — agy validates against this
|
|
364
|
+
// after generating, and a length overshoot costs the whole attempt.
|
|
365
|
+
const schemaText = JSON.stringify(stripAbsorbableCaps(z.toJSONSchema(req.schema)));
|
|
320
366
|
const base =
|
|
321
367
|
`${req.system}\n\n${req.user}\n\n` +
|
|
322
368
|
`Respond with ONLY a JSON object valid against this JSON Schema ("${req.schemaName}"). ` +
|
package/src/producer/beats.ts
CHANGED
|
@@ -20,6 +20,15 @@ import type { LlmProvider } from "./provider";
|
|
|
20
20
|
* so the model is still ASKED for the limit; it just no longer costs a run
|
|
21
21
|
* when the model misses by a word. Truncation prefers the last word boundary,
|
|
22
22
|
* and adds no ellipsis — the prompt explicitly forbids one on cover text.
|
|
23
|
+
*
|
|
24
|
+
* One provider is now an exception (§151). agy does not constrain decoding: it
|
|
25
|
+
* generates, validates against the schema server-side, and REGENERATES on a
|
|
26
|
+
* miss — so there, "still asked" cost the entire attempt, and a run of near
|
|
27
|
+
* misses walked into the print-timeout as a hang. `stripAbsorbableCaps` drops
|
|
28
|
+
* maxLength from agy's copy of the schema, and this truncation is what makes
|
|
29
|
+
* that safe. Every capped string in the beat sheet routes through here for
|
|
30
|
+
* exactly that reason — a single bare `.max()` would turn agy's retry loop
|
|
31
|
+
* into a hard local failure instead.
|
|
23
32
|
*/
|
|
24
33
|
export function cappedText(max: number): z.ZodType<string> {
|
|
25
34
|
return z.preprocess((v) => {
|
|
@@ -86,10 +95,14 @@ export type BeatSheet = z.infer<typeof BeatSheetSchema>;
|
|
|
86
95
|
export const ClipHighlightSchema = z.object({
|
|
87
96
|
startWord: z.number().int().nonnegative(),
|
|
88
97
|
endWord: z.number().int().nonnegative(),
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
// cappedText, not a bare .max(200) (§151): this was the ONE capped string in
|
|
99
|
+
// the beat sheet that REJECTED an overshoot instead of truncating it. That
|
|
100
|
+
// asymmetry is load-bearing now — the agy request drops maxLength from the
|
|
101
|
+
// wire schema precisely because every cap can be absorbed locally, and a
|
|
102
|
+
// single rejecting field would turn a retry loop into a hard failure.
|
|
103
|
+
reason: cappedText(200).describe(
|
|
104
|
+
"one line: why THIS window is the strongest stretch of the take",
|
|
105
|
+
),
|
|
93
106
|
});
|
|
94
107
|
export type ClipHighlight = z.infer<typeof ClipHighlightSchema>;
|
|
95
108
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { SCENE_REGISTRY } from "./scene-registry";
|
|
3
|
+
import type { SceneComponentId } from "./scene-schema";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The Inspector's controls for a scene's non-text props, derived from the
|
|
7
|
+
* component's own schema (§153).
|
|
8
|
+
*
|
|
9
|
+
* Every string prop already has an editor: select the element and type in the
|
|
10
|
+
* Text field. Nothing else did. `inverted`, `kenBurns`, `emphasizeLast` and
|
|
11
|
+
* `fanOut` were reachable only by hand-editing overrides.json, because
|
|
12
|
+
* `elementTextOf` returns null for anything that is not a string and the Text
|
|
13
|
+
* field never renders.
|
|
14
|
+
*
|
|
15
|
+
* Derived rather than hand-listed on purpose. Hand-wiring per component is
|
|
16
|
+
* exactly how ScreenshotFrame shipped a `data-edit-id` naming no prop at all —
|
|
17
|
+
* the UI and the schema drifted and nothing connected them. Reading the schema
|
|
18
|
+
* means a component that gains a boolean gets a control the day it lands.
|
|
19
|
+
*
|
|
20
|
+
* Lives in core, not the editor, and ships through the `browser` entry: zod
|
|
21
|
+
* is already in that module graph (scene-schema + scene-registry), so the
|
|
22
|
+
* editor gets the derivation without pulling a schema library into its own
|
|
23
|
+
* bundle — the same reason `browser.ts` exists at all.
|
|
24
|
+
*/
|
|
25
|
+
export type PropControl = {
|
|
26
|
+
key: string;
|
|
27
|
+
kind: "boolean" | "enum";
|
|
28
|
+
/** The schema's own default — what the scene renders as when unset. */
|
|
29
|
+
fallback?: boolean;
|
|
30
|
+
options?: string[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type JsonSchemaProp = {
|
|
34
|
+
type?: string;
|
|
35
|
+
enum?: unknown[];
|
|
36
|
+
default?: unknown;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function scalarPropControls(component: SceneComponentId): PropControl[] {
|
|
40
|
+
const meta = SCENE_REGISTRY[component];
|
|
41
|
+
if (!meta) return [];
|
|
42
|
+
const schema = z.toJSONSchema(meta.propsSchema as unknown as z.ZodType) as {
|
|
43
|
+
properties?: Record<string, JsonSchemaProp>;
|
|
44
|
+
};
|
|
45
|
+
const controls: PropControl[] = [];
|
|
46
|
+
for (const [key, prop] of Object.entries(schema.properties ?? {})) {
|
|
47
|
+
// Enums first: a string prop with an enum is a CHOICE, not free text, and
|
|
48
|
+
// the Text field would let you type a value the component cannot render.
|
|
49
|
+
if (Array.isArray(prop.enum) && prop.enum.every((v) => typeof v === "string")) {
|
|
50
|
+
controls.push({ key, kind: "enum", options: prop.enum as string[] });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (prop.type === "boolean") {
|
|
54
|
+
// The default matters: kenBurns is true when unset, so a checkbox that
|
|
55
|
+
// assumed false would describe the scene wrongly before you touched it.
|
|
56
|
+
controls.push({ key, kind: "boolean", fallback: prop.default === true });
|
|
57
|
+
}
|
|
58
|
+
// Strings and arrays fall through by design — the per-element Text field
|
|
59
|
+
// and element selection already own them, and a second control writing the
|
|
60
|
+
// same prop is how two sources of truth start disagreeing.
|
|
61
|
+
}
|
|
62
|
+
return controls;
|
|
63
|
+
}
|