@lionhummer/constellation 0.9.1 → 0.9.2

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
@@ -343,6 +343,24 @@ tools: {
343
343
 
344
344
  The SDK records `client.processing` and one span per tool automatically. A response carries at most 512 spans, 128 logs and 1 MiB of telemetry; the SDK trims larger batches and records what it omitted. Do not put secrets or personal data in span names, attributes or log messages.
345
345
 
346
+ ## Opening lines
347
+
348
+ Show a one-sentence opener while the real step is prepared. The operator configures its model and prompt in Constellation.
349
+
350
+ ```ts
351
+ const opener = await client.openers.create({
352
+ idempotencyKey: `${turnId}`,
353
+ workspaceId, userId, externalId: threadId,
354
+ message,
355
+ history: recentTurns, // [{ role: "user" | "assistant", text }], oldest first
356
+ })
357
+ if (opener) void client.openers.sentence(opener.id).then((sentence) => {
358
+ if (sentence && !realOutputStarted) show(sentence)
359
+ })
360
+ ```
361
+
362
+ Start the real request without waiting for the opener. `parseOpenerSentence(text)` applies the same checks to text you already have.
363
+
346
364
  ## Limits and timeouts
347
365
 
348
366
  | Setting | Default | Notes |
@@ -367,6 +385,7 @@ A failed provider call falls back to the next provider only before any output ha
367
385
  | `requests.submit(input)` | Start a request, continue it (`inReplyTo`), or stop it (`action: "observe"`). |
368
386
  | `requests.preview(input)` | Validate a complete input and show routing, without calling a provider. |
369
387
  | `requests.cancel(id)` | Cancel a request. |
388
+ | `openers.create(input)`, `openers.sentence(id)` | Start an opening line for a just-sent message (null when openers are turned off), then wait for its sentence (null when skipped or failed). |
370
389
  | `requests.getState(id)` | Committed state: status, steps, attempt outcomes, usage and cost. |
371
390
  | `requests.getSummary(id)`, `requests.get(id)` | Request detail, without or with spans and logs. |
372
391
  | `requests.list(query)` | Page through requests, e.g. by `externalId` or `status`. |
package/dist/index.js CHANGED
@@ -1,4 +1,15 @@
1
1
  // ../client-core/src/index.ts
2
+ function parseOpenerSentence(raw, maxWords = 25) {
3
+ const text = raw.trim();
4
+ if (!text || /^SKIP\b/i.test(text))
5
+ return null;
6
+ const sentence = (text.match(/^.+?[.!](?=\s|[A-Z]|$)/)?.[0] ?? text).trim();
7
+ if (sentence.includes("`") || sentence.includes(`
8
+ `) || sentence.split(/\s+/).length > maxWords)
9
+ return null;
10
+ return sentence;
11
+ }
12
+
2
13
  class ConstellationError extends Error {
3
14
  status;
4
15
  code;
@@ -300,6 +311,30 @@ class RequestClient {
300
311
  resolve: (input, signal) => this.#call("/v1/registry/resolve", "POST", input, signal)
301
312
  };
302
313
  }
314
+ get openers() {
315
+ return {
316
+ create: async (input, signal) => await this.#call("/v1/openers", "POST", input, signal) ?? null,
317
+ sentence: async (id, options = {}) => {
318
+ let text = "";
319
+ for await (const event of this.#events(id, { signal: options.signal })) {
320
+ if (event.type === "model.completed") {
321
+ const message = event.data.message;
322
+ text = message?.text ?? text;
323
+ break;
324
+ }
325
+ if (event.type === "model.failed" || event.type === "model.cancelled")
326
+ return null;
327
+ if (event.type === "model.delta") {
328
+ for (const entry of event.data.events ?? []) {
329
+ if (entry.event?.type === "text_delta")
330
+ text += entry.event.delta ?? "";
331
+ }
332
+ }
333
+ }
334
+ return parseOpenerSentence(text, options.maxWords);
335
+ }
336
+ };
337
+ }
303
338
  get requests() {
304
339
  return {
305
340
  submit: (input, signal) => this.#submit(input, signal),
@@ -770,6 +805,7 @@ export {
770
805
  uuid7,
771
806
  processEvent,
772
807
  prepareResponse,
808
+ parseOpenerSentence,
773
809
  normalizeTimestamp,
774
810
  PermanentEventError,
775
811
  PERMANENT_RESPONSE_CODES,
@@ -304,6 +304,24 @@ export type ModelDeltaEvent = {
304
304
  };
305
305
  };
306
306
  export type RequestEvent = WebhookEvent | ModelDeltaEvent;
307
+ export type OpenerTurn = {
308
+ role: "user" | "assistant";
309
+ text: string;
310
+ };
311
+ export type OpenerInput = {
312
+ /** Stable per turn, so a retry never admits a second opener. */
313
+ idempotencyKey: string;
314
+ workspaceId: string;
315
+ userId?: string;
316
+ externalId?: string;
317
+ planId?: string;
318
+ /** The message the user just sent. */
319
+ message: string;
320
+ /** Earlier turns, oldest first; operator settings decide how many are used. */
321
+ history?: OpenerTurn[];
322
+ };
323
+ /** The opener line to show: one sentence, no code, within `maxWords`; null for SKIP or anything else. */
324
+ export declare function parseOpenerSentence(raw: string, maxWords?: number): string | null;
307
325
  export type Page<T> = {
308
326
  items: T[];
309
327
  total: number;
@@ -503,6 +521,22 @@ export declare class RequestClient {
503
521
  list: (scope: ModelScope, signal?: AbortSignal) => Promise<Json>;
504
522
  resolve: (input: ModelResolution, signal?: AbortSignal) => Promise<Json>;
505
523
  };
524
+ /**
525
+ * Opening lines: an operator-configured short model call, made when the user
526
+ * sends, naming what the assistant does first. create admits it as an
527
+ * ordinary request; sentence waits for it to end and returns the line to
528
+ * show, or null for SKIP, a failure, or an answer that breaks the format.
529
+ */
530
+ get openers(): {
531
+ /** The opener request, or null when the operator has turned openers off. */
532
+ create: (input: OpenerInput, signal?: AbortSignal) => Promise<{
533
+ id: string;
534
+ } | null>;
535
+ sentence: (id: string, options?: {
536
+ signal?: AbortSignal;
537
+ maxWords?: number;
538
+ }) => Promise<string | null>;
539
+ };
506
540
  /** Durable requests. create returns an ID; execution continues independently. */
507
541
  get requests(): {
508
542
  submit: (input: Admission, signal?: AbortSignal) => Promise<AdmissionReceipt>;
@@ -1,7 +1,7 @@
1
1
  /** External server SDK. Operator APIs and cookie sessions are internal-only clients. */
2
2
  import { RequestClient, type WebhookEvent, type Json, type Page, type PageQuery, type ModelResolution, type RequestRecord, type RequestEvent, type RecordTypes, type Timeline, type TimelineRow, type RequestListQuery, type RequestRecordsQuery, type RequestTimings, type TokenUsage } from "../../client-core/src/index";
3
- export { ConstellationError, uuid7, normalizeTimestamp } from "../../client-core/src/index";
4
- export type { InferenceParameters, Json, Tool, ToolCall, Message, RequestEventModes, RequestInput, Admission, AdmissionReceipt, ResponseOutcome, ObservationResponse, Observations, EventReference, ModelCompletedEvent, ModelDeltaEvent, ModelStreamFrame, RequestTimings, TimingInterval, TokenUsage, TokenPrice, CustomerPricing, BillingFacts, Status, RequestRecord, Step, Attempt, Span, TimelineRow, Timeline, Log, Inspection, RecordTypes, RequestEvent, Page, PageQuery, ModelScope, ModelResolution, WebhookEvent, RequestRecordsQuery, RequestListQuery } from "../../client-core/src/index";
3
+ export { ConstellationError, uuid7, normalizeTimestamp, parseOpenerSentence } from "../../client-core/src/index";
4
+ export type { InferenceParameters, Json, Tool, ToolCall, Message, RequestEventModes, RequestInput, Admission, AdmissionReceipt, ResponseOutcome, ObservationResponse, Observations, EventReference, ModelCompletedEvent, ModelDeltaEvent, ModelStreamFrame, RequestTimings, TimingInterval, TokenUsage, TokenPrice, CustomerPricing, BillingFacts, Status, RequestRecord, Step, Attempt, Span, TimelineRow, Timeline, Log, Inspection, RecordTypes, RequestEvent, Page, PageQuery, ModelScope, ModelResolution, WebhookEvent, RequestRecordsQuery, RequestListQuery, OpenerInput, OpenerTurn } from "../../client-core/src/index";
5
5
  /** OpenAPI contract filtered to this key's permissions and scope. */
6
6
  export type DiscoveryDocument = {
7
7
  openapi: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lionhummer/constellation",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "TypeScript client for durable model requests and model events",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",