@popcomputer/structured-chat 0.1.0 → 0.2.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.
Files changed (50) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +175 -108
  3. package/dist/adapters/openai-compatible-model.d.ts +4 -4
  4. package/dist/adapters/openai-compatible-model.js +33 -31
  5. package/dist/core/answer.d.ts +13 -13
  6. package/dist/core/answer.js +21 -12
  7. package/dist/core/chat.d.ts +12 -15
  8. package/dist/core/chat.js +36 -27
  9. package/dist/core/collect-stage.d.ts +28 -15
  10. package/dist/core/collect-stage.js +58 -37
  11. package/dist/core/command.d.ts +1 -1
  12. package/dist/core/command.js +1 -1
  13. package/dist/core/debug-protocol.d.ts +126 -0
  14. package/dist/core/debug-protocol.js +19 -0
  15. package/dist/core/debug.d.ts +103 -0
  16. package/dist/core/debug.js +276 -0
  17. package/dist/core/json-value.d.ts +1 -1
  18. package/dist/core/json-value.js +8 -1
  19. package/dist/core/model-guard.d.ts +2 -3
  20. package/dist/core/model-guard.js +4 -4
  21. package/dist/core/model.d.ts +15 -19
  22. package/dist/core/model.js +22 -10
  23. package/dist/core/protocol.d.ts +84 -79
  24. package/dist/core/protocol.js +18 -12
  25. package/dist/core/question.js +17 -15
  26. package/dist/core/repair.js +1 -1
  27. package/dist/core/session.d.ts +22 -28
  28. package/dist/core/session.js +16 -7
  29. package/dist/core/stage-name.d.ts +1 -1
  30. package/dist/core/stage-name.js +1 -1
  31. package/dist/core/stage.d.ts +4 -4
  32. package/dist/core/stage.js +11 -8
  33. package/dist/core/tool-set.js +6 -6
  34. package/dist/core/tool.d.ts +36 -39
  35. package/dist/core/tool.js +58 -31
  36. package/dist/core/view.d.ts +64 -39
  37. package/dist/core/view.js +12 -15
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +2 -0
  40. package/dist/integrations/assistant-ui-debug.d.ts +25 -0
  41. package/dist/integrations/assistant-ui-debug.js +1162 -0
  42. package/dist/integrations/assistant-ui.d.ts +10 -3
  43. package/dist/integrations/assistant-ui.js +48 -18
  44. package/dist/testing/in-memory-session-store.js +1 -1
  45. package/dist/testing/scenario.js +6 -6
  46. package/examples/answer-modes.ts +60 -49
  47. package/examples/prompt-injection-policy.ts +20 -20
  48. package/examples/resource-search.ts +104 -0
  49. package/package.json +15 -3
  50. package/examples/agency-search.ts +0 -101
@@ -2,74 +2,74 @@ import { Effect, Schema } from "effect";
2
2
  import { ChatSessionIdSchema, ChatSessionRevisionSchema } from "./session.js";
3
3
  /** Bounded plain text emitted by a structured chat presenter. */
4
4
  export declare const AssistantTextPartSchema: Schema.Struct<{
5
- type: Schema.Literal<["text"]>;
6
- text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
5
+ readonly type: Schema.Literal<"text">;
6
+ readonly text: Schema.Trimmed;
7
7
  }>;
8
8
  /** Provider-neutral named data emitted by a structured chat presenter. */
9
9
  export declare const AssistantDataPartSchema: Schema.Struct<{
10
- type: Schema.Literal<["data"]>;
11
- name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
12
- data: typeof Schema.Unknown;
10
+ readonly type: Schema.Literal<"data">;
11
+ readonly name: Schema.Trimmed;
12
+ readonly data: Schema.Unknown;
13
13
  }>;
14
14
  /** Message parts transported from a structured chat action to a browser. */
15
- export declare const AssistantMessagePartSchema: Schema.Union<[Schema.Struct<{
16
- type: Schema.Literal<["text"]>;
17
- text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
15
+ export declare const AssistantMessagePartSchema: Schema.Union<readonly [Schema.Struct<{
16
+ readonly type: Schema.Literal<"text">;
17
+ readonly text: Schema.Trimmed;
18
18
  }>, Schema.Struct<{
19
- type: Schema.Literal<["data"]>;
20
- name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
21
- data: typeof Schema.Unknown;
19
+ readonly type: Schema.Literal<"data">;
20
+ readonly name: Schema.Trimmed;
21
+ readonly data: Schema.Unknown;
22
22
  }>]>;
23
23
  /** Message parts transported from a structured chat action to a browser. */
24
24
  export type AssistantMessagePart = Schema.Schema.Type<typeof AssistantMessagePartSchema>;
25
25
  /** Strict assistant message returned by one structured chat action. */
26
26
  export declare const StructuredChatAssistantMessageSchema: Schema.Struct<{
27
- role: Schema.Literal<["assistant"]>;
28
- content: Schema.filter<Schema.NonEmptyArray<Schema.Union<[Schema.Struct<{
29
- type: Schema.Literal<["text"]>;
30
- text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
27
+ readonly role: Schema.Literal<"assistant">;
28
+ readonly content: Schema.NonEmptyArray<Schema.Union<readonly [Schema.Struct<{
29
+ readonly type: Schema.Literal<"text">;
30
+ readonly text: Schema.Trimmed;
31
31
  }>, Schema.Struct<{
32
- type: Schema.Literal<["data"]>;
33
- name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
34
- data: typeof Schema.Unknown;
35
- }>]>>>;
32
+ readonly type: Schema.Literal<"data">;
33
+ readonly name: Schema.Trimmed;
34
+ readonly data: Schema.Unknown;
35
+ }>]>>;
36
36
  }>;
37
37
  /** Strict assistant message returned by one structured chat action. */
38
38
  export type StructuredChatAssistantMessage = Schema.Schema.Type<typeof StructuredChatAssistantMessageSchema>;
39
39
  /** Opaque browser-held reference to one server-owned chat session. */
40
40
  export declare const StructuredChatSessionReferenceSchema: Schema.Struct<{
41
- id: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
42
- revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
41
+ readonly id: Schema.Trimmed;
42
+ readonly revision: Schema.Trimmed;
43
43
  }>;
44
44
  /** Opaque browser-held reference to one server-owned chat session. */
45
45
  export type StructuredChatSessionReference = Schema.Schema.Type<typeof StructuredChatSessionReferenceSchema>;
46
46
  /** Browser request carrying no server-owned chat state. */
47
47
  export declare const StructuredChatTurnRequestSchema: Schema.Struct<{
48
- session: Schema.optional<Schema.Struct<{
49
- id: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
50
- revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
48
+ readonly session: Schema.optional<Schema.Struct<{
49
+ readonly id: Schema.Trimmed;
50
+ readonly revision: Schema.Trimmed;
51
51
  }>>;
52
- message: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
52
+ readonly message: Schema.Trimmed;
53
53
  }>;
54
54
  /** Browser request carrying no server-owned chat state. */
55
55
  export type StructuredChatTurnRequest = Schema.Schema.Type<typeof StructuredChatTurnRequestSchema>;
56
56
  /** Versioned browser response for one persisted structured chat turn. */
57
57
  export declare const StructuredChatTurnResponseSchema: Schema.Struct<{
58
- schemaVersion: Schema.Literal<[1]>;
59
- session: Schema.optional<Schema.Struct<{
60
- id: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
61
- revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
58
+ readonly schemaVersion: Schema.Literal<1>;
59
+ readonly session: Schema.optional<Schema.Struct<{
60
+ readonly id: Schema.Trimmed;
61
+ readonly revision: Schema.Trimmed;
62
62
  }>>;
63
- message: Schema.Struct<{
64
- role: Schema.Literal<["assistant"]>;
65
- content: Schema.filter<Schema.NonEmptyArray<Schema.Union<[Schema.Struct<{
66
- type: Schema.Literal<["text"]>;
67
- text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
63
+ readonly message: Schema.Struct<{
64
+ readonly role: Schema.Literal<"assistant">;
65
+ readonly content: Schema.NonEmptyArray<Schema.Union<readonly [Schema.Struct<{
66
+ readonly type: Schema.Literal<"text">;
67
+ readonly text: Schema.Trimmed;
68
68
  }>, Schema.Struct<{
69
- type: Schema.Literal<["data"]>;
70
- name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
71
- data: typeof Schema.Unknown;
72
- }>]>>>;
69
+ readonly type: Schema.Literal<"data">;
70
+ readonly name: Schema.Trimmed;
71
+ readonly data: Schema.Unknown;
72
+ }>]>>;
73
73
  }>;
74
74
  }>;
75
75
  /** Versioned browser response for one persisted structured chat turn. */
@@ -79,102 +79,107 @@ export declare const CollectQuestionView: {
79
79
  name: "collect_question";
80
80
  version: 1;
81
81
  inputSchema: Schema.Struct<{
82
- stage: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
83
- field: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
84
- text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
85
- options: Schema.filter<Schema.Array$<Schema.Struct<{
86
- label: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
87
- }>>>;
82
+ readonly stage: Schema.Trimmed;
83
+ readonly field: Schema.Trimmed;
84
+ readonly text: Schema.Trimmed;
85
+ readonly options: Schema.$Array<Schema.Struct<{
86
+ readonly label: Schema.Trimmed;
87
+ }>>;
88
88
  }>;
89
89
  dataSchema: Schema.Struct<{
90
- readonly schemaVersion: Schema.Literal<[1]>;
90
+ readonly schemaVersion: Schema.Literal<1>;
91
91
  } & {
92
- stage: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
93
- field: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
94
- text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
95
- options: Schema.filter<Schema.Array$<Schema.Struct<{
96
- label: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
97
- }>>>;
92
+ readonly stage: Schema.Trimmed;
93
+ readonly field: Schema.Trimmed;
94
+ readonly text: Schema.Trimmed;
95
+ readonly options: Schema.$Array<Schema.Struct<{
96
+ readonly label: Schema.Trimmed;
97
+ }>>;
98
98
  }>;
99
99
  partSchema: Schema.Struct<{
100
- readonly type: Schema.Literal<["data"]>;
101
- readonly name: Schema.Literal<["collect_question"]>;
100
+ readonly type: Schema.Literal<"data">;
101
+ readonly name: Schema.Literal<"collect_question">;
102
102
  readonly data: Schema.Struct<{
103
- readonly schemaVersion: Schema.Literal<[1]>;
103
+ readonly schemaVersion: Schema.Literal<1>;
104
104
  } & {
105
- stage: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
106
- field: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
107
- text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
108
- options: Schema.filter<Schema.Array$<Schema.Struct<{
109
- label: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
110
- }>>>;
105
+ readonly stage: Schema.Trimmed;
106
+ readonly field: Schema.Trimmed;
107
+ readonly text: Schema.Trimmed;
108
+ readonly options: Schema.$Array<Schema.Struct<{
109
+ readonly label: Schema.Trimmed;
110
+ }>>;
111
111
  }>;
112
112
  }>;
113
113
  make: (input: {
114
- readonly text: string;
115
114
  readonly stage: string;
116
115
  readonly field: string;
116
+ readonly text: string;
117
117
  readonly options: readonly {
118
118
  readonly label: string;
119
119
  }[];
120
120
  }) => {
121
- readonly name: "collect_question";
122
121
  readonly type: "data";
122
+ readonly name: "collect_question";
123
123
  readonly data: {
124
124
  readonly schemaVersion: 1;
125
- readonly text: string;
126
125
  readonly stage: string;
127
126
  readonly field: string;
127
+ readonly text: string;
128
128
  readonly options: readonly {
129
129
  readonly label: string;
130
130
  }[];
131
131
  };
132
132
  };
133
- parseData: (input: import("./json-value.js").JsonValue) => Effect.Effect<{
134
- readonly name: "collect_question";
133
+ parseData: (input: {
134
+ readonly stage: string;
135
+ readonly field: string;
136
+ readonly text: string;
137
+ readonly options: readonly {
138
+ readonly label: string;
139
+ }[];
140
+ }) => Effect.Effect<{
135
141
  readonly type: "data";
142
+ readonly name: "collect_question";
136
143
  readonly data: {
137
144
  readonly schemaVersion: 1;
138
- readonly text: string;
139
145
  readonly stage: string;
140
146
  readonly field: string;
147
+ readonly text: string;
141
148
  readonly options: readonly {
142
149
  readonly label: string;
143
150
  }[];
144
151
  };
145
- }, import("effect/ParseResult").ParseError, never>;
152
+ }, Schema.SchemaError, never>;
146
153
  decode: (input: import("./json-value.js").JsonValue) => Effect.Effect<{
147
- readonly name: "collect_question";
148
154
  readonly type: "data";
155
+ readonly name: "collect_question";
149
156
  readonly data: {
150
157
  readonly schemaVersion: 1;
151
- readonly text: string;
152
158
  readonly stage: string;
153
159
  readonly field: string;
160
+ readonly text: string;
154
161
  readonly options: readonly {
155
162
  readonly label: string;
156
163
  }[];
157
164
  };
158
- }, import("effect/ParseResult").ParseError, never>;
159
- decodeEither: (input: import("./json-value.js").JsonValue) => import("effect/Either").Either<{
160
- readonly name: "collect_question";
165
+ }, Schema.SchemaError, never>;
166
+ decodeResult: (input: import("./json-value.js").JsonValue) => import("effect/Result").Result<{
161
167
  readonly type: "data";
168
+ readonly name: "collect_question";
162
169
  readonly data: {
163
170
  readonly schemaVersion: 1;
164
- readonly text: string;
165
171
  readonly stage: string;
166
172
  readonly field: string;
173
+ readonly text: string;
167
174
  readonly options: readonly {
168
175
  readonly label: string;
169
176
  }[];
170
177
  };
171
- }, import("effect/ParseResult").ParseError>;
178
+ }, Schema.SchemaError>;
172
179
  };
173
- declare const InvalidChatPresentation_base: Schema.TaggedErrorClass<InvalidChatPresentation, "InvalidChatPresentation", {
174
- readonly _tag: Schema.tag<"InvalidChatPresentation">;
175
- } & {
176
- reason: Schema.Literal<["invalid_message"]>;
177
- }>;
180
+ declare const InvalidChatPresentation_base: Schema.Class<InvalidChatPresentation, Schema.TaggedStruct<"InvalidChatPresentation", {
181
+ readonly reason: Schema.Literal<"invalid_message">;
182
+ }>, import("effect/Cause").YieldableError>;
178
183
  /** Safe reason that application-owned message presentation was rejected. */
179
184
  export declare class InvalidChatPresentation extends InvalidChatPresentation_base {
180
185
  }
@@ -4,20 +4,23 @@ import { defineView } from "./view.js";
4
4
  /** Bounded plain text emitted by a structured chat presenter. */
5
5
  export const AssistantTextPartSchema = Schema.Struct({
6
6
  type: Schema.Literal("text"),
7
- text: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(20_000)),
7
+ text: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(20_000)),
8
8
  });
9
9
  /** Provider-neutral named data emitted by a structured chat presenter. */
10
10
  export const AssistantDataPartSchema = Schema.Struct({
11
11
  type: Schema.Literal("data"),
12
- name: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100), Schema.pattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/)),
12
+ name: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100), Schema.isPattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/)),
13
13
  data: Schema.Unknown,
14
14
  });
15
15
  /** Message parts transported from a structured chat action to a browser. */
16
- export const AssistantMessagePartSchema = Schema.Union(AssistantTextPartSchema, AssistantDataPartSchema);
16
+ export const AssistantMessagePartSchema = Schema.Union([
17
+ AssistantTextPartSchema,
18
+ AssistantDataPartSchema,
19
+ ]);
17
20
  /** Strict assistant message returned by one structured chat action. */
18
21
  export const StructuredChatAssistantMessageSchema = Schema.Struct({
19
22
  role: Schema.Literal("assistant"),
20
- content: Schema.NonEmptyArray(AssistantMessagePartSchema).pipe(Schema.maxItems(20)),
23
+ content: Schema.NonEmptyArray(AssistantMessagePartSchema).check(Schema.isMaxLength(20)),
21
24
  });
22
25
  /** Opaque browser-held reference to one server-owned chat session. */
23
26
  export const StructuredChatSessionReferenceSchema = Schema.Struct({
@@ -27,7 +30,7 @@ export const StructuredChatSessionReferenceSchema = Schema.Struct({
27
30
  /** Browser request carrying no server-owned chat state. */
28
31
  export const StructuredChatTurnRequestSchema = Schema.Struct({
29
32
  session: Schema.optional(StructuredChatSessionReferenceSchema),
30
- message: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(50_000)),
33
+ message: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(50_000)),
31
34
  });
32
35
  /** Versioned browser response for one persisted structured chat turn. */
33
36
  export const StructuredChatTurnResponseSchema = Schema.Struct({
@@ -40,25 +43,28 @@ export const CollectQuestionView = defineView({
40
43
  name: "collect_question",
41
44
  version: 1,
42
45
  schema: Schema.Struct({
43
- stage: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100)),
44
- field: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100)),
45
- text: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(500)),
46
+ stage: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100)),
47
+ field: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100)),
48
+ text: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(500)),
46
49
  options: Schema.Array(Schema.Struct({
47
- label: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100)),
48
- })).pipe(Schema.maxItems(20)),
50
+ label: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100)),
51
+ })).check(Schema.isMaxLength(20)),
49
52
  }),
50
53
  });
51
54
  /** Safe reason that application-owned message presentation was rejected. */
52
55
  export class InvalidChatPresentation extends Schema.TaggedError()("InvalidChatPresentation", { reason: Schema.Literal("invalid_message") }) {
53
56
  }
54
57
  /** Construct and validate one plain assistant text part. */
55
- const makeText = (text) => Schema.validateSync(AssistantTextPartSchema)({ type: "text", text });
58
+ const makeText = (text) => Schema.decodeSync(Schema.toType(AssistantTextPartSchema))({
59
+ type: "text",
60
+ text,
61
+ });
56
62
  /** Constructors for deterministic assistant message parts. */
57
63
  export const Text = {
58
64
  make: makeText,
59
65
  };
60
66
  const invalidPresentation = () => new InvalidChatPresentation({ reason: "invalid_message" });
61
- const parseResponse = (input) => Schema.decodeUnknown(StructuredChatTurnResponseSchema)(input, {
67
+ const parseResponse = (input) => Schema.decodeUnknownEffect(StructuredChatTurnResponseSchema)(input, {
62
68
  onExcessProperty: "error",
63
69
  }).pipe(Effect.mapError(invalidPresentation));
64
70
  const buildPresentation = (evaluate) => Effect.try({
@@ -1,7 +1,8 @@
1
- import { Schema, unsafeCoerce } from "effect";
2
- const QuestionTextSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(500));
3
- const QuestionGoalSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(1_000));
4
- const ChoiceLabelSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100));
1
+ import { Schema } from "effect";
2
+ const QuestionTextSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(500));
3
+ const QuestionGoalSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(1_000));
4
+ const ChoiceLabelSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100));
5
+ const ChoiceCountSchema = Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 20 }));
5
6
  const fixed = (text) => ({
6
7
  _tag: "FixedQuestion",
7
8
  text: Schema.decodeSync(QuestionTextSchema)(text),
@@ -12,8 +13,8 @@ const adaptive = (goal, options) => ({
12
13
  fallback: Schema.decodeSync(QuestionTextSchema)(options.fallback),
13
14
  });
14
15
  const adaptiveChoice = (prompt, options) => {
15
- const minimumOptions = Schema.decodeSync(Schema.Number.pipe(Schema.int(), Schema.between(1, 20)))(options.minimumOptions);
16
- const maximumOptions = Schema.decodeSync(Schema.Number.pipe(Schema.int(), Schema.between(1, 20)))(options.maximumOptions);
16
+ const minimumOptions = Schema.decodeSync(ChoiceCountSchema)(options.minimumOptions);
17
+ const maximumOptions = Schema.decodeSync(ChoiceCountSchema)(options.maximumOptions);
17
18
  if (minimumOptions > maximumOptions) {
18
19
  throw new Error("Adaptive choice minimumOptions cannot exceed maximumOptions");
19
20
  }
@@ -36,21 +37,22 @@ const adaptiveChoice = (prompt, options) => {
36
37
  };
37
38
  };
38
39
  const choice = (text, options) => {
39
- const labels = options.map(({ label }) => Schema.decodeSync(ChoiceLabelSchema)(label));
40
- const normalized = labels.map((label) => label.toLocaleLowerCase("en"));
40
+ const firstOption = {
41
+ ...options[0],
42
+ label: Schema.decodeSync(ChoiceLabelSchema)(options[0].label),
43
+ };
44
+ const remainingOptions = options.slice(1).map((option) => ({
45
+ ...option,
46
+ label: Schema.decodeSync(ChoiceLabelSchema)(option.label),
47
+ }));
48
+ const normalized = [firstOption, ...remainingOptions].map(({ label }) => label.toLocaleLowerCase("en"));
41
49
  if (new Set(normalized).size !== normalized.length) {
42
50
  throw new Error("Choice question labels must be unique");
43
51
  }
44
- const parsedOptions = options.map((option, index) => ({
45
- ...option,
46
- label: labels[index] ?? option.label,
47
- }));
48
- // SAFETY: map preserves the non-empty tuple length and each option's value;
49
- // every replacement label was parsed at the same array index.
50
52
  return {
51
53
  _tag: "ChoiceQuestion",
52
54
  text: Schema.decodeSync(QuestionTextSchema)(text),
53
- options: unsafeCoerce(parsedOptions),
55
+ options: [firstOption, ...remainingOptions],
54
56
  };
55
57
  };
56
58
  /** Constructors for static, adaptive, and typed choice questions. */
@@ -1,6 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
  import { structuredDefinition, } from "./definition.js";
3
- const maximumCorrectionsSchema = Schema.Number.pipe(Schema.int(), Schema.between(1, 20));
3
+ const maximumCorrectionsSchema = Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 20 }));
4
4
  const standard = (options = {}) => structuredDefinition("repair")({
5
5
  _tag: "StandardRepair",
6
6
  maximumCorrections: Schema.decodeSync(maximumCorrectionsSchema)(options.maximumCorrections ?? 5),
@@ -1,53 +1,47 @@
1
1
  import { Context, Effect, Schema } from "effect";
2
2
  import { type UntrustedMessage } from "./model.js";
3
3
  /** Stable application-owned identifier for one chat session. */
4
- export declare const ChatSessionIdSchema: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
4
+ export declare const ChatSessionIdSchema: Schema.Trimmed;
5
5
  /** Optional application-owned partition for otherwise public session IDs. */
6
- export declare const ChatSessionNamespaceSchema: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
6
+ export declare const ChatSessionNamespaceSchema: Schema.Trimmed;
7
7
  /** Opaque optimistic revision emitted by a session store adapter. */
8
- export declare const ChatSessionRevisionSchema: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
8
+ export declare const ChatSessionRevisionSchema: Schema.Trimmed;
9
9
  /** Persisted session snapshot revalidated by the chat runtime after loading. */
10
10
  export declare const ChatSessionSnapshotSchema: Schema.Struct<{
11
- revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
12
- state: typeof Schema.Unknown;
13
- messages: Schema.filter<Schema.Array$<Schema.Struct<{
14
- role: Schema.Literal<["user", "assistant"]>;
15
- content: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
16
- }>>>;
11
+ readonly revision: Schema.Trimmed;
12
+ readonly state: Schema.Unknown;
13
+ readonly messages: Schema.$Array<Schema.Struct<{
14
+ readonly role: Schema.Literals<readonly ["user", "assistant"]>;
15
+ readonly content: Schema.Trimmed;
16
+ }>>;
17
17
  }>;
18
18
  /** Parsed persisted chat session snapshot. */
19
19
  export type ChatSessionSnapshot = Schema.Schema.Type<typeof ChatSessionSnapshotSchema>;
20
20
  /** Successful optimistic session replacement returned by an adapter. */
21
21
  export declare const ChatSessionReplacementSchema: Schema.Struct<{
22
- revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
22
+ readonly revision: Schema.Trimmed;
23
23
  }>;
24
24
  /** Successful optimistic session replacement returned by an adapter. */
25
25
  export type ChatSessionReplacement = Schema.Schema.Type<typeof ChatSessionReplacementSchema>;
26
26
  /** Safe reason that a session store dependency was unavailable. */
27
- export declare const ChatSessionStoreUnavailableReasonSchema: Schema.Literal<["load_failed", "write_failed"]>;
28
- declare const ChatSessionStoreUnavailable_base: Schema.TaggedErrorClass<ChatSessionStoreUnavailable, "ChatSessionStoreUnavailable", {
29
- readonly _tag: Schema.tag<"ChatSessionStoreUnavailable">;
30
- } & {
31
- reason: Schema.Literal<["load_failed", "write_failed"]>;
32
- }>;
27
+ export declare const ChatSessionStoreUnavailableReasonSchema: Schema.Literals<readonly ["load_failed", "write_failed"]>;
28
+ declare const ChatSessionStoreUnavailable_base: Schema.Class<ChatSessionStoreUnavailable, Schema.TaggedStruct<"ChatSessionStoreUnavailable", {
29
+ readonly reason: Schema.Literals<readonly ["load_failed", "write_failed"]>;
30
+ }>, import("effect/Cause").YieldableError>;
33
31
  /** A session store adapter could not load or replace state. */
34
32
  export declare class ChatSessionStoreUnavailable extends ChatSessionStoreUnavailable_base {
35
33
  }
36
- declare const ChatSessionConflict_base: Schema.TaggedErrorClass<ChatSessionConflict, "ChatSessionConflict", {
37
- readonly _tag: Schema.tag<"ChatSessionConflict">;
38
- } & {
39
- reason: Schema.Literal<["concurrent_update"]>;
40
- }>;
34
+ declare const ChatSessionConflict_base: Schema.Class<ChatSessionConflict, Schema.TaggedStruct<"ChatSessionConflict", {
35
+ readonly reason: Schema.Literal<"concurrent_update">;
36
+ }>, import("effect/Cause").YieldableError>;
41
37
  /** A concurrent or stale transition attempted to replace newer state. */
42
38
  export declare class ChatSessionConflict extends ChatSessionConflict_base {
43
39
  }
44
40
  /** Safe reason that session input or persisted data was rejected. */
45
- export declare const InvalidChatSessionReasonSchema: Schema.Literal<["invalid_input", "invalid_snapshot", "invalid_state", "invalid_replacement", "history_limit"]>;
46
- declare const InvalidChatSession_base: Schema.TaggedErrorClass<InvalidChatSession, "InvalidChatSession", {
47
- readonly _tag: Schema.tag<"InvalidChatSession">;
48
- } & {
49
- reason: Schema.Literal<["invalid_input", "invalid_snapshot", "invalid_state", "invalid_replacement", "history_limit"]>;
50
- }>;
41
+ export declare const InvalidChatSessionReasonSchema: Schema.Literals<readonly ["invalid_input", "invalid_snapshot", "invalid_state", "invalid_replacement", "history_limit"]>;
42
+ declare const InvalidChatSession_base: Schema.Class<InvalidChatSession, Schema.TaggedStruct<"InvalidChatSession", {
43
+ readonly reason: Schema.Literals<readonly ["invalid_input", "invalid_snapshot", "invalid_state", "invalid_replacement", "history_limit"]>;
44
+ }>, import("effect/Cause").YieldableError>;
51
45
  /** Session input or persisted state failed runtime validation. */
52
46
  export declare class InvalidChatSession extends InvalidChatSession_base {
53
47
  }
@@ -71,7 +65,7 @@ export interface ChatSessionStoreService {
71
65
  /** Atomically replace one complete session at its expected revision. */
72
66
  readonly replace: (input: ReplaceChatSessionInput) => Effect.Effect<unknown, ChatSessionStoreUnavailable | ChatSessionConflict>;
73
67
  }
74
- declare const ChatSessionStore_base: Context.TagClass<ChatSessionStore, "@popcomputer/structured-chat/ChatSessionStore", ChatSessionStoreService>;
68
+ declare const ChatSessionStore_base: Context.ServiceClass<ChatSessionStore, "@popcomputer/structured-chat/ChatSessionStore", ChatSessionStoreService>;
75
69
  /** Effect service for the configured server-owned chat session store. */
76
70
  export declare class ChatSessionStore extends ChatSessionStore_base {
77
71
  }
@@ -1,23 +1,26 @@
1
1
  import { Context, Effect, Schema } from "effect";
2
2
  import { UntrustedMessageSchema } from "./model.js";
3
3
  /** Stable application-owned identifier for one chat session. */
4
- export const ChatSessionIdSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(200), Schema.pattern(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/));
4
+ export const ChatSessionIdSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(200), Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/));
5
5
  /** Optional application-owned partition for otherwise public session IDs. */
6
- export const ChatSessionNamespaceSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(200), Schema.pattern(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/));
6
+ export const ChatSessionNamespaceSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(200), Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/));
7
7
  /** Opaque optimistic revision emitted by a session store adapter. */
8
- export const ChatSessionRevisionSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(200), Schema.pattern(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/));
8
+ export const ChatSessionRevisionSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(200), Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/));
9
9
  /** Persisted session snapshot revalidated by the chat runtime after loading. */
10
10
  export const ChatSessionSnapshotSchema = Schema.Struct({
11
11
  revision: ChatSessionRevisionSchema,
12
12
  state: Schema.Unknown,
13
- messages: Schema.Array(UntrustedMessageSchema).pipe(Schema.maxItems(200)),
13
+ messages: Schema.Array(UntrustedMessageSchema).check(Schema.isMaxLength(200)),
14
14
  });
15
15
  /** Successful optimistic session replacement returned by an adapter. */
16
16
  export const ChatSessionReplacementSchema = Schema.Struct({
17
17
  revision: ChatSessionRevisionSchema,
18
18
  });
19
19
  /** Safe reason that a session store dependency was unavailable. */
20
- export const ChatSessionStoreUnavailableReasonSchema = Schema.Literal("load_failed", "write_failed");
20
+ export const ChatSessionStoreUnavailableReasonSchema = Schema.Literals([
21
+ "load_failed",
22
+ "write_failed",
23
+ ]);
21
24
  /** A session store adapter could not load or replace state. */
22
25
  export class ChatSessionStoreUnavailable extends Schema.TaggedError()("ChatSessionStoreUnavailable", { reason: ChatSessionStoreUnavailableReasonSchema }) {
23
26
  }
@@ -25,10 +28,16 @@ export class ChatSessionStoreUnavailable extends Schema.TaggedError()("ChatSessi
25
28
  export class ChatSessionConflict extends Schema.TaggedError()("ChatSessionConflict", { reason: Schema.Literal("concurrent_update") }) {
26
29
  }
27
30
  /** Safe reason that session input or persisted data was rejected. */
28
- export const InvalidChatSessionReasonSchema = Schema.Literal("invalid_input", "invalid_snapshot", "invalid_state", "invalid_replacement", "history_limit");
31
+ export const InvalidChatSessionReasonSchema = Schema.Literals([
32
+ "invalid_input",
33
+ "invalid_snapshot",
34
+ "invalid_state",
35
+ "invalid_replacement",
36
+ "history_limit",
37
+ ]);
29
38
  /** Session input or persisted state failed runtime validation. */
30
39
  export class InvalidChatSession extends Schema.TaggedError()("InvalidChatSession", { reason: InvalidChatSessionReasonSchema }) {
31
40
  }
32
41
  /** Effect service for the configured server-owned chat session store. */
33
- export class ChatSessionStore extends Context.Tag("@popcomputer/structured-chat/ChatSessionStore")() {
42
+ export class ChatSessionStore extends Context.Service()("@popcomputer/structured-chat/ChatSessionStore") {
34
43
  }
@@ -1,3 +1,3 @@
1
1
  import { Schema } from "effect";
2
2
  /** Stable machine-facing name for one structured chat stage. */
3
- export declare const StageNameSchema: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
3
+ export declare const StageNameSchema: Schema.Trimmed;
@@ -1,3 +1,3 @@
1
1
  import { Schema } from "effect";
2
2
  /** Stable machine-facing name for one structured chat stage. */
3
- export const StageNameSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100), Schema.pattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/));
3
+ export const StageNameSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100), Schema.isPattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/));
@@ -2,11 +2,11 @@ import { Effect, Schema } from "effect";
2
2
  import { StructuredChatModel, type ChatModelUnavailable, type UnsupportedModelToolSchema, type UntrustedMessage } from "./model.js";
3
3
  import type { ModelGuardError, ModelGuardRequirements, ModelGuardTuple } from "./model-guard.js";
4
4
  import { type ToolSet, type ToolSetCall, type ToolSetError, type ToolSetExecution, type ToolSetRequirements, type ToolTuple } from "./tool-set.js";
5
- import type { CommandDefinitionContract, CommandExecutionContext, InvalidToolCall, InvalidToolProjection, StructuredCommand, ToolCall, ToolExecution, QueryToolDefinitionContract } from "./tool.js";
5
+ import type { CommandDefinitionContract, CommandExecutionContext, InvalidToolCall, InvalidToolProjection, StructuredCommand, ToolCall, ToolExecution, QueryToolDefinitionContract, ToolSchema } from "./tool.js";
6
6
  import { type StructuredDefinition } from "./definition.js";
7
7
  export { StageNameSchema } from "./stage-name.js";
8
8
  /** State transition applied after one tool-stage execution. */
9
- export declare const ToolStageAfterExecutionSchema: Schema.Literal<["stay", "complete"]>;
9
+ export declare const ToolStageAfterExecutionSchema: Schema.Literals<readonly ["stay", "complete"]>;
10
10
  /** State transition applied after one tool-stage execution. */
11
11
  export type ToolStageAfterExecution = Schema.Schema.Type<typeof ToolStageAfterExecutionSchema>;
12
12
  /** Definition input for one stage-scoped, repeatable tool step. */
@@ -21,8 +21,8 @@ export interface DefineToolStageInput<Name extends string, Tools extends ToolTup
21
21
  export interface ToolStageRuntime {
22
22
  readonly afterExecution: ToolStageAfterExecution;
23
23
  readonly toolNames: ReadonlyArray<string>;
24
- readonly planWith: (messages: ReadonlyArray<UntrustedMessage>, additionalTool: QueryToolDefinitionContract) => Effect.Effect<ToolCall<string, Schema.Schema.AnyNoContext>, unknown, unknown>;
25
- readonly execute: (call: ToolCall<string, Schema.Schema.AnyNoContext>) => Effect.Effect<unknown, unknown, unknown>;
24
+ readonly planWith: (messages: ReadonlyArray<UntrustedMessage>, additionalTool: QueryToolDefinitionContract) => Effect.Effect<ToolCall<string, ToolSchema>, unknown, unknown>;
25
+ readonly execute: (call: ToolCall<string, ToolSchema>) => Effect.Effect<unknown, unknown, unknown>;
26
26
  readonly run: (messages: ReadonlyArray<UntrustedMessage>) => Effect.Effect<unknown, unknown, unknown>;
27
27
  }
28
28
  declare const toolStageRuntime: unique symbol;