orizu 0.5.39 → 0.5.40
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/README.md +77 -0
- package/dist/dataset-commands.d.ts +3 -1
- package/dist/dataset-commands.d.ts.map +1 -1
- package/dist/dataset-commands.js +101 -62
- package/dist/dataset-delete-workflow.d.ts +4 -0
- package/dist/dataset-delete-workflow.d.ts.map +1 -0
- package/dist/dataset-delete-workflow.js +75 -0
- package/dist/dataset-lock-workflow.d.ts +12 -0
- package/dist/dataset-lock-workflow.d.ts.map +1 -0
- package/dist/dataset-lock-workflow.js +76 -0
- package/dist/dataset-readme-workflow.d.ts +13 -0
- package/dist/dataset-readme-workflow.d.ts.map +1 -0
- package/dist/dataset-readme-workflow.js +82 -0
- package/dist/dataset-row-workflow.d.ts +14 -0
- package/dist/dataset-row-workflow.d.ts.map +1 -0
- package/dist/dataset-row-workflow.js +202 -0
- package/dist/dataset-split-reads.d.ts +28 -0
- package/dist/dataset-split-reads.d.ts.map +1 -0
- package/dist/dataset-split-reads.js +47 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +29 -3
- package/dist/hosted-boot.d.ts +1 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +17 -0
- package/dist/hosted-harness-opencode.d.ts +4 -5
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +66 -52
- package/dist/hosted-loop-lifecycle.d.ts +4 -10
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
- package/dist/hosted-loop-lifecycle.js +4 -10
- package/dist/hosted-loop.d.ts +2 -0
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/instruction-delivery.d.ts +14 -0
- package/dist/instruction-delivery.d.ts.map +1 -0
- package/dist/instruction-delivery.js +103 -0
- package/dist/instruction-set-sync/index.d.ts +17 -0
- package/dist/instruction-set-sync/index.d.ts.map +1 -1
- package/dist/instruction-set-sync/index.js +44 -17
- package/dist/instruction-set-update/index.d.ts +2 -2
- package/dist/instruction-set-update/index.d.ts.map +1 -1
- package/dist/instruction-set-update/index.js +2 -2
- package/dist/instruction-sets-cli.d.ts.map +1 -1
- package/dist/instruction-sets-cli.js +19 -27
- package/dist/json-response.d.ts +1 -0
- package/dist/json-response.d.ts.map +1 -1
- package/dist/json-response.js +7 -0
- package/dist/preview-runtime.d.ts +2 -2
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +37 -7
- package/dist/task-export-workflow.d.ts +4 -0
- package/dist/task-export-workflow.d.ts.map +1 -0
- package/dist/task-export-workflow.js +46 -0
- package/dist/workbench-cli.js +1 -1
- package/package.json +1 -1
- package/src/dataset-commands.ts +92 -92
- package/src/dataset-delete-workflow.ts +72 -0
- package/src/dataset-lock-workflow.ts +74 -0
- package/src/dataset-readme-workflow.ts +80 -0
- package/src/dataset-row-workflow.ts +248 -0
- package/src/dataset-split-reads.ts +43 -0
- package/src/help.ts +29 -3
- package/src/hosted-boot.ts +16 -1
- package/src/hosted-harness-opencode.ts +67 -65
- package/src/hosted-loop-lifecycle.ts +4 -10
- package/src/hosted-loop.ts +6 -4
- package/src/hosted-model-cache.d.mts +2 -0
- package/src/hosted-model-cache.mjs +53 -0
- package/src/index.ts +10 -0
- package/src/instruction-delivery.ts +123 -0
- package/src/instruction-set-sync/index.ts +61 -14
- package/src/instruction-set-update/index.ts +4 -3
- package/src/instruction-sets-cli.ts +19 -27
- package/src/json-response.ts +8 -0
- package/src/preview-runtime.ts +40 -10
- package/src/task-export-workflow.ts +56 -0
- package/src/workbench-cli.ts +1 -1
- package/vendor/skills/orizu/references/cli-reference.md +34 -0
- package/vendor/skills/orizu/references/dataset-design.md +1 -1
- package/dist/dataset-edit-rows.d.ts +0 -10
- package/dist/dataset-edit-rows.d.ts.map +0 -1
- package/dist/dataset-edit-rows.js +0 -90
- package/src/dataset-edit-rows.ts +0 -124
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { authedFetch } from './http.js'
|
|
2
2
|
import { INSTRUCTION_SET_COMPONENT_KEY } from './instruction-set-lock/index.js'
|
|
3
3
|
import { loadInstructionSetManifest, MAX_INSTRUCTION_SET_COMPONENT_BYTES, type InstructionSetManifest } from './instruction-set-manifest.js'
|
|
4
|
-
import { sanitizeHumanInlineText, sanitizeTerminalText } from './json-response.js'
|
|
5
|
-
import { parseSyncTarget, planSyncRequest,
|
|
6
|
-
import { applyPrune,
|
|
4
|
+
import { responsePayload, sanitizeHumanInlineText, sanitizeTerminalText } from './json-response.js'
|
|
5
|
+
import { parseSyncTarget, planSyncRequest, type SyncTarget } from './instruction-set-sync/index.js'
|
|
6
|
+
import { applyPrune, planPrune, PruneKeepUnresolvedError } from './instruction-set-update/index.js'
|
|
7
7
|
import { printVerifyReport, verifyInstructionSetTree } from './instruction-set-verify/index.js'
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
9
9
|
import { join } from 'node:path'
|
|
@@ -74,12 +74,8 @@ function lifecycleOptionValues(args: string[], command: 'update' | 'prune', flag
|
|
|
74
74
|
return values
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!response.ok) {
|
|
80
|
-
throw new Error(`${action} failed (${response.status}): ${typeof payload.error === 'string' ? payload.error : response.statusText}`)
|
|
81
|
-
}
|
|
82
|
-
return payload
|
|
77
|
+
function printInterrupted(io: InstructionSetsCommandIo, message: string) {
|
|
78
|
+
io.print(io.json ? JSON.stringify({ error: message }) : message)
|
|
83
79
|
}
|
|
84
80
|
|
|
85
81
|
const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/u
|
|
@@ -340,23 +336,14 @@ export async function instructionSetsCommand(args: string[], io: InstructionSets
|
|
|
340
336
|
const project = await io.resolveProjectSlug(projectOption)
|
|
341
337
|
if (subcommand === 'update') {
|
|
342
338
|
const output = lifecycleOptionValues(args, 'update', '--out')[0] ?? '.'
|
|
343
|
-
const lock = readUpdateLock(output, project)
|
|
344
|
-
const payloads: SyncPayload[] = []
|
|
345
|
-
for (const [setSlug, set] of Object.entries(lock.instructionSets)) {
|
|
346
|
-
const barePath = `/api/cli/instruction-sets/${encodeURIComponent(setSlug)}/sync?project=${encodeURIComponent(project)}`
|
|
347
|
-
payloads.push(await responsePayload(await authedFetch(barePath, { method: 'GET' }), 'Instruction sets update') as unknown as SyncPayload)
|
|
348
|
-
for (const profileSlugValue of Object.keys(set.profiles)) {
|
|
349
|
-
const identity = lockedProfileIdentity(output, setSlug, profileSlugValue)
|
|
350
|
-
const query = new URLSearchParams({ project, profile: identity })
|
|
351
|
-
const path = `/api/cli/instruction-sets/${encodeURIComponent(setSlug)}/sync?${query.toString()}`
|
|
352
|
-
payloads.push(await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets update') as unknown as SyncPayload)
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
const plan = makeUpdatePlan(project, lock, payloads)
|
|
356
339
|
const isApproved = args.includes('--yes')
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
340
|
+
const { updateInstructionSets } = await import('./instruction-delivery.js')
|
|
341
|
+
const updated = await updateInstructionSets(output, project, isApproved, args.includes('--no-sync'))
|
|
342
|
+
if ('interrupted' in updated) {
|
|
343
|
+
printInterrupted(io, 'Instruction sets update interrupted; some sets may already be updated. Run orizu instructions update again, then orizu instructions verify.')
|
|
344
|
+
return updated.interrupted
|
|
345
|
+
}
|
|
346
|
+
const { plan, result } = updated
|
|
360
347
|
if (io.json) io.print(JSON.stringify({ plan: plan.lines, applied: isApproved, absent: result.absent, warnings: result.warnings }))
|
|
361
348
|
else {
|
|
362
349
|
for (const line of plan.lines) io.print(line)
|
|
@@ -527,11 +514,16 @@ export async function instructionSetsCommand(args: string[], io: InstructionSets
|
|
|
527
514
|
if (unknownOption) throw new Error(`instruction_set_sync_option_unknown:${unknownOption}`)
|
|
528
515
|
const output = requiredOptionValue(args, subcommand, '--out') ?? '.'
|
|
529
516
|
const plan = planSyncRequest(reference, requiredOptionValue(args, subcommand, '--version'), output, project)
|
|
530
|
-
const
|
|
531
|
-
const
|
|
517
|
+
const { syncInstructionSet } = await import('./instruction-delivery.js')
|
|
518
|
+
const delivered = await syncInstructionSet(output, project, plan, {
|
|
532
519
|
forceHelpers: args.includes('--force-helpers'),
|
|
533
520
|
target: syncTarget!,
|
|
534
521
|
})
|
|
522
|
+
if ('interrupted' in delivered) {
|
|
523
|
+
printInterrupted(io, 'Instruction set sync interrupted; any unpublished staged version was removed. Run orizu instructions sync again.')
|
|
524
|
+
return delivered.interrupted
|
|
525
|
+
}
|
|
526
|
+
const { payload, result } = delivered
|
|
535
527
|
if (io.json) io.print(JSON.stringify({ ...payload, warnings: result.warnings }))
|
|
536
528
|
else {
|
|
537
529
|
io.print(`Synced ${reference} to ${result.destination}`)
|
package/src/json-response.ts
CHANGED
|
@@ -35,3 +35,11 @@ export async function parseJsonResponse<T>(
|
|
|
35
35
|
)
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
export async function responsePayload(response: Response, action: string): Promise<Record<string, unknown>> {
|
|
40
|
+
const payload = await response.json().catch(() => ({})) as Record<string, unknown>
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`${action} failed (${response.status}): ${typeof payload.error === 'string' ? payload.error : response.statusText}`)
|
|
43
|
+
}
|
|
44
|
+
return payload
|
|
45
|
+
}
|
package/src/preview-runtime.ts
CHANGED
|
@@ -112,8 +112,8 @@ interface PreviewPage {
|
|
|
112
112
|
screenshot: (options: { path: string; fullPage: boolean }) => Promise<unknown>
|
|
113
113
|
waitForTimeout: (timeout: number) => Promise<unknown>
|
|
114
114
|
once?: (event: string, listener: () => void) => unknown
|
|
115
|
-
on?: (event: string, listener: () => void) => unknown
|
|
116
|
-
off?: (event: string, listener: () => void) => unknown
|
|
115
|
+
on?: (event: string, listener: (error: Error) => void) => unknown
|
|
116
|
+
off?: (event: string, listener: (error: Error) => void) => unknown
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
@@ -256,19 +256,32 @@ export async function runLocalAppPreview(options: AppPreviewOptions): Promise<Ap
|
|
|
256
256
|
headless: !options.headed,
|
|
257
257
|
...(chromiumExecutablePath ? { executablePath: chromiumExecutablePath } : {}),
|
|
258
258
|
})
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
let page: PreviewPage | null = null
|
|
260
|
+
let handleRenderError: ((error: Error) => void) | undefined
|
|
261
|
+
let hasFinishedKeepingOpen = false
|
|
261
262
|
try {
|
|
262
|
-
await
|
|
263
|
-
|
|
263
|
+
page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
|
|
264
|
+
const renderError = new Promise<never>((_, reject) => {
|
|
265
|
+
handleRenderError = error => reject(new Error(`App preview render failed: ${error.message}`))
|
|
266
|
+
page?.on?.('pageerror', handleRenderError)
|
|
267
|
+
})
|
|
268
|
+
// Listen before navigation: React can fail before the ready marker mounts.
|
|
269
|
+
await Promise.race([
|
|
270
|
+
page.goto(url, { waitUntil: 'networkidle' }).then(() =>
|
|
271
|
+
page!.waitForSelector('[data-orizu-preview-ready="true"]', { timeout: 15_000 })
|
|
272
|
+
),
|
|
273
|
+
renderError,
|
|
274
|
+
])
|
|
264
275
|
if (options.screenshotPath) {
|
|
265
276
|
await page.screenshot({ path: options.screenshotPath, fullPage: true })
|
|
266
277
|
}
|
|
267
278
|
if (options.keepOpen) {
|
|
268
279
|
await waitForPreviewClose(page, browser)
|
|
280
|
+
hasFinishedKeepingOpen = true
|
|
269
281
|
}
|
|
270
282
|
} finally {
|
|
271
|
-
if (
|
|
283
|
+
if (handleRenderError) page?.off?.('pageerror', handleRenderError)
|
|
284
|
+
if (!hasFinishedKeepingOpen) {
|
|
272
285
|
await browser.close()
|
|
273
286
|
}
|
|
274
287
|
}
|
|
@@ -826,7 +839,8 @@ export default TextContent
|
|
|
826
839
|
import React from 'react'
|
|
827
840
|
function cn(...values) { return values.filter(Boolean).join(' ') }
|
|
828
841
|
export function CodeBlock({ code, children, className, ...props }) {
|
|
829
|
-
|
|
842
|
+
const codeText = Array.isArray(code) ? code.map(line => line.content).join('\\n') : code
|
|
843
|
+
return <pre {...props} className={cn('overflow-auto rounded-md border bg-muted p-3 font-mono text-xs', className)}><code>{children ?? codeText}</code></pre>
|
|
830
844
|
}
|
|
831
845
|
export default CodeBlock
|
|
832
846
|
`
|
|
@@ -843,8 +857,24 @@ export default Prose
|
|
|
843
857
|
const contentRendererSource = `
|
|
844
858
|
import React from 'react'
|
|
845
859
|
function cn(...values) { return values.filter(Boolean).join(' ') }
|
|
846
|
-
export function ContentRenderer({ content,
|
|
847
|
-
|
|
860
|
+
export function ContentRenderer({ contentType, content, maxHeight = '400px', customRenderer, className }) {
|
|
861
|
+
let renderedContent
|
|
862
|
+
switch (contentType) {
|
|
863
|
+
case 'image':
|
|
864
|
+
renderedContent = <div className="flex items-center justify-center p-2"><img src={content} alt="Content" width={800} height={600} className="max-w-full object-contain" style={{ maxHeight, height: 'auto', width: 'auto' }} /></div>
|
|
865
|
+
break
|
|
866
|
+
case 'video':
|
|
867
|
+
renderedContent = <div className="flex h-full justify-center p-2"><video src={content} controls className="max-h-full max-w-full" /></div>
|
|
868
|
+
break
|
|
869
|
+
case 'custom':
|
|
870
|
+
renderedContent = customRenderer ? customRenderer({ content }) : <p className="text-destructive">Custom renderer required for custom content type</p>
|
|
871
|
+
break
|
|
872
|
+
default:
|
|
873
|
+
renderedContent = <p>Invalid content type</p>
|
|
874
|
+
}
|
|
875
|
+
return maxHeight
|
|
876
|
+
? <div className={cn('overflow-auto', className)} style={{ maxHeight }}>{renderedContent}</div>
|
|
877
|
+
: renderedContent
|
|
848
878
|
}
|
|
849
879
|
export default ContentRenderer
|
|
850
880
|
`
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Effect, Exit, Result } from 'effect'
|
|
2
|
+
import { renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { basename, dirname, join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { authedFetch } from './http.js'
|
|
6
|
+
|
|
7
|
+
export type TaskExportFormat = 'csv' | 'json' | 'jsonl'
|
|
8
|
+
|
|
9
|
+
// Node's "CODE: description, syscall 'path'" without the syscall and path, so a
|
|
10
|
+
// failed save names the user's target rather than the temp file.
|
|
11
|
+
function saveFailureReason(cause: unknown): string {
|
|
12
|
+
const message = cause instanceof Error ? cause.message : String(cause)
|
|
13
|
+
return message.replace(/, \w+( '.*')?$/, '')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Writes the export beside `target` and renames it over the target, so a failed
|
|
18
|
+
* or interrupted save never touches the file the user already has. The temp file
|
|
19
|
+
* is removed whenever the save does not finish.
|
|
20
|
+
*/
|
|
21
|
+
function saveExportFile(target: string, bytes: Uint8Array) {
|
|
22
|
+
const temp = join(dirname(target), `.${basename(target)}.${process.pid}.tmp`)
|
|
23
|
+
return Effect.acquireUseRelease(
|
|
24
|
+
Effect.succeed(temp),
|
|
25
|
+
path => Effect.try({
|
|
26
|
+
try: () => {
|
|
27
|
+
writeFileSync(path, bytes, { flag: 'wx' })
|
|
28
|
+
renameSync(path, target)
|
|
29
|
+
},
|
|
30
|
+
catch: cause => new Error(`Could not save export to ${target}: ${saveFailureReason(cause)}`, { cause }),
|
|
31
|
+
}),
|
|
32
|
+
(path, exit) => Exit.isSuccess(exit) ? Effect.void : Effect.sync(() => rmSync(path, { force: true }))
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `orizu tasks export`: one GET of the export, never retried, then a safe save.
|
|
38
|
+
* A refused export fails with `Download failed: <body>` before anything is written.
|
|
39
|
+
*/
|
|
40
|
+
function downloadTaskExport(taskId: string, format: TaskExportFormat, target: string) {
|
|
41
|
+
return Effect.gen(function* () {
|
|
42
|
+
const response = yield* Effect.promise(() => authedFetch(`/api/cli/tasks/${taskId}/export?format=${format}`))
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const body = yield* Effect.promise(() => response.text())
|
|
45
|
+
return yield* Effect.fail(new Error(`Download failed: ${body}`))
|
|
46
|
+
}
|
|
47
|
+
const bytes = new Uint8Array(yield* Effect.promise(() => response.arrayBuffer()))
|
|
48
|
+
yield* saveExportFile(target, bytes)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Promise runner for the command: rejects with the message the CLI prints. */
|
|
53
|
+
export async function runTaskExportDownload(taskId: string, format: TaskExportFormat, target: string): Promise<void> {
|
|
54
|
+
const result = await Effect.runPromise(Effect.result(downloadTaskExport(taskId, format, target)))
|
|
55
|
+
if (Result.isFailure(result)) throw result.failure
|
|
56
|
+
}
|
package/src/workbench-cli.ts
CHANGED
|
@@ -1176,7 +1176,7 @@ function formatSessionFinishOutcome(result: SessionFinishResult): string {
|
|
|
1176
1176
|
// The web page's failed line, with the reason the server mapped (ORI-2264).
|
|
1177
1177
|
function failedRunLine(run: { status: string; failureReason?: unknown }): string | null {
|
|
1178
1178
|
return run.status === 'failed' && typeof run.failureReason === 'string' && run.failureReason
|
|
1179
|
-
? `The agent stopped: ${run.failureReason}.`
|
|
1179
|
+
? run.failureReason === 'The selected model is unavailable in this session.' ? run.failureReason : `The agent stopped: ${run.failureReason}.`
|
|
1180
1180
|
: null
|
|
1181
1181
|
}
|
|
1182
1182
|
|
|
@@ -351,6 +351,40 @@ orizu datasets lock --dataset <datasetId|datasetUrl> --reason "Finalize for labe
|
|
|
351
351
|
orizu datasets clone --dataset <datasetId|datasetUrl> --name "Batch 1 Copy"
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
+
For validation-first enrichment, start with an explicit immutable version ID
|
|
355
|
+
returned by upload or version creation. Discover the saved names, export the
|
|
356
|
+
assignments, then download only the selected partition:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
orizu datasets splits list DATASET_VERSION_ID --json
|
|
360
|
+
orizu datasets splits show SPLIT_SET_ID --json > split.json
|
|
361
|
+
orizu datasets download --dataset-version DATASET_VERSION_ID --split-set SPLIT_SET_ID --split validation --format jsonl --out validation.jsonl
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The download retains canonical `id` values and existing fields in saved partition
|
|
365
|
+
order; `--format json` is also supported. Custom partition names and empty splits
|
|
366
|
+
are valid. Pinned reads remain unchanged after live row edits and require no
|
|
367
|
+
prompt, runner, scorer or optimization. Unknown/inaccessible or mismatched
|
|
368
|
+
selectors and incomplete artifacts fail without live-data fallback.
|
|
369
|
+
|
|
370
|
+
For saved split names beginning with `--`, use the unambiguous equals spelling:
|
|
371
|
+
`orizu datasets download --dataset-version DATASET_VERSION_ID --split-set SPLIT_SET_ID --split=--validation --format jsonl --out validation.jsonl`.
|
|
372
|
+
The same spelling supports `--split=--json` and `--split=--help`; all pinned selectors remain required.
|
|
373
|
+
|
|
374
|
+
Enrich rows outside Orizu, preserving every canonical `id`. Assemble the complete
|
|
375
|
+
enriched dataset, including other partitions, and upload or version it. Import
|
|
376
|
+
assignments into the explicit new version:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
orizu datasets splits create TARGET_DATASET_VERSION_ID --from-file split.json --json
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Show JSON preserves `name`, `strategy`, `seed`, `metadata`, and
|
|
383
|
+
`partitions: [{name, row_ids, metadata}]`. Set and partition metadata retain
|
|
384
|
+
their JSON values, including arrays, scalars and null. Source identity never overrides the command's
|
|
385
|
+
target. Every referenced ID must exist in the target; missing IDs fail rather
|
|
386
|
+
than being dropped or remapped. Reads follow existing project permissions.
|
|
387
|
+
|
|
354
388
|
Dataset version and split-set commands used by prompt runs are covered in `prompt-control-plane.md`.
|
|
355
389
|
|
|
356
390
|
### Model configs
|
|
@@ -73,7 +73,7 @@ orizu datasets splits create <upload-returned-dataset-version-id> --from-file ./
|
|
|
73
73
|
|
|
74
74
|
Reserve `orizu datasets versions create` for a subsequent snapshot, and give it a new label such as `v2`; labels are unique within a dataset. The dataset README should record source approval, source locators, sampling decisions, deduplication rules, the coverage table, ground-truth status, and the split rationale.
|
|
75
75
|
|
|
76
|
-
The dataset version freezes its rows.
|
|
76
|
+
The dataset version freezes its rows. After creating the split set, re-read its saved membership with `orizu datasets splits show <split-set-id> --json` and confirm it matches the reviewed local split file before relying on it; the saved split set, not the local file, is the canonical record. Do not mutate that server-side split set; pass the recorded `--dataset-version-id` and `--split-set-id` to `orizu optimizations run-gepa`.
|
|
77
77
|
|
|
78
78
|
## Guard live rows before annotation and version later data
|
|
79
79
|
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export interface DatasetEditRowsOptions {
|
|
2
|
-
datasetId: string;
|
|
3
|
-
file: string;
|
|
4
|
-
json: boolean;
|
|
5
|
-
printJson: (value: Record<string, unknown>) => void;
|
|
6
|
-
printLine: (value: string) => void;
|
|
7
|
-
sanitize: (value: string) => string;
|
|
8
|
-
}
|
|
9
|
-
export declare function editDatasetRows(options: DatasetEditRowsOptions): Promise<void>;
|
|
10
|
-
//# sourceMappingURL=dataset-edit-rows.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-edit-rows.d.ts","sourceRoot":"","sources":["../src/dataset-edit-rows.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IACnD,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACpC;AAsDD,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CpF"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { extractErrorMessage } from './error-response.js';
|
|
2
|
-
import { parseDatasetFile } from './file-parser.js';
|
|
3
|
-
import { authedFetch } from './http.js';
|
|
4
|
-
const MAX_REQUEST_BYTES = 3 * 1024 * 1024;
|
|
5
|
-
const MAX_CHUNK_ROWS = 10;
|
|
6
|
-
const EMPTY_REQUEST_BYTES = Buffer.byteLength('{"rows":[]}', 'utf8');
|
|
7
|
-
function normalizeRows(rows) {
|
|
8
|
-
if (rows.length === 0) {
|
|
9
|
-
throw new Error('Dataset edit file must contain at least one row');
|
|
10
|
-
}
|
|
11
|
-
const seenRowIds = new Set();
|
|
12
|
-
return rows.map((row, index) => {
|
|
13
|
-
const rowId = typeof row.id === 'string' ? row.id.trim() : '';
|
|
14
|
-
if (!rowId) {
|
|
15
|
-
throw new Error(`Dataset edit file rows[${index}] must include a non-empty string id`);
|
|
16
|
-
}
|
|
17
|
-
if (seenRowIds.has(rowId)) {
|
|
18
|
-
throw new Error(`Dataset edit file rows[${index}].id duplicates a previous row id`);
|
|
19
|
-
}
|
|
20
|
-
seenRowIds.add(rowId);
|
|
21
|
-
return { ...row, id: rowId };
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
function chunkRows(rows, sanitize) {
|
|
25
|
-
const chunks = [];
|
|
26
|
-
let currentChunk = [];
|
|
27
|
-
let currentBytes = EMPTY_REQUEST_BYTES;
|
|
28
|
-
for (const row of rows) {
|
|
29
|
-
const rowBytes = Buffer.byteLength(JSON.stringify(row), 'utf8');
|
|
30
|
-
const framedRowBytes = rowBytes + (currentChunk.length > 0 ? 1 : 0);
|
|
31
|
-
if (currentChunk.length > 0 &&
|
|
32
|
-
(currentChunk.length >= MAX_CHUNK_ROWS || currentBytes + framedRowBytes > MAX_REQUEST_BYTES)) {
|
|
33
|
-
chunks.push(currentChunk);
|
|
34
|
-
currentChunk = [];
|
|
35
|
-
currentBytes = EMPTY_REQUEST_BYTES;
|
|
36
|
-
}
|
|
37
|
-
const singleRowRequestBytes = EMPTY_REQUEST_BYTES + rowBytes;
|
|
38
|
-
if (singleRowRequestBytes > MAX_REQUEST_BYTES) {
|
|
39
|
-
throw new Error(`Dataset edit row ${sanitize(row.id)} is ${singleRowRequestBytes} bytes, ` +
|
|
40
|
-
`which exceeds the ${MAX_REQUEST_BYTES}-byte edit request limit`);
|
|
41
|
-
}
|
|
42
|
-
currentChunk.push(row);
|
|
43
|
-
currentBytes += rowBytes + (currentChunk.length > 1 ? 1 : 0);
|
|
44
|
-
}
|
|
45
|
-
if (currentChunk.length > 0)
|
|
46
|
-
chunks.push(currentChunk);
|
|
47
|
-
return chunks;
|
|
48
|
-
}
|
|
49
|
-
export async function editDatasetRows(options) {
|
|
50
|
-
const { rows } = parseDatasetFile(options.file);
|
|
51
|
-
const chunks = chunkRows(normalizeRows(rows), options.sanitize);
|
|
52
|
-
let totalUpdated = 0;
|
|
53
|
-
let lastResult = null;
|
|
54
|
-
for (let index = 0; index < chunks.length; index += 1) {
|
|
55
|
-
const chunk = chunks[index];
|
|
56
|
-
if (!options.json && chunks.length > 1) {
|
|
57
|
-
options.printLine(`Updating chunk ${index + 1}/${chunks.length} (${chunk.length} rows)...`);
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(options.datasetId)}/rows`, {
|
|
61
|
-
method: 'PATCH',
|
|
62
|
-
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify({ rows: chunk }),
|
|
64
|
-
});
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
throw new Error(`HTTP ${response.status}: ${await extractErrorMessage(response)}`);
|
|
67
|
-
}
|
|
68
|
-
const data = await response.json();
|
|
69
|
-
if (!Number.isInteger(data.updatedCount) || data.updatedCount !== chunk.length) {
|
|
70
|
-
throw new Error(`Server reported ${String(data.updatedCount)} updated rows for a ${chunk.length}-row chunk`);
|
|
71
|
-
}
|
|
72
|
-
totalUpdated += data.updatedCount;
|
|
73
|
-
lastResult = data;
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
-
throw new Error(`Chunk ${index + 1}/${chunks.length} failed: ${message}\n` +
|
|
78
|
-
`${totalUpdated} rows from ${index} chunk(s) were already updated. ` +
|
|
79
|
-
'Retry with a file containing only the remaining row edits.');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (!lastResult)
|
|
83
|
-
throw new Error('Dataset edit file must contain at least one row');
|
|
84
|
-
if (options.json) {
|
|
85
|
-
options.printJson({ dataset: lastResult.dataset, updatedCount: totalUpdated });
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
options.printLine(`Updated ${totalUpdated} rows in dataset ${options.sanitize(lastResult.dataset.name)} ` +
|
|
89
|
-
`(${options.sanitize(lastResult.dataset.id)}). Current row count: ${lastResult.dataset.rowCount}`);
|
|
90
|
-
}
|
package/src/dataset-edit-rows.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { extractErrorMessage } from './error-response.js'
|
|
2
|
-
import { parseDatasetFile } from './file-parser.js'
|
|
3
|
-
import { authedFetch } from './http.js'
|
|
4
|
-
|
|
5
|
-
const MAX_REQUEST_BYTES = 3 * 1024 * 1024
|
|
6
|
-
const MAX_CHUNK_ROWS = 10
|
|
7
|
-
const EMPTY_REQUEST_BYTES = Buffer.byteLength('{"rows":[]}', 'utf8')
|
|
8
|
-
|
|
9
|
-
type DatasetRowEdit = Record<string, unknown> & { id: string }
|
|
10
|
-
|
|
11
|
-
type EditRowsResponse = {
|
|
12
|
-
dataset: { id: string; name: string; rowCount: number }
|
|
13
|
-
updatedCount: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface DatasetEditRowsOptions {
|
|
17
|
-
datasetId: string
|
|
18
|
-
file: string
|
|
19
|
-
json: boolean
|
|
20
|
-
printJson: (value: Record<string, unknown>) => void
|
|
21
|
-
printLine: (value: string) => void
|
|
22
|
-
sanitize: (value: string) => string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function normalizeRows(rows: Array<Record<string, unknown>>): DatasetRowEdit[] {
|
|
26
|
-
if (rows.length === 0) {
|
|
27
|
-
throw new Error('Dataset edit file must contain at least one row')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const seenRowIds = new Set<string>()
|
|
31
|
-
return rows.map((row, index) => {
|
|
32
|
-
const rowId = typeof row.id === 'string' ? row.id.trim() : ''
|
|
33
|
-
if (!rowId) {
|
|
34
|
-
throw new Error(`Dataset edit file rows[${index}] must include a non-empty string id`)
|
|
35
|
-
}
|
|
36
|
-
if (seenRowIds.has(rowId)) {
|
|
37
|
-
throw new Error(`Dataset edit file rows[${index}].id duplicates a previous row id`)
|
|
38
|
-
}
|
|
39
|
-
seenRowIds.add(rowId)
|
|
40
|
-
return { ...row, id: rowId }
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function chunkRows(rows: DatasetRowEdit[], sanitize: (value: string) => string): DatasetRowEdit[][] {
|
|
45
|
-
const chunks: DatasetRowEdit[][] = []
|
|
46
|
-
let currentChunk: DatasetRowEdit[] = []
|
|
47
|
-
let currentBytes = EMPTY_REQUEST_BYTES
|
|
48
|
-
|
|
49
|
-
for (const row of rows) {
|
|
50
|
-
const rowBytes = Buffer.byteLength(JSON.stringify(row), 'utf8')
|
|
51
|
-
const framedRowBytes = rowBytes + (currentChunk.length > 0 ? 1 : 0)
|
|
52
|
-
if (
|
|
53
|
-
currentChunk.length > 0 &&
|
|
54
|
-
(currentChunk.length >= MAX_CHUNK_ROWS || currentBytes + framedRowBytes > MAX_REQUEST_BYTES)
|
|
55
|
-
) {
|
|
56
|
-
chunks.push(currentChunk)
|
|
57
|
-
currentChunk = []
|
|
58
|
-
currentBytes = EMPTY_REQUEST_BYTES
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const singleRowRequestBytes = EMPTY_REQUEST_BYTES + rowBytes
|
|
62
|
-
if (singleRowRequestBytes > MAX_REQUEST_BYTES) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`Dataset edit row ${sanitize(row.id)} is ${singleRowRequestBytes} bytes, ` +
|
|
65
|
-
`which exceeds the ${MAX_REQUEST_BYTES}-byte edit request limit`
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
currentChunk.push(row)
|
|
70
|
-
currentBytes += rowBytes + (currentChunk.length > 1 ? 1 : 0)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (currentChunk.length > 0) chunks.push(currentChunk)
|
|
74
|
-
return chunks
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function editDatasetRows(options: DatasetEditRowsOptions): Promise<void> {
|
|
78
|
-
const { rows } = parseDatasetFile(options.file)
|
|
79
|
-
const chunks = chunkRows(normalizeRows(rows), options.sanitize)
|
|
80
|
-
let totalUpdated = 0
|
|
81
|
-
let lastResult: EditRowsResponse | null = null
|
|
82
|
-
|
|
83
|
-
for (let index = 0; index < chunks.length; index += 1) {
|
|
84
|
-
const chunk = chunks[index]
|
|
85
|
-
if (!options.json && chunks.length > 1) {
|
|
86
|
-
options.printLine(`Updating chunk ${index + 1}/${chunks.length} (${chunk.length} rows)...`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(options.datasetId)}/rows`, {
|
|
91
|
-
method: 'PATCH',
|
|
92
|
-
headers: { 'Content-Type': 'application/json' },
|
|
93
|
-
body: JSON.stringify({ rows: chunk }),
|
|
94
|
-
})
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
throw new Error(`HTTP ${response.status}: ${await extractErrorMessage(response)}`)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const data = await response.json() as EditRowsResponse
|
|
100
|
-
if (!Number.isInteger(data.updatedCount) || data.updatedCount !== chunk.length) {
|
|
101
|
-
throw new Error(`Server reported ${String(data.updatedCount)} updated rows for a ${chunk.length}-row chunk`)
|
|
102
|
-
}
|
|
103
|
-
totalUpdated += data.updatedCount
|
|
104
|
-
lastResult = data
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
107
|
-
throw new Error(
|
|
108
|
-
`Chunk ${index + 1}/${chunks.length} failed: ${message}\n` +
|
|
109
|
-
`${totalUpdated} rows from ${index} chunk(s) were already updated. ` +
|
|
110
|
-
'Retry with a file containing only the remaining row edits.'
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!lastResult) throw new Error('Dataset edit file must contain at least one row')
|
|
116
|
-
if (options.json) {
|
|
117
|
-
options.printJson({ dataset: lastResult.dataset, updatedCount: totalUpdated })
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
options.printLine(
|
|
121
|
-
`Updated ${totalUpdated} rows in dataset ${options.sanitize(lastResult.dataset.name)} ` +
|
|
122
|
-
`(${options.sanitize(lastResult.dataset.id)}). Current row count: ${lastResult.dataset.rowCount}`
|
|
123
|
-
)
|
|
124
|
-
}
|