@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 +112 -126
- package/dist/index.js +534 -582
- package/dist/{index.d.ts → types/client-core/src/index.d.ts} +69 -255
- package/dist/types/sdk/src/index.d.ts +96 -0
- package/examples/quickstart.mjs +48 -0
- package/package.json +17 -7
package/README.md
CHANGED
|
@@ -1,172 +1,158 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lionhummer/constellation
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
```
|
|
5
|
+
```bash
|
|
6
6
|
npm install @lionhummer/constellation
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
import { Constellation, uuid7, type InputHandlers } from "@lionhummer/constellation";
|
|
9
|
+
## First request (Node.js 22+)
|
|
11
10
|
|
|
12
|
-
|
|
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
|
-
|
|
13
|
+
Set these variables privately in your server environment:
|
|
19
14
|
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
```sh
|
|
24
|
+
node node_modules/@lionhummer/constellation/examples/quickstart.mjs
|
|
25
|
+
```
|
|
29
26
|
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
## Troubleshooting
|
|
51
85
|
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
await client.inputs.poll(requestId, handlers);
|
|
70
|
-
```
|
|
101
|
+
## Events and caller work
|
|
71
102
|
|
|
72
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
132
|
+
## Webhook
|
|
113
133
|
|
|
114
|
-
Configure the execution key
|
|
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,
|
|
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.
|
|
121
|
-
await
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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.
|