@ossclip/core 0.1.28 → 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 +64 -7
- package/src/producer/beats.ts +17 -4
- package/src/producer/index.ts +5 -1
- 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.
|
|
@@ -9,13 +9,24 @@ import { estimateTokens, type LlmUsage } from "./usage";
|
|
|
9
9
|
import { extractJsonObject } from "./claude-cli";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Core's `run()` has no timeout of its own, so this flag is the ONLY clock on
|
|
13
|
+
* the spawn — without it a stuck call is a hang, with it a timeout surfaces as
|
|
14
|
+
* a fall back to the next provider (FINDINGS §132, §143).
|
|
15
|
+
*
|
|
16
|
+
* This is a RECOVERY DEADLINE, not a patience allowance (§149). It was 10m on
|
|
17
|
+
* the theory that a big beat sheet might legitimately need it; the theory was
|
|
18
|
+
* wrong twice over. agy's hang is intermittent and service-side — §143 probed
|
|
19
|
+
* a 95,030-token call that answered in 17.6s — so a call that has said nothing
|
|
20
|
+
* for 90s is not working slowly, it is hung. And what rescues it is the
|
|
21
|
+
* fallback, which costs seconds. Waiting 10m to start a 5s recovery cost one
|
|
22
|
+
* field run 605.9s of dead air on a 96-second video (2026-08-23: 692s total
|
|
23
|
+
* for ~86s of real work).
|
|
24
|
+
*
|
|
25
|
+
* 90s is 2x the slowest healthy call ever measured (46s) and below agy's own
|
|
26
|
+
* 5m default. Go duration format, verified against `agy --help` ("default
|
|
27
|
+
* 5m0s"). Raise it only for a call measured to SUCCEED slower than this.
|
|
17
28
|
*/
|
|
18
|
-
export const AGY_PRINT_TIMEOUT = "
|
|
29
|
+
export const AGY_PRINT_TIMEOUT = "90s";
|
|
19
30
|
|
|
20
31
|
/**
|
|
21
32
|
* agy takes the prompt as an argv argument only — no stdin — and macOS caps
|
|
@@ -25,6 +36,50 @@ export const AGY_PRINT_TIMEOUT = "10m";
|
|
|
25
36
|
*/
|
|
26
37
|
export const MAX_AGY_PROMPT_BYTES = 700_000;
|
|
27
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
|
+
|
|
28
83
|
/**
|
|
29
84
|
* agy's `--effort` levels. Exposed after the §143 hang incident (2026-08-22):
|
|
30
85
|
* untested at real scale whether a lower effort moves the hang, but the knob
|
|
@@ -305,7 +360,9 @@ export class AntigravityProvider implements LlmProvider {
|
|
|
305
360
|
schema: z.ZodType<T>;
|
|
306
361
|
schemaName: string;
|
|
307
362
|
}): Promise<T> {
|
|
308
|
-
|
|
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)));
|
|
309
366
|
const base =
|
|
310
367
|
`${req.system}\n\n${req.user}\n\n` +
|
|
311
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
|
|
package/src/producer/index.ts
CHANGED
|
@@ -30,7 +30,11 @@ export * from "./youtube";
|
|
|
30
30
|
export * from "./scene-props";
|
|
31
31
|
export * from "./repair";
|
|
32
32
|
export { AnthropicProvider, DEFAULT_CLAUDE_MODEL } from "./anthropic";
|
|
33
|
-
|
|
33
|
+
// AGY_PRINT_TIMEOUT is public because the CLI SAYS it: a slow agy call looks
|
|
34
|
+
// identical to a working one on screen, so the spinner names the budget it is
|
|
35
|
+
// waiting out rather than letting the wait read as a freeze (§149). Exported
|
|
36
|
+
// rather than restated in the CLI so the number cannot drift from the flag.
|
|
37
|
+
export { AntigravityProvider, AGY_PRINT_TIMEOUT, type LlmEffort } from "./antigravity";
|
|
34
38
|
export { ClaudeCliProvider } from "./claude-cli";
|
|
35
39
|
export { GeminiProvider, DEFAULT_GEMINI_MODEL } from "./gemini";
|
|
36
40
|
export { MockProvider } from "./mock";
|
|
@@ -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
|
+
}
|