Text inputs and outputs

Send messages to a model, stream the text it writes, and understand usage and stop reasons.

Text is the core of the gateway. You send messages, and the model streams text back. The examples use the connect helper from the quickstart.

Send text and stream the answer

import { connect } from './gateway.js';

const gateway = await connect();

const result = await gateway.turn(
	{
		provider: 'auto',
		model: 'auto',
		messages: [
			{ role: 'system', content: [{ type: 'text', text: 'You are a concise assistant.' }] },
			{ role: 'user', content: [{ type: 'text', text: 'Explain what a WebSocket is.' }] }
		],
		params: { max_tokens: 300, temperature: 0.4 }
	},
	{ onText: (piece) => process.stdout.write(piece) }
);

console.log(result.complete.usage);
gateway.close();

The helper joins the text of every block.delta event whose delta.kind is text. Under the hood, the gateway sends this:

{ "type": "turn.start", "seq": 0, "request_id": "req_8c1f", "provider": "anthropic", "model": "claude-sonnet-5-5", "session_id": "...", "ts": 1791504000000 }
{ "type": "block.start", "seq": 1, "index": 0, "block": { "kind": "text" }, "...": "..." }
{ "type": "block.delta", "seq": 2, "index": 0, "delta": { "kind": "text", "text": "A WebSocket is" },
  "usage": { "input_tokens": 24, "output_tokens": 4, "estimated": true },
  "budget": { "units_used": 0.42, "units_remaining": 9.58, "limit_window": "month" }, "...": "..." }
{ "type": "block.stop", "seq": 9, "index": 0, "...": "..." }
{ "type": "turn.complete", "seq": 10, "request_id": "req_8c1f", "stop_reason": "stop",
  "usage": { "input_tokens": 24, "output_tokens": 61, "estimated": false },
  "attempts": [{ "provider": "anthropic", "model": "claude-sonnet-5-5", "outcome": "completed" }], "...": "..." }

Every stored event has a session_id, a seq that counts up from zero, a timestamp ts, and the provider and model that produced it.

Messages

A request holds a list of messages. Each message has a role and a content array of parts.

RoleUsed for
systemInstructions that shape the model’s behavior. Text only
userWhat the person says. Text, images and JSON
assistantWhat the model said earlier. Text and tool calls
toolResults of tool calls

A text part looks like { "type": "text", "text": "..." }. To send structured data the model should read, use a JSON part, { "type": "json", "data": { ... } }. The gateway serializes it for you.

Generation parameters

Put these in params. All are optional.

ParameterRangeEffect
max_tokens1 or moreCaps the length of the answer
temperature0 to 2Higher values are more varied
top_p0 to 1Limits sampling to the most likely tokens
stopup to 16 stringsEnds the turn when one appears

When the answer ends because of max_tokens the stop reason is length. When a stop string appears it is stop_sequence.

Choose a model

Set provider and model in the request:

You setThe gateway does
Both to real idsUses exactly that model
Both to autoChooses any model that can handle the request
One provider, model: "auto"Chooses among that provider’s models

With auto, the gateway first keeps the models that can do the job: they must support images or tools if you use them, and the conversation plus max_tokens must fit the context window. It then prefers cheaper models. Two optional fields steer the choice:

  • agent_context.difficulty from 1 to 5. Higher values move toward stronger models.
  • agent_context.is_subagent: true moves one step cheaper, for helper tasks.

To restrict auto to certain models, pass candidates, a list of { "provider": "...", "model": "..." }.

If a named model does not exist you get model_not_found. If nothing can serve the request you get no_capable_model.

Read the live catalog

Models change, so read the list instead of hard-coding it:

curl https://gateway.vaktex.com/vaktex/v1/registry 
  -H "Authorization: Bearer $VAKTEX_KEY"
{
  "models": [
    {
      "provider": "anthropic",
      "id": "claude-sonnet-5-5",
      "display_name": "Claude Sonnet 5 5",
      "type": "text",
      "capabilities": { "vision": true, "tools": true, "json_mode": true, "max_context": 1000000, "max_output": 64000 },
      "cost": { "input_per_mtok": 2, "output_per_mtok": 10 },
      "retention_time": 7776000000,
      "retention_reasons": [],
      "preview": false
    }
  ]
}

Use each model’s provider and id in your requests. Costs are US dollars per million tokens, and retention_time is in milliseconds. Only models that are ready to use are listed, and the response is never cached.

Usage and spend

Each block.delta carries running usage and your budget:

  • usage.estimated is true until the provider reports exact counts. turn.complete has the final numbers.
  • budget.units_used and units_remaining are US dollars for the tightest limit that applies to you. limit_window is 4h, week or month. If you have no limit, units_remaining and limit_window are null.
  • usage.cached_input_tokens, when present, counts input the provider reused from a cache. It is already included in input_tokens.

You never send usage to the gateway.

Block kinds

Text arrives in blocks. A block opens with block.start, grows with block.delta events and closes with block.stop. Most answers have one text block. Some models also stream a reasoning block before the answer. Show or hide it as you like. A refusal block holds text from a model that declined to answer. The helper in the quickstart only collects text blocks.

Stop reasons

turn.complete.stop_reason says why the turn ended.

ValueMeaning
stopThe model finished, or you sent a stop frame
lengthThe answer hit max_tokens
stop_sequenceOne of your stop strings appeared
tool_useThe model is waiting for tool results
refusedEvery candidate model refused
errorThe turn failed. An error event came first
rate_limitedA spend limit ended the turn
disconnectYou were away for more than five minutes mid-turn
interruptedThe session restarted mid-turn. Send the turn again

Stop a turn early

Send { "type": "stop" } to abort a running turn. You still get a turn.complete with stop_reason: "stop" and the usage so far.

ws.send(JSON.stringify({ type: 'stop' }));

Only one turn runs at a time on a session. A second start while one is running returns a session_busy error. You can send the next start as soon as turn.complete arrives.

Next steps

enesfrdenbsvfiplarhe