@lionhummer/constellation 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,172 +1,158 @@
1
- # Constellation TypeScript SDK
1
+ # @lionhummer/constellation
2
2
 
3
- `@lionhummer/constellation` is a server-side client for durable model execution. A request keeps running after `requests.create()` returns its ID. Constellation owns admission, routing, provider calls, waits and the execution trace. Your application supplies conversation state, tools and any policy or billing decisions.
3
+ TypeScript client for Constellation's durable request API. Run it on a trusted server with an execution key. Constellation routes model calls and stores the execution trace; your application supplies messages, tool implementations, optional policy decisions, and billing.
4
4
 
5
- ```sh
5
+ ```bash
6
6
  npm install @lionhummer/constellation
7
7
  ```
8
8
 
9
- ```ts
10
- import { Constellation, uuid7, type InputHandlers } from "@lionhummer/constellation";
9
+ ## First request (Node.js 22+)
11
10
 
12
- const client = new Constellation({
13
- baseUrl: "https://your-constellation-host.example",
14
- apiKey: process.env.CONSTELLATION_EXECUTION_KEY!,
15
- });
16
- ```
11
+ An operator must first configure an enabled provider, its credential reference, a model/offering, and access rules for your workspace. Ask them for the API base URL and an **execution** key. A console login is not an SDK credential. The base URL is the host root, without `/v1` or `/internal`.
17
12
 
18
- Keep keys on a trusted server. One key may have execution, management and telemetry permissions; the method namespaces below map to those API permissions. An `AbortSignal` stops the current SDK operation, not the durable request. Use `requests.cancel(id)` to cancel execution.
13
+ Set these variables privately in your server environment:
19
14
 
20
- ## Choose an execution kind
15
+ - `CONSTELLATION_BASE_URL`: your server's URL (local API: `http://127.0.0.1:8080`).
16
+ - `CONSTELLATION_EXECUTION_KEY`: the secret copied when the operator creates your key.
17
+ - `CONSTELLATION_WORKSPACE_ID`: the exact workspace allowed by the key and access rules.
18
+ - `CONSTELLATION_USER_ID`: required when the key is user-scoped; otherwise optional.
19
+ - `CONSTELLATION_PLAN_ID`: optional pricing plan ID, when your operator supplies one.
21
20
 
22
- | Kind | Model steps | Caller participation |
23
- | --- | --- | --- |
24
- | `generate` | Exactly one. Tools are not accepted. | Observe the result. |
25
- | `run` | Continues through model steps and caller tools. | Implement tools; there is no review gate before tool dispatch. |
26
- | `agent` | First step executes from the complete submitted input; later steps use fresh complete inputs. | Review model output and usage before tools, review after tools, and decide whether to continue. |
21
+ Run the included example to discover accessible models. This does not dispatch a provider:
27
22
 
28
- All three use `client.requests.create(input)` and return `{ id }`. Persist the UUIDv7 `idempotencyKey` before submitting. If the POST outcome is uncertain, retry with that same key **and identical input**. `externalId` groups separate requests and can hint at prior-provider affinity; it does not retrieve conversation history.
23
+ ```sh
24
+ node node_modules/@lionhummer/constellation/examples/quickstart.mjs
25
+ ```
29
26
 
30
- ### One-step generation
27
+ Set `CONSTELLATION_MODEL` to a returned `models[].id` and rerun the command. The second run submits a real provider request, prints its ID/events, then shows `completed` and the output text. Provider usage can incur charges. It needs no webhook, tool handler or wait checkpoint.
31
28
 
32
- ```ts
33
- const { id } = await client.requests.create({
34
- kind: "generate",
35
- idempotencyKey: uuid7(),
36
- workspaceId: "workspace-123",
37
- model: "catalog/example-model",
38
- messages: [{ role: "user", text: "Explain this result." }],
29
+ The complete example is included in the package:
30
+
31
+ ```js
32
+ import { Constellation, uuid7 } from "@lionhummer/constellation";
33
+
34
+ function required(name) {
35
+ const value = process.env[name];
36
+ if (!value) throw new Error(`Set ${name} in your server environment.`);
37
+ return value;
38
+ }
39
+
40
+ const workspaceId = required("CONSTELLATION_WORKSPACE_ID");
41
+ const userId = process.env.CONSTELLATION_USER_ID || undefined;
42
+ const planId = process.env.CONSTELLATION_PLAN_ID || undefined;
43
+ const client = new Constellation({
44
+ baseUrl: required("CONSTELLATION_BASE_URL"),
45
+ apiKey: required("CONSTELLATION_EXECUTION_KEY"),
39
46
  });
40
47
 
41
- for await (const event of client.requests.streamEvents(id)) {
42
- // Store event.seq to resume after a disconnect.
43
- if (["request.completed", "request.failed", "request.cancelled"].includes(event.type)) break;
48
+ // Discovery uses the same scope as execution. It does not call a provider.
49
+ const catalog = await client.models.list({ workspaceId, userId, planId });
50
+ console.log("Available models:", JSON.stringify(catalog, null, 2));
51
+ const model = process.env.CONSTELLATION_MODEL;
52
+ if (!model) {
53
+ console.log("Set CONSTELLATION_MODEL to one of the returned models[].id values, then rerun.");
54
+ } else {
55
+ const input = {
56
+ idempotencyKey: uuid7(), // Save this input/key if submission needs retrying.
57
+ workspaceId,
58
+ userId,
59
+ planId,
60
+ model,
61
+ messages: [{ role: "user", text: "Say hello in one short sentence." }],
62
+ maxOutputTokens: 256,
63
+ timeoutMs: 120_000,
64
+ // No tools or wait checkpoints: no webhook or input consumer is needed.
65
+ };
66
+ const { id } = await client.requests.create(input);
67
+ console.log("Request ID:", id);
68
+ for await (const event of client.requests.streamEvents(id, {
69
+ signal: AbortSignal.timeout(150_000),
70
+ })) {
71
+ console.log(event.seq, event.type);
72
+ }
73
+ const { request } = await client.requests.getSummary(id);
74
+ console.log("Final status:", request.status);
75
+ console.log("Output:", request.output?.text ?? "");
76
+ if (request.status !== "completed") {
77
+ throw new Error(`Request ${id} ended as ${request.status}: ${request.reason ?? "inspect the request error and attempts"}`);
78
+ }
44
79
  }
45
- const final = await client.requests.get(id);
46
80
  ```
47
81
 
48
- `requests.preview(input)` applies access, pricing and routing rules without creating or dispatching a request. `models.list({ workspaceId })` discovers accessible models; `models.resolve({ workspaceId, targetId })` previews one model's candidates.
82
+ Each invocation creates a new request. For application retries, persist the complete input and its UUIDv7 idempotency key **before** submission and reuse both after an uncertain POST; do not generate a fresh key on each retry. `externalId` groups independent requests and may supply a routing-affinity hint; it does not load conversation history. The observer's abort only stops observing; use `requests.cancel(id)` to cancel execution.
49
83
 
50
- ### Tool-capable run
84
+ ## Troubleshooting
51
85
 
52
- Use `run` when tools should execute without an application review gate after each model response. Each tool call becomes a durable caller input. The same handlers work with webhook delivery and explicit polling:
86
+ | Symptom | What to check |
87
+ | --- | --- |
88
+ | 401 | Correct host and execution key; the key may have been revoked. Browser cookies do not authenticate the SDK. |
89
+ | 403 | Execution permission, matching workspace/user scope and model access rules. Management/telemetry permissions do not imply execution. |
90
+ | No models | Operator has enabled a provider, model and offering with an eligible route/access rule. Discovery uses your workspace/user scope. |
91
+ | 409 | Read the error code/message. Reuse the exact original input for an idempotency retry; renew an expired input claim before answering. |
92
+ | 429 / 503 | Respect `Retry-After`. Retry uncertain admission with the saved input/key. |
93
+ | `queued` | A worker must be running and connected to the same PostgreSQL/Redis environment. |
94
+ | `waiting` | Handle the pending input. Tools always require a consumer; wait checkpoints do too. SSE observation does not answer inputs. |
95
+ | `failed` | Inspect `request.reason`, `request.error`, attempts and timeline for provider, routing or deadline failure. |
53
96
 
54
- ```ts
55
- const handlers = {
56
- consumerId: "stable-worker-id",
57
- tools: {
58
- lookup: async (args, { inputId, signal }) => {
59
- // Deduplicate external effects by inputId: callbacks can be delivered again.
60
- return lookupOnce(args, inputId, signal);
61
- },
62
- },
63
- } satisfies InputHandlers;
97
+ ## Release 0.3.0
64
98
 
65
- // From one verified input webhook:
66
- await client.inputs.handle(requestId, inputId, handlers);
99
+ This pre-1.0 release breaks the earlier SDK contract. Use namespaced execution methods; flat aliases and the old drive/protocol surface are removed. Keys, credentials, users and capacity administration are internal-only. Provider-definition writes also require an internal operator. External telemetry and other registry authoring remain available. Deploy a server containing these API changes before switching clients; publishing this package does not deploy the server or upgrade any downstream application dependency.
67
100
 
68
- // Or run a polling consumer until terminal:
69
- await client.inputs.poll(requestId, handlers);
70
- ```
101
+ ## Events and caller work
71
102
 
72
- `inputs.handle()` processes only the named input and returns. `inputs.poll()` repeatedly inspects and handles inputs until the request is terminal. Neither silently switches delivery modes. For custom consumers, `inputs.claim()` and `inputs.respond()` expose the underlying claim and response operations.
103
+ All event modes default to `off`. `beforeModel`, `afterModel`, and `afterStep` accept `off`, `notify`, or `wait`. `outputDelta` and `terminal` accept `off` or `notify`. Notifications are delivered to the execution key's configured webhook. A wait creates a durable input that your application must claim and answer. Tool calls always create durable inputs, including parallel calls.
73
104
 
74
- ### Caller-controlled agent
105
+ `afterStep` happens after the model when it has no tool calls, or after all tool results. It includes output, usage, cost, and tool results. Use it to settle billing once and decide whether to complete or continue. Continuing requires a complete next `stepInput`, including model, messages, instructions, tools, parameters, output limit, and cache policy. Constellation routes each later step separately.
75
106
 
76
- An agent submission contains the **complete first step**. Do not submit a placeholder message expecting an initial preparation callback. The SDK sets the server's `prepareStep` protocol flag for `agent` requests.
107
+ The following integration sketches contain application-owned functions such as `lookupOnce`, `settleOnce` and `buildCompleteNextStep`; implement those before enabling tools or wait checkpoints. Type handlers explicitly so TypeScript checks their callback arguments:
77
108
 
78
109
  ```ts
79
- const { id } = await client.requests.create({
80
- kind: "agent",
81
- idempotencyKey: uuid7(),
82
- workspaceId: "workspace-123",
83
- externalId: "conversation-456",
84
- model: "catalog/example-model",
85
- instructions: "Work carefully and use tools when needed.",
86
- messages: fullFirstStepHistory,
87
- tools: toolDefinitions,
88
- parameters: {},
89
- maxOutputTokens: 4096,
90
- });
91
- ```
92
-
93
- After each model step, Constellation issues `caller_review` with output, usage, customer cost, supplier cost and pending tools. Your application can settle usage and decide whether to allow tools. After tools, another review can finish or request the next step. Before a later model step, `prepare_step` asks for the **whole next input**: model, instructions, messages, tools, parameters, output limit and cache policy.
110
+ import type { InputHandlers } from "@lionhummer/constellation"
94
111
 
95
- ```ts
96
- const handlers = {
112
+ const handlers: InputHandlers = {
97
113
  consumerId: "stable-worker-id",
98
- callerReview: async (review, state, { inputId }) => ({
99
- action: await settleAndDecideOnce(review, state, inputId),
100
- }),
101
114
  tools: {
102
115
  lookup: async (args, { inputId, signal }) => lookupOnce(args, inputId, signal),
103
116
  },
104
- prepareStep: async (state, { signal, parentSpanId }) => ({
105
- stepInput: await buildCompleteNextStep(state, { signal, parentSpanId }),
106
- }),
107
- } satisfies InputHandlers;
117
+ checkpoints: {
118
+ afterStep: async (payload, state, { inputId, parentSpanId, signal }) => {
119
+ await settleOnce(inputId, payload, { parentSpanId, signal })
120
+ return shouldContinue(state)
121
+ ? { action: "continue", stepInput: await buildCompleteNextStep(state) }
122
+ : { action: "complete" }
123
+ },
124
+ },
125
+ }
126
+
127
+ await client.inputs.handle(id, inputId, handlers)
108
128
  ```
109
129
 
110
- Deduplicate billing and tool side effects by input ID. The SDK claims an input, renews an agent claim while its handler runs, adds parent spans for review/preparation/tool callbacks, and responds. If handling fails transiently, return a non-2xx webhook response so delivery retries. A terminal request rejects new caller spans and logs.
130
+ `inputs.handle` processes one named input, which suits webhook delivery. `inputs.poll` continuously inspects and handles inputs until terminal and must be started explicitly. Both claim and renew inputs while handlers run. Side effects should be deduplicated by input ID because delivery and responses can be retried. Low-level `inputs.claim` and `inputs.respond` are available when you manage the lifecycle yourself.
111
131
 
112
- ## Webhooks and events
132
+ ## Webhook
113
133
 
114
- Configure the execution key's HTTPS `inputWebhook` URL and random token when creating the key, or later with `management.keys.setInputWebhook()`. The SDK can verify the token and parse the three callback shapes:
134
+ Configure an HTTPS callback URL and token on the execution key. Verify `x-constellation-webhook-token`; return 2xx after your effects are durable. Transient non-2xx responses are retried. A permanent input rejection can use HTTP 422 with the documented `constellation_input_rejected` body; repeated 401/403 stops delivery. Return 2xx only after durable effects. Model text/reasoning chunks arrive as `request.events` when `outputDelta` is `notify`; terminal notification carries a request ID and event sequence.
115
135
 
116
136
  ```ts
117
- const event = await client.webhooks.verify(request, process.env.CONSTELLATION_WEBHOOK_TOKEN!);
137
+ const event = await client.webhooks.verify(request, webhookToken)
118
138
  if ("inputId" in event) {
119
- await client.inputs.handle(event.requestId, event.inputId, handlers);
120
- } else if (event.kind === "request.model_events") {
121
- await storeDeltaBatchOnce(event.requestId, event.afterSeq, event.events);
139
+ await client.inputs.handle(event.requestId, event.inputId, handlers)
140
+ } else if (event.kind === "request.events") {
141
+ await storeEventBatchOnce(event.requestId, event.afterSeq, event.events)
122
142
  } else {
123
- const state = await client.requests.getSummary(event.requestId);
124
- await finalizeOnce(event.requestId, event.eventSeq, state);
143
+ await finalizeOnce(event.requestId, event.eventSeq,
144
+ await client.requests.getSummary(event.requestId))
125
145
  }
126
- // Return 2xx after your input response or other effects are durable.
127
146
  ```
128
147
 
129
- Input callbacks include `{requestId,inputId,kind,payload}` and `x-constellation-input-id`. Model-event callbacks contain `{requestId,kind:"request.model_events",afterSeq,events}`. Terminal callbacks contain `{requestId,eventSeq,kind}`. All carry `x-constellation-webhook-token`. Delivery is at least once. Model deltas may arrive late; use the final inspection as authority after terminal.
130
-
131
- `requests.streamEvents(id, { after, signal })` is SSE with durable sequence replay. `requests.listEvents(id, after)` reads a bounded event page. SSE observes output; it does not process caller inputs. A dropped stream reconnects by cursor and does not cancel the request.
148
+ ## Inspection and other APIs
132
149
 
133
- ## Inspect and instrument
150
+ `requests.get` returns the request and its records. For long executions, use `requests.getSummary`, `requests.listRecords`, `requests.getTimeline`, and the paginated request list. `requests.listEvents` reads the durable event journal; `requests.streamEvents` reconnects SSE from a cursor. Stopping an observer does not cancel execution; use `requests.cancel` explicitly.
134
151
 
135
- ```ts
136
- const summary = await client.requests.getSummary(id);
137
- const steps = await client.requests.listRecords(id, "steps", { limit: 25, offset: 0 });
138
- const timeline = await client.requests.getTimeline(id);
139
-
140
- const span = await client.spans.start(id, "application.context", { inputId });
141
- try {
142
- await rebuildContext();
143
- await span.end();
144
- } catch (error) {
145
- await span.end({ status: "error" });
146
- throw error;
147
- }
148
- ```
152
+ Execution methods are top-level (`models`, `requests`, `inputs`, `spans`, `logs`). Registry authoring lives under `management.registry`; read-only operational data lives under `telemetry`. API-key administration, provider credentials, users and capacity controls are internal operator APIs and are absent from this external SDK. The SDK requires a bearer key, omits browser cookies, and has no `/internal` routing option. Provider definitions are readable externally; imports, changes and rollback that modify providers require internal operator access because endpoints and credential references control where stored secrets are sent. The same API timeline is shown in the console. Caller spans can attach to a wait using `inputId` and nest beneath a span using `parentSpanId`.
149
153
 
150
- `requests.get(id)` loads full inspection. For long runs, use `getSummary()` and `listRecords()` for steps, attempts, inputs, spans or logs. `getTimeline()` returns the same recorded and derived rows as the console. `getHttpExchange()` and `getHttpBody()` inspect a provider attempt. Caller spans may link to an `inputId`, `stepId`, `attemptId` or `parentSpanId`; `client.spans.add()` and `client.logs.add()` write batches.
151
-
152
- User messages may contain HTTPS image or PDF URL parts. Constellation stores URL references, not media bytes. Provider adapters forward supported URLs or download within limits immediately before dispatch. Keep signed URLs valid until the provider fetches them; refresh URLs in a later agent `prepareStep` callback.
153
-
154
- ## Other namespaces
155
-
156
- | Namespace | Methods |
157
- | --- | --- |
158
- | `management.registry` | `get`, `listRows`, `update`, `applyChanges`, `listHistory`, `rollback`, `preview`, `testRules` |
159
- | `management.keys` | `list`, `listPage`, `create`, `revoke`, `setInputWebhook` |
160
- | `management.credentials` | `list`, `listPage`, `create`, `update`, `delete` |
161
- | `management.executionSettings` | `get`, `update`, `listHistory` |
162
- | `telemetry.requests` | `list`, `get`, `getSummary`, `listEvents`, `listRecords`, `getTimeline`, `getHttpExchange`, `getHttpBody` |
163
- | `telemetry.system` | `get`, `listHistory`, `getComponent` |
164
- | `telemetry.observability` | `get`, `getSeries`, `getTiming` |
165
- | `telemetry.jobs` | `listHistory` |
166
- | `telemetry.notifications` | `list`, `get`, `listEvents` |
167
- | `telemetry.logs` | `searchSystem`, `searchRequests`, `searchAudit` |
168
- | `health` | `live`, `ready` (public probes) |
169
-
170
- Top-level `logs.add()` writes caller logs on an execution key. `telemetry.logs` searches operational logs with a telemetry key. The API's exact parameter and response schemas are served at `/v1/openapi.json` and shown inline in the console's API reference.
171
-
172
- The client defaults to a 30-second HTTP timeout; configure `timeoutMs` or pass an `AbortSignal`. `ConstellationError` carries HTTP status, server code and message. Older flat method names remain as temporary aliases for existing callers; new integrations should use the names above.
154
+ Use the documented namespaces throughout: `requests.create`, `requests.get`,
155
+ `requests.getSummary`, `requests.preview`, `requests.listRecords`,
156
+ `requests.streamEvents`, `inputs.claim`, `inputs.respond`, `spans.start`,
157
+ `spans.add`, and `logs.add`. Their implementation helpers are private; flat
158
+ method aliases are not part of the client API.