orizu 0.5.28 → 0.5.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/README.md +41 -2
- package/dist/account-directory.d.ts +132 -0
- package/dist/account-directory.d.ts.map +1 -0
- package/dist/account-directory.js +434 -0
- package/dist/app-commands.d.ts +29 -0
- package/dist/app-commands.d.ts.map +1 -0
- package/dist/app-commands.js +375 -0
- package/dist/auth-commands.d.ts +11 -0
- package/dist/auth-commands.d.ts.map +1 -0
- package/dist/auth-commands.js +321 -0
- package/dist/cli-console-output.d.ts +12 -0
- package/dist/cli-console-output.d.ts.map +1 -0
- package/dist/cli-console-output.js +57 -0
- package/dist/cli-project-slug.d.ts +20 -0
- package/dist/cli-project-slug.d.ts.map +1 -0
- package/dist/cli-project-slug.js +36 -0
- package/dist/command-line-arguments.d.ts +15 -0
- package/dist/command-line-arguments.d.ts.map +1 -0
- package/dist/command-line-arguments.js +77 -0
- package/dist/credentials.d.ts +6 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +58 -4
- package/dist/dataset-commands.d.ts +16 -0
- package/dist/dataset-commands.d.ts.map +1 -0
- package/dist/dataset-commands.js +634 -0
- package/dist/dataset-download.d.ts.map +1 -1
- package/dist/dataset-download.js +4 -3
- package/dist/egress-policy.d.ts +8 -5
- package/dist/egress-policy.d.ts.map +1 -1
- package/dist/egress-policy.js +18 -7
- package/dist/feedback-cli.d.ts +14 -0
- package/dist/feedback-cli.d.ts.map +1 -0
- package/dist/feedback-cli.js +615 -0
- package/dist/feedback-contract.d.ts +79 -0
- package/dist/feedback-contract.d.ts.map +1 -0
- package/dist/feedback-contract.js +428 -0
- package/dist/feedback-scrub.d.ts +16 -0
- package/dist/feedback-scrub.d.ts.map +1 -0
- package/dist/feedback-scrub.js +610 -0
- package/dist/gepa-engine-dispatch.d.ts.map +1 -1
- package/dist/gepa-engine-dispatch.js +12 -2
- package/dist/global-flags.d.ts +1 -0
- package/dist/global-flags.d.ts.map +1 -1
- package/dist/global-flags.js +7 -0
- package/dist/headless-login.d.ts +2 -0
- package/dist/headless-login.d.ts.map +1 -1
- package/dist/headless-login.js +14 -11
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +103 -2
- package/dist/hosted-boot.d.ts +5 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +12 -3
- package/dist/hosted-environment.d.ts +10 -0
- package/dist/hosted-environment.d.ts.map +1 -1
- package/dist/hosted-environment.js +81 -0
- package/dist/hosted-gepa-cli.d.ts.map +1 -1
- package/dist/hosted-gepa-cli.js +37 -5
- package/dist/hosted-harness-claude.d.ts +1 -2
- package/dist/hosted-harness-claude.d.ts.map +1 -1
- package/dist/hosted-harness-claude.js +8 -4
- package/dist/hosted-harness-opencode.d.ts +15 -10
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +27 -14
- package/dist/hosted-optimization-refusals.d.ts +6 -1
- package/dist/hosted-optimization-refusals.d.ts.map +1 -1
- package/dist/hosted-optimization-refusals.js +8 -2
- package/dist/hosted-optimization.d.ts.map +1 -1
- package/dist/hosted-optimization.js +40 -10
- package/dist/hosted-provider-credentials.d.ts +16 -0
- package/dist/hosted-provider-credentials.d.ts.map +1 -0
- package/dist/hosted-provider-credentials.js +36 -0
- package/dist/hosted-provider-settings.d.ts +7 -4
- package/dist/hosted-provider-settings.d.ts.map +1 -1
- package/dist/hosted-provider-settings.js +16 -13
- package/dist/http.d.ts +16 -3
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +57 -27
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +382 -3686
- package/dist/instruction-set-sync/index.d.ts +1 -0
- package/dist/instruction-set-sync/index.d.ts.map +1 -1
- package/dist/instruction-set-sync/index.js +15 -6
- package/dist/last-error-caps.d.ts +7 -0
- package/dist/last-error-caps.d.ts.map +1 -0
- package/dist/last-error-caps.js +6 -0
- package/dist/last-error-record.d.ts +37 -0
- package/dist/last-error-record.d.ts.map +1 -0
- package/dist/last-error-record.js +316 -0
- package/dist/model-config-fields.d.ts +2 -4
- package/dist/model-config-fields.d.ts.map +1 -1
- package/dist/model-config-fields.js +26 -9
- package/dist/model-config-price.d.ts +17 -0
- package/dist/model-config-price.d.ts.map +1 -0
- package/dist/model-config-price.js +42 -0
- package/dist/model-configs-cli.d.ts +2 -0
- package/dist/model-configs-cli.d.ts.map +1 -1
- package/dist/model-configs-cli.js +47 -4
- package/dist/optimization-export-cli.d.ts.map +1 -1
- package/dist/optimization-export-cli.js +19 -0
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +11 -4
- package/dist/prompt-length-wire.d.ts +1 -1
- package/dist/prompt-length-wire.d.ts.map +1 -1
- package/dist/prompt-length-wire.js +1 -0
- package/dist/provider-lookup.d.ts +24 -0
- package/dist/provider-lookup.d.ts.map +1 -0
- package/dist/provider-lookup.js +43 -0
- package/dist/provider-probe.d.ts +24 -0
- package/dist/provider-probe.d.ts.map +1 -0
- package/dist/provider-probe.js +88 -0
- package/dist/provider-refusals.d.ts +83 -0
- package/dist/provider-refusals.d.ts.map +1 -0
- package/dist/provider-refusals.js +48 -0
- package/dist/provider-registry.d.ts +146 -0
- package/dist/provider-registry.d.ts.map +1 -0
- package/dist/provider-registry.js +155 -0
- package/dist/providers-cli.d.ts +3 -0
- package/dist/providers-cli.d.ts.map +1 -0
- package/dist/providers-cli.js +178 -0
- package/dist/runner-env-allowlist.d.ts +4 -0
- package/dist/runner-env-allowlist.d.ts.map +1 -0
- package/dist/runner-env-allowlist.js +36 -0
- package/dist/runner-env.d.ts +7 -0
- package/dist/runner-env.d.ts.map +1 -0
- package/dist/runner-env.js +52 -0
- package/dist/self-update.d.ts +42 -0
- package/dist/self-update.d.ts.map +1 -0
- package/dist/self-update.js +506 -0
- package/dist/setup-and-skills-commands.d.ts +11 -0
- package/dist/setup-and-skills-commands.d.ts.map +1 -0
- package/dist/setup-and-skills-commands.js +1055 -0
- package/dist/task-commands.d.ts +13 -0
- package/dist/task-commands.d.ts.map +1 -0
- package/dist/task-commands.js +512 -0
- package/dist/workspace.d.ts +1 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +11 -0
- package/package.json +1 -1
- package/src/account-directory.ts +751 -0
- package/src/app-commands.ts +520 -0
- package/src/auth-commands.ts +388 -0
- package/src/cli-console-output.ts +83 -0
- package/src/cli-project-slug.ts +53 -0
- package/src/command-line-arguments.ts +111 -0
- package/src/credentials.ts +62 -4
- package/src/dataset-commands.ts +863 -0
- package/src/dataset-download.ts +4 -3
- package/src/egress-policy.ts +19 -8
- package/src/feedback-cli.ts +778 -0
- package/src/feedback-contract.ts +492 -0
- package/src/feedback-scrub.ts +799 -0
- package/src/gepa-engine-dispatch.ts +16 -2
- package/src/global-flags.ts +8 -0
- package/src/headless-login.ts +25 -11
- package/src/help.ts +103 -2
- package/src/hosted-boot.ts +21 -3
- package/src/hosted-environment.ts +70 -0
- package/src/hosted-gepa-cli.ts +35 -5
- package/src/hosted-harness-claude.ts +9 -4
- package/src/hosted-harness-opencode.ts +28 -14
- package/src/hosted-optimization-refusals.ts +11 -3
- package/src/hosted-optimization.ts +42 -12
- package/src/hosted-provider-credentials.js +2 -0
- package/src/hosted-provider-credentials.ts +42 -0
- package/src/hosted-provider-settings.ts +21 -13
- package/src/http.ts +75 -26
- package/src/index.ts +1134 -5348
- package/src/instruction-set-sync/index.ts +25 -6
- package/src/last-error-caps.js +4 -0
- package/src/last-error-caps.ts +6 -0
- package/src/last-error-record.ts +415 -0
- package/src/model-config-fields.ts +29 -10
- package/src/model-config-price.js +2 -0
- package/src/model-config-price.ts +55 -0
- package/src/model-configs-cli.ts +41 -4
- package/src/optimization-export-cli.ts +23 -0
- package/src/preview-runtime.ts +11 -4
- package/src/prompt-length-wire.ts +1 -0
- package/src/provider-lookup.js +2 -0
- package/src/provider-lookup.ts +73 -0
- package/src/provider-probe.ts +101 -0
- package/src/provider-refusals.ts +53 -0
- package/src/provider-registry.js +4 -0
- package/src/provider-registry.ts +186 -0
- package/src/providers-cli.ts +153 -0
- package/src/runner-env-allowlist.js +4 -0
- package/src/runner-env-allowlist.ts +38 -0
- package/src/runner-env.js +4 -0
- package/src/runner-env.ts +58 -0
- package/src/secret-redaction.js +4 -0
- package/src/self-update.ts +548 -0
- package/src/setup-and-skills-commands.ts +1242 -0
- package/src/task-commands.ts +703 -0
- package/src/workspace.ts +11 -0
- package/vendor/orizu-gepa/pyproject.toml +7 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +77 -39
- package/vendor/orizu-gepa/src/orizu_gepa_connector/providers.generated.json +32 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +5 -3
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +16 -2
- package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +14 -1
- package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +91 -24
- package/vendor/orizu-gepa-python/pyproject.toml +7 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +3 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/providers.generated.json +32 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/providers.py +321 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +269 -89
- package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +40 -6
- package/vendor/skills/orizu/SKILL.md +26 -22
- package/vendor/skills/orizu/flows/expand.md +2 -0
- package/vendor/skills/orizu/flows/first-win.md +2 -0
- package/vendor/skills/orizu/flows/onboard.md +2 -0
- package/vendor/skills/orizu/flows/promote.md +5 -3
- package/vendor/skills/orizu/flows/recurse.md +2 -0
- package/vendor/skills/orizu/flows/triage.md +7 -5
- package/vendor/skills/orizu/references/assess-and-plan.md +16 -16
- package/vendor/skills/orizu/references/authority-map.md +1 -0
- package/vendor/skills/orizu/references/building-apps.md +92 -92
- package/vendor/skills/orizu/references/building-judges.md +6 -6
- package/vendor/skills/orizu/references/cli-reference.md +80 -14
- package/vendor/skills/orizu/references/dataset-design.md +3 -3
- package/vendor/skills/orizu/references/eval-strategy.md +8 -8
- package/vendor/skills/orizu/references/feedback.md +113 -0
- package/vendor/skills/orizu/references/instructions-after-prompts.md +5 -5
- package/vendor/skills/orizu/references/migrate-existing-gepa-setup.md +15 -15
- package/vendor/skills/orizu/references/migrate-hand-rolled-instruction-layout.md +4 -4
- package/vendor/skills/orizu/references/optimization-reports.md +5 -5
- package/vendor/skills/orizu/references/optimization-with-gepa.md +41 -35
- package/vendor/skills/orizu/references/orizu-in-your-codebase.md +8 -8
- package/vendor/skills/orizu/references/primer.md +42 -42
- package/vendor/skills/orizu/references/prompt-control-plane.md +29 -29
- package/vendor/skills/orizu/references/vocabulary.md +18 -18
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { assertOutputConfined, createManagedArtifactJournal, emitManagedArtifacts } from './helpers.js'
|
|
15
15
|
|
|
16
16
|
const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/u
|
|
17
|
-
const SYNC_LOCK_WAIT_MS = 5_000
|
|
17
|
+
export const SYNC_LOCK_WAIT_MS = 5_000
|
|
18
18
|
const SYNC_LOCK_RETRY_MS = 50
|
|
19
19
|
const SYNC_LOCK_STALE_MS = 60_000
|
|
20
20
|
|
|
@@ -513,7 +513,12 @@ export function withSyncLock<T>(appRoot: string, operation: () => T): T {
|
|
|
513
513
|
mkdirSync(appRoot, { recursive: true })
|
|
514
514
|
const lockPath = join(appRoot, '.orizu.lock.json.lock')
|
|
515
515
|
const metadata = `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`
|
|
516
|
-
const
|
|
516
|
+
const lockWaitMs = processTestMilliseconds(
|
|
517
|
+
'ORIZU_TEST_SYNC_LOCK_WAIT_MS',
|
|
518
|
+
100,
|
|
519
|
+
SYNC_LOCK_WAIT_MS
|
|
520
|
+
) ?? SYNC_LOCK_WAIT_MS
|
|
521
|
+
const deadline = Date.now() + lockWaitMs
|
|
517
522
|
|
|
518
523
|
while (true) {
|
|
519
524
|
try {
|
|
@@ -561,14 +566,28 @@ export function withSyncLock<T>(appRoot: string, operation: () => T): T {
|
|
|
561
566
|
}
|
|
562
567
|
|
|
563
568
|
// Test-only process boundary hooks. Production behavior is unchanged unless a
|
|
564
|
-
// test child explicitly supplies one of the bounded
|
|
565
|
-
function
|
|
569
|
+
// test child explicitly supplies one of the bounded ORIZU_TEST_SYNC_* values.
|
|
570
|
+
function processTestMilliseconds(
|
|
571
|
+
name: string,
|
|
572
|
+
minimum: number,
|
|
573
|
+
maximum: number
|
|
574
|
+
): number | undefined {
|
|
566
575
|
const raw = process.env[name]
|
|
567
|
-
if (raw === undefined) return
|
|
576
|
+
if (raw === undefined) return undefined
|
|
568
577
|
const milliseconds = Number(raw)
|
|
569
|
-
if (
|
|
578
|
+
if (
|
|
579
|
+
!Number.isSafeInteger(milliseconds) ||
|
|
580
|
+
milliseconds < minimum ||
|
|
581
|
+
milliseconds > maximum
|
|
582
|
+
) {
|
|
570
583
|
throw new Error(`instruction_set_sync_test_hold_invalid:${name}`)
|
|
571
584
|
}
|
|
585
|
+
return milliseconds
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function holdForProcessTest(name: string): void {
|
|
589
|
+
const milliseconds = processTestMilliseconds(name, 0, 10_000)
|
|
590
|
+
if (milliseconds === undefined) return
|
|
572
591
|
sleepSync(milliseconds)
|
|
573
592
|
}
|
|
574
593
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const LAST_ERROR_MESSAGE_MAX_CHARS = 2_000
|
|
2
|
+
export const LAST_ERROR_ARG_MAX_CHARS = 200
|
|
3
|
+
export const LAST_ERROR_MAX_ARGS = 32
|
|
4
|
+
export const LAST_ERROR_FILE_MAX_BYTES = 16_384
|
|
5
|
+
export const LAST_ERROR_SERVER_BASE_URL_MAX_BYTES = 256
|
|
6
|
+
export const LAST_ERROR_CODE_MAX_CHARS = 64
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto'
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
closeSync,
|
|
5
|
+
fsyncSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'fs'
|
|
14
|
+
import { homedir } from 'os'
|
|
15
|
+
import { join } from 'path'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
getConfigDir,
|
|
19
|
+
getSecretsSeenThisProcess,
|
|
20
|
+
resolveAuthTokenForBaseUrl,
|
|
21
|
+
resolveEnvBearerToken,
|
|
22
|
+
} from './credentials.js'
|
|
23
|
+
import { scrubFeedbackText } from './feedback-scrub.js'
|
|
24
|
+
import { getBaseUrl } from './http.js'
|
|
25
|
+
import {
|
|
26
|
+
LAST_ERROR_ARG_MAX_CHARS,
|
|
27
|
+
LAST_ERROR_CODE_MAX_CHARS,
|
|
28
|
+
LAST_ERROR_FILE_MAX_BYTES,
|
|
29
|
+
LAST_ERROR_MAX_ARGS,
|
|
30
|
+
LAST_ERROR_MESSAGE_MAX_CHARS,
|
|
31
|
+
LAST_ERROR_SERVER_BASE_URL_MAX_BYTES,
|
|
32
|
+
} from './last-error-caps.js'
|
|
33
|
+
import { runnerForwardedSecretValues } from './runner-env.js'
|
|
34
|
+
|
|
35
|
+
export interface LastErrorRecord {
|
|
36
|
+
version: 1
|
|
37
|
+
recordedAt: string
|
|
38
|
+
cliVersion: string | null
|
|
39
|
+
code: string | null
|
|
40
|
+
serverBaseUrl: string | null
|
|
41
|
+
teamSlug: string | null
|
|
42
|
+
command: string | null
|
|
43
|
+
argv: string[]
|
|
44
|
+
argvTruncated: boolean
|
|
45
|
+
message: string
|
|
46
|
+
messageTruncated: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const LAST_ERROR_RECORD_VERSION = 1
|
|
50
|
+
export const LAST_ERROR_FILENAME = 'last-error.json'
|
|
51
|
+
export const LAST_ERROR_ATTACH_MAX_AGE_MS = 86_400_000
|
|
52
|
+
export const LAST_ERROR_TRUNCATION_MARKER = '[truncated]'
|
|
53
|
+
|
|
54
|
+
const LAST_ERROR_MESSAGE_PRE_SCRUB_MAX_CHARS = 65_536
|
|
55
|
+
const CREDENTIAL_BEARING_FLAGS = new Set([
|
|
56
|
+
'--model-key',
|
|
57
|
+
'--token',
|
|
58
|
+
'--api-key',
|
|
59
|
+
'--password',
|
|
60
|
+
'--secret',
|
|
61
|
+
'--access-token',
|
|
62
|
+
'--refresh-token',
|
|
63
|
+
'--private-key',
|
|
64
|
+
'--client-secret',
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
export interface LastErrorRecordInput {
|
|
68
|
+
argv: readonly string[]
|
|
69
|
+
message: string
|
|
70
|
+
cliVersion: string | null
|
|
71
|
+
code?: string | null
|
|
72
|
+
serverBaseUrl?: string | null
|
|
73
|
+
teamSlug?: string | null
|
|
74
|
+
homeDir?: string | null
|
|
75
|
+
secrets?: readonly string[]
|
|
76
|
+
now?: Date
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface WriteLastErrorRecordOptions {
|
|
80
|
+
configDir?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface ScrubContext {
|
|
84
|
+
homeDir: string | null
|
|
85
|
+
secrets: readonly string[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function removeTrailingCredentialResidue(input: string): string {
|
|
89
|
+
const hasMarker = input.endsWith(LAST_ERROR_TRUNCATION_MARKER)
|
|
90
|
+
const body = hasMarker
|
|
91
|
+
? input.slice(0, -LAST_ERROR_TRUNCATION_MARKER.length)
|
|
92
|
+
: input
|
|
93
|
+
const withoutResidue = body.replace(
|
|
94
|
+
/(?:\borizu_(?:agent|pat)_|\bgithub_pat_|\bgh[posur]_?|\bsk-(?:ant-|proj-)?)[^\s]*$/i,
|
|
95
|
+
''
|
|
96
|
+
)
|
|
97
|
+
return hasMarker
|
|
98
|
+
? `${withoutResidue}${LAST_ERROR_TRUNCATION_MARKER}`
|
|
99
|
+
: withoutResidue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function scrubWithContext(input: string, context: ScrubContext): string {
|
|
103
|
+
return scrubFeedbackText(input, {
|
|
104
|
+
reporterEmail: undefined,
|
|
105
|
+
homeDir: context.homeDir,
|
|
106
|
+
secrets: context.secrets,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function scrubAndCap(
|
|
111
|
+
input: string,
|
|
112
|
+
maximumCharacters: number,
|
|
113
|
+
context: ScrubContext
|
|
114
|
+
): { value: string; wasTruncated: boolean } {
|
|
115
|
+
const scrubbed = scrubWithContext(input, context)
|
|
116
|
+
const wasTruncated = scrubbed.length > maximumCharacters
|
|
117
|
+
const capped = wasTruncated
|
|
118
|
+
? `${scrubbed.slice(0, maximumCharacters)}${LAST_ERROR_TRUNCATION_MARKER}`
|
|
119
|
+
: scrubbed
|
|
120
|
+
const withoutResidue = removeTrailingCredentialResidue(capped)
|
|
121
|
+
const finalScrubbed = scrubWithContext(withoutResidue, context)
|
|
122
|
+
const finalBody = wasTruncated && finalScrubbed.endsWith(LAST_ERROR_TRUNCATION_MARKER)
|
|
123
|
+
? finalScrubbed.slice(0, -LAST_ERROR_TRUNCATION_MARKER.length)
|
|
124
|
+
: finalScrubbed
|
|
125
|
+
return {
|
|
126
|
+
value: wasTruncated
|
|
127
|
+
? `${finalBody.slice(0, maximumCharacters)}${LAST_ERROR_TRUNCATION_MARKER}`
|
|
128
|
+
: finalBody.slice(0, maximumCharacters),
|
|
129
|
+
wasTruncated,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isCredentialBearingFlag(argument: string): boolean {
|
|
134
|
+
const normalizedArgument = argument.toLowerCase()
|
|
135
|
+
return CREDENTIAL_BEARING_FLAGS.has(normalizedArgument)
|
|
136
|
+
|| /^--.+[_.-](?:key|token|secret|password)$/.test(normalizedArgument)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function credentialBearingArgvSecrets(argv: readonly string[]): string[] {
|
|
140
|
+
const secrets: string[] = []
|
|
141
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
142
|
+
const argument = argv[index]
|
|
143
|
+
const equalsIndex = argument.indexOf('=')
|
|
144
|
+
if (equalsIndex > 0 && isCredentialBearingFlag(argument.slice(0, equalsIndex))) {
|
|
145
|
+
const value = argument.slice(equalsIndex + 1)
|
|
146
|
+
if (value.length >= 8) secrets.push(value)
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!isCredentialBearingFlag(argument)) continue
|
|
151
|
+
const value = argv[index + 1]
|
|
152
|
+
if (value && !value.startsWith('--') && value.length >= 8) secrets.push(value)
|
|
153
|
+
index += 1
|
|
154
|
+
}
|
|
155
|
+
return secrets
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function maskCredentialBearingArgument(
|
|
159
|
+
argv: readonly string[],
|
|
160
|
+
index: number
|
|
161
|
+
): string {
|
|
162
|
+
const argument = argv[index]
|
|
163
|
+
if (
|
|
164
|
+
index > 0
|
|
165
|
+
&& isCredentialBearingFlag(argv[index - 1])
|
|
166
|
+
) {
|
|
167
|
+
return '[redacted]'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const equalsIndex = argument.indexOf('=')
|
|
171
|
+
if (
|
|
172
|
+
equalsIndex > 0
|
|
173
|
+
&& isCredentialBearingFlag(argument.slice(0, equalsIndex))
|
|
174
|
+
) {
|
|
175
|
+
return `${argument.slice(0, equalsIndex + 1)}[redacted]`
|
|
176
|
+
}
|
|
177
|
+
return argument
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function commandAfterGlobalFlags(argv: readonly string[]): string | null {
|
|
181
|
+
const commandArguments: string[] = []
|
|
182
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
183
|
+
const argument = argv[index]
|
|
184
|
+
if (argument === '--local') continue
|
|
185
|
+
if (argument === '--server') {
|
|
186
|
+
index += 1
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
commandArguments.push(argument)
|
|
190
|
+
}
|
|
191
|
+
if (commandArguments[0] === '--json') commandArguments.shift()
|
|
192
|
+
return commandArguments[0] ?? null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function serializeRecord(record: LastErrorRecord): string {
|
|
196
|
+
return `${JSON.stringify(record, null, 2)}\n`
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isSerializedRecordTooLarge(record: LastErrorRecord): boolean {
|
|
200
|
+
return Buffer.byteLength(serializeRecord(record), 'utf8') > LAST_ERROR_FILE_MAX_BYTES
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function resolveActiveProcessSecrets(): readonly string[] {
|
|
204
|
+
const rememberedSecrets = getSecretsSeenThisProcess()
|
|
205
|
+
try {
|
|
206
|
+
const environmentBearer = resolveEnvBearerToken()
|
|
207
|
+
const currentBearer = environmentBearer ?? resolveAuthTokenForBaseUrl(getBaseUrl())
|
|
208
|
+
return [...new Set([...rememberedSecrets, currentBearer])]
|
|
209
|
+
} catch {
|
|
210
|
+
return rememberedSecrets
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function propertyValue(value: unknown, key: string, ownOnly = false): unknown {
|
|
215
|
+
if ((typeof value !== 'object' || value === null) && typeof value !== 'function') return undefined
|
|
216
|
+
try {
|
|
217
|
+
return !ownOnly || Object.hasOwn(value, key) ? Reflect.get(value, key) : undefined
|
|
218
|
+
} catch {
|
|
219
|
+
return undefined
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function extractLastErrorCode(error: unknown): string | null {
|
|
224
|
+
const ownCode = propertyValue(error, 'code', true)
|
|
225
|
+
if (typeof ownCode === 'string') return ownCode
|
|
226
|
+
const causeCode = propertyValue(propertyValue(error, 'cause'), 'code')
|
|
227
|
+
return typeof causeCode === 'string' ? causeCode : null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function resolveHomeDir(): string | null {
|
|
231
|
+
try {
|
|
232
|
+
return homedir()
|
|
233
|
+
} catch {
|
|
234
|
+
return null
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isStringWithinCap(
|
|
239
|
+
value: unknown,
|
|
240
|
+
maximumCharacters: number,
|
|
241
|
+
wasTruncated: boolean
|
|
242
|
+
): value is string {
|
|
243
|
+
if (typeof value !== 'string') return false
|
|
244
|
+
if (value.length <= maximumCharacters) return true
|
|
245
|
+
return wasTruncated
|
|
246
|
+
&& value.length <= maximumCharacters + LAST_ERROR_TRUNCATION_MARKER.length
|
|
247
|
+
&& value.endsWith(LAST_ERROR_TRUNCATION_MARKER)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function boundedServerBaseUrl(value: string | null | undefined): string | null {
|
|
251
|
+
return value && Buffer.byteLength(value, 'utf8') <= LAST_ERROR_SERVER_BASE_URL_MAX_BYTES
|
|
252
|
+
? value
|
|
253
|
+
: null
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isLastErrorRecord(value: unknown): value is LastErrorRecord {
|
|
257
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
|
|
258
|
+
const record = value as Partial<LastErrorRecord>
|
|
259
|
+
if (record.version !== LAST_ERROR_RECORD_VERSION) return false
|
|
260
|
+
if (typeof record.recordedAt !== 'string' || !Number.isFinite(Date.parse(record.recordedAt))) return false
|
|
261
|
+
if (typeof record.cliVersion !== 'string' && record.cliVersion !== null) return false
|
|
262
|
+
if (record.code !== undefined && record.code !== null && (
|
|
263
|
+
typeof record.code !== 'string'
|
|
264
|
+
|| Array.from(record.code).length > LAST_ERROR_CODE_MAX_CHARS
|
|
265
|
+
)) return false
|
|
266
|
+
if (record.serverBaseUrl !== undefined && typeof record.serverBaseUrl !== 'string' && record.serverBaseUrl !== null) return false
|
|
267
|
+
if (record.teamSlug !== undefined && typeof record.teamSlug !== 'string' && record.teamSlug !== null) return false
|
|
268
|
+
if (typeof record.argvTruncated !== 'boolean') return false
|
|
269
|
+
const argvTruncated = record.argvTruncated
|
|
270
|
+
if (record.command !== null && !isStringWithinCap(
|
|
271
|
+
record.command,
|
|
272
|
+
LAST_ERROR_ARG_MAX_CHARS,
|
|
273
|
+
argvTruncated
|
|
274
|
+
)) return false
|
|
275
|
+
if (!Array.isArray(record.argv) || record.argv.length > LAST_ERROR_MAX_ARGS) return false
|
|
276
|
+
if (!record.argv.every(argument => isStringWithinCap(
|
|
277
|
+
argument,
|
|
278
|
+
LAST_ERROR_ARG_MAX_CHARS,
|
|
279
|
+
argvTruncated
|
|
280
|
+
))) return false
|
|
281
|
+
if (typeof record.messageTruncated !== 'boolean') return false
|
|
282
|
+
return isStringWithinCap(
|
|
283
|
+
record.message,
|
|
284
|
+
LAST_ERROR_MESSAGE_MAX_CHARS,
|
|
285
|
+
record.messageTruncated
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function buildLastErrorRecord(input: LastErrorRecordInput): LastErrorRecord {
|
|
290
|
+
const context: ScrubContext = {
|
|
291
|
+
homeDir: input.homeDir ?? null,
|
|
292
|
+
secrets: [
|
|
293
|
+
...(input.secrets ?? []),
|
|
294
|
+
...credentialBearingArgvSecrets(input.argv),
|
|
295
|
+
],
|
|
296
|
+
}
|
|
297
|
+
const messageInput = input.message.slice(0, LAST_ERROR_MESSAGE_PRE_SCRUB_MAX_CHARS)
|
|
298
|
+
const cappedMessage = scrubAndCap(messageInput, LAST_ERROR_MESSAGE_MAX_CHARS, context)
|
|
299
|
+
const retainedArguments = input.argv.slice(0, LAST_ERROR_MAX_ARGS)
|
|
300
|
+
let argvTruncated = input.argv.length > LAST_ERROR_MAX_ARGS
|
|
301
|
+
const argv = retainedArguments.map((_argument, index) => {
|
|
302
|
+
const maskedArgument = maskCredentialBearingArgument(input.argv, index)
|
|
303
|
+
const cappedArgument = scrubAndCap(maskedArgument, LAST_ERROR_ARG_MAX_CHARS, context)
|
|
304
|
+
argvTruncated ||= cappedArgument.wasTruncated
|
|
305
|
+
return cappedArgument.value
|
|
306
|
+
})
|
|
307
|
+
const rawCommand = commandAfterGlobalFlags(input.argv)
|
|
308
|
+
const command = rawCommand === null
|
|
309
|
+
? null
|
|
310
|
+
: scrubAndCap(rawCommand, LAST_ERROR_ARG_MAX_CHARS, context).value
|
|
311
|
+
const code = input.code === undefined || input.code === null
|
|
312
|
+
? null
|
|
313
|
+
: Array.from(scrubAndCap(
|
|
314
|
+
input.code, LAST_ERROR_CODE_MAX_CHARS * 2, context
|
|
315
|
+
).value).slice(0, LAST_ERROR_CODE_MAX_CHARS).join('')
|
|
316
|
+
|
|
317
|
+
const record: LastErrorRecord = {
|
|
318
|
+
version: LAST_ERROR_RECORD_VERSION,
|
|
319
|
+
recordedAt: (input.now ?? new Date()).toISOString(),
|
|
320
|
+
cliVersion: input.cliVersion,
|
|
321
|
+
code,
|
|
322
|
+
serverBaseUrl: boundedServerBaseUrl(input.serverBaseUrl),
|
|
323
|
+
teamSlug: input.teamSlug ?? null,
|
|
324
|
+
command,
|
|
325
|
+
argv,
|
|
326
|
+
argvTruncated,
|
|
327
|
+
message: cappedMessage.value,
|
|
328
|
+
messageTruncated: cappedMessage.wasTruncated || input.message.length > LAST_ERROR_MESSAGE_PRE_SCRUB_MAX_CHARS,
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (isSerializedRecordTooLarge(record)) {
|
|
332
|
+
record.argv = []
|
|
333
|
+
record.argvTruncated = true
|
|
334
|
+
}
|
|
335
|
+
while (isSerializedRecordTooLarge(record) && record.message.length > LAST_ERROR_TRUNCATION_MARKER.length) {
|
|
336
|
+
const content = record.message.endsWith(LAST_ERROR_TRUNCATION_MARKER)
|
|
337
|
+
? record.message.slice(0, -LAST_ERROR_TRUNCATION_MARKER.length)
|
|
338
|
+
: record.message
|
|
339
|
+
record.message = scrubAndCap(content, Math.floor(content.length / 2), context).value
|
|
340
|
+
if (!record.message.endsWith(LAST_ERROR_TRUNCATION_MARKER)) {
|
|
341
|
+
record.message += LAST_ERROR_TRUNCATION_MARKER
|
|
342
|
+
}
|
|
343
|
+
record.messageTruncated = true
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return record
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function getLastErrorRecordPath(configDir?: string): string {
|
|
350
|
+
return join(configDir ?? getConfigDir(), LAST_ERROR_FILENAME)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function writeLastErrorRecord(
|
|
354
|
+
input: LastErrorRecordInput,
|
|
355
|
+
options: WriteLastErrorRecordOptions = {}
|
|
356
|
+
): void {
|
|
357
|
+
try {
|
|
358
|
+
const configDirectory = options.configDir ?? getConfigDir()
|
|
359
|
+
mkdirSync(configDirectory, { recursive: true, mode: 0o700 })
|
|
360
|
+
chmodSync(configDirectory, 0o700)
|
|
361
|
+
|
|
362
|
+
const record = buildLastErrorRecord({
|
|
363
|
+
...input,
|
|
364
|
+
homeDir: input.homeDir === undefined ? resolveHomeDir() : input.homeDir,
|
|
365
|
+
secrets: [
|
|
366
|
+
...resolveActiveProcessSecrets(),
|
|
367
|
+
...runnerForwardedSecretValues(),
|
|
368
|
+
...(input.secrets ?? []),
|
|
369
|
+
],
|
|
370
|
+
})
|
|
371
|
+
const serialized = serializeRecord(record)
|
|
372
|
+
if (Buffer.byteLength(serialized, 'utf8') > LAST_ERROR_FILE_MAX_BYTES) return
|
|
373
|
+
|
|
374
|
+
const path = getLastErrorRecordPath(configDirectory)
|
|
375
|
+
const temporaryPath = join(
|
|
376
|
+
configDirectory,
|
|
377
|
+
`.${LAST_ERROR_FILENAME}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const fileDescriptor = openSync(temporaryPath, 'wx', 0o600)
|
|
382
|
+
try {
|
|
383
|
+
writeFileSync(fileDescriptor, serialized, 'utf8')
|
|
384
|
+
fsyncSync(fileDescriptor)
|
|
385
|
+
} finally {
|
|
386
|
+
closeSync(fileDescriptor)
|
|
387
|
+
}
|
|
388
|
+
chmodSync(temporaryPath, 0o600)
|
|
389
|
+
renameSync(temporaryPath, path)
|
|
390
|
+
chmodSync(path, 0o600)
|
|
391
|
+
} finally {
|
|
392
|
+
rmSync(temporaryPath, { force: true })
|
|
393
|
+
}
|
|
394
|
+
} catch {
|
|
395
|
+
// A diagnostic record is best effort and must never replace the original CLI failure.
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function readLastErrorRecord(configDir?: string): LastErrorRecord | null {
|
|
400
|
+
try {
|
|
401
|
+
const path = getLastErrorRecordPath(configDir)
|
|
402
|
+
if (statSync(path).size > LAST_ERROR_FILE_MAX_BYTES) return null
|
|
403
|
+
const record = JSON.parse(readFileSync(path, 'utf8')) as unknown
|
|
404
|
+
return isLastErrorRecord(record)
|
|
405
|
+
? {
|
|
406
|
+
...record,
|
|
407
|
+
code: record.code ?? null,
|
|
408
|
+
serverBaseUrl: boundedServerBaseUrl(record.serverBaseUrl),
|
|
409
|
+
teamSlug: record.teamSlug ?? null,
|
|
410
|
+
}
|
|
411
|
+
: null
|
|
412
|
+
} catch {
|
|
413
|
+
return null
|
|
414
|
+
}
|
|
415
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { WireProtocol } from './provider-registry.js'
|
|
2
|
+
|
|
1
3
|
export const MODEL_CONFIG_FIELDS = [
|
|
2
4
|
// The pilot's frozen settings carry the provider model name under `model`.
|
|
3
5
|
{ field: 'MODEL', settingsKeys: ['model'], kind: 'string' },
|
|
@@ -7,9 +9,9 @@ export const MODEL_CONFIG_FIELDS = [
|
|
|
7
9
|
{ field: 'THINKING_LEVEL', settingsKeys: ['reasoning_effort', 'reasoning.effort', 'output_config.effort'], kind: 'string' },
|
|
8
10
|
// Responses uses `max_output_tokens`; Messages uses `max_tokens` for the output limit.
|
|
9
11
|
{ field: 'MAX_OUTPUT_TOKENS', settingsKeys: ['max_output_tokens', 'max_tokens'], kind: 'number' },
|
|
10
|
-
// `temperature` feeds the temperature call option for
|
|
12
|
+
// `temperature` feeds the temperature call option for the applicable protocols.
|
|
11
13
|
{ field: 'TEMPERATURE', settingsKeys: ['temperature'], kind: 'number' },
|
|
12
|
-
// `top_p` feeds the top-p call option accepted by
|
|
14
|
+
// `top_p` feeds the top-p call option accepted by the applicable protocols.
|
|
13
15
|
{ field: 'TOP_P', settingsKeys: ['top_p'], kind: 'number' },
|
|
14
16
|
// `strict_json_schema` controls strict schema enforcement in a structured-output call.
|
|
15
17
|
{ field: 'STRICT_JSON_SCHEMA', settingsKeys: ['strict_json_schema'], kind: 'boolean' },
|
|
@@ -18,14 +20,31 @@ export const MODEL_CONFIG_FIELDS = [
|
|
|
18
20
|
export type ModelConfigField = typeof MODEL_CONFIG_FIELDS[number]['field']
|
|
19
21
|
|
|
20
22
|
// Dotted settings keys resolve exactly one object level; they are not a general path language.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
// Keyed by WIRE PROTOCOL, not by provider name: two providers speaking the same
|
|
24
|
+
// protocol take the same request fields (ORI-2031 row 3).
|
|
25
|
+
//
|
|
26
|
+
// Every cell is MEASURED against a live endpoint, never taken from
|
|
27
|
+
// documentation (ADR-013). T1 shipped this table without an `openai-chat`
|
|
28
|
+
// column because Orizu had never sent a chat-completions request; ORI-2032
|
|
29
|
+
// measured that protocol on 2026-09-12 and filled it in. The measured
|
|
30
|
+
// contract, including which fields each model accepted, is recorded in
|
|
31
|
+
// docs/requirements/any-provider-support/t2-python-transport-plan.md.
|
|
32
|
+
export const PROVIDER_REQUEST_FIELDS: Record<ModelConfigField, Record<WireProtocol, string | null>> = {
|
|
33
|
+
MODEL: { 'openai-responses': 'model', 'openai-chat': 'model', 'anthropic-messages': 'model' },
|
|
23
34
|
// Orizu consumes protocol selection; it is not a provider request field.
|
|
24
|
-
PROTOCOL: { openai: null, anthropic: null },
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
PROTOCOL: { 'openai-responses': null, 'openai-chat': null, 'anthropic-messages': null },
|
|
36
|
+
// null for openai-chat: Orizu's chat builder sends NO reasoning field. The
|
|
37
|
+
// protocol is not enough to decide one — OpenRouter takes `reasoning.effort`
|
|
38
|
+
// and OpenAI chat a top-level `reasoning_effort` — and a customer's own
|
|
39
|
+
// reflection_provider_settings reach the provider verbatim either way.
|
|
40
|
+
THINKING_LEVEL: { 'openai-responses': 'reasoning.effort', 'openai-chat': null, 'anthropic-messages': 'output_config.effort' },
|
|
41
|
+
// 'max_completion_tokens' is measured: gpt-4o-mini accepted either field,
|
|
42
|
+
// gpt-5-mini and o4-mini rejected 'max_tokens' with unsupported_parameter.
|
|
43
|
+
MAX_OUTPUT_TOKENS: { 'openai-responses': 'max_output_tokens', 'openai-chat': 'max_completion_tokens', 'anthropic-messages': 'max_tokens' },
|
|
44
|
+
// openai-chat: accepted by gpt-4o-mini; rejected with 400 by gpt-5-mini and o4-mini, so the builder only sends temperature when set.
|
|
45
|
+
TEMPERATURE: { 'openai-responses': 'temperature', 'openai-chat': 'temperature', 'anthropic-messages': 'temperature' },
|
|
46
|
+
// openai-chat: accepted by gpt-4o-mini; rejected with 400 by gpt-5-mini and o4-mini, so the builder only sends top_p when set.
|
|
47
|
+
TOP_P: { 'openai-responses': 'top_p', 'openai-chat': 'top_p', 'anthropic-messages': 'top_p' },
|
|
29
48
|
// Orizu consumes strict schema selection; it is not a provider request field.
|
|
30
|
-
STRICT_JSON_SCHEMA: { openai: null, anthropic: null },
|
|
49
|
+
STRICT_JSON_SCHEMA: { 'openai-responses': null, 'openai-chat': null, 'anthropic-messages': null },
|
|
31
50
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Current model-config price metadata; never invocation settings. */
|
|
2
|
+
export interface ModelConfigPrice {
|
|
3
|
+
currency?: 'USD'
|
|
4
|
+
input_usd_per_million_tokens: number
|
|
5
|
+
output_usd_per_million_tokens: number
|
|
6
|
+
cache_read_usd_per_million_tokens?: number
|
|
7
|
+
cache_write_usd_per_million_tokens?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ModelConfigTokenRates {
|
|
11
|
+
inputUsdPerToken: number
|
|
12
|
+
outputUsdPerToken: number
|
|
13
|
+
cachedInputUsdPerToken?: number
|
|
14
|
+
cacheWriteUsdPerToken?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const rateFields = [
|
|
18
|
+
'input_usd_per_million_tokens',
|
|
19
|
+
'output_usd_per_million_tokens',
|
|
20
|
+
'cache_read_usd_per_million_tokens',
|
|
21
|
+
'cache_write_usd_per_million_tokens',
|
|
22
|
+
] as const
|
|
23
|
+
const allowedFields = new Set<string>(['currency', ...rateFields])
|
|
24
|
+
|
|
25
|
+
function invalid(field: string): never {
|
|
26
|
+
throw new Error(`model_config_price_invalid:${field}`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseModelConfigPrice(value: unknown): ModelConfigPrice {
|
|
30
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) invalid('price')
|
|
31
|
+
const record = value as Record<string, unknown>
|
|
32
|
+
for (const key of Object.keys(record)) if (!allowedFields.has(key)) invalid(key)
|
|
33
|
+
if ('currency' in record && record.currency !== 'USD') invalid('currency')
|
|
34
|
+
for (const field of rateFields) {
|
|
35
|
+
if (field.startsWith('cache_') && !(field in record)) continue
|
|
36
|
+
const rate = record[field]
|
|
37
|
+
if (typeof rate !== 'number' || !Number.isFinite(rate) || rate < 0 || rate > 1e308) invalid(field)
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...(record.currency === 'USD' ? { currency: 'USD' } : {}),
|
|
41
|
+
input_usd_per_million_tokens: record.input_usd_per_million_tokens as number,
|
|
42
|
+
output_usd_per_million_tokens: record.output_usd_per_million_tokens as number,
|
|
43
|
+
...('cache_read_usd_per_million_tokens' in record ? { cache_read_usd_per_million_tokens: record.cache_read_usd_per_million_tokens as number } : {}),
|
|
44
|
+
...('cache_write_usd_per_million_tokens' in record ? { cache_write_usd_per_million_tokens: record.cache_write_usd_per_million_tokens as number } : {}),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ratesFromModelConfigPrice(price: ModelConfigPrice): ModelConfigTokenRates {
|
|
49
|
+
return {
|
|
50
|
+
inputUsdPerToken: price.input_usd_per_million_tokens / 1_000_000,
|
|
51
|
+
outputUsdPerToken: price.output_usd_per_million_tokens / 1_000_000,
|
|
52
|
+
...(price.cache_read_usd_per_million_tokens === undefined ? {} : { cachedInputUsdPerToken: price.cache_read_usd_per_million_tokens / 1_000_000 }),
|
|
53
|
+
...(price.cache_write_usd_per_million_tokens === undefined ? {} : { cacheWriteUsdPerToken: price.cache_write_usd_per_million_tokens / 1_000_000 }),
|
|
54
|
+
}
|
|
55
|
+
}
|