@popcomputer/structured-chat 0.1.0 → 0.2.0-rc.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 (41) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +122 -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 +11 -14
  10. package/dist/core/collect-stage.js +40 -37
  11. package/dist/core/command.d.ts +1 -1
  12. package/dist/core/command.js +1 -1
  13. package/dist/core/json-value.d.ts +1 -1
  14. package/dist/core/json-value.js +8 -1
  15. package/dist/core/model-guard.d.ts +2 -3
  16. package/dist/core/model-guard.js +4 -4
  17. package/dist/core/model.d.ts +15 -19
  18. package/dist/core/model.js +22 -10
  19. package/dist/core/protocol.d.ts +84 -79
  20. package/dist/core/protocol.js +18 -12
  21. package/dist/core/question.js +17 -15
  22. package/dist/core/repair.js +1 -1
  23. package/dist/core/session.d.ts +22 -28
  24. package/dist/core/session.js +16 -7
  25. package/dist/core/stage-name.d.ts +1 -1
  26. package/dist/core/stage-name.js +1 -1
  27. package/dist/core/stage.d.ts +4 -4
  28. package/dist/core/stage.js +11 -8
  29. package/dist/core/tool-set.js +6 -6
  30. package/dist/core/tool.d.ts +36 -39
  31. package/dist/core/tool.js +58 -31
  32. package/dist/core/view.d.ts +64 -39
  33. package/dist/core/view.js +12 -15
  34. package/dist/integrations/assistant-ui.js +21 -14
  35. package/dist/testing/in-memory-session-store.js +1 -1
  36. package/dist/testing/scenario.js +6 -6
  37. package/examples/answer-modes.ts +60 -49
  38. package/examples/prompt-injection-policy.ts +20 -20
  39. package/examples/resource-search.ts +104 -0
  40. package/package.json +11 -3
  41. package/examples/agency-search.ts +0 -101
@@ -1,3 +1,10 @@
1
1
  import { Schema } from "effect";
2
2
  /** Runtime parser for recursively JSON-serializable values. */
3
- export const JsonValueSchema = Schema.suspend(() => Schema.Union(Schema.String, Schema.JsonNumber, Schema.Boolean, Schema.Null, Schema.Array(JsonValueSchema), Schema.Record({ key: Schema.String, value: JsonValueSchema })));
3
+ export const JsonValueSchema = Schema.suspend(() => Schema.Union([
4
+ Schema.String,
5
+ Schema.Finite,
6
+ Schema.Boolean,
7
+ Schema.Null,
8
+ Schema.Array(JsonValueSchema),
9
+ Schema.Record(Schema.String, JsonValueSchema),
10
+ ]));
@@ -1,9 +1,8 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import { type StructuredDefinition } from "./definition.js";
3
3
  import type { UntrustedMessage } from "./model.js";
4
- import type { JsonValue } from "./json-value.js";
5
4
  /** Stable machine-facing name for one model-boundary policy guard. */
6
- export declare const ModelGuardNameSchema: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
5
+ export declare const ModelGuardNameSchema: Schema.Trimmed;
7
6
  /** Safe context supplied before a structured model request begins. */
8
7
  export interface ModelGuardContext {
9
8
  readonly messages: ReadonlyArray<UntrustedMessage>;
@@ -12,7 +11,7 @@ export interface ModelGuardContext {
12
11
  /** Strictly parsed model proposal supplied before application execution. */
13
12
  export interface ModelGuardCall {
14
13
  readonly name: string;
15
- readonly arguments: JsonValue;
14
+ readonly arguments: unknown;
16
15
  }
17
16
  /** Safe context supplied after parsing and before a tool executes. */
18
17
  export interface ModelGuardCallContext extends ModelGuardContext {
@@ -1,7 +1,7 @@
1
- import { Effect, Schema, unsafeCoerce } from "effect";
1
+ import { Effect, Function as Fn, Schema } from "effect";
2
2
  import { structuredDefinition, } from "./definition.js";
3
3
  /** Stable machine-facing name for one model-boundary policy guard. */
4
- export const ModelGuardNameSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100), Schema.pattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/));
4
+ export const ModelGuardNameSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100), Schema.isPattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/));
5
5
  /** Define Effect-native policy checks around a structured model step. */
6
6
  export const defineModelGuard = (definition) => {
7
7
  Schema.decodeSync(ModelGuardNameSchema)(definition.name);
@@ -17,7 +17,7 @@ export const defineModelGuard = (definition) => {
17
17
  const runtimeModelGuard = (guard) => {
18
18
  // SAFETY: ModelGuardDefinitionContract carries the package-owned nominal
19
19
  // identity and can only be constructed by defineModelGuard.
20
- return unsafeCoerce(guard);
20
+ return Fn.cast(guard);
21
21
  };
22
22
  const runGuardPhase = (guards, phase, run) => {
23
23
  const execution = Effect.forEach(guards, (guard) => (run(runtimeModelGuard(guard)) ?? Effect.void).pipe(Effect.withSpan("popcomputer.structured_chat.model_guard.check", {
@@ -25,7 +25,7 @@ const runGuardPhase = (guards, phase, run) => {
25
25
  })), { concurrency: 1, discard: true });
26
26
  // SAFETY: guards run sequentially without recovering failures, so the
27
27
  // erased Effect has exactly the conditional error and requirement unions.
28
- return execution;
28
+ return Fn.cast(execution);
29
29
  };
30
30
  /** @internal */
31
31
  export const runModelGuards = (guards, context) => {
@@ -4,39 +4,35 @@ import { type ModelGuardError, type ModelGuardRequirements, type ModelGuardTuple
4
4
  import type { ToolSet, ToolSetCall, ToolSetError, ToolSetExecution, ToolSetRequirements, ModelToolTuple, ToolCallPlanner, ToolTuple } from "./tool-set.js";
5
5
  import type { JsonValue } from "./json-value.js";
6
6
  /** Bounded application-authored instruction supplied to a model adapter. */
7
- export declare const TrustedInstructionSchema: Schema.brand<Schema.filter<typeof Schema.NonEmptyTrimmedString>, "TrustedInstruction">;
7
+ export declare const TrustedInstructionSchema: Schema.brand<Schema.Trimmed, "TrustedInstruction">;
8
8
  /** Bounded application-authored instruction supplied to a model adapter. */
9
9
  export type TrustedInstruction = Schema.Schema.Type<typeof TrustedInstructionSchema>;
10
10
  /** Conversation role accepted as untrusted model context. */
11
- export declare const ConversationRoleSchema: Schema.Literal<["user", "assistant"]>;
11
+ export declare const ConversationRoleSchema: Schema.Literals<readonly ["user", "assistant"]>;
12
12
  /** One bounded conversation message treated as untrusted model context. */
13
13
  export declare const UntrustedMessageSchema: Schema.Struct<{
14
- role: Schema.Literal<["user", "assistant"]>;
15
- content: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
14
+ readonly role: Schema.Literals<readonly ["user", "assistant"]>;
15
+ readonly content: Schema.Trimmed;
16
16
  }>;
17
17
  /** One bounded conversation message treated as untrusted model context. */
18
18
  export type UntrustedMessage = Schema.Schema.Type<typeof UntrustedMessageSchema>;
19
19
  /** @internal Exact content-character count for bounded message arrays. */
20
20
  export declare const countUntrustedMessageCharacters: (messages: ReadonlyArray<UntrustedMessage>) => number;
21
21
  /** Safe reason that a configured chat model could not complete a step. */
22
- export declare const ChatModelUnavailableReasonSchema: Schema.Literal<["request_failed", "timed_out", "response_blocked", "invalid_response"]>;
23
- declare const ChatModelUnavailable_base: Schema.TaggedErrorClass<ChatModelUnavailable, "ChatModelUnavailable", {
24
- readonly _tag: Schema.tag<"ChatModelUnavailable">;
25
- } & {
26
- reason: Schema.Literal<["request_failed", "timed_out", "response_blocked", "invalid_response"]>;
27
- }>;
22
+ export declare const ChatModelUnavailableReasonSchema: Schema.Literals<readonly ["request_failed", "timed_out", "response_blocked", "invalid_response"]>;
23
+ declare const ChatModelUnavailable_base: Schema.Class<ChatModelUnavailable, Schema.TaggedStruct<"ChatModelUnavailable", {
24
+ readonly reason: Schema.Literals<readonly ["request_failed", "timed_out", "response_blocked", "invalid_response"]>;
25
+ }>, import("effect/Cause").YieldableError>;
28
26
  /** A configured chat model could not complete a structured step. */
29
27
  export declare class ChatModelUnavailable extends ChatModelUnavailable_base {
30
28
  }
31
29
  /** Safe reason that a tool schema cannot use strict provider decoding. */
32
- export declare const UnsupportedModelToolSchemaReasonSchema: Schema.Literal<["root_not_object", "additional_properties_allowed", "optional_property"]>;
33
- declare const UnsupportedModelToolSchema_base: Schema.TaggedErrorClass<UnsupportedModelToolSchema, "UnsupportedModelToolSchema", {
34
- readonly _tag: Schema.tag<"UnsupportedModelToolSchema">;
35
- } & {
36
- tool: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
37
- path: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
38
- reason: Schema.Literal<["root_not_object", "additional_properties_allowed", "optional_property"]>;
39
- }>;
30
+ export declare const UnsupportedModelToolSchemaReasonSchema: Schema.Literals<readonly ["root_not_object", "additional_properties_allowed", "optional_property"]>;
31
+ declare const UnsupportedModelToolSchema_base: Schema.Class<UnsupportedModelToolSchema, Schema.TaggedStruct<"UnsupportedModelToolSchema", {
32
+ readonly tool: Schema.Trimmed;
33
+ readonly path: Schema.Trimmed;
34
+ readonly reason: Schema.Literals<readonly ["root_not_object", "additional_properties_allowed", "optional_property"]>;
35
+ }>, import("effect/Cause").YieldableError>;
40
36
  /** A model tool schema is incompatible with strict provider decoding. */
41
37
  export declare class UnsupportedModelToolSchema extends UnsupportedModelToolSchema_base {
42
38
  }
@@ -54,7 +50,7 @@ export interface StructuredChatModelService {
54
50
  /** Return one untrusted provider tool call for runtime validation. */
55
51
  readonly requestTool: (request: ToolModelRequest) => Effect.Effect<JsonValue, ChatModelUnavailable | UnsupportedModelToolSchema>;
56
52
  }
57
- declare const StructuredChatModel_base: Context.TagClass<StructuredChatModel, "@popcomputer/structured-chat/StructuredChatModel", StructuredChatModelService>;
53
+ declare const StructuredChatModel_base: Context.ServiceClass<StructuredChatModel, "@popcomputer/structured-chat/StructuredChatModel", StructuredChatModelService>;
58
54
  /** Effect service for the configured structured chat model adapter. */
59
55
  export declare class StructuredChatModel extends StructuredChatModel_base {
60
56
  }
@@ -1,32 +1,44 @@
1
1
  import { Context, Effect, Schema } from "effect";
2
2
  import { runModelCallGuards, runModelGuards, } from "./model-guard.js";
3
3
  /** Bounded application-authored instruction supplied to a model adapter. */
4
- export const TrustedInstructionSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(20_000), Schema.brand("TrustedInstruction"));
4
+ export const TrustedInstructionSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(20_000)).pipe(Schema.brand("TrustedInstruction"));
5
5
  /** Conversation role accepted as untrusted model context. */
6
- export const ConversationRoleSchema = Schema.Literal("user", "assistant");
6
+ export const ConversationRoleSchema = Schema.Literals([
7
+ "user",
8
+ "assistant",
9
+ ]);
7
10
  /** One bounded conversation message treated as untrusted model context. */
8
11
  export const UntrustedMessageSchema = Schema.Struct({
9
12
  role: ConversationRoleSchema,
10
- content: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(50_000)),
13
+ content: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(50_000)),
11
14
  });
12
15
  /** @internal Exact content-character count for bounded message arrays. */
13
16
  export const countUntrustedMessageCharacters = (messages) => messages.reduce((total, message) => total + message.content.length, 0);
14
17
  /** Safe reason that a configured chat model could not complete a step. */
15
- export const ChatModelUnavailableReasonSchema = Schema.Literal("request_failed", "timed_out", "response_blocked", "invalid_response");
18
+ export const ChatModelUnavailableReasonSchema = Schema.Literals([
19
+ "request_failed",
20
+ "timed_out",
21
+ "response_blocked",
22
+ "invalid_response",
23
+ ]);
16
24
  /** A configured chat model could not complete a structured step. */
17
25
  export class ChatModelUnavailable extends Schema.TaggedError()("ChatModelUnavailable", { reason: ChatModelUnavailableReasonSchema }) {
18
26
  }
19
27
  /** Safe reason that a tool schema cannot use strict provider decoding. */
20
- export const UnsupportedModelToolSchemaReasonSchema = Schema.Literal("root_not_object", "additional_properties_allowed", "optional_property");
28
+ export const UnsupportedModelToolSchemaReasonSchema = Schema.Literals([
29
+ "root_not_object",
30
+ "additional_properties_allowed",
31
+ "optional_property",
32
+ ]);
21
33
  /** A model tool schema is incompatible with strict provider decoding. */
22
34
  export class UnsupportedModelToolSchema extends Schema.TaggedError()("UnsupportedModelToolSchema", {
23
- tool: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100)),
24
- path: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(2_000)),
35
+ tool: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100)),
36
+ path: Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(2_000)),
25
37
  reason: UnsupportedModelToolSchemaReasonSchema,
26
38
  }) {
27
39
  }
28
40
  /** Effect service for the configured structured chat model adapter. */
29
- export class StructuredChatModel extends Context.Tag("@popcomputer/structured-chat/StructuredChatModel")() {
41
+ export class StructuredChatModel extends Context.Service()("@popcomputer/structured-chat/StructuredChatModel") {
30
42
  }
31
43
  const invalidOutputRepairInstruction = Schema.decodeSync(TrustedInstructionSchema)("Your previous response did not satisfy the required tool-call contract. Call exactly one listed tool and return only arguments allowed by its JSON Schema.");
32
44
  const isRepairableModelOutput = (error) => error._tag === "InvalidToolCall" ||
@@ -36,7 +48,7 @@ const isRepairableModelOutput = (error) => error._tag === "InvalidToolCall" ||
36
48
  export const planToolCall = (input) => runModelGuards(input.guards ?? [], {
37
49
  messages: input.messages,
38
50
  toolNames: input.tools.models.map(({ name }) => name),
39
- }).pipe(Effect.zipRight(StructuredChatModel), Effect.flatMap((model) => {
51
+ }).pipe(Effect.andThen(StructuredChatModel), Effect.flatMap((model) => {
40
52
  const requestParsedCall = (instructions, attempt) => model
41
53
  .requestTool({
42
54
  instructions,
@@ -68,7 +80,7 @@ export const planToolCall = (input) => runModelGuards(input.guards ?? [], {
68
80
  errorTag: error._tag,
69
81
  errorReason: error.reason,
70
82
  };
71
- return Effect.logWarning("Retrying structured model output").pipe(Effect.annotateLogs(annotations), Effect.zipRight(requestParsedCall([
83
+ return Effect.logWarning("Retrying structured model output").pipe(Effect.annotateLogs(annotations), Effect.andThen(requestParsedCall([
72
84
  ...input.instructions,
73
85
  invalidOutputRepairInstruction,
74
86
  ], 2)));
@@ -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),