# Hosted text generation

Create a text-generation Block with account BYOK, explicit cost approval, and verified outputs.

Use `runtime: "capability"` to implement a portable Block that asks StillMade to
generate text. The package declares the prompt, instructions, output contract,
and fixtures. StillMade owns account authentication, model choice, provider
keys, cost confirmation, and execution. This is a separate declarative runtime;
`fetch`, `ctx.capabilities`, and arbitrary host calls remain unavailable inside
JavaScript Blocks.

### Create and package

```sh
node packages/block-cli/cli.js create ./script-draft capability
node packages/block-cli/cli.js validate ./script-draft
node packages/block-cli/cli.js pack ./script-draft script-draft.stillmade.json
```

The folder contains `stillmade.block.json`, `src/capability.json`, and
`tests/fixtures.json`. A packed JSON contains `{manifest, capability, tests}`
and may include a sandboxed `view`. No provider key, endpoint, model, billing
selection, or receipt belongs in the package. Those fields are rejected.

Download [the complete script-draft example](/block-sdk/examples/script-draft.stillmade.json).
Its manifest uses `entry: "src/capability.json"`, one primary `prompt` input of
type `text`, one primary `script` output of type `script`, and:

```json
{"project":[],"capabilities":["text.generate"],"network":[],"filesystem":[],"secrets":[]}
```

Its capability declaration is:

```json
{
  "schemaVersion": 1,
  "operation": "text.generate",
  "prompt": {"$input": "prompt"},
  "system": "Write a concise narration script. Return only the script text.",
  "maxTokens": 1200,
  "output": "script"
}
```

Declare exactly one prompt input (`text`, `script`, or `brief`) and exactly one
output (`text`, `script`, or `json`). Structured prompt inputs contain
`{"schemaVersion":1,"text":"..."}`. A generated script returns that same
structured shape; a text output returns a string. Typed ports, primary markers,
semantic roles, and scoped project context retain their SDK meaning. A context
prompt needs its declared read permission and fresh authorized project snapshot.
Do not infer scene or character structure from an unstructured generated script.

Up to three other read-only inputs (for example the saved project script) can
also reach the model: list them in the descriptor as `"context": ["script"]`.
StillMade appends their current values after the prompt as delimited data the
model is told never to treat as instructions, and the review covers that exact
text. An input that is not listed (one a custom view only displays) is never
sent.

#### Conversations

To continue a conversation, declare one optional `json` input for the earlier
turns and bind it as `history`:

```json
{"schemaVersion":1,"operation":"text.generate","prompt":{"$input":"message"},
 "history":{"$input":"history"},"system":"You are a friendly script coach.",
 "maxTokens":800,"output":"reply"}
```

The history value is up to 20 turns of `{"role":"user"|"assistant","content":"..."}`
(each at most 8,000 characters, 24,000 in total); StillMade sends them between
the system instructions and the prompt, and the review covers the exact turns.
Other fields and other roles are rejected. A missing history starts a fresh
conversation. A custom view or a JavaScript Step can keep the turns and pass
them back on the next run.

#### Structured replies (JSON schema)

For data instead of prose, declare a `json` output and add `schema`, the JSON
Schema the reply must match:

```json
{"schemaVersion":1,"operation":"text.generate","prompt":{"$input":"topic"},
 "system":"You plan short podcast intros.","maxTokens":800,"output":"plan",
 "schema":{"type":"object","additionalProperties":false,"required":["title","script"],
  "properties":{"title":{"type":"string","maxLength":80},
   "script":{"type":"string","maxLength":1200},
   "beats":{"type":"array","items":{"type":"string"},"minItems":3,"maxItems":6}}}}
```

StillMade tells the model to reply with JSON matching the schema, parses the
reply (one surrounding code fence is tolerated) and checks it against the
schema before any Step receives it. A reply that does not match fails the run
with the first mismatch, for example `$.beats: needs at least 3 items`. The
output port then carries the parsed value, so a JavaScript Block downstream
reads `input.plan.title` directly.

The supported subset is `type` (one or a list of `object`, `array`, `string`,
`number`, `integer`, `boolean`, `null`), `description`, `title`, `enum`,
`properties`, `required`, `additionalProperties`, `items`, `minItems`,
`maxItems`, `minLength`, `maxLength`, `pattern`, `format` (`date`,
`date-time`, `email`, `uri`), `minimum` and `maximum`; up to 8 levels deep and
8,192 characters. The system instructions plus the schema instruction must fit
in 8,000 characters. Fixture expectations for a json output are
`{"kind":"json"}` with optional `includes` literal phrases.

Limits: package 256 KiB; prompt 32,000 characters; system instructions 8,000
characters; generated text 32,768 characters; `maxTokens` integer 1–4096.
The operation is one text generation call. Image, audio, video, tool calling,
streaming, multi-call agent loops, and arbitrary API requests are not supported
by this declaration.

### Fixtures and real review

Generated text varies, so fixtures use bounded expectations instead of an exact
`expected` string:

```json
[{
  "name": "A short opening narration",
  "input": {"prompt":"Write two narration sentences about a quiet forest at sunrise."},
  "expectations": {"script":{"kind":"script","minLength":20,"maxLength":6000}}
}]
```

Include 1–3 fixtures. Every output has its matching `kind`, positive `minLength`,
and `maxLength` no larger than the output limit. Optional `includes` contains at
most eight required literal strings of at most 256 characters each; regular
expressions are not supported. Expectations check the declared interface and
bounded behavior, not factual correctness. Review the actual generated result.

Offline `validate` and `pack` check source only. `pack` reports `tests: 0`,
`reviewRequired: true`, and `liveVerified: false`. Offline `test` and `preview`
fail with `RUNTIME_UNAVAILABLE`; they do not invent a generation result.

Import the JSON or ZIP into StillMade. Test and preview loads available models
and payment methods. **Review cost** obtains a non-billable quote. Only the
separate **Run tests and sample** action submits one call per fixture and one
additional sample call. The host displays the complete call count and credit
cost first. BYOK uses your encrypted account OpenRouter key; provider usage is
billed by that provider and does not consume StillMade generation credits.
A missing BYOK key never silently switches to credits.

The server binds the report to your account, exact source digest, inputs,
resolved model/payment selection, quote, and execution policy. Every fixture,
the separate sample, typed outputs, and expectations must pass. Inspect the
sample and source rights, then **Confirm Import** to install. A client-supplied
`passed` flag or an LLM-written report cannot authorize import or release.
AI authoring generates and statically validates a draft; it stops for this
hosted review and does not silently run the drafted capability.

### Run in a project and recover

Add the reviewed Block to a Project Type to make a Step. Its controls and
optional custom interface use the same host execution dialog. Guest UI cannot
choose a model, reveal keys, or confirm a quote. Existing matching review
results can be reused for project execution, but the current project input
always gets its own quote and explicit run confirmation. An expired import
review can be reused for a project only after the server revalidates its source,
selection, policy, and current project edit access.

Each confirmed request has an account-scoped UUID and durable execution record.
The host advances one invocation at a time, retains results, and makes duplicate
requests idempotent. An unknown provider outcome is not automatically submitted
again. If the creation response is lost, the host finds the original request by
its UUID without generating. Continuing an unfinished recovered review requires
another explicit Continue generation action; it advances the already confirmed
plan, not a new request. Cancellation cannot guarantee that a provider stops already submitted
work; completed results are retained, while a cancelled or stale project run
cannot be automatically accepted into the project. Failed StillMade-credit
charges use the existing idempotent refund ledger. BYOK provider charges follow
the provider account and cannot be refunded by StillMade.

A Project Type can be imported after every embedded hosted package receives its
own current account review; see [review hosted Project Types](/docs/project-types/hosted-review).
This is package admission, not proof that the connected flow ran. Connected
automatic runs and unattended future-media jobs stop at the hosted capability
boundary; they do not grant advance permission for paid calls.

### Publish a verified listing sample

After releasing the Block publicly, choose **Publish verified sample** in its
listing editor. Confirm the public display of that sample's input and output.
StillMade reuses the exact retained sample from the release review; it does not
call the provider or charge again. The source, account ownership, current runtime
policy, quarantine state, complete fixture evidence, and sample contract are
checked again. An expired import receipt or a removed API key does not invalidate
historical output. Project-run receipts cannot publish a private project sample
through this action. The stored preview records the original model and generation
time, so it is not represented as a new execution.

### Host integration API

```js
import {runPackageAsync, prepareCapabilityInvocation, capabilityOutputs}
  from './packages/block-sdk/index.js';

const result = await runPackageAsync(pkg, input, {
  capability: async (reviewedPackage, values, {signal}) => {
    const request = prepareCapabilityInvocation(reviewedPackage, values);
    // Trusted host: authenticate, review cost and approval, execute once,
    // retain the result, then return typed outputs. Never implement this in
    // package code or treat this SDK callback as proof of import review.
    const text = await approvedHostTextRun(request, {signal});
    return {outputs: capabilityOutputs(reviewedPackage, text)};
  }
});
```

`approvedHostTextRun` is a host integration placeholder, not an SDK function.
The SDK validates declaration and output types; StillMade's server supplies the
account receipt and billing guarantees. Optional returned metadata is bounded
to 8 KiB and is not an authorization receipt. Without a trusted executor,
`runPackageAsync` fails closed. `validateCapability`,
`prepareCapabilityInvocation`, `capabilityOutputs`,
`validateCapabilityExpectations`, and `testCapabilityOutputs` are the portable
validation helpers. They never access a provider.

StillMade's authenticated `/api/block-capabilities` routes provide options,
quotes, review creation/lookup, run creation/status/poll/cancel, and read-only
`GET /requests/:requestId` recovery. Status GETs never execute calls or reconcile
refunds; POST polling/cancellation handles those mutations. They use StillMade's
credit ledger and encrypted BYOK vault or platform model key; Block authors
configure nothing.
