From b950bdb29a5ddd9076fd716e79a634b6a7927dfd Mon Sep 17 00:00:00 2001 From: Raymond Weitekamp <19483938+rawwerks@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:45:36 -0400 Subject: [PATCH] Quote holds with the run's options and accept webhook binding keys The service's hold now depends on model, reasoning effort, environment, declared tools and bound repositories. run quote and the run submit plan send the given --model, --reasoning-effort, --environment and repositories=1 for --repo/--commit-output; the job create plan sends the spec's model, reasoning_effort, environment and repositories=1 for a repository URL. Nothing is defaulted, so a flagless quote is unchanged. Replace the flat-hold wording in holdBasis, human output, help, guide, SPEC and docs. Accept model, reasoning_effort, repository_url, repository_branch and output in webhook job specs. Co-Authored-By: Claude Opus 5.5 --- cli/CHANGELOG.md | 14 +++ cli/SPEC.md | 20 +++- cli/bun/src/core/service/http.ts | 23 ++++ cli/bun/src/core/service/jobs.ts | 28 ++++- cli/bun/src/core/service/programs.ts | 2 +- cli/bun/src/core/service/render.ts | 2 +- cli/bun/src/core/service/runs.ts | 30 +++-- .../framework/help-json-group-records.json | 16 +-- .../framework/service-capabilities-json.json | 6 +- .../framework/service-capabilities-jsonl.json | 6 +- .../framework/service-guide-human.json | 2 +- .../service/framework/service-guide-json.json | 4 +- .../framework/service-guide-jsonl.json | 4 +- ...b-create-schedule-empty-strings-quote.json | 81 +++++++++++++ ...job-create-schedule-environment-quote.json | 85 ++++++++++++++ .../job-create-webhook-binding-quote.json | 89 ++++++++++++++ .../runs/alias-run-create-previews.json | 3 + .../runs/quote-accepts-submit-arguments.json | 7 +- .../runs/quote-commit-output-only.json | 98 ++++++++++++++++ ...te-default-environment-unknown-fields.json | 2 +- .../runs/quote-default-environment.json | 2 +- .../cases/service/runs/quote-human.json | 2 +- .../service/runs/quote-linux-environment.json | 2 +- .../runs/quote-sends-hold-options.json | 109 ++++++++++++++++++ .../submit-preview-hidden-model-accepted.json | 3 + .../runs/submit-preview-repo-quote.json | 103 +++++++++++++++++ .../runs/submit-preview-summary-human.json | 5 +- .../service/runs/submit-preview-summary.json | 4 + .../prose-runner-core/src/service/jobs.rs | 28 ++++- .../prose-runner-core/src/service/programs.rs | 3 +- .../prose-runner-core/src/service/render.rs | 4 +- .../prose-runner-core/src/service/runs.rs | 74 +++++++++--- cli/shared/schemas/service/runs.schema.json | 4 +- cli/shared/service/guide.v1.md | 15 ++- cli/shared/service/help.v1.json | 12 +- cli/shared/service/operations.v1.json | 49 ++++++-- docs/hosted-service-client.md | 2 +- docs/service/jobs.md | 11 +- docs/service/programs.md | 2 +- docs/service/runs.md | 6 +- 40 files changed, 862 insertions(+), 100 deletions(-) create mode 100644 cli/conformance/cases/service/jobs/job-create-schedule-empty-strings-quote.json create mode 100644 cli/conformance/cases/service/jobs/job-create-schedule-environment-quote.json create mode 100644 cli/conformance/cases/service/jobs/job-create-webhook-binding-quote.json create mode 100644 cli/conformance/cases/service/runs/quote-commit-output-only.json create mode 100644 cli/conformance/cases/service/runs/quote-sends-hold-options.json create mode 100644 cli/conformance/cases/service/runs/submit-preview-repo-quote.json diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 069553a..cd722bd 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -11,6 +11,20 @@ a candidate do not establish public availability or authorize publication. ## [Unreleased] +### Hosted service + +- Hold quotes follow the service's per-run holds. `run quote` and the + `run submit` plan send the given `--model`, `--reasoning-effort` and + `--environment`, plus `repositories=1` for any `--repo` or + `--commit-output`; the `job create` plan sends the spec's `model`, + `reasoning_effort` and `environment`, plus `repositories=1` for a + `repository_url` or `context_repository_url`. Nothing is defaulted, so a + flagless quote is unchanged. `holdBasis`, the human quote and the help no + longer call the hold flat. +- `job create` webhook specs accept `model`, `reasoning_effort`, + `repository_url`, `repository_branch` and `output` for the connected + contract (the service requires `program_ref` with them). + ## [0.15.0-rc.2] — 2026-10-02 These are accumulated changes in the 0.15 candidate train; some capabilities diff --git a/cli/SPEC.md b/cli/SPEC.md index 9c63356..2e9d8c1 100644 --- a/cli/SPEC.md +++ b/cli/SPEC.md @@ -2469,14 +2469,23 @@ sorted keys of `inputs` as `inputKeys`, `interval_seconds`, `delivery_mode`, `visibility`, `role`, `amount_cents`), omitted when none is present; secrets, program text and input values never appear. Human previews print it as `Summary: field=value; ...` (cents also in dollars) and `CONFIRMATION_REQUIRED` -as `Planned summary:`; a plan with a quote prints `Hold: $X (flat hold, -independent of program and model)`. `run submit` and `program draft` check a +as `Planned summary:`; a plan with a quote prints `Hold: $X (set aside from +the wallet while the run is live; not its price)`. `run submit` and `program draft` check a `--model` against `GET /models` (manifest request `when: "--model"`) before the confirmation gate: a name that is neither offered nor the default is `INVOCATION_INVALID` naming the three nearest offered models, with `suggestedArgv` the same command using the nearest; a failed lookup is advisory (the service decides) except an interrupt. `program draft` -quotes the flat run hold (`GET /run/quote`, advisory) into its plan. `org +quotes the run hold (`GET /run/quote` with no parameters, advisory) into its +plan. The hold depends on model, reasoning effort, environment, declared tools +and bound repositories. `run quote` and the `run submit` plan send only the +`model`, `reasoning_effort` and `environment` the user gave, plus +`repositories=1` for any `--repo` or `--commit-output`; the `job create` plan +sends the spec's `model`, `reasoning_effort` and `environment`, plus +`repositories=1` for a `repository_url` or `context_repository_url`. Nothing +is defaulted, the program is not inspected for the quote and tools are never +sent, so a quote does not cover the program's own run settings or declared +tools. `org create` checks the service slug rule (1-63 lowercase letters, digits or interior hyphens; not `openprose`, `system`, `default`, `invitations` or UUID-shaped) before any request, suggesting a derived slug as `suggestedArgv` @@ -2495,8 +2504,9 @@ the `--yes` help line reads `required because .` and `CONFIRMATION_REQUIRED` carries `details.reason` `--yes is required because `; `render_service_help.py --check` and the manifest schema refuse a missing, misplaced or circular reason. `run quote` results carry -`holdBasis` and its human output says the hold is flat and the price is known -only after settlement. +`holdBasis` and its human output says what the hold depends on, that the +quote covers only the options given, and that the price is known only after +settlement. **Transport.** Requests never follow redirects and are never retried, not even GETs. Each operation has a transport class with bounded time and size: diff --git a/cli/bun/src/core/service/http.ts b/cli/bun/src/core/service/http.ts index 376e03b..a35a377 100644 --- a/cli/bun/src/core/service/http.ts +++ b/cli/bun/src/core/service/http.ts @@ -52,6 +52,29 @@ export function requestFor(operation: ManifestOperation, index: number, path: st }; } +/** The options a `GET /run/quote` hold depends on, as the caller gave them. */ +export interface HoldOptions { + model?: string | undefined; + reasoningEffort?: string | undefined; + environment?: string | undefined; + repositoriesBound: boolean; +} + +/** + * The `GET /run/quote` query (manifest order): only what was given, never a + * default, so a quote with no such option stays parameter-free. Any bound + * repository sends `repositories=1`; declared tools are never sent, because + * the CLI does not read the program. + */ +export function holdQuery(options: HoldOptions): Array<[string, string]> { + const query: Array<[string, string]> = []; + if (options.model !== undefined) query.push(["model", options.model]); + if (options.reasoningEffort !== undefined) query.push(["reasoning_effort", options.reasoningEffort]); + if (options.environment !== undefined) query.push(["environment", options.environment]); + if (options.repositoriesBound) query.push(["repositories", "1"]); + return query; +} + /** Sets a compact JSON body with sorted keys (both products serialize identically). */ export function withJsonBody(request: Request, value: Json): Request { return { ...request, body: new TextEncoder().encode(canonicalJson(value)) }; diff --git a/cli/bun/src/core/service/jobs.ts b/cli/bun/src/core/service/jobs.ts index aa431d0..8150011 100644 --- a/cli/bun/src/core/service/jobs.ts +++ b/cli/bun/src/core/service/jobs.ts @@ -31,7 +31,7 @@ import { failure, invocationFailure } from "../errors"; import { humanSafeScalar, quote as quoteText } from "../output"; import { RunnerFailure } from "../types"; import { readSource } from "./fs"; -import { encodeSegment, jsonObject, parseJson, requestFor, type Request } from "./http"; +import { encodeSegment, holdQuery, jsonObject, parseJson, requestFor, type Request } from "./http"; import type { Context } from "./index"; import { didYouMean, type Environment, type Json, type JsonObject } from "./manifest"; import { parseOwnAllowed, parseProgramRef, pinned, resolveToRun, validSlug } from "./program-ref"; @@ -584,9 +584,27 @@ async function show(context: Context): Promise { return result; } -/** The anonymous GET /run/quote hold for the confirmation plan (default environment); the price policy reference stays internal. */ -async function quote(context: Context): Promise { - const body = await getJson(context, 0, "/run/quote"); +/** A spec key's value when it is a nonempty string (a hold option the spec gives). */ +function specString(spec: JsonObject, key: string): string | undefined { + const value = spec[key]; + return typeof value === "string" && value.length > 0 ? value : undefined; +} + +/** + * The anonymous GET /run/quote hold for the confirmation plan, quoted from the + * spec's model, reasoning effort, environment and bound repositories (only + * those the spec gives); the price policy reference stays internal. + */ +async function quote(context: Context, spec: JsonObject): Promise { + const repository = (key: string): boolean => (specString(spec, key) ?? "").length > 0; + const request = requestFor(context.operation, 0, "/run/quote"); + request.query.push(...holdQuery({ + model: specString(spec, "model"), + reasoningEffort: specString(spec, "reasoning_effort"), + environment: specString(spec, "environment"), + repositoriesBound: repository("repository_url") || repository("context_repository_url"), + })); + const body = jsonObject(await context.send(request)); const hold = object(body.hold ?? null, "quote.hold"); const holdUsd = hold.hold_usd; if (typeof holdUsd !== "string" || !/^-?[0-9]+\.[0-9]{2}$/u.test(holdUsd)) throw protocol("quote.hold.hold_usd"); @@ -617,7 +635,7 @@ async function create(context: Context): Promise { const planned = context.planned(1, "/triggers", [], body); // A webhook with no program starts no runs: nothing is held. if (unpaid(context, spec)) planned.effect = "write"; - else if (context.invocation.preview || !context.invocation.yes) planned.quote = await quote(context); + else if (context.invocation.preview || !context.invocation.yes) planned.quote = await quote(context, spec); const gate = context.gate(planned); if (gate.kind === "preview") return gate.result; const request: Request = { ...requestFor(context.operation, 1, "/triggers"), body }; diff --git a/cli/bun/src/core/service/programs.ts b/cli/bun/src/core/service/programs.ts index af651c1..d37088b 100644 --- a/cli/bun/src/core/service/programs.ts +++ b/cli/bun/src/core/service/programs.ts @@ -437,7 +437,7 @@ async function draft(context: Context): Promise { const request: Request = { ...base, headers: [...base.headers, ["Accept", "text/event-stream"]] }; const planned = context.planned(0, "/write", [], request.body); if (context.invocation.preview || !context.invocation.yes) { - // A draft reserves the same flat hold as a run (request 2, advisory). + // A draft reserves a hold as a run does (request 2, advisory; parameter-free). const quote = await context.advisoryQuote(2); if (quote !== undefined) planned.quote = quote; } diff --git a/cli/bun/src/core/service/render.ts b/cli/bun/src/core/service/render.ts index 7d30f1f..0d21b75 100644 --- a/cli/bun/src/core/service/render.ts +++ b/cli/bun/src/core/service/render.ts @@ -238,7 +238,7 @@ function firstRunLine(planned: JsonObject): string | undefined { return `First run: about 1 second after the job is created, then every ${durationText(interval)}`; } -/** The human hold line of a plan with a quote (The hold is flat, not an estimate of this run's price). */ +/** The human hold line of a plan with a quote (the hold is a reservation, not an estimate of this run's price). */ function holdLine(planned: JsonObject): string | undefined { const hold = ((planned.quote as JsonObject | undefined)?.hold as JsonObject | undefined)?.hold_usd; return typeof hold === "string" ? `Hold: $${humanSafeScalar(hold)} (set aside from the wallet while the run is live; not its price)` : undefined; diff --git a/cli/bun/src/core/service/runs.ts b/cli/bun/src/core/service/runs.ts index b4d9637..0255492 100644 --- a/cli/bun/src/core/service/runs.ts +++ b/cli/bun/src/core/service/runs.ts @@ -18,7 +18,7 @@ import { failure, hostedRunFailed, invocationFailure } from "../errors"; import { humanSafeMultiline, humanSafeScalar, quote as quoteText } from "../output"; import { RunnerFailure } from "../types"; import { readText, validRelativePath } from "./fs"; -import { encodeSegment, jsonObject, parseJson, requestFor, type Request, type Response } from "./http"; +import { encodeSegment, holdQuery, jsonObject, parseJson, requestFor, type Request, type Response } from "./http"; import type { Context } from "./index"; import { validSession, type JournalEntry } from "./journal"; import { didYouMean, manifest, type Environment, type Json, type JsonObject } from "./manifest"; @@ -255,8 +255,15 @@ async function quote(context: Context): Promise { if (fallback === undefined || !validText(fallback, 64)) throw protocol("service status environments.default is missing or malformed"); environment = fallback; } + // The hold depends on these options; only those given are sent. The + // program, inputs and runtime are accepted but never sent. const request = requestFor(context.operation, 1, "/run/quote"); - if (requested !== undefined) request.query.push(["environment", requested]); + request.query.push(...holdQuery({ + model: context.option("--model"), + reasoningEffort: context.option("--reasoning-effort"), + environment: requested, + repositoriesBound: context.optionValues("--repo").length > 0 || context.option("--commit-output") !== undefined, + })); const body = jsonObject(await context.send(request)); const { hold } = quoteFields(body); let note = ""; @@ -265,15 +272,20 @@ async function quote(context: Context): Promise { note = cleanLine(body.note, 512); } let text = `Environment: ${humanSafeScalar(environment)}\n`; - text += `Hold: $${humanSafeScalar(String(hold.hold_usd))}, set aside from the wallet while a run is live; not its price. The same for every program and model; released within ${String(hold.ttl_seconds)} s when unused\n`; + text += `Hold: $${humanSafeScalar(String(hold.hold_usd))}, set aside from the wallet while a run is live; not its price. Released within ${String(hold.ttl_seconds)} s when unused\n`; + text += `Depends on: ${HOLD_DEPENDS_ON}; ${HOLD_COVERAGE}\n`; text += `Price: known only after a run settles; read it with \`${context.command("run show RUN_ID")}\`\n`; if (note.length > 0) text += `Note: ${humanSafeScalar(note)}\n`; context.human = text; return { environment, hold, holdBasis: HOLD_BASIS, note }; } -/** `run quote` holdBasis: the hold is not a price estimate. */ -const HOLD_BASIS = "flat hold, independent of program and model; a run's price is known only after it settles"; +/** What the hold depends on (the service prices the hold from these). */ +const HOLD_DEPENDS_ON = "model, reasoning effort, environment, declared tools and repositories"; +/** What a CLI quote covers: it never reads the program. */ +const HOLD_COVERAGE = "quoted from the options given, without the program's own run settings or declared tools"; +/** `run quote` holdBasis: what the hold depends on; it is not a price estimate. */ +const HOLD_BASIS = `depends on ${HOLD_DEPENDS_ON}; ${HOLD_COVERAGE}; a run's price is known only after it settles`; // --------------------------------------------------------------------------- // Event projection (decision 5) and terminal mapping (decision 9). @@ -876,7 +888,8 @@ interface Submission { sourceSha256: string | null; session: string | undefined; waitMs: number; - environment: string | undefined; + /** The `GET /run/quote` query of the plan: the hold options given. */ + quoteQuery: Array<[string, string]>; } async function prepareSubmission(context: Context): Promise { @@ -955,7 +968,8 @@ async function prepareSubmission(context: Context): Promise { if (bytes.length > MAX_BODY_BYTES) { throw invocationFailure(`the submission is ${bytes.length} bytes, above the ${MAX_BODY_BYTES}-byte limit; shrink the program or inputs`); } - return { body: bytes, extraQuery, sourceSha256, session, waitMs, environment }; + const quoteQuery = holdQuery({ model, reasoningEffort: effort, environment, repositoriesBound: repositories.length > 0 || commit !== undefined }); + return { body: bytes, extraQuery, sourceSha256, session, waitMs, quoteQuery }; } /** @@ -1048,7 +1062,7 @@ async function submit(context: Context): Promise { const query = submitQuery(submission.session ?? "{session}", submission.extraQuery); const planned = context.planned(2, "/run", query, submission.body); const quoteRequest: Request = { ...requestFor(context.operation, 1, "/run/quote"), class: "control" }; - if (submission.environment !== undefined) quoteRequest.query.push(["environment", submission.environment]); + quoteRequest.query.push(...submission.quoteQuery); // The quote is advisory: a failed quote never hides the plan. try { const { hold } = quoteFields(jsonObject(await context.send(quoteRequest))); diff --git a/cli/conformance/cases/service/framework/help-json-group-records.json b/cli/conformance/cases/service/framework/help-json-group-records.json index 359b03b..6c15ba6 100644 --- a/cli/conformance/cases/service/framework/help-json-group-records.json +++ b/cli/conformance/cases/service/framework/help-json-group-records.json @@ -30,7 +30,7 @@ { "arguments": [ { - "description": "Program file, or - for standard input, as `cli run submit` takes it. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Program file, or - for standard input, as `cli run submit` takes it. Accepted so a quote names the same command as `cli run submit`; the quote does not read the program, so the program's own run settings and declared tools are not included.", "name": "FILE", "required": false, "variadic": false @@ -74,7 +74,7 @@ "mutation": false, "options": [ { - "description": "Run a saved program; a bare SLUG is your own program. @REV is a rev_id, or @N (@revN) for revision N of your own program; without @REV the latest revision is pinned. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Run a saved program; a bare SLUG is your own program. @REV is a rev_id, or @N (@revN) for revision N of your own program; without @REV the latest revision is pinned. Accepted so a quote names the same command as `cli run submit`; the quote does not read the program, so the program's own run settings and declared tools are not included.", "name": "--from", "repeatable": false, "required": false, @@ -96,7 +96,7 @@ }, { "default": "the service's default_model (`cli model list`)", - "description": "Hosted model id (see `cli model list`). Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Hosted model id (see `cli model list`). Sent with the quote: the hold depends on it.", "name": "--model", "repeatable": false, "required": false, @@ -110,7 +110,7 @@ "high", "xhigh" ], - "description": "Reasoning effort supported by the model. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Reasoning effort supported by the model. Sent with the quote: the hold depends on it.", "name": "--reasoning-effort", "repeatable": false, "required": false, @@ -118,7 +118,7 @@ }, { "default": "the service's default environment", - "description": "Execution environment offered by the service (for example builtin or linux).", + "description": "Execution environment offered by the service (for example builtin or linux). Sent with the quote: the hold depends on it.", "name": "--environment", "repeatable": false, "required": false, @@ -133,14 +133,14 @@ "value": "RUNTIME" }, { - "description": "Read-only repository context. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Read-only repository context. Any --repo or --commit-output sends repositories=1 with the quote: a bound repository adds to the hold.", "name": "--repo", "repeatable": true, "required": false, "value": "OWNER/NAME[@BRANCH]" }, { - "description": "Writable repository that receives the run's commit. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Writable repository that receives the run's commit. Any --repo or --commit-output sends repositories=1 with the quote: a bound repository adds to the hold.", "name": "--commit-output", "repeatable": false, "required": false, @@ -153,7 +153,7 @@ "stream": false }, "preview": false, - "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold is flat, the same for every program and model, and what the run does not use is released when it settles. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and model `cli run submit` takes are accepted, so you can quote the exact command you will submit." + "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold depends on the model, reasoning effort, environment, declared tools and bound repositories, and what the run does not use is released when it settles. The quote sends only the --model, --reasoning-effort, --environment, --repo and --commit-output you give. It does not read the program, so a program that sets its own model, effort or environment, or declares tools, can hold a different amount. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and options `cli run submit` takes are accepted, so you can quote the exact command you will submit." }, { "arguments": [ diff --git a/cli/conformance/cases/service/framework/service-capabilities-json.json b/cli/conformance/cases/service/framework/service-capabilities-json.json index 732ddff..bd4e2d3 100644 --- a/cli/conformance/cases/service/framework/service-capabilities-json.json +++ b/cli/conformance/cases/service/framework/service-capabilities-json.json @@ -203,7 +203,7 @@ "--json" ], "schema": "openprose.service-operations/1", - "sha256": "40813b94d3ec38153a3b012ec1eb187681e9791692f65490cff211b0c0b73f2c" + "sha256": "c3aef4f39d4fa26f6af3334e311806c5aedca0cb9934c47c325678467454edd5" }, "nouns": { "auth": [ @@ -442,7 +442,7 @@ "prose cli run quote hello.prose.md --input topic=cats --json" ], "id": "run.quote", - "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold is flat, the same for every program and model, and what the run does not use is released when it settles. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and model `cli run submit` takes are accepted, so you can quote the exact command you will submit." + "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold depends on the model, reasoning effort, environment, declared tools and bound repositories, and what the run does not use is released when it settles. The quote sends only the --model, --reasoning-effort, --environment, --repo and --commit-output you give. It does not read the program, so a program that sets its own model, effort or environment, or declares tools, can hold a different amount. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and options `cli run submit` takes are accepted, so you can quote the exact command you will submit." }, { "command": [ @@ -762,7 +762,7 @@ "prose cli job create --spec-file job.json --yes" ], "id": "job.create", - "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id." + "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live; with program_ref also model, reasoning_effort, repository_url, repository_branch, output. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id." }, { "command": [ diff --git a/cli/conformance/cases/service/framework/service-capabilities-jsonl.json b/cli/conformance/cases/service/framework/service-capabilities-jsonl.json index 73c8dfe..e2b1e20 100644 --- a/cli/conformance/cases/service/framework/service-capabilities-jsonl.json +++ b/cli/conformance/cases/service/framework/service-capabilities-jsonl.json @@ -209,7 +209,7 @@ "--json" ], "schema": "openprose.service-operations/1", - "sha256": "40813b94d3ec38153a3b012ec1eb187681e9791692f65490cff211b0c0b73f2c" + "sha256": "c3aef4f39d4fa26f6af3334e311806c5aedca0cb9934c47c325678467454edd5" }, "nouns": { "auth": [ @@ -448,7 +448,7 @@ "prose cli run quote hello.prose.md --input topic=cats --json" ], "id": "run.quote", - "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold is flat, the same for every program and model, and what the run does not use is released when it settles. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and model `cli run submit` takes are accepted, so you can quote the exact command you will submit." + "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold depends on the model, reasoning effort, environment, declared tools and bound repositories, and what the run does not use is released when it settles. The quote sends only the --model, --reasoning-effort, --environment, --repo and --commit-output you give. It does not read the program, so a program that sets its own model, effort or environment, or declares tools, can hold a different amount. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and options `cli run submit` takes are accepted, so you can quote the exact command you will submit." }, { "command": [ @@ -768,7 +768,7 @@ "prose cli job create --spec-file job.json --yes" ], "id": "job.create", - "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id." + "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live; with program_ref also model, reasoning_effort, repository_url, repository_branch, output. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id." }, { "command": [ diff --git a/cli/conformance/cases/service/framework/service-guide-human.json b/cli/conformance/cases/service/framework/service-guide-human.json index b01ee3c..eca9c2f 100644 --- a/cli/conformance/cases/service/framework/service-guide-human.json +++ b/cli/conformance/cases/service/framework/service-guide-human.json @@ -13,7 +13,7 @@ }, "exitCode": 0, "stdout": { - "text": "# OpenProse service guide\n\n## Start here\n\n`prose cli` commands reach the hosted OpenProse service. They never\nprompt and never run anything locally. This guide is the workflow view;\nthe per-command facts come from these commands:\n\n- `prose cli service triage --json`: where this session stands, in one call:\n health, whether the key works (or the variable to set), balance, default\n organization, recent runs, jobs and `nextCommands`. Each section has its\n own `problem`; it exits 0 whenever it produces the report.\n- `prose cli service capabilities --json`: grammar, exit code of every error\n code, environment variables and every command with examples, on one line.\n- `prose cli service operations --json`: every command with its arguments,\n options and effect, as JSON.\n- `prose cli --help`: one command, ending with its exit codes.\n- `prose cli service guide --json`: this guide as\n `{\"sections\":[{\"id\",\"title\",\"body\"}]}` in the usual envelope.\n\nProgram files (`.prose.md`) are not described here: read a working one with\n`prose cli example list` and `prose cli example show NAME`. New to the\nservice? Start with \"Your first program\" below, then \"Run it every day\",\n\"Scripting\", \"What will it cost\", \"Headless and CI keys\" and \"Sharing\".\n\n## Your first program\n\nStart from a working example, run it once, then save it.\n\n```sh\nprose cli example list\nprose cli example show NAME --output-file hello.prose.md\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\nprose cli program save hello hello.prose.md\n```\n\n1. `cli example list` prints the example names. `cli example show` with\n `--output-file` writes one to a new file; edit it with any editor.\n2. `--preview` shows what the run would do and what it would reserve, and\n sends nothing. `--yes` starts the paid run and streams its answer.\n3. `cli program save SLUG FILE` keeps it as your own private program. Run\n the saved program with `prose cli run submit --from hello --yes`.\n\nTools a run can use depend on its environment (`--environment`): `builtin`\nhas file tools only; `linux` adds a shell, without network access. A\nprogram reaches the web through the `browser:interact` tool.\n\n## Run it every day\n\nA job runs a saved program on its own. A schedule job repeats every\n`interval_seconds` (60 to 2678400; 86400 is once a day). The first run\nstarts about one second after the job is created. There is no cron\nexpression or time of day yet.\n\n```sh\nprose cli program save hello hello.prose.md --json\nprose cli program show hello --json\nprintf '%s' '{\"type\":\"schedule\",\"program_ref\":\"OWNER/hello@REV\",\"interval_seconds\":86400}' > daily.json\nprose cli job create --spec-file daily.json --preview\nprose cli job create --spec-file daily.json --yes --json\nprose cli job list\nprose cli job delete JOB_ID --yes\n```\n\n`program_ref` is the pinned reference `result.program.ref` that\n`program save` and `program show` print (`OWNER/SLUG@REV`). Each run is paid\nlike any other. `cli job list` shows the next run time; `cli job delete`\nstops the schedule for good.\n\nTo run at a fixed time of day instead, let the operating system's scheduler\n(cron, launchd) run one command with `OPENPROSE_API_KEY` set:\n\n```sh\nprose cli run submit --from OWNER/hello --yes --json | jq .result.run.response\n```\n\n## Scripting\n\n- Pass `--json` and read `result`; on an error, `problem.code` and\n `problem.details`. Every exit code is in the table in \"Exit codes, resume\n and detach\"; a script branches on it with `case $?`.\n- The run id is `result.runId` (`result.run.run_id` once the run ends).\n- For a long run, submit with `--detach` (exit 0 as soon as the run id is\n known), then follow it with `prose cli run watch RUN_ID --wait 10m --json`.\n If `--wait` passes first, the exit is 21 and `problem.details.resumeArgv`\n is the command to run next. Never submit again to resume.\n- `cli run list` and `cli wallet events` return one page. Pass\n `result.nextBefore` to `--before` for the next one; it is `null` on the\n last page.\n\n```sh\nprose cli run submit --from OWNER/hello --yes --detach --json > run.json\nprose cli run watch RUN_ID --wait 10m --json\nprose cli run list --limit 50 --before CURSOR --json\n```\n\n## What will it cost\n\nA run reserves a hold before it starts: money set aside from the wallet\n(currently $1.02), not the price. The price is known only after the run\nsettles: `price_cents` in `prose cli run show RUN_ID --json`, of which\n`environment_price_cents` is the environment's share. What the run did not\nuse is released. Short runs usually cost a few cents.\n\nEstimate from your own history: `prose cli wallet usage` prints runs and\nprices by day, and `prose cli wallet balance` shows what is available and\nwhat is reserved right now. `prose cli run quote --json` reports the hold.\n\nPremium models unlock with any wallet top-up; `prose cli model list` shows\neach model's status.\n\n## Headless and CI keys\n\nA machine without a person at a browser uses an API key in\n`OPENPROSE_API_KEY`; it wins over the stored key. Keep it in the CI system's\nsecret store and set it only for the commands that need it.\n`prose cli auth login` stores its key in the operating system credential\nstore, which a launchd or cron job cannot reach without a login session:\nset `OPENPROSE_API_KEY` in the job instead. Check a key with\n`prose cli auth status --json`.\n\n## Sharing\n\n`prose cli run share RUN_ID --yes` creates a public link to a run's outputs.\nAnyone with the link can open it for 24 hours, and it cannot be revoked. To\ngive specific people access, invite them to your organization instead:\n`prose cli org invite --help`.\n\n## Grammar\n\n```text\nprose [--output human|json|jsonl] cli [ARGUMENTS] [OPTIONS]\n```\n\n- Every service command starts with `cli`. Without it (`run submit` or\n `wallet balance` right after `prose`) the command exits 2 and\n `details.suggestedArgv` names the `cli` command.\n- Global options go before `cli`; command options go after the command path.\n `--output` may also follow the command path.\n A command option before `cli` (`--json`, `--yes`, `--limit 5`) exits 2 and\n `details.suggestedArgv` moves it after the command path.\n- `--json` is `--output json`: one JSON object on one line. Streaming\n commands (`cli run submit`, `cli run watch`) print one event per line with\n `--output jsonl` and end with exactly one `service.completed`,\n `service.failed` or `service.detached` line (`service.detached` is exit 21:\n the run continues). List commands (`cli run list`, `cli job list`,\n `cli model list`, ...) print one `openprose.service-record/1` line per item\n with `--output jsonl`, then one `openprose.service-page/1` line with\n `count`, `nextBefore` and the rest of the result in `meta`. That last line\n carries `exitCode`, the process exit code. Every JSON line has its keys\n sorted, so the same response prints the same bytes.\n- Times the service sends as epoch milliseconds (`createdAt`, `nextFireAt`,\n `updated_at`, ...) also appear as `_iso` (RFC 3339 UTC). Human output\n shows money in dollars and ends with `Next:` commands you can copy.\n- Nothing is guessed. A misspelled or foreign word exits 2\n (`INVOCATION_INVALID`); `details.suggestedArgv` is the corrected argv (the\n words after `prose`). Rerun it; it keeps your output mode. A few exact\n spellings are aliases and run their command: `cli run status` and\n `cli run get` are `cli run show`, `cli run logs`, `cli run tail` and\n `cli run wait` are `cli run watch`, `cli run create` and `cli run start` are\n `cli run submit`, `cli program push` and `cli program upload` are\n `cli program save`, and `cli program rm` and `cli job rm` are `delete` (still\n with `--yes`). The manifest lists them in `grammar.intentInference.commandAliases`.\n A suggestion is a complete command that parses: a group gains its verb\n (`cli model list`), a dollar amount becomes cents (`--amount-cents 500`),\n and an out-of-range `--limit` becomes the nearest accepted value. When the\n fix reads standard input, `details.suggestedStdin` holds what to pipe in;\n a secret (a credit code, an invitation token) is never echoed, so pipe\n your own.\n\n## Credentials\n\nThe key, first match wins:\n\n1. `OPENPROSE_API_KEY`, when set and non-empty;\n2. the key `prose cli auth login` stored in the operating-system credential\n store (login needs a person at a browser).\n\nA variable always wins over the stored key, so a bad variable must be fixed\nor unset. `prose cli auth status --json` reports `credentialSource`.\n`SERVICE_AUTH_REQUIRED` names `details.credentialVariable` and\n`details.credentialProblem` (`missing`, `malformed` or `rejected`), and its\nAction follows where the key came from: an environment key is replaced or\nunset, never fixed by `cli auth login`. Nothing ever falls back to another\nkey. The output mode is `--json` or `--output`, then `PROSE_OUTPUT`, then\nhuman.\n\n## Confirm and preview\n\nA command that spends money, publishes, deletes or cannot be undone needs\n`--yes`. Without it nothing is changed: it exits 2 with `CONFIRMATION_REQUIRED`,\n`details.reason` (what `--yes` would do, for example that rotating a secret\ninvalidates the old one now), `details.plannedRequest` (the method, what the\nrequest does as `description`, its non-secret inputs as `summary`: model, programRef, inputKeys,\namount_cents, slug; and for `cli run submit`, `cli program draft` and a paid\n`cli job create` the service's flat hold quote), `details.confirmArgv` and\n`details.previewArgv`. `--preview` prints the same plan, exits 0 and changes\nnothing. An unknown `--model`, an `--environment` or `--runtime` that\n`prose cli service status --json` does not list (`environments`), an input the\nprogram's `Parameters` section does not declare (or a declared one that is\nmissing and not marked optional), an invalid organization slug or a job spec\nthe service would reject fails here, before any confirmation, with every\nproblem listed:\n\n```sh\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\n```\n\n`prose cli service operations` shows which commands need `--yes` (CONFIRM).\n\n## Exit codes, resume and detach\n\n| Exit | Meaning | What a script does |\n| --- | --- | --- |\n| 0 | success | read `result` |\n| 2 | `INVOCATION_INVALID` or `CONFIRMATION_REQUIRED`; nothing was sent | rerun `details.suggestedArgv`, or add `--yes` on purpose |\n| 10 | service or credential error | read `problem.code` and `details.reason`; retry only when `problem.retryable` is true |\n| 20 | this installation cannot run the command | reinstall the CLI; retrying will not help |\n| 21 | detached: the run continues | run `details.resumeArgv`; never submit again |\n| 22 | the run failed, or the submission is ambiguous | read `details.reason`; if ambiguous, rerun `details.resumeArgv` (it reuses the same session, never a new run) |\n| 24 | cancelled | stop; nothing to resume |\n\nIn JSON mode the error is `problem` (`code`, `retryable`, `action`,\n`details`), with `result` null. This holds for every `cli` command line,\nincluding a near miss or a misplaced flag: then `operation` is `cli` if the\nargv names no operation. Only the language and the commands for this machine\n(listed by the top-level help) print a bare runner error. The exit of every\ncode is in\n`prose cli service capabilities --json` (`exitCodes`).\n\nExit 21 means the CLI stopped following a run that keeps running:\n`--wait` passed (`SERVICE_WATCH_DEADLINE`) or Ctrl-C or a lost stream\n(`HOSTED_RUN_DETACHED`); both are not retryable and carry\n`details.resumable: true`. A `--wait` that passes before the run id arrives\nkeeps reading until the id is known, so it is still exit 21 with the run\nnamed. A successful `--detach` exits 0 with `result.detached`,\n`result.resumeArgv`, `result.cancelArgv` and `result.run` as\n`{run_id, status}`. Results carry the run id as `result.run_id` and\n`result.runId`.\nResume with `resumeArgv` (a `cli run watch` command with `--session`). Never resume by\nsubmitting again: a new submit is a second paid run. Only\n`prose cli run cancel RUN_ID --yes` cancels; on a run that already ended it\nexits 0 with `result.status` `already_ended`. Steering an ended run with\n`cli run input` is not retryable: read it with `details.suggestedArgv`.\n\n`SERVICE_RESOURCE_NOT_FOUND` (10) is not retryable. `details.resource`\nnames the kind and id, and `details.suggestedArgv` lists the ones that\nexist: run it and retry with an id it prints. A well-formed run id the\nservice does not know (`cli run show`, `cli run watch`) and a `--file` the\nrun does not list are this error. A run id with fewer than 64 hex digits was\ncut off when copied: it exits 2 before any request. `latest` in place of a\nrun id is your newest run (`cli run show latest`). `program delete` and\n`job delete` with `--yes` of something already gone exit 0 with\n`result.alreadyAbsent`.\n\n`RUN_SUBMISSION_AMBIGUOUS` (22) means the connection was lost before the\nrun id was known, and the CLI did not repeat the submission. Rerun\n`details.resumeArgv`: the same `cli run submit` with the same `--session`\nand `--detach`. A session never starts a second run: `run submit --session S`\nof a session that already has a run (in this machine's journal, or as the\nservice reports) returns that run with `result.reused: true` and exit 0.\n`prose cli run list --limit 5 --json` (`details.suggestedArgv`) lists recent\nruns.\n\nA run its owner stopped has `cancelled: true` in `cli run show` and\n`cli run list`, and `cli run watch` of it exits 24.\n\n## Paging\n\n`cli run list` and `cli wallet events` are paged. Pass\n`result.nextBefore` to `--before` for the next page; it is `null` on the last\npage.\nThe cursor is opaque. Human mode prints a `Next page:` line with the full\ncommand, and `--output jsonl` carries `nextBefore` in the page line.\n\n```sh\nprose cli run list --limit 50 --json\nprose cli run list --limit 50 --before CURSOR --json\n```\n\n`cli result list` takes `--limit` only (at most 100) and has no cursor.\n\n## Get a run's answer\n\n- While it runs: human mode prints the program's text on stdout. With\n `--json`, the answer is `result.run.response`; `result.run.files` lists the\n files it wrote.\n- After a detach (runs submitted from this machine): replay the whole stream\n with `prose cli run watch RUN_ID --json` and read `result.run.response`.\n For a run started elsewhere, the same command reports an ended run's\n status and files from its record (`result.source` is `record`, no\n response text).\n- Any run, from anywhere: `prose cli run show RUN_ID --json` gives status,\n files and price but never the response text. `result.run.response` is the\n run's text as written, trailing whitespace included. Read a file with\n `prose cli run show RUN_ID --file outputs/result.json`, or fetch them all\n into `./RUN_ID` with `prose cli run download RUN_ID` (or `--output-dir DIR`).\n- A result someone published (`OWNER/SLUG`) is not a run record:\n `prose cli result show OWNER/SLUG --latest --json`.\n\n## Recipes\n\n### Submit and wait\n\n```sh\nprose cli run quote --json\nprose cli run submit hello.prose.md --input topic=otters --preview\nprose cli run submit hello.prose.md --input topic=otters --yes --json > run.json\njq -r '.result.run.response' run.json\n```\n\n`cli run quote` is the wallet hold a run reserves, not its price; the settled\nprice is `result.run.price_cents`. A saved program runs with\n`--from SLUG` (your own) or `--from OWNER/SLUG@REV`; every program record\n(`program list|show|save|revisions --json`) carries that pinned reference as\n`ref`, and `prose cli program show SLUG@1 --json` resolves a revision number\nof your own program to it. On exit 21, rerun\n`problem.details.resumeArgv`; for a long run submit with `--detach` and follow\nwith `prose cli run watch RUN_ID --wait 10m --json`.\n\n### Publish a result\n\nA published result is a completed run of a saved revision of a public\nprogram that wrote `outputs/result.json`.\n\n```sh\nprose cli program save haiku haiku.prose.md --json\nprose cli run submit --from haiku --yes --json\nprose cli program visibility haiku public --preview\nprose cli result publish haiku --run RUN_ID --yes --json\nprose cli result show haiku --json\n```\n\nMaking a program public exposes its source; the CLI never adds `--yes` to that\nstep for you. Reading published results needs no key; a bare `SLUG` (your\nown program) is resolved with your key first, and without a publication id\n`result show` reads the newest.\n\n### Create a webhook job\n\nA job starts runs on its own. The spec is snake_case JSON; a test-mode webhook\nreceives events and never starts runs.\n\n```sh\nprintf '%s' '{\"type\":\"webhook\",\"name\":\"my-hook\",\"delivery_mode\":\"test\"}' > hook.json\nprose cli job create --spec-file hook.json --preview\nprose cli job create --spec-file hook.json --yes --json\nprose cli job deliveries JOB_ID --json\n```\n\n`result.endpoint` and `result.signing_secret` appear only in the create result\n(and in `cli job rotate-secret`); store them then. `result.endpointUrl` is the\nabsolute URL to configure in the sender; `cli job show` repeats it. `prose cli job create --help`\nlists the schedule and webhook spec keys; `prose cli job list --json` lists the\njob types. The keys each type accepts are the `spec` of `job.create` in\n`prose cli service operations --json`; a spec is checked against it before any\nrequest and every problem is reported at once in `details.violations`. A\nwebhook with no `program_ref` starts no runs, so its plan has effect `write`\nand no hold.\n" + "text": "# OpenProse service guide\n\n## Start here\n\n`prose cli` commands reach the hosted OpenProse service. They never\nprompt and never run anything locally. This guide is the workflow view;\nthe per-command facts come from these commands:\n\n- `prose cli service triage --json`: where this session stands, in one call:\n health, whether the key works (or the variable to set), balance, default\n organization, recent runs, jobs and `nextCommands`. Each section has its\n own `problem`; it exits 0 whenever it produces the report.\n- `prose cli service capabilities --json`: grammar, exit code of every error\n code, environment variables and every command with examples, on one line.\n- `prose cli service operations --json`: every command with its arguments,\n options and effect, as JSON.\n- `prose cli --help`: one command, ending with its exit codes.\n- `prose cli service guide --json`: this guide as\n `{\"sections\":[{\"id\",\"title\",\"body\"}]}` in the usual envelope.\n\nProgram files (`.prose.md`) are not described here: read a working one with\n`prose cli example list` and `prose cli example show NAME`. New to the\nservice? Start with \"Your first program\" below, then \"Run it every day\",\n\"Scripting\", \"What will it cost\", \"Headless and CI keys\" and \"Sharing\".\n\n## Your first program\n\nStart from a working example, run it once, then save it.\n\n```sh\nprose cli example list\nprose cli example show NAME --output-file hello.prose.md\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\nprose cli program save hello hello.prose.md\n```\n\n1. `cli example list` prints the example names. `cli example show` with\n `--output-file` writes one to a new file; edit it with any editor.\n2. `--preview` shows what the run would do and what it would reserve, and\n sends nothing. `--yes` starts the paid run and streams its answer.\n3. `cli program save SLUG FILE` keeps it as your own private program. Run\n the saved program with `prose cli run submit --from hello --yes`.\n\nTools a run can use depend on its environment (`--environment`): `builtin`\nhas file tools only; `linux` adds a shell, without network access. A\nprogram reaches the web through the `browser:interact` tool.\n\n## Run it every day\n\nA job runs a saved program on its own. A schedule job repeats every\n`interval_seconds` (60 to 2678400; 86400 is once a day). The first run\nstarts about one second after the job is created. There is no cron\nexpression or time of day yet.\n\n```sh\nprose cli program save hello hello.prose.md --json\nprose cli program show hello --json\nprintf '%s' '{\"type\":\"schedule\",\"program_ref\":\"OWNER/hello@REV\",\"interval_seconds\":86400}' > daily.json\nprose cli job create --spec-file daily.json --preview\nprose cli job create --spec-file daily.json --yes --json\nprose cli job list\nprose cli job delete JOB_ID --yes\n```\n\n`program_ref` is the pinned reference `result.program.ref` that\n`program save` and `program show` print (`OWNER/SLUG@REV`). Each run is paid\nlike any other. `cli job list` shows the next run time; `cli job delete`\nstops the schedule for good.\n\nTo run at a fixed time of day instead, let the operating system's scheduler\n(cron, launchd) run one command with `OPENPROSE_API_KEY` set:\n\n```sh\nprose cli run submit --from OWNER/hello --yes --json | jq .result.run.response\n```\n\n## Scripting\n\n- Pass `--json` and read `result`; on an error, `problem.code` and\n `problem.details`. Every exit code is in the table in \"Exit codes, resume\n and detach\"; a script branches on it with `case $?`.\n- The run id is `result.runId` (`result.run.run_id` once the run ends).\n- For a long run, submit with `--detach` (exit 0 as soon as the run id is\n known), then follow it with `prose cli run watch RUN_ID --wait 10m --json`.\n If `--wait` passes first, the exit is 21 and `problem.details.resumeArgv`\n is the command to run next. Never submit again to resume.\n- `cli run list` and `cli wallet events` return one page. Pass\n `result.nextBefore` to `--before` for the next one; it is `null` on the\n last page.\n\n```sh\nprose cli run submit --from OWNER/hello --yes --detach --json > run.json\nprose cli run watch RUN_ID --wait 10m --json\nprose cli run list --limit 50 --before CURSOR --json\n```\n\n## What will it cost\n\nA run reserves a hold before it starts: money set aside from the wallet,\nnot the price. The hold depends on the model, reasoning effort, environment,\ndeclared tools and bound repositories. The price is known only after the run\nsettles: `price_cents` in `prose cli run show RUN_ID --json`, of which\n`environment_price_cents` is the environment's share. What the run did not\nuse is released. Short runs usually cost a few cents.\n\nEstimate from your own history: `prose cli wallet usage` prints runs and\nprices by day, and `prose cli wallet balance` shows what is available and\nwhat is reserved right now. `prose cli run quote --json` reports the hold for\nthe `--model`, `--reasoning-effort`, `--environment`, `--repo` and\n`--commit-output` you give it. It does not read the program, so a program\nthat sets its own model, effort or environment, or declares tools, can hold a\ndifferent amount.\n\nPremium models unlock with any wallet top-up; `prose cli model list` shows\neach model's status.\n\n## Headless and CI keys\n\nA machine without a person at a browser uses an API key in\n`OPENPROSE_API_KEY`; it wins over the stored key. Keep it in the CI system's\nsecret store and set it only for the commands that need it.\n`prose cli auth login` stores its key in the operating system credential\nstore, which a launchd or cron job cannot reach without a login session:\nset `OPENPROSE_API_KEY` in the job instead. Check a key with\n`prose cli auth status --json`.\n\n## Sharing\n\n`prose cli run share RUN_ID --yes` creates a public link to a run's outputs.\nAnyone with the link can open it for 24 hours, and it cannot be revoked. To\ngive specific people access, invite them to your organization instead:\n`prose cli org invite --help`.\n\n## Grammar\n\n```text\nprose [--output human|json|jsonl] cli [ARGUMENTS] [OPTIONS]\n```\n\n- Every service command starts with `cli`. Without it (`run submit` or\n `wallet balance` right after `prose`) the command exits 2 and\n `details.suggestedArgv` names the `cli` command.\n- Global options go before `cli`; command options go after the command path.\n `--output` may also follow the command path.\n A command option before `cli` (`--json`, `--yes`, `--limit 5`) exits 2 and\n `details.suggestedArgv` moves it after the command path.\n- `--json` is `--output json`: one JSON object on one line. Streaming\n commands (`cli run submit`, `cli run watch`) print one event per line with\n `--output jsonl` and end with exactly one `service.completed`,\n `service.failed` or `service.detached` line (`service.detached` is exit 21:\n the run continues). List commands (`cli run list`, `cli job list`,\n `cli model list`, ...) print one `openprose.service-record/1` line per item\n with `--output jsonl`, then one `openprose.service-page/1` line with\n `count`, `nextBefore` and the rest of the result in `meta`. That last line\n carries `exitCode`, the process exit code. Every JSON line has its keys\n sorted, so the same response prints the same bytes.\n- Times the service sends as epoch milliseconds (`createdAt`, `nextFireAt`,\n `updated_at`, ...) also appear as `_iso` (RFC 3339 UTC). Human output\n shows money in dollars and ends with `Next:` commands you can copy.\n- Nothing is guessed. A misspelled or foreign word exits 2\n (`INVOCATION_INVALID`); `details.suggestedArgv` is the corrected argv (the\n words after `prose`). Rerun it; it keeps your output mode. A few exact\n spellings are aliases and run their command: `cli run status` and\n `cli run get` are `cli run show`, `cli run logs`, `cli run tail` and\n `cli run wait` are `cli run watch`, `cli run create` and `cli run start` are\n `cli run submit`, `cli program push` and `cli program upload` are\n `cli program save`, and `cli program rm` and `cli job rm` are `delete` (still\n with `--yes`). The manifest lists them in `grammar.intentInference.commandAliases`.\n A suggestion is a complete command that parses: a group gains its verb\n (`cli model list`), a dollar amount becomes cents (`--amount-cents 500`),\n and an out-of-range `--limit` becomes the nearest accepted value. When the\n fix reads standard input, `details.suggestedStdin` holds what to pipe in;\n a secret (a credit code, an invitation token) is never echoed, so pipe\n your own.\n\n## Credentials\n\nThe key, first match wins:\n\n1. `OPENPROSE_API_KEY`, when set and non-empty;\n2. the key `prose cli auth login` stored in the operating-system credential\n store (login needs a person at a browser).\n\nA variable always wins over the stored key, so a bad variable must be fixed\nor unset. `prose cli auth status --json` reports `credentialSource`.\n`SERVICE_AUTH_REQUIRED` names `details.credentialVariable` and\n`details.credentialProblem` (`missing`, `malformed` or `rejected`), and its\nAction follows where the key came from: an environment key is replaced or\nunset, never fixed by `cli auth login`. Nothing ever falls back to another\nkey. The output mode is `--json` or `--output`, then `PROSE_OUTPUT`, then\nhuman.\n\n## Confirm and preview\n\nA command that spends money, publishes, deletes or cannot be undone needs\n`--yes`. Without it nothing is changed: it exits 2 with `CONFIRMATION_REQUIRED`,\n`details.reason` (what `--yes` would do, for example that rotating a secret\ninvalidates the old one now), `details.plannedRequest` (the method, what the\nrequest does as `description`, its non-secret inputs as `summary`: model, programRef, inputKeys,\namount_cents, slug; and for `cli run submit`, `cli program draft` and a paid\n`cli job create` the service's hold quote for the options or spec given, and\nfor `cli program draft` the parameter-free quote),\n`details.confirmArgv` and\n`details.previewArgv`. `--preview` prints the same plan, exits 0 and changes\nnothing. An unknown `--model`, an `--environment` or `--runtime` that\n`prose cli service status --json` does not list (`environments`), an input the\nprogram's `Parameters` section does not declare (or a declared one that is\nmissing and not marked optional), an invalid organization slug or a job spec\nthe service would reject fails here, before any confirmation, with every\nproblem listed:\n\n```sh\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\n```\n\n`prose cli service operations` shows which commands need `--yes` (CONFIRM).\n\n## Exit codes, resume and detach\n\n| Exit | Meaning | What a script does |\n| --- | --- | --- |\n| 0 | success | read `result` |\n| 2 | `INVOCATION_INVALID` or `CONFIRMATION_REQUIRED`; nothing was sent | rerun `details.suggestedArgv`, or add `--yes` on purpose |\n| 10 | service or credential error | read `problem.code` and `details.reason`; retry only when `problem.retryable` is true |\n| 20 | this installation cannot run the command | reinstall the CLI; retrying will not help |\n| 21 | detached: the run continues | run `details.resumeArgv`; never submit again |\n| 22 | the run failed, or the submission is ambiguous | read `details.reason`; if ambiguous, rerun `details.resumeArgv` (it reuses the same session, never a new run) |\n| 24 | cancelled | stop; nothing to resume |\n\nIn JSON mode the error is `problem` (`code`, `retryable`, `action`,\n`details`), with `result` null. This holds for every `cli` command line,\nincluding a near miss or a misplaced flag: then `operation` is `cli` if the\nargv names no operation. Only the language and the commands for this machine\n(listed by the top-level help) print a bare runner error. The exit of every\ncode is in\n`prose cli service capabilities --json` (`exitCodes`).\n\nExit 21 means the CLI stopped following a run that keeps running:\n`--wait` passed (`SERVICE_WATCH_DEADLINE`) or Ctrl-C or a lost stream\n(`HOSTED_RUN_DETACHED`); both are not retryable and carry\n`details.resumable: true`. A `--wait` that passes before the run id arrives\nkeeps reading until the id is known, so it is still exit 21 with the run\nnamed. A successful `--detach` exits 0 with `result.detached`,\n`result.resumeArgv`, `result.cancelArgv` and `result.run` as\n`{run_id, status}`. Results carry the run id as `result.run_id` and\n`result.runId`.\nResume with `resumeArgv` (a `cli run watch` command with `--session`). Never resume by\nsubmitting again: a new submit is a second paid run. Only\n`prose cli run cancel RUN_ID --yes` cancels; on a run that already ended it\nexits 0 with `result.status` `already_ended`. Steering an ended run with\n`cli run input` is not retryable: read it with `details.suggestedArgv`.\n\n`SERVICE_RESOURCE_NOT_FOUND` (10) is not retryable. `details.resource`\nnames the kind and id, and `details.suggestedArgv` lists the ones that\nexist: run it and retry with an id it prints. A well-formed run id the\nservice does not know (`cli run show`, `cli run watch`) and a `--file` the\nrun does not list are this error. A run id with fewer than 64 hex digits was\ncut off when copied: it exits 2 before any request. `latest` in place of a\nrun id is your newest run (`cli run show latest`). `program delete` and\n`job delete` with `--yes` of something already gone exit 0 with\n`result.alreadyAbsent`.\n\n`RUN_SUBMISSION_AMBIGUOUS` (22) means the connection was lost before the\nrun id was known, and the CLI did not repeat the submission. Rerun\n`details.resumeArgv`: the same `cli run submit` with the same `--session`\nand `--detach`. A session never starts a second run: `run submit --session S`\nof a session that already has a run (in this machine's journal, or as the\nservice reports) returns that run with `result.reused: true` and exit 0.\n`prose cli run list --limit 5 --json` (`details.suggestedArgv`) lists recent\nruns.\n\nA run its owner stopped has `cancelled: true` in `cli run show` and\n`cli run list`, and `cli run watch` of it exits 24.\n\n## Paging\n\n`cli run list` and `cli wallet events` are paged. Pass\n`result.nextBefore` to `--before` for the next page; it is `null` on the last\npage.\nThe cursor is opaque. Human mode prints a `Next page:` line with the full\ncommand, and `--output jsonl` carries `nextBefore` in the page line.\n\n```sh\nprose cli run list --limit 50 --json\nprose cli run list --limit 50 --before CURSOR --json\n```\n\n`cli result list` takes `--limit` only (at most 100) and has no cursor.\n\n## Get a run's answer\n\n- While it runs: human mode prints the program's text on stdout. With\n `--json`, the answer is `result.run.response`; `result.run.files` lists the\n files it wrote.\n- After a detach (runs submitted from this machine): replay the whole stream\n with `prose cli run watch RUN_ID --json` and read `result.run.response`.\n For a run started elsewhere, the same command reports an ended run's\n status and files from its record (`result.source` is `record`, no\n response text).\n- Any run, from anywhere: `prose cli run show RUN_ID --json` gives status,\n files and price but never the response text. `result.run.response` is the\n run's text as written, trailing whitespace included. Read a file with\n `prose cli run show RUN_ID --file outputs/result.json`, or fetch them all\n into `./RUN_ID` with `prose cli run download RUN_ID` (or `--output-dir DIR`).\n- A result someone published (`OWNER/SLUG`) is not a run record:\n `prose cli result show OWNER/SLUG --latest --json`.\n\n## Recipes\n\n### Submit and wait\n\n```sh\nprose cli run quote --json\nprose cli run submit hello.prose.md --input topic=otters --preview\nprose cli run submit hello.prose.md --input topic=otters --yes --json > run.json\njq -r '.result.run.response' run.json\n```\n\n`cli run quote` is the wallet hold a run reserves, not its price; the settled\nprice is `result.run.price_cents`. A saved program runs with\n`--from SLUG` (your own) or `--from OWNER/SLUG@REV`; every program record\n(`program list|show|save|revisions --json`) carries that pinned reference as\n`ref`, and `prose cli program show SLUG@1 --json` resolves a revision number\nof your own program to it. On exit 21, rerun\n`problem.details.resumeArgv`; for a long run submit with `--detach` and follow\nwith `prose cli run watch RUN_ID --wait 10m --json`.\n\n### Publish a result\n\nA published result is a completed run of a saved revision of a public\nprogram that wrote `outputs/result.json`.\n\n```sh\nprose cli program save haiku haiku.prose.md --json\nprose cli run submit --from haiku --yes --json\nprose cli program visibility haiku public --preview\nprose cli result publish haiku --run RUN_ID --yes --json\nprose cli result show haiku --json\n```\n\nMaking a program public exposes its source; the CLI never adds `--yes` to that\nstep for you. Reading published results needs no key; a bare `SLUG` (your\nown program) is resolved with your key first, and without a publication id\n`result show` reads the newest.\n\n### Create a webhook job\n\nA job starts runs on its own. The spec is snake_case JSON; a test-mode webhook\nreceives events and never starts runs.\n\n```sh\nprintf '%s' '{\"type\":\"webhook\",\"name\":\"my-hook\",\"delivery_mode\":\"test\"}' > hook.json\nprose cli job create --spec-file hook.json --preview\nprose cli job create --spec-file hook.json --yes --json\nprose cli job deliveries JOB_ID --json\n```\n\n`result.endpoint` and `result.signing_secret` appear only in the create result\n(and in `cli job rotate-secret`); store them then. `result.endpointUrl` is the\nabsolute URL to configure in the sender; `cli job show` repeats it. `prose cli job create --help`\nlists the schedule and webhook spec keys; `prose cli job list --json` lists the\njob types. The keys each type accepts are the `spec` of `job.create` in\n`prose cli service operations --json`; a spec is checked against it before any\nrequest and every problem is reported at once in `details.violations`. A\nwebhook with no `program_ref` starts no runs, so its plan has effect `write`\nand no hold.\n" }, "stderr": { "text": "" diff --git a/cli/conformance/cases/service/framework/service-guide-json.json b/cli/conformance/cases/service/framework/service-guide-json.json index 9b9e15a..298e52d 100644 --- a/cli/conformance/cases/service/framework/service-guide-json.json +++ b/cli/conformance/cases/service/framework/service-guide-json.json @@ -43,7 +43,7 @@ { "id": "what-will-it-cost", "title": "What will it cost", - "body": "A run reserves a hold before it starts: money set aside from the wallet\n(currently $1.02), not the price. The price is known only after the run\nsettles: `price_cents` in `prose cli run show RUN_ID --json`, of which\n`environment_price_cents` is the environment's share. What the run did not\nuse is released. Short runs usually cost a few cents.\n\nEstimate from your own history: `prose cli wallet usage` prints runs and\nprices by day, and `prose cli wallet balance` shows what is available and\nwhat is reserved right now. `prose cli run quote --json` reports the hold.\n\nPremium models unlock with any wallet top-up; `prose cli model list` shows\neach model's status." + "body": "A run reserves a hold before it starts: money set aside from the wallet,\nnot the price. The hold depends on the model, reasoning effort, environment,\ndeclared tools and bound repositories. The price is known only after the run\nsettles: `price_cents` in `prose cli run show RUN_ID --json`, of which\n`environment_price_cents` is the environment's share. What the run did not\nuse is released. Short runs usually cost a few cents.\n\nEstimate from your own history: `prose cli wallet usage` prints runs and\nprices by day, and `prose cli wallet balance` shows what is available and\nwhat is reserved right now. `prose cli run quote --json` reports the hold for\nthe `--model`, `--reasoning-effort`, `--environment`, `--repo` and\n`--commit-output` you give it. It does not read the program, so a program\nthat sets its own model, effort or environment, or declares tools, can hold a\ndifferent amount.\n\nPremium models unlock with any wallet top-up; `prose cli model list` shows\neach model's status." }, { "id": "headless-and-ci-keys", @@ -68,7 +68,7 @@ { "id": "confirm-and-preview", "title": "Confirm and preview", - "body": "A command that spends money, publishes, deletes or cannot be undone needs\n`--yes`. Without it nothing is changed: it exits 2 with `CONFIRMATION_REQUIRED`,\n`details.reason` (what `--yes` would do, for example that rotating a secret\ninvalidates the old one now), `details.plannedRequest` (the method, what the\nrequest does as `description`, its non-secret inputs as `summary`: model, programRef, inputKeys,\namount_cents, slug; and for `cli run submit`, `cli program draft` and a paid\n`cli job create` the service's flat hold quote), `details.confirmArgv` and\n`details.previewArgv`. `--preview` prints the same plan, exits 0 and changes\nnothing. An unknown `--model`, an `--environment` or `--runtime` that\n`prose cli service status --json` does not list (`environments`), an input the\nprogram's `Parameters` section does not declare (or a declared one that is\nmissing and not marked optional), an invalid organization slug or a job spec\nthe service would reject fails here, before any confirmation, with every\nproblem listed:\n\n```sh\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\n```\n\n`prose cli service operations` shows which commands need `--yes` (CONFIRM)." + "body": "A command that spends money, publishes, deletes or cannot be undone needs\n`--yes`. Without it nothing is changed: it exits 2 with `CONFIRMATION_REQUIRED`,\n`details.reason` (what `--yes` would do, for example that rotating a secret\ninvalidates the old one now), `details.plannedRequest` (the method, what the\nrequest does as `description`, its non-secret inputs as `summary`: model, programRef, inputKeys,\namount_cents, slug; and for `cli run submit`, `cli program draft` and a paid\n`cli job create` the service's hold quote for the options or spec given, and\nfor `cli program draft` the parameter-free quote),\n`details.confirmArgv` and\n`details.previewArgv`. `--preview` prints the same plan, exits 0 and changes\nnothing. An unknown `--model`, an `--environment` or `--runtime` that\n`prose cli service status --json` does not list (`environments`), an input the\nprogram's `Parameters` section does not declare (or a declared one that is\nmissing and not marked optional), an invalid organization slug or a job spec\nthe service would reject fails here, before any confirmation, with every\nproblem listed:\n\n```sh\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\n```\n\n`prose cli service operations` shows which commands need `--yes` (CONFIRM)." }, { "id": "exit-codes-resume-and-detach", diff --git a/cli/conformance/cases/service/framework/service-guide-jsonl.json b/cli/conformance/cases/service/framework/service-guide-jsonl.json index 1d2a951..dff4d82 100644 --- a/cli/conformance/cases/service/framework/service-guide-jsonl.json +++ b/cli/conformance/cases/service/framework/service-guide-jsonl.json @@ -49,7 +49,7 @@ { "id": "what-will-it-cost", "title": "What will it cost", - "body": "A run reserves a hold before it starts: money set aside from the wallet\n(currently $1.02), not the price. The price is known only after the run\nsettles: `price_cents` in `prose cli run show RUN_ID --json`, of which\n`environment_price_cents` is the environment's share. What the run did not\nuse is released. Short runs usually cost a few cents.\n\nEstimate from your own history: `prose cli wallet usage` prints runs and\nprices by day, and `prose cli wallet balance` shows what is available and\nwhat is reserved right now. `prose cli run quote --json` reports the hold.\n\nPremium models unlock with any wallet top-up; `prose cli model list` shows\neach model's status." + "body": "A run reserves a hold before it starts: money set aside from the wallet,\nnot the price. The hold depends on the model, reasoning effort, environment,\ndeclared tools and bound repositories. The price is known only after the run\nsettles: `price_cents` in `prose cli run show RUN_ID --json`, of which\n`environment_price_cents` is the environment's share. What the run did not\nuse is released. Short runs usually cost a few cents.\n\nEstimate from your own history: `prose cli wallet usage` prints runs and\nprices by day, and `prose cli wallet balance` shows what is available and\nwhat is reserved right now. `prose cli run quote --json` reports the hold for\nthe `--model`, `--reasoning-effort`, `--environment`, `--repo` and\n`--commit-output` you give it. It does not read the program, so a program\nthat sets its own model, effort or environment, or declares tools, can hold a\ndifferent amount.\n\nPremium models unlock with any wallet top-up; `prose cli model list` shows\neach model's status." }, { "id": "headless-and-ci-keys", @@ -74,7 +74,7 @@ { "id": "confirm-and-preview", "title": "Confirm and preview", - "body": "A command that spends money, publishes, deletes or cannot be undone needs\n`--yes`. Without it nothing is changed: it exits 2 with `CONFIRMATION_REQUIRED`,\n`details.reason` (what `--yes` would do, for example that rotating a secret\ninvalidates the old one now), `details.plannedRequest` (the method, what the\nrequest does as `description`, its non-secret inputs as `summary`: model, programRef, inputKeys,\namount_cents, slug; and for `cli run submit`, `cli program draft` and a paid\n`cli job create` the service's flat hold quote), `details.confirmArgv` and\n`details.previewArgv`. `--preview` prints the same plan, exits 0 and changes\nnothing. An unknown `--model`, an `--environment` or `--runtime` that\n`prose cli service status --json` does not list (`environments`), an input the\nprogram's `Parameters` section does not declare (or a declared one that is\nmissing and not marked optional), an invalid organization slug or a job spec\nthe service would reject fails here, before any confirmation, with every\nproblem listed:\n\n```sh\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\n```\n\n`prose cli service operations` shows which commands need `--yes` (CONFIRM)." + "body": "A command that spends money, publishes, deletes or cannot be undone needs\n`--yes`. Without it nothing is changed: it exits 2 with `CONFIRMATION_REQUIRED`,\n`details.reason` (what `--yes` would do, for example that rotating a secret\ninvalidates the old one now), `details.plannedRequest` (the method, what the\nrequest does as `description`, its non-secret inputs as `summary`: model, programRef, inputKeys,\namount_cents, slug; and for `cli run submit`, `cli program draft` and a paid\n`cli job create` the service's hold quote for the options or spec given, and\nfor `cli program draft` the parameter-free quote),\n`details.confirmArgv` and\n`details.previewArgv`. `--preview` prints the same plan, exits 0 and changes\nnothing. An unknown `--model`, an `--environment` or `--runtime` that\n`prose cli service status --json` does not list (`environments`), an input the\nprogram's `Parameters` section does not declare (or a declared one that is\nmissing and not marked optional), an invalid organization slug or a job spec\nthe service would reject fails here, before any confirmation, with every\nproblem listed:\n\n```sh\nprose cli run submit hello.prose.md --preview\nprose cli run submit hello.prose.md --yes\n```\n\n`prose cli service operations` shows which commands need `--yes` (CONFIRM)." }, { "id": "exit-codes-resume-and-detach", diff --git a/cli/conformance/cases/service/jobs/job-create-schedule-empty-strings-quote.json b/cli/conformance/cases/service/jobs/job-create-schedule-empty-strings-quote.json new file mode 100644 index 0000000..703f9c4 --- /dev/null +++ b/cli/conformance/cases/service/jobs/job-create-schedule-empty-strings-quote.json @@ -0,0 +1,81 @@ +{ + "id": "job-create-schedule-empty-strings-quote", + "feature": "jobs", + "operation": "job.create", + "description": "Empty spec strings are not hold options: a schedule with an empty model, reasoning_effort, environment and repository_url quotes with no parameters.", + "argv": [ + "--output", + "json", + "cli", + "job", + "create", + "--spec-file", + "spec.json", + "--preview" + ], + "files": [ + { + "path": "spec.json", + "content": "{\"type\": \"schedule\", \"program_ref\": \"exowner1/jobs-example@0123456789abcdef\", \"interval_seconds\": 86400, \"model\": \"\", \"environment\": \"\", \"repository_url\": \"\"}" + } + ], + "fixture": { + "environment": "production", + "credentials": { + "production": "rr_test_0123456789abcdef0123456789abcdef" + }, + "storeAvailable": true, + "exchanges": [ + { + "method": "GET", + "path": "/run/quote", + "status": 200, + "body": { + "hold": { + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "pricing_policy_id": "pricing-policy.sha256.f114eadc02b7550e58f12c49996382b04f867cb774ac295506806f599ef1e3cd", + "note": "Unused hold is released when the run settles." + }, + "requestHeaders": { + "Authorization": { + "present": false + } + } + } + ] + }, + "exitCode": 0, + "stdout": { + "json": { + "interaction": "job.deploy", + "operation": "job.create", + "problem": null, + "result": { + "plannedRequest": { + "bodyBytes": 158, + "bodySha256": "8e72d4a2cc20e1156d0a33e0bcc5ce4aee84a45d79c66bc1198f4e8f509db656", + "description": "Create a job from a JSON spec.", + "effect": "money", + "method": "POST", + "operation": "job.create", + "quote": { + "hold": { + "hold_cents": 102, + "hold_usd": "1.02", + "ttl_seconds": 900 + } + }, + "summary": { + "interval_seconds": 86400, + "programRef": "exowner1/jobs-example@0123456789abcdef", + "type": "schedule" + } + }, + "preview": true + }, + "schema": "openprose.service-operation/1" + } + } +} diff --git a/cli/conformance/cases/service/jobs/job-create-schedule-environment-quote.json b/cli/conformance/cases/service/jobs/job-create-schedule-environment-quote.json new file mode 100644 index 0000000..0ceabc3 --- /dev/null +++ b/cli/conformance/cases/service/jobs/job-create-schedule-environment-quote.json @@ -0,0 +1,85 @@ +{ + "id": "job-create-schedule-environment-quote", + "feature": "jobs", + "operation": "job.create", + "description": "A schedule's environment and repository_url are sent with the plan's quote as environment and repositories=1.", + "argv": [ + "--output", + "json", + "cli", + "job", + "create", + "--spec-file", + "spec.json", + "--preview" + ], + "files": [ + { + "path": "spec.json", + "content": "{\"type\": \"schedule\", \"program_ref\": \"exowner1/jobs-example@0123456789abcdef\", \"interval_seconds\": 86400, \"environment\": \"linux\", \"repository_url\": \"https://github.com/exowner1/context\"}" + } + ], + "fixture": { + "environment": "production", + "credentials": { + "production": "rr_test_0123456789abcdef0123456789abcdef" + }, + "storeAvailable": true, + "exchanges": [ + { + "method": "GET", + "path": "/run/quote", + "query": { + "environment": "linux", + "repositories": "1" + }, + "status": 200, + "body": { + "hold": { + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "pricing_policy_id": "pricing-policy.sha256.f114eadc02b7550e58f12c49996382b04f867cb774ac295506806f599ef1e3cd", + "note": "Unused hold is released when the run settles." + }, + "requestHeaders": { + "Authorization": { + "present": false + } + } + } + ] + }, + "exitCode": 0, + "stdout": { + "json": { + "interaction": "job.deploy", + "operation": "job.create", + "problem": null, + "result": { + "plannedRequest": { + "bodyBytes": 185, + "bodySha256": "1df5e93428e3e2e5977d384edcba1611d94058ed01a7e8d0610431e74543e74f", + "description": "Create a job from a JSON spec.", + "effect": "money", + "method": "POST", + "operation": "job.create", + "quote": { + "hold": { + "hold_cents": 102, + "hold_usd": "1.02", + "ttl_seconds": 900 + } + }, + "summary": { + "interval_seconds": 86400, + "programRef": "exowner1/jobs-example@0123456789abcdef", + "type": "schedule" + } + }, + "preview": true + }, + "schema": "openprose.service-operation/1" + } + } +} diff --git a/cli/conformance/cases/service/jobs/job-create-webhook-binding-quote.json b/cli/conformance/cases/service/jobs/job-create-webhook-binding-quote.json new file mode 100644 index 0000000..bcdcdc1 --- /dev/null +++ b/cli/conformance/cases/service/jobs/job-create-webhook-binding-quote.json @@ -0,0 +1,89 @@ +{ + "id": "job-create-webhook-binding-quote", + "feature": "jobs", + "operation": "job.create", + "description": "A webhook with a program may carry its contract's model, reasoning_effort and repository_url; the plan's quote sends the model, the effort and repositories=1.", + "argv": [ + "--output", + "json", + "cli", + "job", + "create", + "--spec-file", + "spec.json", + "--preview" + ], + "files": [ + { + "path": "spec.json", + "content": "{\"type\": \"webhook\", \"name\": \"pr-review\", \"delivery_mode\": \"test\", \"program_ref\": \"exowner1/jobs-example@0123456789abcdef\", \"model\": \"model-sol\", \"reasoning_effort\": \"high\", \"repository_url\": \"https://github.com/exowner1/context\"}" + } + ], + "fixture": { + "environment": "production", + "credentials": { + "production": "rr_test_0123456789abcdef0123456789abcdef" + }, + "storeAvailable": true, + "exchanges": [ + { + "method": "GET", + "path": "/run/quote", + "query": { + "model": "model-sol", + "reasoning_effort": "high", + "repositories": "1" + }, + "requestHeaders": { + "Authorization": { + "present": false + } + }, + "status": 200, + "body": { + "hold": { + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "pricing_policy_id": "pricing-policy.sha256.f114eadc02b7550e58f12c49996382b04f867cb774ac295506806f599ef1e3cd", + "note": "Unused hold is released when the run settles." + } + } + ] + }, + "exitCode": 0, + "stdout": { + "json": { + "interaction": "job.deploy", + "operation": "job.create", + "problem": null, + "result": { + "plannedRequest": { + "bodyBytes": 229, + "bodySha256": "6642f65d92b035ae66b01992c9650209f1c611ac164d14a42c80705a5cf3fb05", + "description": "Create a job from a JSON spec.", + "effect": "money", + "method": "POST", + "operation": "job.create", + "quote": { + "hold": { + "hold_cents": 102, + "hold_usd": "1.02", + "ttl_seconds": 900 + } + }, + "summary": { + "delivery_mode": "test", + "model": "model-sol", + "name": "pr-review", + "programRef": "exowner1/jobs-example@0123456789abcdef", + "reasoning_effort": "high", + "type": "webhook" + } + }, + "preview": true + }, + "schema": "openprose.service-operation/1" + } + } +} diff --git a/cli/conformance/cases/service/runs/alias-run-create-previews.json b/cli/conformance/cases/service/runs/alias-run-create-previews.json index e0bd577..300c737 100644 --- a/cli/conformance/cases/service/runs/alias-run-create-previews.json +++ b/cli/conformance/cases/service/runs/alias-run-create-previews.json @@ -52,6 +52,9 @@ { "method": "GET", "path": "/run/quote", + "query": { + "model": "model-luna" + }, "requestHeaders": { "Authorization": { "present": false diff --git a/cli/conformance/cases/service/runs/quote-accepts-submit-arguments.json b/cli/conformance/cases/service/runs/quote-accepts-submit-arguments.json index eeaa0fa..d99931e 100644 --- a/cli/conformance/cases/service/runs/quote-accepts-submit-arguments.json +++ b/cli/conformance/cases/service/runs/quote-accepts-submit-arguments.json @@ -2,7 +2,7 @@ "id": "quote-accepts-submit-arguments", "feature": "runs", "operation": "run.quote", - "description": "Quote takes the program, inputs and model `cli run submit` takes, so the quoted command is the one to submit; the hold does not depend on them.", + "description": "Quote takes the program, inputs and options `cli run submit` takes, so the quoted command is the one to submit. --model is sent with the quote; the program and inputs are not.", "argv": [ "cli", "run", @@ -50,6 +50,9 @@ { "method": "GET", "path": "/run/quote", + "query": { + "model": "model-sol" + }, "requestHeaders": { "Authorization": { "present": false @@ -69,7 +72,7 @@ }, "exitCode": 0, "stdout": { - "text": "Environment: builtin\nHold: $1.02, set aside from the wallet while a run is live; not its price. The same for every program and model; released within 900 s when unused\nPrice: known only after a run settles; read it with `prose cli run show RUN_ID`\nNote: Unused hold is released when the run settles.\n" + "text": "Environment: builtin\nHold: $1.02, set aside from the wallet while a run is live; not its price. Released within 900 s when unused\nDepends on: model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools\nPrice: known only after a run settles; read it with `prose cli run show RUN_ID`\nNote: Unused hold is released when the run settles.\n" }, "stderr": { "text": "" diff --git a/cli/conformance/cases/service/runs/quote-commit-output-only.json b/cli/conformance/cases/service/runs/quote-commit-output-only.json new file mode 100644 index 0000000..7b5141a --- /dev/null +++ b/cli/conformance/cases/service/runs/quote-commit-output-only.json @@ -0,0 +1,98 @@ +{ + "id": "quote-commit-output-only", + "feature": "runs", + "operation": "run.quote", + "description": "`run quote --commit-output` alone sends repositories=1: a bound repository adds to the hold.", + "argv": [ + "--output", + "json", + "cli", + "run", + "quote", + "--commit-output", + "exowner1/context" + ], + "fixture": { + "environment": "production", + "credentials": { + "production": "rr_test_0123456789abcdef0123456789abcdef" + }, + "storeAvailable": true, + "exchanges": [ + { + "method": "GET", + "path": "/health", + "status": 200, + "body": { + "status": "ok", + "environments": { + "default": "builtin", + "available": [ + "builtin", + "linux" + ] + }, + "models": [ + "model-luna", + "model-sol" + ], + "default_model": "model-luna", + "version": "9.9.9-extra", + "capabilities": { + "web:search": { + "available": true, + "tool": "web_search" + } + } + } + }, + { + "method": "GET", + "path": "/run/quote", + "query": { + "repositories": "1" + }, + "requestHeaders": { + "Authorization": { + "present": false + } + }, + "status": 200, + "body": { + "hold": { + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "pricing_policy_id": "pricing-policy.sha256.f114eadc02b7550e58f12c49996382b04f867cb774ac295506806f599ef1e3cd", + "note": "Unused hold is released when the run settles." + } + } + ] + }, + "exitCode": 0, + "stdout": { + "json": { + "interaction": "run.quote", + "operation": "run.quote", + "problem": null, + "result": { + "environment": "builtin", + "hold": { + "hold_cents": 102, + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "holdBasis": "depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles", + "note": "Unused hold is released when the run settles." + }, + "schema": "openprose.service-operation/1" + } + }, + "stderr": { + "text": "" + }, + "forbid": [ + "9.9.9-extra", + "web_search" + ] +} diff --git a/cli/conformance/cases/service/runs/quote-default-environment-unknown-fields.json b/cli/conformance/cases/service/runs/quote-default-environment-unknown-fields.json index b2f7271..68b08a5 100644 --- a/cli/conformance/cases/service/runs/quote-default-environment-unknown-fields.json +++ b/cli/conformance/cases/service/runs/quote-default-environment-unknown-fields.json @@ -147,7 +147,7 @@ "hold_usd": "1.02", "ttl_seconds": 900 }, - "holdBasis": "flat hold, independent of program and model; a run's price is known only after it settles", + "holdBasis": "depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles", "note": "Unused hold is released when the run settles." }, "schema": "openprose.service-operation/1" diff --git a/cli/conformance/cases/service/runs/quote-default-environment.json b/cli/conformance/cases/service/runs/quote-default-environment.json index 7120351..860abe9 100644 --- a/cli/conformance/cases/service/runs/quote-default-environment.json +++ b/cli/conformance/cases/service/runs/quote-default-environment.json @@ -77,7 +77,7 @@ "hold_usd": "1.02", "ttl_seconds": 900 }, - "holdBasis": "flat hold, independent of program and model; a run's price is known only after it settles", + "holdBasis": "depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles", "note": "Unused hold is released when the run settles." }, "schema": "openprose.service-operation/1" diff --git a/cli/conformance/cases/service/runs/quote-human.json b/cli/conformance/cases/service/runs/quote-human.json index 88b9da5..3a96101 100644 --- a/cli/conformance/cases/service/runs/quote-human.json +++ b/cli/conformance/cases/service/runs/quote-human.json @@ -64,7 +64,7 @@ }, "exitCode": 0, "stdout": { - "text": "Environment: builtin\nHold: $1.02, set aside from the wallet while a run is live; not its price. The same for every program and model; released within 900 s when unused\nPrice: known only after a run settles; read it with `prose cli run show RUN_ID`\nNote: Unused hold is released when the run settles.\n" + "text": "Environment: builtin\nHold: $1.02, set aside from the wallet while a run is live; not its price. Released within 900 s when unused\nDepends on: model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools\nPrice: known only after a run settles; read it with `prose cli run show RUN_ID`\nNote: Unused hold is released when the run settles.\n" }, "stderr": { "text": "" diff --git a/cli/conformance/cases/service/runs/quote-linux-environment.json b/cli/conformance/cases/service/runs/quote-linux-environment.json index 3973160..28d8114 100644 --- a/cli/conformance/cases/service/runs/quote-linux-environment.json +++ b/cli/conformance/cases/service/runs/quote-linux-environment.json @@ -82,7 +82,7 @@ "hold_usd": "1.02", "ttl_seconds": 900 }, - "holdBasis": "flat hold, independent of program and model; a run's price is known only after it settles", + "holdBasis": "depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles", "note": "Unused hold is released when the run settles." }, "schema": "openprose.service-operation/1" diff --git a/cli/conformance/cases/service/runs/quote-sends-hold-options.json b/cli/conformance/cases/service/runs/quote-sends-hold-options.json new file mode 100644 index 0000000..0001a61 --- /dev/null +++ b/cli/conformance/cases/service/runs/quote-sends-hold-options.json @@ -0,0 +1,109 @@ +{ + "id": "quote-sends-hold-options", + "feature": "runs", + "operation": "run.quote", + "description": "`run quote` sends the hold options it was given: --model, --reasoning-effort, --environment, and repositories=1 for any --repo or --commit-output. The program and inputs are never sent.", + "argv": [ + "--output", + "json", + "cli", + "run", + "quote", + "p.prose.md", + "--input", + "topic=cats", + "--model", + "model-luna", + "--reasoning-effort", + "low", + "--environment", + "linux", + "--repo", + "exowner1/context", + "--commit-output", + "exowner1/context" + ], + "fixture": { + "environment": "production", + "credentials": { + "production": "rr_test_0123456789abcdef0123456789abcdef" + }, + "storeAvailable": true, + "exchanges": [ + { + "method": "GET", + "path": "/health", + "status": 200, + "body": { + "status": "ok", + "environments": { + "default": "builtin", + "available": [ + "builtin", + "linux" + ] + }, + "models": [ + "model-luna", + "model-sol" + ], + "default_model": "model-luna", + "version": "9.9.9-extra", + "capabilities": { + "web:search": { + "available": true, + "tool": "web_search" + } + } + } + }, + { + "method": "GET", + "path": "/run/quote", + "query": { + "model": "model-luna", + "reasoning_effort": "low", + "environment": "linux", + "repositories": "1" + }, + "requestHeaders": { + "Authorization": { + "present": false + } + }, + "status": 200, + "body": { + "hold": { + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "pricing_policy_id": "pricing-policy.sha256.f114eadc02b7550e58f12c49996382b04f867cb774ac295506806f599ef1e3cd", + "note": "Unused hold is released when the run settles." + } + } + ] + }, + "exitCode": 0, + "stdout": { + "json": { + "interaction": "run.quote", + "operation": "run.quote", + "problem": null, + "result": { + "environment": "linux", + "hold": { + "hold_cents": 102, + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "holdBasis": "depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles", + "note": "Unused hold is released when the run settles." + }, + "schema": "openprose.service-operation/1" + } + }, + "forbid": [ + "9.9.9-extra", + "web_search" + ] +} diff --git a/cli/conformance/cases/service/runs/submit-preview-hidden-model-accepted.json b/cli/conformance/cases/service/runs/submit-preview-hidden-model-accepted.json index 17e4a36..b4a2254 100644 --- a/cli/conformance/cases/service/runs/submit-preview-hidden-model-accepted.json +++ b/cli/conformance/cases/service/runs/submit-preview-hidden-model-accepted.json @@ -66,6 +66,9 @@ { "method": "GET", "path": "/run/quote", + "query": { + "model": "model-sol-legacy" + }, "requestHeaders": { "Authorization": { "present": false diff --git a/cli/conformance/cases/service/runs/submit-preview-repo-quote.json b/cli/conformance/cases/service/runs/submit-preview-repo-quote.json new file mode 100644 index 0000000..03ae772 --- /dev/null +++ b/cli/conformance/cases/service/runs/submit-preview-repo-quote.json @@ -0,0 +1,103 @@ +{ + "id": "submit-preview-repo-quote", + "feature": "runs", + "operation": "run.submit", + "description": "A --repo makes the plan's quote send repositories=1 with the environment: a bound repository adds to the hold. The repository itself is not sent to the quote.", + "argv": [ + "--output", + "json", + "cli", + "run", + "submit", + "p.prose.md", + "--environment", + "linux", + "--repo", + "exowner1/context", + "--preview" + ], + "files": [ + { + "path": "p.prose.md", + "content": "---\nname: hello\nkind: function\n---\n\nReply with the word ok.\n" + } + ], + "fixture": { + "environment": "production", + "credentials": { + "production": "rr_test_0123456789abcdef0123456789abcdef" + }, + "storeAvailable": true, + "exchanges": [ + { + "method": "GET", + "path": "/health", + "status": 200, + "body": { + "status": "ok", + "environments": { + "default": "builtin", + "available": [ + "builtin", + "linux" + ] + }, + "models": [ + "model-luna", + "model-sol" + ], + "default_model": "model-luna" + } + }, + { + "method": "GET", + "path": "/run/quote", + "query": { + "environment": "linux", + "repositories": "1" + }, + "requestHeaders": { + "Authorization": { + "present": false + } + }, + "status": 200, + "body": { + "hold": { + "hold_usd": "1.02", + "ttl_seconds": 900 + }, + "pricing_policy_id": "pricing-policy.sha256.f114eadc02b7550e58f12c49996382b04f867cb774ac295506806f599ef1e3cd", + "note": "Unused hold is released when the run settles." + } + } + ] + }, + "exitCode": 0, + "stdout": { + "json": { + "interaction": "run.submit", + "operation": "run.submit", + "problem": null, + "result": { + "plannedRequest": { + "bodyBytes": 143, + "bodySha256": "2e953120dee6861bc0f1f02c946096995fdc1ff0490834809a43a826938c9769", + "description": "Run a program on the hosted service and stream it until it finishes, detaches or the deadline passes.", + "effect": "money", + "method": "POST", + "operation": "run.submit", + "quote": { + "hold": { + "hold_cents": 102, + "hold_usd": "1.02", + "ttl_seconds": 900 + } + } + }, + "preview": true + }, + "schema": "openprose.service-operation/1" + } + } +} diff --git a/cli/conformance/cases/service/runs/submit-preview-summary-human.json b/cli/conformance/cases/service/runs/submit-preview-summary-human.json index 3932e03..5ff01cf 100644 --- a/cli/conformance/cases/service/runs/submit-preview-summary-human.json +++ b/cli/conformance/cases/service/runs/submit-preview-summary-human.json @@ -2,7 +2,7 @@ "id": "submit-preview-summary-human", "feature": "runs", "operation": "run.submit", - "description": "The human preview prints plannedRequest.summary (model, input keys; never input values) and the flat hold.", + "description": "The human preview prints plannedRequest.summary (model, input keys; never input values) and the hold quoted for that model.", "argv": [ "cli", "run", @@ -52,6 +52,9 @@ { "method": "GET", "path": "/run/quote", + "query": { + "model": "model-luna" + }, "requestHeaders": { "Authorization": { "present": false diff --git a/cli/conformance/cases/service/runs/submit-preview-summary.json b/cli/conformance/cases/service/runs/submit-preview-summary.json index ae6284f..cb2cc18 100644 --- a/cli/conformance/cases/service/runs/submit-preview-summary.json +++ b/cli/conformance/cases/service/runs/submit-preview-summary.json @@ -56,6 +56,10 @@ { "method": "GET", "path": "/run/quote", + "query": { + "model": "model-luna", + "reasoning_effort": "high" + }, "requestHeaders": { "Authorization": { "present": false diff --git a/cli/rust/crates/prose-runner-core/src/service/jobs.rs b/cli/rust/crates/prose-runner-core/src/service/jobs.rs index 0c1fb39..c7df69d 100644 --- a/cli/rust/crates/prose-runner-core/src/service/jobs.rs +++ b/cli/rust/crates/prose-runner-core/src/service/jobs.rs @@ -1118,7 +1118,7 @@ fn create(context: &mut Context<'_>) -> Result { // A webhook with no program starts no runs: nothing is held. planned["effect"] = json!("write"); } else if context.invocation.preview || !context.invocation.yes { - planned["quote"] = quote(context)?; + planned["quote"] = quote(context, &spec)?; } if let Gate::Preview(result) = context.gate(planned)? { return Ok(result); @@ -1134,11 +1134,27 @@ fn create(context: &mut Context<'_>) -> Result { Ok(result) } -/// The anonymous `GET /run/quote` hold for the confirmation plan (the -/// default environment; the job's own environment is not sent). The price -/// policy reference stays internal. -fn quote(context: &mut Context<'_>) -> Result { - let body = get_json(context, 0, "/run/quote")?; +/// The anonymous `GET /run/quote` hold for the confirmation plan. It sends +/// the hold options the spec gives (`model`, `reasoning_effort`, +/// `environment`, and `repositories=1` for a `repository_url` or +/// `context_repository_url`), never defaults; the program is not read, so its +/// declared tools are not sent. The price policy reference stays internal. +fn quote(context: &mut Context<'_>, spec: &Map) -> Result { + let text = |key: &str| { + spec.get(key) + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + }; + let repositories_bound = + text("repository_url").is_some() || text("context_repository_url").is_some(); + let request = super::runs::hold_query( + Request::from_manifest(context.operation, 0, "/run/quote"), + text("model"), + text("reasoning_effort"), + text("environment"), + repositories_bound, + ); + let body = context.send(&request)?.json_object()?; let hold = object(body.get("hold").unwrap_or(&Value::Null), "quote.hold")?; let hold_usd = hold .get("hold_usd") diff --git a/cli/rust/crates/prose-runner-core/src/service/programs.rs b/cli/rust/crates/prose-runner-core/src/service/programs.rs index abbb6f3..3ff89da 100644 --- a/cli/rust/crates/prose-runner-core/src/service/programs.rs +++ b/cli/rust/crates/prose-runner-core/src/service/programs.rs @@ -787,7 +787,8 @@ fn draft(context: &mut Context<'_>) -> Result { .header("Accept", "text/event-stream"); let mut planned = context.planned(0, "/write", &[], request.body.as_deref()); if context.invocation.preview || !context.invocation.yes { - // A draft reserves the same flat hold as a run (request 2, advisory). + // A draft reserves a run's hold, quoted without options (request 2, + // advisory). if let Some(quote) = context.advisory_quote(2, None) { planned["quote"] = quote; } diff --git a/cli/rust/crates/prose-runner-core/src/service/render.rs b/cli/rust/crates/prose-runner-core/src/service/render.rs index cad796e..8e37ee3 100644 --- a/cli/rust/crates/prose-runner-core/src/service/render.rs +++ b/cli/rust/crates/prose-runner-core/src/service/render.rs @@ -395,8 +395,8 @@ fn first_run_line(planned: &Value) -> Option { }) } -/// The human hold line of a plan with a quote (The hold is -/// flat, not an estimate of this run's price). +/// The human hold line of a plan with a quote (the hold quoted for the +/// plan's options; a reservation, not an estimate of this run's price). fn hold_line(planned: &Value) -> Option { let hold = planned["quote"]["hold"]["hold_usd"].as_str()?; Some(format!( diff --git a/cli/rust/crates/prose-runner-core/src/service/runs.rs b/cli/rust/crates/prose-runner-core/src/service/runs.rs index e395aa9..ff2a190 100644 --- a/cli/rust/crates/prose-runner-core/src/service/runs.rs +++ b/cli/rust/crates/prose-runner-core/src/service/runs.rs @@ -400,9 +400,37 @@ fn quote_fields(body: &Map) -> Result { Ok(json!({"hold_usd": hold_usd, "hold_cents": hold_cents, "ttl_seconds": ttl})) } -/// `run quote` `holdBasis`: the hold is not a price estimate. -const HOLD_BASIS: &str = - "flat hold, independent of program and model; a run's price is known only after it settles"; +/// `run quote` `holdBasis`: what the hold depends on and what the quote +/// covers; the hold is not a price estimate. +const HOLD_BASIS: &str = "depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles"; + +/// The `run quote` human line naming what the hold depends on. +const HOLD_DEPENDS_ON: &str = "Depends on: model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools"; + +/// Adds the hold options the caller gave to a `GET /run/quote` request, in +/// manifest order. Only given options are sent, never defaults, so a quote +/// without them stays parameter-free. Any bound repository sends +/// `repositories=1`. The program is never read, so declared tools are not +/// sent. +pub(super) fn hold_query( + mut request: Request, + model: Option<&str>, + reasoning_effort: Option<&str>, + environment: Option<&str>, + repositories_bound: bool, +) -> Request { + for (name, value) in [ + ("model", model), + ("reasoning_effort", reasoning_effort), + ("environment", environment), + ("repositories", repositories_bound.then_some("1")), + ] { + if let Some(value) = value { + request = request.query(name, value.to_owned()); + } + } + request +} fn quote(context: &mut Context<'_>) -> Result { let requested = context.option("--environment").map(str::to_owned); @@ -445,10 +473,15 @@ fn quote(context: &mut Context<'_>) -> Result { .ok_or_else(|| protocol("service status environments.default is missing or malformed"))? .to_owned(), }; - let mut request = Request::from_manifest(context.operation, 1, "/run/quote"); - if let Some(environment) = &requested { - request = request.query("environment", environment.clone()); - } + let repositories_bound = !context.invocation.option_values("--repo").is_empty() + || context.option("--commit-output").is_some(); + let request = hold_query( + Request::from_manifest(context.operation, 1, "/run/quote"), + context.option("--model"), + context.option("--reasoning-effort"), + requested.as_deref(), + repositories_bound, + ); let body = context.send(&request)?.json_object()?; let hold = quote_fields(&body)?; let note = match body.get("note") { @@ -459,10 +492,11 @@ fn quote(context: &mut Context<'_>) -> Result { let mut text = format!("Environment: {}\n", human_safe_scalar(&environment)); let _ = writeln!( text, - "Hold: ${}, set aside from the wallet while a run is live; not its price. The same for every program and model; released within {} s when unused", + "Hold: ${}, set aside from the wallet while a run is live; not its price. Released within {} s when unused", human_safe_scalar(hold["hold_usd"].as_str().unwrap_or_default()), hold["ttl_seconds"] ); + let _ = writeln!(text, "{HOLD_DEPENDS_ON}"); let _ = writeln!( text, "Price: known only after a run settles; read it with `{}`", @@ -1514,6 +1548,11 @@ struct Submission { session: Option, wait_ms: u64, environment: Option, + /// The hold options sent with the plan's quote: --model, + /// --reasoning-effort and whether any repository is bound. + model: Option, + reasoning_effort: Option, + repositories_bound: bool, } fn prepare_submission(context: &mut Context<'_>) -> Result { @@ -1599,6 +1638,9 @@ fn prepare_submission(context: &mut Context<'_>) -> Result) -> Result) -> Result { let placeholder = submission.session.as_deref().unwrap_or("{session}"); let query = submit_query(placeholder, &submission.extra_query); let mut planned = context.planned(2, "/run", &query, Some(&submission.body)); - let mut quote_request = Request::from_manifest(context.operation, 1, "/run/quote") - .class(TransportClass::Control); - if let Some(environment) = &submission.environment { - quote_request = quote_request.query("environment", environment.clone()); - } + let quote_request = hold_query( + Request::from_manifest(context.operation, 1, "/run/quote") + .class(TransportClass::Control), + submission.model.as_deref(), + submission.reasoning_effort.as_deref(), + submission.environment.as_deref(), + submission.repositories_bound, + ); // The quote is advisory: a failed quote never hides the plan. if let Ok(Ok(body)) = context .send("e_request) diff --git a/cli/shared/schemas/service/runs.schema.json b/cli/shared/schemas/service/runs.schema.json index 0efdf46..e0d74dd 100644 --- a/cli/shared/schemas/service/runs.schema.json +++ b/cli/shared/schemas/service/runs.schema.json @@ -23,8 +23,8 @@ "$ref": "common.schema.json#/$defs/holdQuote" }, "holdBasis": { - "description": "The hold is a flat reservation, not a price estimate.", - "const": "flat hold, independent of program and model; a run's price is known only after it settles" + "description": "What the hold depends on and what this quote covers; the hold is a reservation, not a price estimate.", + "const": "depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles" }, "note": { "type": "string", diff --git a/cli/shared/service/guide.v1.md b/cli/shared/service/guide.v1.md index 59c97fb..58c61fa 100644 --- a/cli/shared/service/guide.v1.md +++ b/cli/shared/service/guide.v1.md @@ -97,15 +97,20 @@ prose cli run list --limit 50 --before CURSOR --json ## What will it cost -A run reserves a hold before it starts: money set aside from the wallet -(currently $1.02), not the price. The price is known only after the run +A run reserves a hold before it starts: money set aside from the wallet, +not the price. The hold depends on the model, reasoning effort, environment, +declared tools and bound repositories. The price is known only after the run settles: `price_cents` in `prose cli run show RUN_ID --json`, of which `environment_price_cents` is the environment's share. What the run did not use is released. Short runs usually cost a few cents. Estimate from your own history: `prose cli wallet usage` prints runs and prices by day, and `prose cli wallet balance` shows what is available and -what is reserved right now. `prose cli run quote --json` reports the hold. +what is reserved right now. `prose cli run quote --json` reports the hold for +the `--model`, `--reasoning-effort`, `--environment`, `--repo` and +`--commit-output` you give it. It does not read the program, so a program +that sets its own model, effort or environment, or declares tools, can hold a +different amount. Premium models unlock with any wallet top-up; `prose cli model list` shows each model's status. @@ -194,7 +199,9 @@ A command that spends money, publishes, deletes or cannot be undone needs invalidates the old one now), `details.plannedRequest` (the method, what the request does as `description`, its non-secret inputs as `summary`: model, programRef, inputKeys, amount_cents, slug; and for `cli run submit`, `cli program draft` and a paid -`cli job create` the service's flat hold quote), `details.confirmArgv` and +`cli job create` the service's hold quote for the options or spec given, and +for `cli program draft` the parameter-free quote), +`details.confirmArgv` and `details.previewArgv`. `--preview` prints the same plan, exits 0 and changes nothing. An unknown `--model`, an `--environment` or `--runtime` that `prose cli service status --json` does not list (`environments`), an input the diff --git a/cli/shared/service/help.v1.json b/cli/shared/service/help.v1.json index 49b08cf..f65abb1 100644 --- a/cli/shared/service/help.v1.json +++ b/cli/shared/service/help.v1.json @@ -1,6 +1,6 @@ { "schema": "openprose.service-help/1", - "manifestSha256": "0943c951e2d1cb9233e783010e631a841a2310927367edf2bf5f37c89a8d2114", + "manifestSha256": "796c3fae2e9355f55a137e12b1547636f445c89fba34ec642ca60bca5be22bcd", "topics": { "cli": "Usage: prose [GLOBAL OPTIONS] cli [ARGUMENTS] [OPTIONS]\n\nOpenProse service and account commands. They reach the hosted OpenProse service\nand never prompt. New here? `prose cli service guide` walks through a first\nprogram, a daily schedule, scripting and costs. Commands for this machine\n(doctor, config) are listed by `prose --help`.\n\nCommands:\n run Run a program on the hosted service, then follow, read or cancel it: submit, watch, show, list, cancel, download, input, quote, share.\n program Save your programs and manage their revisions: save, list, show, delete, draft, revisions, visibility.\n example Working example programs to read and copy: list, show.\n job Run a saved program on a schedule or from a webhook: create, list, show, delete, configure, contract, deliveries, rotate-secret, update.\n wallet Balance, usage and credit: balance, usage, events, topup, redeem.\n auth Sign in, check or remove the stored key: login, status, logout.\n model Hosted models: list.\n result Published results of public programs (OWNER/SLUG); a run's own output is `cli run show` or `cli run download`: show, list, publish, unpublish.\n org Organizations, members and invitations: list, show, create, default, invitation, invite, member, rename.\n repo Repositories the service can read: list.\n package Registry packages (no source execution): fetch, list, publish, withdraw.\n service Service status, triage, the guide and the command list: triage, status, guide, capabilities, operations.\n\nExamples:\n prose cli run submit hello.prose.md --preview\n prose cli program save hello hello.prose.md --preview\n prose cli example list --json\n prose cli job create --spec-file job.json --preview\n prose cli wallet balance --json\n prose cli auth login\n prose cli model list --json\n prose cli result show exowner1/hello --latest\n prose cli org list --json\n prose cli repo list --json\n prose cli package publish my-package --organization acme --name tool --version 1.0.0 --json\n prose cli service triage --json\n\nGlobal options:\n --output human|json|jsonl Output mode; the same as the PROSE_OUTPUT setting.\n\nRun `prose cli --help` for details. `prose cli service operations --json` prints every command as JSON.\n", "cli auth": "Usage: prose [GLOBAL OPTIONS] cli auth [ARGUMENTS] [OPTIONS]\n\nAccount credentials for the OpenProse service. Every other service command\nreads the key these commands manage, or OPENPROSE_API_KEY.\n\nCommands:\n login Sign in with the GitHub device flow and store the key in the OS credential store.\n status Report whether a credential is available and valid.\n logout Remove the stored credential.\n\nExamples:\n prose cli auth login\n prose cli auth status --json\n prose cli auth logout --json\n\nGlobal options:\n --output human|json|jsonl Output mode; the same as the PROSE_OUTPUT setting.\n\nCredentials:\n OPENPROSE_API_KEY API key; a non-empty value wins over the stored key.\n\nDevice flow: `cli auth login` prints a one-time code and https://github.com/login/device\non stderr, never the key, and never opens a browser. A person approves the code\nthere within 15 minutes. An agent without a person sets the variable instead.\n\nRun `prose cli auth --help` for details. `prose cli service operations --json` prints every command as JSON.\n", @@ -16,7 +16,7 @@ "cli job contract attach": "Usage: prose [GLOBAL OPTIONS] cli job contract attach [OPTIONS]\n\nAttach a pinned program to a job.\n\nArguments:\n JOB_ID Job id.\n OWNER/SLUG@REV Pinned program reference.\n\nOptions:\n --model MODEL Hosted model for runs of this program.\n --yes Confirm this operation; required because the job starts running this program, a paid run, each time it fires.\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job contract attach 3c1a9e57-0b4d-4f2a-9e61-5d7b2c8a4f10 exowner1/hello@0123456789abcdef --yes\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 24 interrupted.\n", "cli job contract detach": "Usage: prose [GLOBAL OPTIONS] cli job contract detach [OPTIONS]\n\nDetach a program from a job.\n\nArguments:\n JOB_ID Job id.\n OWNER/SLUG@REV Pinned program reference.\n\nOptions:\n --yes Confirm this operation; required because the job stops running this program from now on.\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job contract detach 3c1a9e57-0b4d-4f2a-9e61-5d7b2c8a4f10 exowner1/hello@0123456789abcdef --yes\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 24 interrupted.\n", "cli job contract list": "Usage: prose [GLOBAL OPTIONS] cli job contract list [OPTIONS]\n\nList the programs attached to a job.\n\nArguments:\n JOB_ID Job id.\n\nOptions:\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job contract list 3c1a9e57-0b4d-4f2a-9e61-5d7b2c8a4f10 --json\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error; 24 interrupted.\n", - "cli job create": "Usage: prose [GLOBAL OPTIONS] cli job create [OPTIONS]\n\nCreate a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id.\n\nOptions:\n --spec-file FILE Job spec: a JSON object of at most 64 KiB (see above), or - for standard input. Required.\n --yes Confirm this operation; required because the job starts paid runs on its own schedule or events until it is deleted (a webhook with no program starts none until one is attached).\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job create --spec-file job.json --preview\n prose cli job create --spec-file job.json --yes\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 24 interrupted.\n", + "cli job create": "Usage: prose [GLOBAL OPTIONS] cli job create [OPTIONS]\n\nCreate a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live; with program_ref also model, reasoning_effort, repository_url, repository_branch, output. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id.\n\nOptions:\n --spec-file FILE Job spec: a JSON object of at most 64 KiB (see above), or - for standard input. Required.\n --yes Confirm this operation; required because the job starts paid runs on its own schedule or events until it is deleted (a webhook with no program starts none until one is attached).\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job create --spec-file job.json --preview\n prose cli job create --spec-file job.json --yes\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 24 interrupted.\n", "cli job delete": "Usage: prose [GLOBAL OPTIONS] cli job delete [OPTIONS]\n\nDelete a job. A job that is already gone exits 0 with alreadyAbsent: true; a JOB_ID that is not a UUID is refused (exit 2) before any request.\n\nArguments:\n JOB_ID Job id: a lowercase UUID as `cli job list` prints it; anything else is refused before any request.\n\nOptions:\n --yes Confirm this operation; required because it deletes the job permanently: its schedule or endpoint stops and cannot be restored.\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job delete 3c1a9e57-0b4d-4f2a-9e61-5d7b2c8a4f10 --preview\n prose cli job delete 3c1a9e57-0b4d-4f2a-9e61-5d7b2c8a4f10 --yes\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 24 interrupted.\n", "cli job deliveries": "Usage: prose [GLOBAL OPTIONS] cli job deliveries [OPTIONS]\n\nList a webhook job's recent deliveries. Other job types have none: a schedule's runs are in `cli job show JOB_ID` and `cli run list`.\n\nArguments:\n JOB_ID Job id.\n\nOptions:\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job deliveries 3c1a9e57-0b4d-4f2a-9e61-5d7b2c8a4f10 --json\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error; 24 interrupted.\n", "cli job list": "Usage: prose [GLOBAL OPTIONS] cli job list [OPTIONS]\n\nList jobs, the account's job limit and the available job types.\n\nOptions:\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli job list --json\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error; 24 interrupted.\n", @@ -64,7 +64,7 @@ "cli run download": "Usage: prose [GLOBAL OPTIONS] cli run download [OPTIONS]\n\nDownload every file listed in a run manifest into a new directory (./RUN_ID unless --output-dir).\n\nArguments:\n RUN_ID Run id (run_ and 64 hex digits), or latest for your newest run.\n\nOptions:\n --output-dir DIR Destination directory; it must not exist. Default: ./RUN_ID.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run download run_4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c\n prose cli run download run_4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c --output-dir run-output\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error; 24 interrupted.\n", "cli run input": "Usage: prose [GLOBAL OPTIONS] cli run input [OPTIONS]\n\nSend an instruction to a live run. The instruction id makes retries idempotent. Without a live session on this machine the run record is read first: an ended run is refused as SERVICE_WRITE_CONFLICT (not retryable).\n\nArguments:\n RUN_ID Run id (run_...).\n TEXT Instruction text, 1 to 4000 characters.\n\nOptions:\n --id UUID Instruction id to reuse when retrying; a new UUID is minted otherwise.\n --session UUID Live session to use instead of the one recorded in the local run journal.\n --yes Confirm this operation; required because the live run acts on the instruction as soon as it arrives, and it cannot be taken back.\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run input run_4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c 'Also write a haiku.' --yes\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 24 interrupted.\n", "cli run list": "Usage: prose [GLOBAL OPTIONS] cli run list [OPTIONS]\n\nList runs, newest first, one page at a time.\n\nOptions:\n --limit N Page size, 1 to 200. Default: 20.\n --before CURSOR Opaque cursor from a previous result's nextBefore.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run list --limit 10 --json\n\nOutput: openprose.service-operation/1 (--json).\nPaging: pass the result's nextBefore to --before for the next page.\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error; 24 interrupted.\n", - "cli run quote": "Usage: prose [GLOBAL OPTIONS] cli run quote [FILE] [OPTIONS]\n\nReport the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold is flat, the same for every program and model, and what the run does not use is released when it settles. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and model `cli run submit` takes are accepted, so you can quote the exact command you will submit.\n\nArguments:\n FILE Program file, or - for standard input, as `cli run submit` takes it. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.\n\nOptions:\n --from [OWNER/]SLUG[@REV] Run a saved program; a bare SLUG is your own program. @REV is a rev_id, or @N (@revN) for revision N of your own program; without @REV the latest revision is pinned. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.\n --input KEY=VALUE|KEY=@FILE\n Program input; @FILE reads UTF-8 text of at most 1 MiB. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it. Repeatable.\n --inputs-file FILE JSON object of string inputs. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.\n --model MODEL Hosted model id (see `cli model list`). Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it. Default: the service's default_model (`cli model list`).\n --reasoning-effort EFFORT Reasoning effort supported by the model. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it. One of: minimal, low, medium, high, xhigh.\n --environment ENV Execution environment offered by the service (for example builtin or linux). Default: the service's default environment.\n --runtime RUNTIME Runtime offered by the service. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it. Default: the service's default runtime.\n --repo OWNER/NAME[@BRANCH] Read-only repository context. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it. Repeatable.\n --commit-output OWNER/NAME[@BRANCH]\n Writable repository that receives the run's commit. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run quote --json\n prose cli run quote hello.prose.md --input topic=cats --json\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error; 24 interrupted.\n", + "cli run quote": "Usage: prose [GLOBAL OPTIONS] cli run quote [FILE] [OPTIONS]\n\nReport the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold depends on the model, reasoning effort, environment, declared tools and bound repositories, and what the run does not use is released when it settles. The quote sends only the --model, --reasoning-effort, --environment, --repo and --commit-output you give. It does not read the program, so a program that sets its own model, effort or environment, or declares tools, can hold a different amount. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and options `cli run submit` takes are accepted, so you can quote the exact command you will submit.\n\nArguments:\n FILE Program file, or - for standard input, as `cli run submit` takes it. Accepted so a quote names the same command as `cli run submit`; the quote does not read the program, so the program's own run settings and declared tools are not included.\n\nOptions:\n --from [OWNER/]SLUG[@REV] Run a saved program; a bare SLUG is your own program. @REV is a rev_id, or @N (@revN) for revision N of your own program; without @REV the latest revision is pinned. Accepted so a quote names the same command as `cli run submit`; the quote does not read the program, so the program's own run settings and declared tools are not included.\n --input KEY=VALUE|KEY=@FILE\n Program input; @FILE reads UTF-8 text of at most 1 MiB. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it. Repeatable.\n --inputs-file FILE JSON object of string inputs. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.\n --model MODEL Hosted model id (see `cli model list`). Sent with the quote: the hold depends on it. Default: the service's default_model (`cli model list`).\n --reasoning-effort EFFORT Reasoning effort supported by the model. Sent with the quote: the hold depends on it. One of: minimal, low, medium, high, xhigh.\n --environment ENV Execution environment offered by the service (for example builtin or linux). Sent with the quote: the hold depends on it. Default: the service's default environment.\n --runtime RUNTIME Runtime offered by the service. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it. Default: the service's default runtime.\n --repo OWNER/NAME[@BRANCH] Read-only repository context. Any --repo or --commit-output sends repositories=1 with the quote: a bound repository adds to the hold. Repeatable.\n --commit-output OWNER/NAME[@BRANCH]\n Writable repository that receives the run's commit. Any --repo or --commit-output sends repositories=1 with the quote: a bound repository adds to the hold.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run quote --json\n prose cli run quote hello.prose.md --input topic=cats --json\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error; 24 interrupted.\n", "cli run share": "Usage: prose [GLOBAL OPTIONS] cli run share [OPTIONS]\n\nCreate a public link to a run's outputs. Anyone with the link can open it for 24 hours, and it cannot be revoked.\nTo give specific people access instead, invite them to your organization (`cli org invite`).\n\nArguments:\n RUN_ID Run id (run_ and 64 hex digits), or latest for your newest run.\n\nOptions:\n --yes Confirm this operation; required because it creates a public link to the run's outputs that anyone who has it can open for 24 hours, and it cannot be revoked.\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run share run_4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c --yes\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 24 interrupted.\n", "cli run show": "Usage: prose [GLOBAL OPTIONS] cli run show [OPTIONS]\n\nShow a run manifest (status, files, price), or one output file with --file. Signed file URLs are never printed.\nThe manifest has no final response text. Read the run's outputs with `cli run show RUN_ID --file outputs/result.json` or `cli run download RUN_ID --output-dir DIR`; replay the stream, whose terminal event carries the response, with `cli run watch RUN_ID --after 0` (runs submitted from this machine, or pass --session).\n\nArguments:\n RUN_ID Run id (run_ and 64 hex digits), or latest for your newest run.\n\nOptions:\n --file PATH Output file path exactly as listed in the manifest.\n --output-file FILE Write the bytes to FILE, which must not exist, instead of including them in the result.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run show run_4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c --json\n prose cli run show run_4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c4f1c2d3e4f5a6b7c --file outputs/result.json\n prose cli run show latest --json\n\nOutput: openprose.service-operation/1 (--json).\nExit codes: 0 success; 2 invalid invocation or configuration; 10 service or credential error, or no such run or --file (SERVICE_RESOURCE_NOT_FOUND); 24 interrupted.\n", "cli run submit": "Usage: prose [GLOBAL OPTIONS] cli run submit [FILE] [OPTIONS]\n\nRun a program on the hosted service and stream it until it finishes, detaches or the deadline passes.\nInputs: --input KEY=VALUE overrides the same key from --inputs-file.\nA --wait shorter than the run exits 21 with details.resumeArgv: the run continues, and that command follows it again. Never submit again to resume; that starts a second paid run.\n\nArguments:\n FILE Program file, or - for standard input. Omit when --from is given.\n\nOptions:\n --from [OWNER/]SLUG[@REV] Run a saved program; a bare SLUG is your own program. @REV is a rev_id, or @N (@revN) for revision N of your own program; without @REV the latest revision is pinned.\n --input KEY=VALUE|KEY=@FILE\n Program input; @FILE reads UTF-8 text of at most 1 MiB. It overrides the same key from --inputs-file. Repeatable.\n --inputs-file FILE JSON object of string inputs; an --input of the same key wins.\n --model MODEL Hosted model id (see `cli model list`). Default: the service's default_model (`cli model list`).\n --reasoning-effort EFFORT Reasoning effort supported by the model. One of: minimal, low, medium, high, xhigh.\n --environment ENV Where the run executes (for example builtin: file tools only; linux: a shell without network access). Web access is through the browser:interact tool. Default: the service's default environment.\n --runtime RUNTIME Runtime offered by the service. Default: the service's default runtime.\n --repo OWNER/NAME[@BRANCH] Read-only repository context. Repeatable.\n --commit-output OWNER/NAME[@BRANCH]\n Writable repository that receives the run's commit.\n --detach Return as soon as the run id is known.\n --wait DURATION Stop following after this long and exit 21 (for example 90s, 10m, 2h; maximum 6h). The run continues. Default: 30m.\n --session UUID Use this session UUID instead of a new one (recovery only).\n --yes Confirm this operation; required because it starts a paid run: a hold (money set aside from the wallet, not the price) is reserved now, and the run's price is charged when it settles.\n --preview Print the planned request of a mutation and send nothing.\n --json Print one JSON result; the same as the global --output json.\n --help Show help for this command.\n\nExamples:\n prose cli run submit hello.prose.md --preview\n prose cli run submit hello.prose.md --yes\n prose cli run submit --from exowner1/hello --input topic=rain --yes --detach --json\n\nOutput: openprose.service-operation/1 (--json) or openprose.service-event/1 lines (--output jsonl). The run id is result.runId (and result.run.run_id once the run ends); the answer is result.run.response.\nExit codes: 0 success; 2 invalid invocation, configuration or CONFIRMATION_REQUIRED; 10 service or credential error; 21 deadline or detached: the run id is known and the run continues (resume with details.resumeArgv); 22 run failed, or submission ambiguous (recover with details.resumeArgv: the same session with --detach, never a new run); 24 cancelled on the service.\n", @@ -87,7 +87,7 @@ "contract": "service/1", "manifest": { "schema": "openprose.service-operations/1", - "sha256": "40813b94d3ec38153a3b012ec1eb187681e9791692f65490cff211b0c0b73f2c", + "sha256": "c3aef4f39d4fa26f6af3334e311806c5aedca0cb9934c47c325678467454edd5", "argv": [ "cli", "service", @@ -501,7 +501,7 @@ "run", "quote" ], - "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold is flat, the same for every program and model, and what the run does not use is released when it settles. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and model `cli run submit` takes are accepted, so you can quote the exact command you will submit.", + "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold depends on the model, reasoning effort, environment, declared tools and bound repositories, and what the run does not use is released when it settles. The quote sends only the --model, --reasoning-effort, --environment, --repo and --commit-output you give. It does not read the program, so a program that sets its own model, effort or environment, or declares tools, can hold a different amount. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and options `cli run submit` takes are accepted, so you can quote the exact command you will submit.", "effect": "read", "confirm": false, "examples": [ @@ -821,7 +821,7 @@ "job", "create" ], - "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id.", + "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live; with program_ref also model, reasoning_effort, repository_url, repository_branch, output. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id.", "effect": "money", "confirm": true, "examples": [ diff --git a/cli/shared/service/operations.v1.json b/cli/shared/service/operations.v1.json index a942fa0..d23ba11 100644 --- a/cli/shared/service/operations.v1.json +++ b/cli/shared/service/operations.v1.json @@ -1954,7 +1954,7 @@ ], "contract": "service/1", "feature": "runs", - "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold is flat, the same for every program and model, and what the run does not use is released when it settles. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and model `cli run submit` takes are accepted, so you can quote the exact command you will submit.", + "summary": "Report the hold a run reserves: money set aside from the wallet while the run is live. It is not the price. The environment is checked first.\nThe hold depends on the model, reasoning effort, environment, declared tools and bound repositories, and what the run does not use is released when it settles. The quote sends only the --model, --reasoning-effort, --environment, --repo and --commit-output you give. It does not read the program, so a program that sets its own model, effort or environment, or declares tools, can hold a different amount. A run's price is known only after it settles (`cli run show RUN_ID`, price_cents). The program, inputs and options `cli run submit` takes are accepted, so you can quote the exact command you will submit.", "interaction": "run.quote", "interactions": [ "health.read", @@ -1963,7 +1963,7 @@ "arguments": [ { "name": "FILE", - "description": "Program file, or - for standard input, as `cli run submit` takes it. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Program file, or - for standard input, as `cli run submit` takes it. Accepted so a quote names the same command as `cli run submit`; the quote does not read the program, so the program's own run settings and declared tools are not included.", "required": false, "variadic": false } @@ -1972,7 +1972,7 @@ { "name": "--from", "value": "[OWNER/]SLUG[@REV]", - "description": "Run a saved program; a bare SLUG is your own program. @REV is a rev_id, or @N (@revN) for revision N of your own program; without @REV the latest revision is pinned. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Run a saved program; a bare SLUG is your own program. @REV is a rev_id, or @N (@revN) for revision N of your own program; without @REV the latest revision is pinned. Accepted so a quote names the same command as `cli run submit`; the quote does not read the program, so the program's own run settings and declared tools are not included.", "repeatable": false, "required": false }, @@ -1993,7 +1993,7 @@ { "name": "--model", "value": "MODEL", - "description": "Hosted model id (see `cli model list`). Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Hosted model id (see `cli model list`). Sent with the quote: the hold depends on it.", "default": "the service's default_model (`cli model list`)", "repeatable": false, "required": false @@ -2001,7 +2001,7 @@ { "name": "--reasoning-effort", "value": "EFFORT", - "description": "Reasoning effort supported by the model. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Reasoning effort supported by the model. Sent with the quote: the hold depends on it.", "repeatable": false, "required": false, "choices": [ @@ -2015,7 +2015,7 @@ { "name": "--environment", "value": "ENV", - "description": "Execution environment offered by the service (for example builtin or linux).", + "description": "Execution environment offered by the service (for example builtin or linux). Sent with the quote: the hold depends on it.", "default": "the service's default environment", "repeatable": false, "required": false @@ -2031,14 +2031,14 @@ { "name": "--repo", "value": "OWNER/NAME[@BRANCH]", - "description": "Read-only repository context. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Read-only repository context. Any --repo or --commit-output sends repositories=1 with the quote: a bound repository adds to the hold.", "repeatable": true, "required": false }, { "name": "--commit-output", "value": "OWNER/NAME[@BRANCH]", - "description": "Writable repository that receives the run's commit. Accepted so a quote names the same command as `cli run submit`; the hold does not depend on it.", + "description": "Writable repository that receives the run's commit. Any --repo or --commit-output sends repositories=1 with the quote: a bound repository adds to the hold.", "repeatable": false, "required": false } @@ -2063,7 +2063,10 @@ "method": "GET", "path": "/run/quote", "query": { - "environment": "{environment}" + "model": "{model}", + "reasoning_effort": "{reasoning_effort}", + "environment": "{environment}", + "repositories": "1" }, "auth": "none", "body": null, @@ -2256,7 +2259,10 @@ "method": "GET", "path": "/run/quote", "query": { - "environment": "{environment}" + "model": "{model}", + "reasoning_effort": "{reasoning_effort}", + "environment": "{environment}", + "repositories": "1" }, "auth": "none", "body": null, @@ -4623,7 +4629,7 @@ ], "contract": "service/1", "feature": "jobs", - "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id.", + "summary": "Create a job from a JSON spec. A job starts paid runs on its own, on a schedule or from webhook events, until it is deleted.\nSpec keys are snake_case. `type` is required; `cli job list` prints every type with its config_fields. Each type accepts the keys listed under this command's `spec` in `cli service operations --json`; every problem is reported at once.\n Schedule: {\"type\":\"schedule\",\"program_ref\":\"OWNER/SLUG@REV\",\"interval_seconds\":86400}\n interval_seconds 60 to 2678400 (86400 is once a day). The first run starts about one second after the job is created, then one every interval.\n Cron expressions and a time of day are not supported.\n Optional: model, reasoning_effort, environment, inputs (name -> string), files, repository_url, repository_branch, output.\n Webhook: {\"type\":\"webhook\",\"name\":\"NAME\",\"delivery_mode\":\"test\"}\n optional name, program_ref, receiver, receiver_secret, reply, reply_secret; delivery_mode test or live; with program_ref also model, reasoning_effort, repository_url, repository_branch, output. Without program_ref it starts no runs: no hold, effect write.\nREV is the program's program.rev_id (printed by `cli program save` and `cli program show OWNER/SLUG`), not its commit_id.", "interaction": "job.deploy", "interactions": [ "job.deploy", @@ -4646,6 +4652,12 @@ "interaction": "run.quote", "method": "GET", "path": "/run/quote", + "query": { + "model": "{model}", + "reasoning_effort": "{reasoning_effort}", + "environment": "{environment}", + "repositories": "1" + }, "auth": "none", "body": null, "when": "confirmation", @@ -4782,7 +4794,20 @@ ] }, "reply": "object", - "reply_secret": "text" + "reply_secret": "text", + "model": "text", + "reasoning_effort": { + "enum": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + }, + "repository_url": "text", + "repository_branch": "text", + "output": "object" } }, "email": { diff --git a/docs/hosted-service-client.md b/docs/hosted-service-client.md index 28490bc..68dfa2d 100644 --- a/docs/hosted-service-client.md +++ b/docs/hosted-service-client.md @@ -24,7 +24,7 @@ For the workflow rather than the facts, read `prose cli service guide`: which ke ## Confirmation -Commands never prompt. A command that spends money, publishes, deletes or cannot be reversed requires `--yes`. Without it, the command exits 2 with `CONFIRMATION_REQUIRED`, says in `details.reason` what `--yes` would do (the manifest's `confirmReason`, also on the `--yes` line of the command's help: rotating a webhook secret invalidates the old one now, detaching a contract stops that program, unpublishing removes the public page), and prints the exact planned request. The plan's `summary` shows the non-secret body fields (`model`, `programRef`, `inputKeys`, `amount_cents`, `slug` and similar; never secrets, program text or input values); human output prints it as `Summary:` (cents also in dollars). For `run submit`, `program draft` and a paid `job create`, the plan also includes the server's hold quote, a flat hold independent of program and model (`run quote` says the same; the price is known only after the run settles). `--preview` prints the plan for any mutation and sends nothing. Before any confirmation, `run submit` and `program draft` check `--model` against `cli model list` (an unknown name lists the three nearest and `details.suggestedArgv` retries with the nearest), `org create` checks the slug rule, and `job create|update|configure` check the spec against the closed per-type schema published as the operation's `spec` in `cli service operations --json`, reporting every problem at once in `details.violations`. A webhook job with no `program_ref` starts no runs: its plan is effect `write` with no hold. +Commands never prompt. A command that spends money, publishes, deletes or cannot be reversed requires `--yes`. Without it, the command exits 2 with `CONFIRMATION_REQUIRED`, says in `details.reason` what `--yes` would do (the manifest's `confirmReason`, also on the `--yes` line of the command's help: rotating a webhook secret invalidates the old one now, detaching a contract stops that program, unpublishing removes the public page), and prints the exact planned request. The plan's `summary` shows the non-secret body fields (`model`, `programRef`, `inputKeys`, `amount_cents`, `slug` and similar; never secrets, program text or input values); human output prints it as `Summary:` (cents also in dollars). For `run submit` and a paid `job create`, the plan also includes the server's hold quote for the model, effort, environment and repositories given, and `program draft` includes the parameter-free quote (`run quote` reports the same hold; it does not cover the program's own run settings or declared tools, and the price is known only after the run settles). `--preview` prints the plan for any mutation and sends nothing. Before any confirmation, `run submit` and `program draft` check `--model` against `cli model list` (an unknown name lists the three nearest and `details.suggestedArgv` retries with the nearest), `org create` checks the slug rule, and `job create|update|configure` check the spec against the closed per-type schema published as the operation's `spec` in `cli service operations --json`, reporting every problem at once in `details.violations`. A webhook job with no `program_ref` starts no runs: its plan is effect `write` with no hold. ## Runs diff --git a/docs/service/jobs.md b/docs/service/jobs.md index 5d82db5..6df2380 100644 --- a/docs/service/jobs.md +++ b/docs/service/jobs.md @@ -70,8 +70,10 @@ printf '%s' '{"type":"webhook","delivery_mode":"test","name":"my-hook"}' \ created**, then every `interval_seconds`. That is why `job create` is a money operation: its plan (`--preview` or `CONFIRMATION_REQUIRED`) includes the service's hold quote (`plannedRequest.quote`, from the anonymous - `GET /run/quote` for the default environment; a flat hold, independent of - program and model). With `--yes` no quote is read. A webhook with no + `GET /run/quote` with the spec's `model`, `reasoning_effort` and + `environment`, and `repositories=1` when it names a `repository_url` or + `context_repository_url`; the program's own run settings and declared tools + are not included). With `--yes` no quote is read. A webhook with no `program_ref` starts no runs: its plan has effect `write`, no quote is read and no hold is shown. - `prose cli job create --help` prints minimal schedule and webhook specs and @@ -86,7 +88,10 @@ printf '%s' '{"type":"webhook","delivery_mode":"test","name":"my-hook"}' \ `model`, `reasoning_effort`, `environment`, `inputs` (name → string), `files`, `repository_url`, `repository_branch`, `output`. Webhook spec keys: `type`, and optionally `name`, `program_ref`, `delivery_mode` (`test` or `live`), - `receiver`, `receiver_secret`, `reply`, `reply_secret`. + `receiver`, `receiver_secret`, `reply`, `reply_secret`, and, with a + `program_ref`, the connected contract's `model`, `reasoning_effort`, + `repository_url`, `repository_branch` and `output` (without `program_ref` + the service refuses them). - A webhook `create` result carries `endpoint` and `signing_secret`. **They appear only in that result** (and in `rotate-secret`), never in `job show` or stderr. Human mode prints them once on stdout and a warning on stderr. Both diff --git a/docs/service/programs.md b/docs/service/programs.md index 0e0fe37..4ae4afe 100644 --- a/docs/service/programs.md +++ b/docs/service/programs.md @@ -134,7 +134,7 @@ prose cli program draft "Also return a title" --current haiku.prose.md --output- ``` - The writer is a hosted run and is charged like one (a short draft costs a - few cents). It reserves the same flat hold as a run: the plan (`--preview` or + few cents). It reserves a hold like a run: the plan (`--preview` or `CONFIRMATION_REQUIRED`) carries `plannedRequest.quote` from the anonymous `GET /run/quote` (advisory; a failed quote never hides the plan), and a `--model` is checked against `GET /models` before the gate (an unknown name diff --git a/docs/service/runs.md b/docs/service/runs.md index 617ec00..9d2073a 100644 --- a/docs/service/runs.md +++ b/docs/service/runs.md @@ -20,10 +20,10 @@ prose cli run quote --json ```json {"schema":"openprose.service-operation/1","operation":"run.quote","interaction":"run.quote", - "result":{"environment":"builtin","hold":{"hold_usd":"1.02","hold_cents":102,"ttl_seconds":900},"holdBasis":"flat hold, independent of program and model; a run's price is known only after it settles","note":"Unused hold is released when the run settles."},"problem":null} + "result":{"environment":"builtin","hold":{"hold_usd":"1.02","hold_cents":102,"ttl_seconds":900},"holdBasis":"depends on model, reasoning effort, environment, declared tools and repositories; quoted from the options given, without the program's own run settings or declared tools; a run's price is known only after it settles","note":"Unused hold is released when the run settles."},"problem":null} ``` -The quote is the hold, not the price: a flat hold, independent of program and model (`result.holdBasis` says so, and human output adds a `Price:` line naming `run show`); the settled price appears in the finished run (`price_cents`). An unknown `--environment` is rejected before `/run/quote` is called (`INVOCATION_INVALID`, naming the advertised environments). +The quote is the hold, not the price (human output adds a `Price:` line naming `run show`); the settled price appears in the finished run (`price_cents`). The hold depends on the model, reasoning effort, environment, declared tools and bound repositories. `run quote` sends only the `--model`, `--reasoning-effort` and `--environment` you give, plus `repositories=1` for any `--repo` or `--commit-output`; with none of them it is the parameter-free quote for the service's defaults. It never reads the program, so a program that sets its own model, effort or environment, or declares tools, can hold a different amount (`result.holdBasis` says so). An unknown `--environment` is rejected before `/run/quote` is called (`INVOCATION_INVALID`, naming the advertised environments). ## Submit @@ -31,7 +31,7 @@ The quote is the hold, not the price: a flat hold, independent of program and mo prose cli run submit hello.prose.md --model model-luna --yes --output jsonl ``` -- **Without `--yes`** nothing is sent: exit 2 `CONFIRMATION_REQUIRED` with `details.plannedRequest` (method, `description`, body SHA-256 and size, effect `money`; never the service route) plus `plannedRequest.quote` (the current flat hold), `plannedRequest.summary` (the non-secret body fields: `model`, `reasoning_effort`, `programRef` for `--from`, and `inputKeys`, the sorted input names without values), `details.reason` (what `--yes` does: reserve the hold and start a paid run), and `details.confirmArgv` / `details.previewArgv` — the same command with `--yes` or `--preview`. `--preview` prints the same plan and exits 0. +- **Without `--yes`** nothing is sent: exit 2 `CONFIRMATION_REQUIRED` with `details.plannedRequest` (method, `description`, body SHA-256 and size, effect `money`; never the service route) plus `plannedRequest.quote` (the hold for the given `--model`, `--reasoning-effort`, `--environment` and repositories, as `run quote` sends them), `plannedRequest.summary` (the non-secret body fields: `model`, `reasoning_effort`, `programRef` for `--from`, and `inputKeys`, the sorted input names without values), `details.reason` (what `--yes` does: reserve the hold and start a paid run), and `details.confirmArgv` / `details.previewArgv` — the same command with `--yes` or `--preview`. `--preview` prints the same plan and exits 0. - **Source:** a local file (at most 1 MiB of UTF-8, not blank), `-` for standard input, or `--from OWNER/SLUG[@REV]` (without `@REV` the latest revision is resolved and a pinned `program_ref` is sent). URLs are refused. - **Inputs:** `--input KEY=VALUE`, `--input KEY=@FILE` (UTF-8, at most 1 MiB) and `--inputs-file FILE` (a JSON object of strings). `--input` overrides a key from `--inputs-file`; the same `--input` key twice is an error. The whole submission is at most 8 MiB. - **Model and placement:** `--model`, `--reasoning-effort`, `--environment`, `--runtime` (see `cli model list`). A `--model` is checked against `GET /models` before the confirmation gate: a name that is not offered and not the default is `INVOCATION_INVALID` listing the three nearest offered models, with `details.suggestedArgv` retrying with the nearest; if the lookup itself fails the service decides.