@to11ai/sdk 0.0.2 → 0.1.1

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.
@@ -5,14 +5,26 @@ import { buildCacheKey, PromptCache } from "./cache";
5
5
  import type {
6
6
  ArchivePromptInput,
7
7
  ArchivePromptResult,
8
+ CreateProjectPromptLabelInput,
9
+ CreateProjectPromptLabelResult,
8
10
  CreatePromptInput,
9
11
  CreatePromptResult,
10
12
  CreatePromptVersionInput,
11
13
  CreatePromptVersionResult,
14
+ DeleteProjectPromptLabelInput,
15
+ DeleteProjectPromptLabelResult,
16
+ GetProjectPromptLabelInput,
17
+ GetProjectPromptLabelResult,
12
18
  GetPromptInput,
13
19
  GetPromptResult,
14
20
  GetPromptVersionInput,
15
21
  GetPromptVersionResult,
22
+ ListLabelEventsInput,
23
+ ListLabelEventsResult,
24
+ ListProjectPromptLabelsInput,
25
+ ListProjectPromptLabelsResult,
26
+ ListProjectPromptLabelUsagesInput,
27
+ ListProjectPromptLabelUsagesResult,
16
28
  ListPromptLabelsInput,
17
29
  ListPromptLabelsResult,
18
30
  ListPromptsInput,
@@ -24,6 +36,8 @@ import type {
24
36
  PaginatedResult,
25
37
  ResolvePromptInput,
26
38
  ResolvePromptResult,
39
+ UpdateProjectPromptLabelInput,
40
+ UpdateProjectPromptLabelResult,
27
41
  UpdatePromptInput,
28
42
  UpdatePromptResult,
29
43
  } from "./types";
@@ -34,14 +48,26 @@ export type {
34
48
  ArchivePromptInput,
35
49
  ArchivePromptResult,
36
50
  ChatMessage,
51
+ CreateProjectPromptLabelInput,
52
+ CreateProjectPromptLabelResult,
37
53
  CreatePromptInput,
38
54
  CreatePromptResult,
39
55
  CreatePromptVersionInput,
40
56
  CreatePromptVersionResult,
57
+ DeleteProjectPromptLabelInput,
58
+ DeleteProjectPromptLabelResult,
59
+ GetProjectPromptLabelInput,
60
+ GetProjectPromptLabelResult,
41
61
  GetPromptInput,
42
62
  GetPromptResult,
43
63
  GetPromptVersionInput,
44
64
  GetPromptVersionResult,
65
+ ListLabelEventsInput,
66
+ ListLabelEventsResult,
67
+ ListProjectPromptLabelsInput,
68
+ ListProjectPromptLabelsResult,
69
+ ListProjectPromptLabelUsagesInput,
70
+ ListProjectPromptLabelUsagesResult,
45
71
  ListPromptLabelsInput,
46
72
  ListPromptLabelsResult,
47
73
  ListPromptsInput,
@@ -52,15 +78,26 @@ export type {
52
78
  MovePromptLabelResult,
53
79
  PaginatedResult,
54
80
  PaginationInfo,
81
+ PaletteToken,
82
+ ProjectPromptLabel,
83
+ ProjectPromptLabelUsage,
55
84
  Prompt,
56
85
  PromptFormat,
86
+ PromptLabelEvent,
57
87
  PromptLabelPointer,
58
88
  PromptStatus,
59
89
  PromptVersion,
90
+ RenderedChatMessage,
60
91
  RenderedPrompt,
61
92
  ResolvedPrompt,
62
93
  ResolvePromptInput,
63
94
  ResolvePromptResult,
95
+ RowError,
96
+ TextChatMessage,
97
+ TextChatRole,
98
+ ToolChatMessage,
99
+ UpdateProjectPromptLabelInput,
100
+ UpdateProjectPromptLabelResult,
64
101
  UpdatePromptInput,
65
102
  UpdatePromptResult,
66
103
  } from "./types";
@@ -80,6 +117,38 @@ function getProjectPromptPath(projectId: string, promptId: string): string {
80
117
  return `${getProjectPromptsPath(projectId)}/${encodeURIComponent(promptId)}`;
81
118
  }
82
119
 
120
+ function getProjectPromptLabelsPath(projectId: string): string {
121
+ return `/v1/projects/${encodeURIComponent(projectId)}/prompt-labels`;
122
+ }
123
+
124
+ function getProjectPromptLabelPath(projectId: string, label: string): string {
125
+ return `${getProjectPromptLabelsPath(projectId)}/${encodeURIComponent(label)}`;
126
+ }
127
+
128
+ /**
129
+ * Builds the query string for `listLabelEvents`. Scope-specific filters:
130
+ * - `label` is forwarded for `"prompt"` and `"project"` scopes only
131
+ * (it's already encoded in the path for `"label"` scope).
132
+ * - `promptId` is forwarded for `"project"` scope only.
133
+ *
134
+ * `undefined` values are omitted so the wire request stays minimal.
135
+ */
136
+ function buildLabelEventsQuery(input: ListLabelEventsInput): string {
137
+ const params = new URLSearchParams();
138
+ if (input.page !== undefined) params.set("page", String(input.page));
139
+ if (input.limit !== undefined) params.set("limit", String(input.limit));
140
+ if (input.since !== undefined) params.set("since", input.since);
141
+ if (input.until !== undefined) params.set("until", input.until);
142
+ if (input.scope === "prompt" && input.label !== undefined) {
143
+ params.set("label", input.label);
144
+ }
145
+ if (input.scope === "project") {
146
+ if (input.label !== undefined) params.set("label", input.label);
147
+ if (input.promptId !== undefined) params.set("promptId", input.promptId);
148
+ }
149
+ return params.toString();
150
+ }
151
+
83
152
  export class PromptResource {
84
153
  private readonly transport: Transport;
85
154
  private readonly cache: PromptCache;
@@ -276,4 +345,167 @@ export class PromptResource {
276
345
  throw err;
277
346
  }
278
347
  }
348
+
349
+ // ─── Project Label Registry (TO11-666 / phase 2) ─────────────────────────
350
+
351
+ /**
352
+ * List the project-level label registry.
353
+ *
354
+ * Excludes archived entries by default. Pass `includeArchived: true` to
355
+ * include rows with a non-null `archivedAt` (useful for admin / history
356
+ * affordances). Gated on `prompt:read` server-side.
357
+ */
358
+ async listProjectLabels(
359
+ input: ListProjectPromptLabelsInput,
360
+ ): Promise<ListProjectPromptLabelsResult> {
361
+ const { projectId, includeArchived } = input;
362
+ const envelope =
363
+ await this.transport.request<ListProjectPromptLabelsResult>(
364
+ "GET",
365
+ getProjectPromptLabelsPath(projectId),
366
+ {
367
+ query: includeArchived ? { includeArchived: "true" } : undefined,
368
+ },
369
+ );
370
+ return unwrap(envelope);
371
+ }
372
+
373
+ /**
374
+ * Create a new project label.
375
+ *
376
+ * Requires the `project:manage_labels` permission. Returns the canonical
377
+ * row as written to the registry (including server-assigned
378
+ * `displayColor` if not supplied).
379
+ */
380
+ async createProjectLabel(
381
+ input: CreateProjectPromptLabelInput,
382
+ ): Promise<CreateProjectPromptLabelResult> {
383
+ const { projectId, ...body } = input;
384
+ const envelope =
385
+ await this.transport.request<CreateProjectPromptLabelResult>(
386
+ "POST",
387
+ getProjectPromptLabelsPath(projectId),
388
+ { body },
389
+ );
390
+ return unwrap(envelope);
391
+ }
392
+
393
+ async getProjectLabel(
394
+ input: GetProjectPromptLabelInput,
395
+ ): Promise<GetProjectPromptLabelResult> {
396
+ const envelope = await this.transport.request<GetProjectPromptLabelResult>(
397
+ "GET",
398
+ getProjectPromptLabelPath(input.projectId, input.label),
399
+ );
400
+ return unwrap(envelope);
401
+ }
402
+
403
+ /**
404
+ * PATCH a project label.
405
+ *
406
+ * Note on `description` semantics: `undefined` means "do not change" —
407
+ * the SDK omits the field from the body in that case. Pass
408
+ * `description: ""` (empty string) or `description: null` to explicitly
409
+ * clear it (the server normalises both).
410
+ */
411
+ async updateProjectLabel(
412
+ input: UpdateProjectPromptLabelInput,
413
+ ): Promise<UpdateProjectPromptLabelResult> {
414
+ const { projectId, label, ...rest } = input;
415
+ const body: Record<string, unknown> = {};
416
+ if (rest.isDefault !== undefined) body.isDefault = rest.isDefault;
417
+ if (rest.description !== undefined) body.description = rest.description;
418
+ if (rest.displayColor !== undefined) body.displayColor = rest.displayColor;
419
+ const envelope =
420
+ await this.transport.request<UpdateProjectPromptLabelResult>(
421
+ "PATCH",
422
+ getProjectPromptLabelPath(projectId, label),
423
+ { body },
424
+ );
425
+ return unwrap(envelope);
426
+ }
427
+
428
+ /**
429
+ * Archive (soft-delete) a project label.
430
+ *
431
+ * Returns the archived row (matches the `archive` pattern — the API
432
+ * returns 200 with the soft-deleted record so call sites can show
433
+ * "archived just now" without a follow-up GET). System labels
434
+ * (`isSystem: true`) cannot be archived; the server returns 409
435
+ * `PROJECT_LABEL_SYSTEM_NOT_DELETABLE` for those.
436
+ */
437
+ async deleteProjectLabel(
438
+ input: DeleteProjectPromptLabelInput,
439
+ ): Promise<DeleteProjectPromptLabelResult> {
440
+ const envelope =
441
+ await this.transport.request<DeleteProjectPromptLabelResult>(
442
+ "DELETE",
443
+ getProjectPromptLabelPath(input.projectId, input.label),
444
+ );
445
+ return unwrap(envelope);
446
+ }
447
+
448
+ // ─── Reverse lookup (TO11-667 / phase 3) ─────────────────────────────────
449
+
450
+ /**
451
+ * List every prompt currently pointed at by `label`.
452
+ *
453
+ * Returns an empty list if no prompt references the label. Gated on
454
+ * `prompt:read` server-side.
455
+ */
456
+ async listProjectLabelUsages(
457
+ input: ListProjectPromptLabelUsagesInput,
458
+ ): Promise<ListProjectPromptLabelUsagesResult> {
459
+ const envelope =
460
+ await this.transport.request<ListProjectPromptLabelUsagesResult>(
461
+ "GET",
462
+ `${getProjectPromptLabelPath(input.projectId, input.label)}/usages`,
463
+ );
464
+ return unwrap(envelope);
465
+ }
466
+
467
+ // ─── Event history (TO11-668 / phase 4) ──────────────────────────────────
468
+
469
+ /**
470
+ * List immutable label-movement events at one of three scopes.
471
+ *
472
+ * - `scope: "label"` — narrows to a single label on a single prompt.
473
+ * Requires `promptId` and `label`.
474
+ * - `scope: "prompt"` — all label moves on the prompt. Requires
475
+ * `promptId`. `label` is an optional server-side filter.
476
+ * - `scope: "project"` — all label moves in the project. `label` and
477
+ * `promptId` are optional filters.
478
+ *
479
+ * `since` / `until` are ISO-8601 timestamps. The server returns 422
480
+ * `INVALID_DATE_RANGE` if `since` is after `until`.
481
+ *
482
+ * Gated on `prompt:read` server-side.
483
+ */
484
+ async listLabelEvents(
485
+ input: ListLabelEventsInput,
486
+ ): Promise<ListLabelEventsResult> {
487
+ const projectSeg = encodeURIComponent(input.projectId);
488
+ let path: string;
489
+ switch (input.scope) {
490
+ case "label":
491
+ path = `/v1/projects/${projectSeg}/prompts/${encodeURIComponent(
492
+ input.promptId,
493
+ )}/labels/${encodeURIComponent(input.label)}/events`;
494
+ break;
495
+ case "prompt":
496
+ path = `/v1/projects/${projectSeg}/prompts/${encodeURIComponent(
497
+ input.promptId,
498
+ )}/label-events`;
499
+ break;
500
+ case "project":
501
+ path = `/v1/projects/${projectSeg}/label-events`;
502
+ break;
503
+ }
504
+ const query = buildLabelEventsQuery(input);
505
+ const envelope = await this.transport.request<ListLabelEventsResult>(
506
+ "GET",
507
+ query ? `${path}?${query}` : path,
508
+ );
509
+ return unwrap(envelope);
510
+ }
279
511
  }
@@ -1,13 +1,91 @@
1
+ /**
2
+ * Per-row pointer carried on 422 validation envelopes.
3
+ *
4
+ * The published `@to11ai/sdk` package does NOT depend on the internal
5
+ * `@to11/shared-kernel` workspace package (it would pull workspace-only
6
+ * types into a public npm release). The shape below is therefore
7
+ * declared identically to `RowError` in
8
+ * `packages/shared-kernel/src/schemas/api-schemas.ts` — keep them in
9
+ * lockstep when adding new fields.
10
+ *
11
+ * 422 responses for prompt-version writes carry an optional
12
+ * `error.rowErrors` array — one entry per failing field. Each row
13
+ * carries:
14
+ *
15
+ * - `path`: JSON-pointer-equivalent array, e.g.
16
+ * `["messages", 3, "name"]`.
17
+ * - `code`: stable identifier such as `tool_name_invalid_charset`
18
+ * or `messages_empty` — see the platform spec for the full set.
19
+ * - `message`: a human-readable summary.
20
+ *
21
+ * SDK consumers can detect the structured shape by inspecting
22
+ * `error.code === "validation_failed"` and walking `error.rowErrors`
23
+ * (always optional; absent on non-validation 422s).
24
+ */
25
+ export interface RowError {
26
+ path: ReadonlyArray<string | number>;
27
+ code: string;
28
+ message: string;
29
+ }
30
+
1
31
  // ─── Shared primitives ─────────────────────────────────────────────────────
2
32
 
3
33
  export type PromptStatus = "active" | "archived";
4
34
  export type PromptFormat = "text" | "chat";
5
35
 
6
- export interface ChatMessage {
7
- role: "system" | "user" | "assistant" | "tool";
36
+ /**
37
+ * Roles supported by `TextChatMessage`.
38
+ *
39
+ * V1 server contract: `developer` is accepted at the wire boundary and
40
+ * rendered as `user` in `:resolve` responses. The literal is preserved
41
+ * here so SDK consumers can author developer-tagged messages today and
42
+ * V2 can flip the rendered role without a wire-level break.
43
+ */
44
+ export type TextChatRole = "system" | "user" | "assistant" | "developer";
45
+
46
+ /**
47
+ * Text-style chat message. Carries a Liquid template body in `content`.
48
+ *
49
+ * `disabled` skips the message at render time but preserves it through
50
+ * edit / list / get round-trips so authors can toggle messages without
51
+ * losing them.
52
+ */
53
+ export interface TextChatMessage {
54
+ role: TextChatRole;
8
55
  content: string;
56
+ disabled?: boolean;
57
+ }
58
+
59
+ /**
60
+ * Tool-call chat message. Structurally distinct from text messages: it
61
+ * carries `name` / `description` / `parameters` instead of `content`.
62
+ *
63
+ * - `name`: 1..200 chars, matches the tool name the gateway exposes to
64
+ * providers.
65
+ * - `description`: optional human-readable summary surfaced in the
66
+ * provider's tool-choice UI.
67
+ * - `parameters`: JSON Schema (an object) describing the tool's
68
+ * arguments. Not validated against the JSON Schema meta-schema at
69
+ * edit time — the gateway is the source of truth.
70
+ */
71
+ export interface ToolChatMessage {
72
+ role: "tool";
73
+ name: string;
74
+ description?: string;
75
+ parameters: unknown;
76
+ disabled?: boolean;
9
77
  }
10
78
 
79
+ /**
80
+ * Discriminated union of text and tool chat messages.
81
+ *
82
+ * Legacy `{role: "tool", content: "..."}` rows are auto-upgraded by the
83
+ * server on read: they decode as `{role: "tool", name: "(unnamed)",
84
+ * description: <legacy content>, parameters: {}}`. Writes always emit
85
+ * the new shape.
86
+ */
87
+ export type ChatMessage = TextChatMessage | ToolChatMessage;
88
+
11
89
  // ─── Prompt ────────────────────────────────────────────────────────────────
12
90
 
13
91
  export interface Prompt {
@@ -50,9 +128,22 @@ export interface PromptLabelPointer {
50
128
 
51
129
  // ─── ResolvedPrompt ────────────────────────────────────────────────────────
52
130
 
131
+ /**
132
+ * A single message in a rendered chat prompt returned by `:resolve`.
133
+ *
134
+ * Always carries a flat `{role, content}` shape — even tool/developer
135
+ * messages, since the server normalises them at render time
136
+ * (V1 contract: tool messages are filtered, developer is mapped to user).
137
+ * The `role` literal stays wide so V2 changes are wire-compatible.
138
+ */
139
+ export interface RenderedChatMessage {
140
+ role: "system" | "user" | "assistant" | "developer" | "tool";
141
+ content: string;
142
+ }
143
+
53
144
  export type RenderedPrompt =
54
145
  | { format: "text"; text: string }
55
- | { format: "chat"; messages: ChatMessage[] };
146
+ | { format: "chat"; messages: RenderedChatMessage[] };
56
147
 
57
148
  export interface ResolvedPrompt {
58
149
  promptId: string;
@@ -200,3 +291,179 @@ export interface ResolvePromptInput {
200
291
  }
201
292
 
202
293
  export type ResolvePromptResult = ResolvedPrompt;
294
+
295
+ // ─── Project label registry (TO11-666 / phase 2) ───────────────────────────
296
+ //
297
+ // Wire shapes mirror the API's `ProjectPromptLabel`, `ProjectPromptLabelUsage`,
298
+ // and `PromptLabelEvent` schemas. The palette token enum is kept in lockstep
299
+ // with the DB CHECK constraint introduced in the prompt-label-registry
300
+ // migration.
301
+
302
+ /** Palette token assigned to a project label for UI rendering. */
303
+ export type PaletteToken =
304
+ | "accent"
305
+ | "red"
306
+ | "orange"
307
+ | "amber"
308
+ | "green"
309
+ | "teal"
310
+ | "blue"
311
+ | "indigo"
312
+ | "purple"
313
+ | "pink";
314
+
315
+ /**
316
+ * Project-level label registry entry.
317
+ *
318
+ * System labels (`isSystem: true`) cannot be archived. `archivedAt` is
319
+ * non-null for soft-deleted entries; the list endpoint excludes archived
320
+ * entries by default — pass `includeArchived: true` if the call site needs
321
+ * them (e.g. admin / history affordances).
322
+ */
323
+ export interface ProjectPromptLabel {
324
+ projectId: string;
325
+ label: string;
326
+ isSystem: boolean;
327
+ isDefault: boolean;
328
+ description: string | null;
329
+ displayColor: PaletteToken;
330
+ createdByUserId: string;
331
+ createdAt: string;
332
+ updatedAt: string;
333
+ archivedAt: string | null;
334
+ }
335
+
336
+ /**
337
+ * A single prompt currently pointed at by a given project label.
338
+ *
339
+ * Returned by `GET /v1/projects/:projectId/prompt-labels/:label/usages`.
340
+ * `updatedAt` / `updatedByUserId` carry the last-move metadata so the UI
341
+ * can render a "moved 3 days ago by …" affordance without a second
342
+ * roundtrip.
343
+ */
344
+ export interface ProjectPromptLabelUsage {
345
+ promptId: string;
346
+ promptSlug: string;
347
+ promptName: string;
348
+ versionId: string;
349
+ version: number;
350
+ updatedAt: string;
351
+ updatedByUserId: string;
352
+ }
353
+
354
+ /**
355
+ * An immutable history row for a single label movement.
356
+ *
357
+ * `fromVersionId` is `null` for the first time the label is pointed at a
358
+ * version (no prior pointer). `reason` is the optional caller-supplied
359
+ * audit string.
360
+ */
361
+ export interface PromptLabelEvent {
362
+ id: string;
363
+ promptId: string;
364
+ label: string;
365
+ fromVersionId: string | null;
366
+ toVersionId: string;
367
+ movedByUserId: string;
368
+ movedAt: string;
369
+ reason: string | null;
370
+ }
371
+
372
+ // ─── Project label registry inputs ─────────────────────────────────────────
373
+
374
+ export interface ListProjectPromptLabelsInput {
375
+ projectId: string;
376
+ /** Set true to include archived (soft-deleted) entries. Default: false. */
377
+ includeArchived?: boolean;
378
+ }
379
+
380
+ export type ListProjectPromptLabelsResult = ProjectPromptLabel[];
381
+
382
+ export interface CreateProjectPromptLabelInput {
383
+ projectId: string;
384
+ label: string;
385
+ isDefault?: boolean;
386
+ description?: string;
387
+ displayColor?: PaletteToken;
388
+ }
389
+
390
+ export type CreateProjectPromptLabelResult = ProjectPromptLabel;
391
+
392
+ export interface GetProjectPromptLabelInput {
393
+ projectId: string;
394
+ label: string;
395
+ }
396
+
397
+ export type GetProjectPromptLabelResult = ProjectPromptLabel;
398
+
399
+ export interface UpdateProjectPromptLabelInput {
400
+ projectId: string;
401
+ label: string;
402
+ isDefault?: boolean;
403
+ description?: string | null;
404
+ displayColor?: PaletteToken;
405
+ }
406
+
407
+ export type UpdateProjectPromptLabelResult = ProjectPromptLabel;
408
+
409
+ export interface DeleteProjectPromptLabelInput {
410
+ projectId: string;
411
+ label: string;
412
+ }
413
+
414
+ /**
415
+ * The DELETE endpoint returns 200 with the archived row (mirrors the
416
+ * `archivePrompt` pattern — call sites can show "archived just now"
417
+ * without a follow-up GET). System labels (`isSystem: true`) cannot be
418
+ * archived; the server returns 409 `PROJECT_LABEL_SYSTEM_NOT_DELETABLE`.
419
+ */
420
+ export type DeleteProjectPromptLabelResult = ProjectPromptLabel;
421
+
422
+ export interface ListProjectPromptLabelUsagesInput {
423
+ projectId: string;
424
+ label: string;
425
+ }
426
+
427
+ export type ListProjectPromptLabelUsagesResult = ProjectPromptLabelUsage[];
428
+
429
+ // ─── Label event history (TO11-668 / phase 4) ──────────────────────────────
430
+ //
431
+ // One polymorphic SDK method `listLabelEvents` covers all three URL shapes
432
+ // the server exposes. The `scope` discriminator selects between:
433
+ // - `"label"` → `/v1/projects/:p/prompts/:pp/labels/:l/events`
434
+ // - `"prompt"` → `/v1/projects/:p/prompts/:pp/label-events`
435
+ // - `"project"` → `/v1/projects/:p/label-events`
436
+
437
+ export type ListLabelEventsInput =
438
+ | {
439
+ scope: "label";
440
+ projectId: string;
441
+ promptId: string;
442
+ label: string;
443
+ page?: number;
444
+ limit?: number;
445
+ since?: string;
446
+ until?: string;
447
+ }
448
+ | {
449
+ scope: "prompt";
450
+ projectId: string;
451
+ promptId: string;
452
+ label?: string;
453
+ page?: number;
454
+ limit?: number;
455
+ since?: string;
456
+ until?: string;
457
+ }
458
+ | {
459
+ scope: "project";
460
+ projectId: string;
461
+ promptId?: string;
462
+ label?: string;
463
+ page?: number;
464
+ limit?: number;
465
+ since?: string;
466
+ until?: string;
467
+ };
468
+
469
+ export type ListLabelEventsResult = PaginatedResult<PromptLabelEvent>;