@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
@@ -0,0 +1,276 @@
1
+ import { cast, Effect, Schema } from "effect";
2
+ import { AnswerModeSchema } from "./answer.js";
3
+ import { ChatNameSchema, ChatVersionSchema, } from "./chat.js";
4
+ import { readCollectStageInspection, } from "./collect-stage.js";
5
+ import { JsonValueSchema } from "./json-value.js";
6
+ import { readCommandStageRuntime, readToolStageRuntime, ToolStageAfterExecutionSchema, } from "./stage.js";
7
+ import { StageNameSchema } from "./stage-name.js";
8
+ import { ToolNameSchema } from "./tool.js";
9
+ const DebugIndexSchema = Schema.Natural;
10
+ const DebugIssuedQuestionSchema = Schema.Struct({
11
+ messageIndex: DebugIndexSchema,
12
+ text: Schema.String,
13
+ });
14
+ const DebugAnswerEvidenceSchema = Schema.Struct({
15
+ messageIndex: DebugIndexSchema,
16
+ quote: Schema.String,
17
+ });
18
+ const DebugQuestionSchema = Schema.Union([
19
+ Schema.Struct({
20
+ _tag: Schema.Literal("FixedQuestion"),
21
+ text: Schema.String,
22
+ }),
23
+ Schema.Struct({
24
+ _tag: Schema.Literal("AdaptiveQuestion"),
25
+ goal: Schema.String,
26
+ fallback: Schema.String,
27
+ }),
28
+ Schema.Struct({
29
+ _tag: Schema.Literal("AdaptiveChoiceQuestion"),
30
+ prompt: Schema.String,
31
+ minimumOptions: Schema.Natural,
32
+ maximumOptions: Schema.Natural,
33
+ fallbackOptions: Schema.Array(Schema.String),
34
+ }),
35
+ Schema.Struct({
36
+ _tag: Schema.Literal("ChoiceQuestion"),
37
+ text: Schema.String,
38
+ options: Schema.Array(Schema.Struct({
39
+ label: Schema.String,
40
+ })),
41
+ }),
42
+ ]);
43
+ const DebugFieldStateSchema = Schema.Union([
44
+ Schema.Struct({
45
+ _tag: Schema.Literal("Missing"),
46
+ }),
47
+ Schema.Struct({
48
+ _tag: Schema.Literal("Asked"),
49
+ issuedQuestion: DebugIssuedQuestionSchema,
50
+ }),
51
+ Schema.Struct({
52
+ _tag: Schema.Literal("Accepted"),
53
+ value: JsonValueSchema,
54
+ evidence: Schema.NullOr(DebugAnswerEvidenceSchema),
55
+ issuedQuestion: Schema.NullOr(DebugIssuedQuestionSchema),
56
+ }),
57
+ ]);
58
+ const DebugFieldSchema = Schema.Struct({
59
+ field: Schema.String,
60
+ mode: AnswerModeSchema,
61
+ description: Schema.String,
62
+ question: DebugQuestionSchema,
63
+ state: DebugFieldStateSchema,
64
+ });
65
+ const DebugStageStatusSchema = Schema.Literals([
66
+ "complete",
67
+ "current",
68
+ "upcoming",
69
+ ]);
70
+ const DebugStageBaseFields = {
71
+ index: DebugIndexSchema,
72
+ name: StageNameSchema,
73
+ status: DebugStageStatusSchema,
74
+ repairPending: Schema.Boolean,
75
+ };
76
+ const DebugStageSchema = Schema.Union([
77
+ Schema.Struct({
78
+ _tag: Schema.Literal("CollectStage"),
79
+ ...DebugStageBaseFields,
80
+ satisfiedFields: Schema.Natural,
81
+ totalFields: Schema.Natural,
82
+ fields: Schema.Array(DebugFieldSchema),
83
+ }),
84
+ Schema.Struct({
85
+ _tag: Schema.Literal("ToolStage"),
86
+ ...DebugStageBaseFields,
87
+ tools: Schema.Array(ToolNameSchema),
88
+ afterExecution: ToolStageAfterExecutionSchema,
89
+ }),
90
+ Schema.Struct({
91
+ _tag: Schema.Literal("CommandStage"),
92
+ ...DebugStageBaseFields,
93
+ command: ToolNameSchema,
94
+ }),
95
+ ]);
96
+ /** Runtime schema for one JSON-safe structured-chat debug snapshot. */
97
+ export const StructuredChatDebugSnapshotSchema = Schema.Struct({
98
+ schemaVersion: Schema.Literal(1),
99
+ chat: Schema.Struct({
100
+ name: ChatNameSchema,
101
+ version: ChatVersionSchema,
102
+ }),
103
+ status: Schema.Literals(["active", "complete"]),
104
+ currentStage: Schema.Struct({
105
+ index: DebugIndexSchema,
106
+ name: StageNameSchema,
107
+ kind: Schema.Literals(["collect", "tool", "command"]),
108
+ }),
109
+ stages: Schema.Array(DebugStageSchema),
110
+ });
111
+ const InspectChatStateOptionsSchema = Schema.Struct({
112
+ evidence: Schema.optionalKey(Schema.Literals(["include", "omit"])),
113
+ });
114
+ const InvalidChatDebugProjectionReasonSchema = Schema.Literals([
115
+ "invalid_options",
116
+ "invalid_state",
117
+ "invalid_answer_value",
118
+ "invalid_snapshot",
119
+ ]);
120
+ /** A chat state or answer could not be projected into safe debug JSON. */
121
+ export class InvalidChatDebugProjection extends Schema.TaggedError()("InvalidChatDebugProjection", { reason: InvalidChatDebugProjectionReasonSchema }) {
122
+ }
123
+ const invalidProjection = (reason) => new InvalidChatDebugProjection({ reason });
124
+ const projectQuestion = (question) => {
125
+ switch (question._tag) {
126
+ case "FixedQuestion":
127
+ return { _tag: question._tag, text: question.text };
128
+ case "AdaptiveQuestion":
129
+ return {
130
+ _tag: question._tag,
131
+ goal: question.goal,
132
+ fallback: question.fallback,
133
+ };
134
+ case "AdaptiveChoiceQuestion":
135
+ return {
136
+ _tag: question._tag,
137
+ prompt: question.prompt,
138
+ minimumOptions: question.minimumOptions,
139
+ maximumOptions: question.maximumOptions,
140
+ fallbackOptions: question.fallbackOptions,
141
+ };
142
+ case "ChoiceQuestion":
143
+ return {
144
+ _tag: question._tag,
145
+ text: question.text,
146
+ options: question.options.map(({ label }) => ({ label })),
147
+ };
148
+ }
149
+ };
150
+ const stageKind = (stage) => {
151
+ switch (stage._tag) {
152
+ case "CollectStage":
153
+ return "collect";
154
+ case "ToolStage":
155
+ return "tool";
156
+ case "CommandStage":
157
+ return "command";
158
+ }
159
+ };
160
+ const stageStatus = (state, index) => {
161
+ if (index === state.stage) {
162
+ return state.status === "complete" ? "complete" : "current";
163
+ }
164
+ if (index < state.stage) {
165
+ return "complete";
166
+ }
167
+ return "upcoming";
168
+ };
169
+ /**
170
+ * Project one trusted chat definition and Type-side state into browser-safe
171
+ * inspector data without exposing choice values or raw Effect schemas.
172
+ */
173
+ export const inspectChatState = (chat, state, options = {}) => Effect.gen(function* () {
174
+ const parsedOptions = yield* Schema.decodeUnknownEffect(InspectChatStateOptionsSchema)(options, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidProjection("invalid_options")));
175
+ const parsedState = yield* Schema.decodeUnknownEffect(Schema.toType(chat.stateSchema))(state, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidProjection("invalid_state")));
176
+ // SAFETY: this definition's exact state schema parsed the envelope and all
177
+ // named collect-stage states immediately above; only tuple correlations are
178
+ // erased for definition-ordered read-only projection.
179
+ const runtimeState = cast(parsedState);
180
+ const currentStage = chat.stages[runtimeState.stage];
181
+ if (currentStage === undefined) {
182
+ return yield* Effect.fail(invalidProjection("invalid_state"));
183
+ }
184
+ const stages = [];
185
+ for (const [index, stage] of chat.stages.entries()) {
186
+ const repairPending = runtimeState.repair?.pendingStages.includes(index) ?? false;
187
+ if (stage._tag === "ToolStage") {
188
+ const runtime = readToolStageRuntime(stage);
189
+ stages.push({
190
+ _tag: "ToolStage",
191
+ index,
192
+ name: stage.name,
193
+ status: stageStatus(runtimeState, index),
194
+ repairPending,
195
+ tools: runtime.toolNames,
196
+ afterExecution: runtime.afterExecution,
197
+ });
198
+ continue;
199
+ }
200
+ if (stage._tag === "CommandStage") {
201
+ stages.push({
202
+ _tag: "CommandStage",
203
+ index,
204
+ name: stage.name,
205
+ status: stageStatus(runtimeState, index),
206
+ repairPending,
207
+ command: readCommandStageRuntime(stage).commandName,
208
+ });
209
+ continue;
210
+ }
211
+ const collectState = runtimeState.stages[stage.name];
212
+ if (collectState === undefined) {
213
+ return yield* Effect.fail(invalidProjection("invalid_state"));
214
+ }
215
+ const inspection = readCollectStageInspection(stage);
216
+ const fields = [];
217
+ let satisfiedFields = 0;
218
+ for (const field of inspection.fields) {
219
+ const accepted = collectState.accepted[field.field];
220
+ const issuedQuestion = collectState.asked[field.field];
221
+ const fieldBase = {
222
+ field: field.field,
223
+ mode: field.mode,
224
+ description: field.description,
225
+ question: projectQuestion(field.question),
226
+ };
227
+ if (accepted === undefined) {
228
+ fields.push(issuedQuestion === undefined
229
+ ? { ...fieldBase, state: { _tag: "Missing" } }
230
+ : {
231
+ ...fieldBase,
232
+ state: {
233
+ _tag: "Asked",
234
+ issuedQuestion,
235
+ },
236
+ });
237
+ continue;
238
+ }
239
+ const encoded = yield* field.encodeValue(accepted.value).pipe(Effect.mapError(() => invalidProjection("invalid_answer_value")));
240
+ const value = yield* Schema.decodeUnknownEffect(JsonValueSchema)(encoded, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidProjection("invalid_answer_value")));
241
+ satisfiedFields += 1;
242
+ fields.push({
243
+ ...fieldBase,
244
+ state: {
245
+ _tag: "Accepted",
246
+ value,
247
+ evidence: (parsedOptions.evidence ?? "include") === "include"
248
+ ? accepted.evidence
249
+ : null,
250
+ issuedQuestion: issuedQuestion ?? null,
251
+ },
252
+ });
253
+ }
254
+ stages.push({
255
+ _tag: "CollectStage",
256
+ index,
257
+ name: stage.name,
258
+ status: stageStatus(runtimeState, index),
259
+ repairPending,
260
+ satisfiedFields,
261
+ totalFields: inspection.fields.length,
262
+ fields,
263
+ });
264
+ }
265
+ return yield* Schema.decodeUnknownEffect(StructuredChatDebugSnapshotSchema)({
266
+ schemaVersion: 1,
267
+ chat: { name: chat.name, version: chat.version },
268
+ status: runtimeState.status,
269
+ currentStage: {
270
+ index: runtimeState.stage,
271
+ name: currentStage.name,
272
+ kind: stageKind(currentStage),
273
+ },
274
+ stages,
275
+ }, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidProjection("invalid_snapshot")));
276
+ });
@@ -8,4 +8,4 @@ export interface JsonObject {
8
8
  /** Recursively typed value accepted at serialized JSON boundaries. */
9
9
  export type JsonValue = JsonPrimitive | JsonObject | ReadonlyArray<JsonValue>;
10
10
  /** Runtime parser for recursively JSON-serializable values. */
11
- export declare const JsonValueSchema: Schema.Schema<JsonValue>;
11
+ export declare const JsonValueSchema: Schema.Codec<JsonValue>;
@@ -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)));