orizu 0.5.41 → 0.5.43
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/dist/account-directory.d.ts.map +1 -1
- package/dist/account-directory.js +6 -32
- package/dist/app-publish-workspace.js +1 -1
- package/dist/archive-artifact-workflow.d.ts +23 -0
- package/dist/archive-artifact-workflow.d.ts.map +1 -0
- package/dist/archive-artifact-workflow.js +100 -0
- package/dist/archive-artifacts-cli.d.ts.map +1 -1
- package/dist/archive-artifacts-cli.js +7 -11
- package/dist/artifact-archive.d.ts.map +1 -1
- package/dist/artifact-archive.js +2 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +2 -0
- package/dist/dataset-commands.d.ts.map +1 -1
- package/dist/dataset-commands.js +19 -29
- package/dist/dataset-split-create-workflow.d.ts +16 -0
- package/dist/dataset-split-create-workflow.d.ts.map +1 -0
- package/dist/dataset-split-create-workflow.js +115 -0
- package/dist/git-credential.js +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +10 -2
- package/dist/hosted-boot.d.ts +7 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +290 -60
- package/dist/hosted-harness.d.ts +1 -0
- package/dist/hosted-harness.d.ts.map +1 -1
- package/dist/hosted-harness.js +2 -0
- package/dist/hosted-harvest.d.ts +2 -1
- package/dist/hosted-harvest.d.ts.map +1 -1
- package/dist/hosted-harvest.js +29 -17
- package/dist/hosted-live-channel.d.ts +31 -0
- package/dist/hosted-live-channel.d.ts.map +1 -0
- package/dist/hosted-live-channel.js +147 -0
- package/dist/hosted-loop.d.ts +8 -1
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +45 -0
- package/dist/hosted-run-event-sink.d.ts +19 -0
- package/dist/hosted-run-event-sink.d.ts.map +1 -1
- package/dist/hosted-run-event-sink.js +106 -3
- package/dist/hosted-runtime-assets.js +1 -1
- package/dist/hosted-session-cli.d.ts.map +1 -1
- package/dist/hosted-session-cli.js +4 -0
- package/dist/hosted-session-seed.d.ts +46 -6
- package/dist/hosted-session-seed.d.ts.map +1 -1
- package/dist/hosted-session-seed.js +95 -9
- package/dist/hosted-skill-staging.d.ts +36 -12
- package/dist/hosted-skill-staging.d.ts.map +1 -1
- package/dist/hosted-skill-staging.js +96 -34
- package/dist/http.d.ts +2 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -16
- package/dist/orizu-skill-links.d.ts +48 -0
- package/dist/orizu-skill-links.d.ts.map +1 -0
- package/dist/orizu-skill-links.js +99 -0
- package/dist/preview-runtime.d.ts +1 -0
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +15 -0
- package/dist/repository-backup-core.d.ts.map +1 -1
- package/dist/repository-backup-core.js +2 -0
- package/dist/request-outcome.d.ts +5 -0
- package/dist/request-outcome.d.ts.map +1 -0
- package/dist/request-outcome.js +27 -0
- package/dist/score-save-workflow.d.ts +26 -0
- package/dist/score-save-workflow.d.ts.map +1 -0
- package/dist/score-save-workflow.js +120 -0
- package/dist/setup-and-skills-commands.d.ts.map +1 -1
- package/dist/setup-and-skills-commands.js +52 -7
- package/dist/setup-onboarding.d.ts.map +1 -1
- package/dist/setup-onboarding.js +6 -2
- package/dist/skill-installer.d.ts +16 -0
- package/dist/skill-installer.d.ts.map +1 -1
- package/dist/skill-installer.js +35 -5
- package/dist/team-create-workflow.d.ts +16 -0
- package/dist/team-create-workflow.d.ts.map +1 -0
- package/dist/team-create-workflow.js +107 -0
- package/dist/team-member-change-workflow.d.ts +25 -0
- package/dist/team-member-change-workflow.d.ts.map +1 -0
- package/dist/team-member-change-workflow.js +132 -0
- package/dist/workbench-cli.d.ts.map +1 -1
- package/dist/workbench-cli.js +24 -4
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +2 -0
- package/package.json +1 -1
- package/src/account-directory.ts +6 -41
- package/src/app-publish-workspace.ts +1 -1
- package/src/archive-artifact-workflow.ts +101 -0
- package/src/archive-artifacts-cli.ts +7 -28
- package/src/artifact-archive.ts +2 -0
- package/src/credentials.ts +2 -0
- package/src/dataset-commands.ts +19 -31
- package/src/dataset-split-create-workflow.ts +128 -0
- package/src/git-credential.ts +1 -1
- package/src/help.ts +10 -2
- package/src/hosted-boot.ts +292 -59
- package/src/hosted-harness.ts +2 -0
- package/src/hosted-harvest.ts +30 -17
- package/src/hosted-live-channel.ts +167 -0
- package/src/hosted-loop.ts +50 -0
- package/src/hosted-run-event-sink.ts +118 -3
- package/src/hosted-runtime-assets.ts +1 -1
- package/src/hosted-session-cli.ts +4 -0
- package/src/hosted-session-seed.ts +111 -10
- package/src/hosted-skill-staging.ts +110 -36
- package/src/http.ts +12 -7
- package/src/index.ts +17 -21
- package/src/orizu-skill-links.ts +114 -0
- package/src/preview-runtime.ts +16 -0
- package/src/repository-backup-core.ts +2 -0
- package/src/request-outcome.ts +28 -0
- package/src/score-save-workflow.ts +132 -0
- package/src/setup-and-skills-commands.ts +60 -6
- package/src/setup-onboarding.ts +6 -2
- package/src/skill-installer.ts +46 -5
- package/src/team-create-workflow.ts +109 -0
- package/src/team-member-change-workflow.ts +146 -0
- package/src/workbench-cli.ts +24 -4
- package/src/workspace.ts +2 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
4
|
+
import { isJsonErrorResponsePayload } from './error-response.js'
|
|
5
|
+
import { authedFetch } from './http.js'
|
|
6
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
7
|
+
|
|
8
|
+
// Loaded only through `await import(...)` from archive-artifacts-cli.
|
|
9
|
+
|
|
10
|
+
const MAY_BE_SAVED = 'The change may already be saved: running the same command again is safe and shows its current state.'
|
|
11
|
+
|
|
12
|
+
const ArchiveReply = Schema.Struct({
|
|
13
|
+
artifact: Schema.Struct({
|
|
14
|
+
id: Schema.String,
|
|
15
|
+
type: Schema.String,
|
|
16
|
+
status: Schema.Literals(['active', 'archived']),
|
|
17
|
+
archivedAt: Schema.NullOr(Schema.String),
|
|
18
|
+
assigneeId: Schema.optionalKey(Schema.String),
|
|
19
|
+
}),
|
|
20
|
+
})
|
|
21
|
+
export type ArchiveReply = typeof ArchiveReply.Type
|
|
22
|
+
|
|
23
|
+
export interface ArchiveRequest {
|
|
24
|
+
path: string
|
|
25
|
+
body: { archived: boolean; assigneeId?: string }
|
|
26
|
+
// For example "archive dataset": the start of every failure message.
|
|
27
|
+
label: string
|
|
28
|
+
fetcher?: (path: string, init?: RequestInit) => Promise<Response>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A refusal (4xx) means nothing changed. After any other failure once the
|
|
32
|
+
// request was sent, the change may have committed; repeating it is harmless.
|
|
33
|
+
class ArchiveFailure extends Error {
|
|
34
|
+
constructor(message: string, readonly mayBeSaved: boolean) { super(message) }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function reason(response: Response): Promise<string> {
|
|
38
|
+
const text = await response.text()
|
|
39
|
+
let payload: unknown = null
|
|
40
|
+
try { payload = JSON.parse(text) } catch { /* not JSON */ }
|
|
41
|
+
if (isJsonErrorResponsePayload(response, payload) && payload.error.length > 0) {
|
|
42
|
+
return sanitizeTerminalText(payload.error).slice(0, 180)
|
|
43
|
+
}
|
|
44
|
+
// Never print a page body: a gateway's HTML says nothing useful.
|
|
45
|
+
return `the server returned a page that isn't JSON (status ${response.status})`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function archiveRequest(request: ArchiveRequest, timeoutMs: number) {
|
|
49
|
+
return Effect.suspend(() => {
|
|
50
|
+
let sent = false
|
|
51
|
+
let timedOut = false
|
|
52
|
+
return Effect.tryPromise({
|
|
53
|
+
try: async signal => {
|
|
54
|
+
const controller = new AbortController()
|
|
55
|
+
const abort = () => controller.abort()
|
|
56
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort() }, timeoutMs)
|
|
57
|
+
if (signal.aborted) abort()
|
|
58
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
59
|
+
try {
|
|
60
|
+
const init: RequestInit = {
|
|
61
|
+
method: 'PATCH',
|
|
62
|
+
headers: { 'content-type': 'application/json' },
|
|
63
|
+
body: JSON.stringify(request.body),
|
|
64
|
+
signal: controller.signal,
|
|
65
|
+
}
|
|
66
|
+
const response = request.fetcher
|
|
67
|
+
? (sent = true, await request.fetcher(request.path, init))
|
|
68
|
+
: await authedFetch(request.path, init, () => { sent = true })
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new ArchiveFailure(`Failed to ${request.label}: ${await reason(response)}`, response.status < 400 || response.status >= 500)
|
|
71
|
+
}
|
|
72
|
+
let data: unknown = null
|
|
73
|
+
try { data = JSON.parse(await response.text()) } catch { /* decoded below */ }
|
|
74
|
+
const decoded = Schema.decodeUnknownResult(ArchiveReply)(data)
|
|
75
|
+
if (Result.isFailure(decoded)) {
|
|
76
|
+
throw new ArchiveFailure(`Failed to ${request.label}: the server's answer could not be read (status ${response.status})`, true)
|
|
77
|
+
}
|
|
78
|
+
return decoded.success
|
|
79
|
+
} finally {
|
|
80
|
+
clearTimeout(timer)
|
|
81
|
+
signal.removeEventListener('abort', abort)
|
|
82
|
+
controller.abort()
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
catch: cause => {
|
|
86
|
+
if (timedOut) return new ArchiveFailure(`Failed to ${request.label}: the request exceeded its deadline`, true)
|
|
87
|
+
if (cause instanceof ArchiveFailure) return cause
|
|
88
|
+
const message = cause instanceof Error ? cause.message : String(cause)
|
|
89
|
+
// Before sending (for example, not signed in) the message stands alone.
|
|
90
|
+
return new ArchiveFailure(sent ? `Failed to ${request.label}: ${message}` : message, sent)
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
}).pipe(Effect.mapError(failure => new Error(failure.mayBeSaved ? `${failure.message}\n${MAY_BE_SAVED}` : failure.message)))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Nothing to clean up, so signals keep Node's default handling.
|
|
97
|
+
export async function archiveArtifact(request: ArchiveRequest): Promise<ArchiveReply> {
|
|
98
|
+
const exit = await Effect.runPromiseExit(archiveRequest(request, datasetTransferTimeoutMs()))
|
|
99
|
+
if (Exit.isSuccess(exit)) return exit.value
|
|
100
|
+
throw Cause.squash(exit.cause)
|
|
101
|
+
}
|
|
@@ -27,14 +27,6 @@ export interface ArchiveArtifactsIo {
|
|
|
27
27
|
fetcher?: (path: string, init?: RequestInit) => Promise<Response>
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
interface ArchiveArtifactResult {
|
|
31
|
-
id: string
|
|
32
|
-
type: ArchiveArtifactType
|
|
33
|
-
status: 'active' | 'archived'
|
|
34
|
-
archivedAt: string | null
|
|
35
|
-
assigneeId?: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
30
|
interface AssignmentSummary {
|
|
39
31
|
taskId: string
|
|
40
32
|
taskTitle: string
|
|
@@ -262,27 +254,14 @@ export async function archiveArtifactCommand(
|
|
|
262
254
|
archived: action === 'archive',
|
|
263
255
|
...(assigneeId ? { assigneeId } : {}),
|
|
264
256
|
}
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
`/api/cli/archive-artifacts/${artifactType}/${encodeURIComponent(artifactId)}` +
|
|
257
|
+
const { archiveArtifact } = await import('./archive-artifact-workflow.js')
|
|
258
|
+
const data = await archiveArtifact({
|
|
259
|
+
path: `/api/cli/archive-artifacts/${artifactType}/${encodeURIComponent(artifactId)}` +
|
|
268
260
|
`?${params.toString()}`,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
)
|
|
275
|
-
if (!response.ok) {
|
|
276
|
-
throw new Error(
|
|
277
|
-
`Failed to ${action} ${artifactType}: ` +
|
|
278
|
-
await extractErrorMessage(response)
|
|
279
|
-
)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const data = await parseJsonPayload<{ artifact: ArchiveArtifactResult }>(
|
|
283
|
-
response,
|
|
284
|
-
`${action === 'archive' ? 'Archive' : 'Restore'} ${artifactType}`
|
|
285
|
-
)
|
|
261
|
+
body,
|
|
262
|
+
label: `${action} ${artifactType}`,
|
|
263
|
+
fetcher: io.fetcher,
|
|
264
|
+
})
|
|
286
265
|
if (io.json) {
|
|
287
266
|
io.print(JSON.stringify(data))
|
|
288
267
|
return
|
package/src/artifact-archive.ts
CHANGED
package/src/credentials.ts
CHANGED
package/src/dataset-commands.ts
CHANGED
|
@@ -20,8 +20,7 @@ import {
|
|
|
20
20
|
selectDatasetInteractively,
|
|
21
21
|
selectTaskIdInteractively,
|
|
22
22
|
} from './account-directory.js'
|
|
23
|
-
import { sanitizeTerminalText
|
|
24
|
-
import { authedFetch } from './http.js'
|
|
23
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
25
24
|
import { printJson, printLine } from './cli-console-output.js'
|
|
26
25
|
import { readJsonFile } from './app-commands.js'
|
|
27
26
|
import { printDatasetSummaries } from './archive-artifacts-cli.js'
|
|
@@ -78,15 +77,16 @@ function parseRatioFlag(name: string, fallback: number): number {
|
|
|
78
77
|
return parsed
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
|
|
80
|
+
// The app accepts 0 and stores seeds as 32-bit integers.
|
|
81
|
+
function parseSeedFlag(name: string, fallback: number): number {
|
|
82
82
|
const value = getArg(name)
|
|
83
83
|
if (!value) {
|
|
84
84
|
return fallback
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const parsed = Number(value)
|
|
88
|
-
if (!Number.isInteger(parsed) || parsed
|
|
89
|
-
throw new Error(`${name} must be a
|
|
88
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 2147483647) {
|
|
89
|
+
throw new Error(`${name} must be a whole number from 0 to 2147483647`)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
return parsed
|
|
@@ -189,12 +189,15 @@ async function createDatasetSplitSet() {
|
|
|
189
189
|
const splitSpec = splitFile ? readJsonFile(splitFile) : null
|
|
190
190
|
const name = getArg('--name') || (typeof splitSpec?.name === 'string' ? splitSpec.name : 'default')
|
|
191
191
|
const strategy = getArg('--strategy') || (typeof splitSpec?.strategy === 'string' ? splitSpec.strategy : 'random')
|
|
192
|
-
|
|
192
|
+
// A file's seed goes to the app as written, so one that isn't a whole number
|
|
193
|
+
// is refused there instead of quietly replaced.
|
|
194
|
+
const seed = splitSpec && Object.prototype.hasOwnProperty.call(splitSpec, 'seed')
|
|
193
195
|
? splitSpec.seed
|
|
194
|
-
:
|
|
196
|
+
: parseSeedFlag('--seed', 1)
|
|
195
197
|
const train = parseRatioFlag('--train', 0.7)
|
|
196
198
|
const validation = parseRatioFlag('--validation', 0.2)
|
|
197
|
-
|
|
199
|
+
// Without --test, test takes whatever train and validation leave.
|
|
200
|
+
const test = getArg('--test') ? parseRatioFlag('--test', 0) : undefined
|
|
198
201
|
|
|
199
202
|
if (!datasetVersionId) {
|
|
200
203
|
throw new Error(
|
|
@@ -202,37 +205,22 @@ async function createDatasetSplitSet() {
|
|
|
202
205
|
)
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
const
|
|
208
|
+
const { createSplitSet, splitRatioProblem } = await import('./dataset-split-create-workflow.js')
|
|
209
|
+
const ratioProblem = splitRatioProblem(train, validation, test)
|
|
210
|
+
if (ratioProblem) throw new Error(ratioProblem)
|
|
211
|
+
|
|
212
|
+
const partitions = splitSpec && Object.prototype.hasOwnProperty.call(splitSpec, 'partitions') ? splitSpec.partitions : undefined
|
|
206
213
|
const metadata = splitSpec && Object.prototype.hasOwnProperty.call(splitSpec, 'metadata')
|
|
207
214
|
? splitSpec.metadata
|
|
208
215
|
: undefined
|
|
209
216
|
|
|
210
|
-
const
|
|
211
|
-
method: 'POST',
|
|
212
|
-
headers: { 'Content-Type': 'application/json' },
|
|
213
|
-
body: JSON.stringify({
|
|
214
|
-
name,
|
|
215
|
-
strategy,
|
|
216
|
-
seed,
|
|
217
|
-
train,
|
|
218
|
-
validation,
|
|
219
|
-
test,
|
|
220
|
-
partitions,
|
|
221
|
-
metadata,
|
|
222
|
-
}),
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
if (!response.ok) {
|
|
226
|
-
throw new Error(`Failed to create dataset split set: ${await response.text()}`)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const data = await parseJsonResponse<{ splitSet: { id: string } }>(response, 'Dataset split set create')
|
|
217
|
+
const splitSetId = await createSplitSet({ versionId: datasetVersionId, name, strategy, seed, train, validation, test, partitions, metadata })
|
|
230
218
|
if (hasJsonFlag()) {
|
|
231
|
-
printJson({ split_set_id:
|
|
219
|
+
printJson({ split_set_id: splitSetId })
|
|
232
220
|
return
|
|
233
221
|
}
|
|
234
222
|
|
|
235
|
-
printLine(`Created split set ${sanitizeTerminalText(
|
|
223
|
+
printLine(`Created split set ${sanitizeTerminalText(splitSetId)}`)
|
|
236
224
|
}
|
|
237
225
|
|
|
238
226
|
async function listDatasets() {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
4
|
+
import { authedFetch } from './http.js'
|
|
5
|
+
import { parseJsonResponse } from './json-response.js'
|
|
6
|
+
|
|
7
|
+
// Loaded only through `await import(...)` from dataset-commands, like clone.
|
|
8
|
+
|
|
9
|
+
const SERVER_MAY_BE_CREATED = 'Dataset split set may already be created'
|
|
10
|
+
// Ratios like 0.7 + 0.2 + 0.1 don't add up to exactly 1 in floating point.
|
|
11
|
+
const RATIO_GAP = 1e-9
|
|
12
|
+
|
|
13
|
+
const CreateReply = Schema.Struct({ splitSet: Schema.Struct({ id: Schema.String }) })
|
|
14
|
+
const ErrorReply = Schema.Struct({ error: Schema.String })
|
|
15
|
+
|
|
16
|
+
export interface SplitSetRequest {
|
|
17
|
+
versionId: string
|
|
18
|
+
name: string
|
|
19
|
+
strategy: string
|
|
20
|
+
seed: unknown
|
|
21
|
+
train: number
|
|
22
|
+
validation: number
|
|
23
|
+
/** Absent means "whatever train and validation leave". */
|
|
24
|
+
test: number | undefined
|
|
25
|
+
partitions: unknown
|
|
26
|
+
metadata: unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const shown = (value: number) => Math.round(value * 1e12) / 1e12
|
|
30
|
+
|
|
31
|
+
/** The same rule the app enforces, checked before anything is sent. */
|
|
32
|
+
export function splitRatioProblem(train: number, validation: number, test: number | undefined): string | null {
|
|
33
|
+
if (train + validation > 1 + RATIO_GAP) return `--train plus --validation must not be more than 1 (they add up to ${shown(train + validation)})`
|
|
34
|
+
if (test !== undefined && Math.abs(train + validation + test - 1) > RATIO_GAP) {
|
|
35
|
+
return `--train, --validation and --test must add up to 1 (they add up to ${shown(train + validation + test)})`
|
|
36
|
+
}
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A refusal means nothing was saved. After any other failure once the request
|
|
41
|
+
// was sent, the set may exist. The version allows one set per name, so running
|
|
42
|
+
// the same command again can't make a second copy; it is still never retried
|
|
43
|
+
// automatically, and the user is told how to check.
|
|
44
|
+
class SplitSetFailure extends Error {
|
|
45
|
+
constructor(message: string, readonly mayBeCreated: boolean) { super(message) }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function mayBeCreatedGuidance(request: SplitSetRequest): string {
|
|
49
|
+
return `The split set ${JSON.stringify(request.name)} may already have been created. Run \`orizu datasets splits list ${request.versionId}\` to check. Running the same command again is safe: a set with that name is refused, not duplicated.`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// authedFetch raises this code only when it sent nothing, or when the server
|
|
53
|
+
// answered 401 and the changed credentials stopped a replay. Nothing was saved.
|
|
54
|
+
function isAuthContextChange(cause: unknown): boolean {
|
|
55
|
+
return cause instanceof Error && cause.message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function serverError(text: string): string | null {
|
|
59
|
+
try {
|
|
60
|
+
const decoded = Schema.decodeUnknownResult(ErrorReply)(JSON.parse(text))
|
|
61
|
+
return Result.isSuccess(decoded) ? decoded.success.error : null
|
|
62
|
+
} catch {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createRequest(request: SplitSetRequest, timeoutMs: number) {
|
|
68
|
+
return Effect.suspend(() => {
|
|
69
|
+
let sent = false
|
|
70
|
+
let timedOut = false
|
|
71
|
+
return Effect.tryPromise({
|
|
72
|
+
try: async signal => {
|
|
73
|
+
const abortController = new AbortController()
|
|
74
|
+
const abort = () => abortController.abort()
|
|
75
|
+
const timer = setTimeout(() => { timedOut = true; abortController.abort() }, timeoutMs)
|
|
76
|
+
if (signal.aborted) abort()
|
|
77
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
78
|
+
try {
|
|
79
|
+
const response = await authedFetch(`/api/cli/dataset-versions/${encodeURIComponent(request.versionId)}/split-sets`, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
name: request.name,
|
|
84
|
+
strategy: request.strategy,
|
|
85
|
+
seed: request.seed,
|
|
86
|
+
train: request.train,
|
|
87
|
+
validation: request.validation,
|
|
88
|
+
test: request.test,
|
|
89
|
+
partitions: request.partitions,
|
|
90
|
+
metadata: request.metadata,
|
|
91
|
+
}),
|
|
92
|
+
signal: abortController.signal,
|
|
93
|
+
}, () => { sent = true })
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const text = await response.text()
|
|
96
|
+
const error = serverError(text)
|
|
97
|
+
// The app's own 5xx bodies name what happened; anything else at 5xx
|
|
98
|
+
// (a gateway page, an empty body) says nothing about the set.
|
|
99
|
+
const mayBeCreated = response.status < 400 || error === SERVER_MAY_BE_CREATED || (response.status >= 500 && error === null)
|
|
100
|
+
throw new SplitSetFailure(`Failed to create dataset split set: ${text}`, mayBeCreated)
|
|
101
|
+
}
|
|
102
|
+
const data = await parseJsonResponse<unknown>(response, 'Dataset split set create')
|
|
103
|
+
const decoded = Schema.decodeUnknownResult(CreateReply)(data)
|
|
104
|
+
if (Result.isFailure(decoded)) {
|
|
105
|
+
throw new SplitSetFailure(`Dataset split set create returned an unexpected response (status ${response.status}).`, true)
|
|
106
|
+
}
|
|
107
|
+
return decoded.success.splitSet.id
|
|
108
|
+
} finally {
|
|
109
|
+
clearTimeout(timer)
|
|
110
|
+
signal.removeEventListener('abort', abort)
|
|
111
|
+
abortController.abort()
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
catch: cause => {
|
|
115
|
+
if (timedOut) return new SplitSetFailure('DATASET_TRANSFER_TIMEOUT: Dataset split set create exceeded its deadline', sent)
|
|
116
|
+
if (cause instanceof SplitSetFailure) return cause
|
|
117
|
+
return new SplitSetFailure(cause instanceof Error ? cause.message : String(cause), sent && !isAuthContextChange(cause))
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
}).pipe(Effect.mapError(failure => new Error(failure.mayBeCreated ? `${failure.message}\n${mayBeCreatedGuidance(request)}` : failure.message)))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Nothing to clean up, so signals keep Node's default handling.
|
|
124
|
+
export async function createSplitSet(request: SplitSetRequest): Promise<string> {
|
|
125
|
+
const exit = await Effect.runPromiseExit(createRequest(request, datasetTransferTimeoutMs()))
|
|
126
|
+
if (Exit.isSuccess(exit)) return exit.value
|
|
127
|
+
throw Cause.squash(exit.cause)
|
|
128
|
+
}
|
package/src/git-credential.ts
CHANGED
|
@@ -377,7 +377,7 @@ async function mintArtifactsCredential(
|
|
|
377
377
|
`/api/cli/workspaces/${encodeURIComponent(workspaceId)}/repo-token`,
|
|
378
378
|
{
|
|
379
379
|
method: 'POST',
|
|
380
|
-
headers: { 'Content-Type': 'application/json' },
|
|
380
|
+
headers: { 'Content-Type': 'application/json', 'X-Orizu-Session-Seed-Protocol': '1' },
|
|
381
381
|
body: JSON.stringify({ purpose, ...(purpose === 'team_read' ? { sessionId: process.env.ORIZU_SESSION_ID?.trim() } : {}) }),
|
|
382
382
|
}
|
|
383
383
|
)
|
package/src/help.ts
CHANGED
|
@@ -966,9 +966,17 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
966
966
|
},
|
|
967
967
|
{
|
|
968
968
|
path: ['datasets', 'splits', 'create'],
|
|
969
|
-
usage: 'orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--json]',
|
|
970
|
-
summary: 'Create a split set for the explicit target dataset version. --from-file accepts splits show JSON; canonical row IDs must exist in the target.',
|
|
969
|
+
usage: 'orizu datasets splits create <datasetVersionId> [--from-file <split.json>] [--name <name>] [--seed <n>] [--train <ratio>] [--validation <ratio>] [--test <ratio>] [--json]',
|
|
970
|
+
summary: 'Create a split set for the explicit target dataset version. --from-file accepts splits show JSON; canonical row IDs must exist in the target. Each version allows one set per name.',
|
|
971
971
|
group: 'Datasets',
|
|
972
|
+
options: [
|
|
973
|
+
{ name: '--from-file <split.json>', help: 'Saved partitions to import, for example from splits show --json.' },
|
|
974
|
+
{ name: '--name <name>', help: 'Set name; defaults to the file name field, then "default".' },
|
|
975
|
+
{ name: '--seed <n>', help: 'Whole number from 0 to 2147483647 for a random split (default 1). The same seed gives the same rows.' },
|
|
976
|
+
{ name: '--train <ratio>', help: 'Share of rows for train, from 0 to 1 (default 0.7).' },
|
|
977
|
+
{ name: '--validation <ratio>', help: 'Share of rows for validation (default 0.2). Train plus validation must not be more than 1.' },
|
|
978
|
+
{ name: '--test <ratio>', help: 'Share of rows for test. Defaults to what train and validation leave; if given, the three must add up to 1.' },
|
|
979
|
+
],
|
|
972
980
|
},
|
|
973
981
|
{
|
|
974
982
|
path: ['datasets', 'download'],
|