@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.
- package/LICENSE +0 -1
- package/README.md +175 -108
- package/dist/adapters/openai-compatible-model.d.ts +4 -4
- package/dist/adapters/openai-compatible-model.js +33 -31
- package/dist/core/answer.d.ts +13 -13
- package/dist/core/answer.js +21 -12
- package/dist/core/chat.d.ts +12 -15
- package/dist/core/chat.js +36 -27
- package/dist/core/collect-stage.d.ts +28 -15
- package/dist/core/collect-stage.js +58 -37
- package/dist/core/command.d.ts +1 -1
- package/dist/core/command.js +1 -1
- package/dist/core/debug-protocol.d.ts +126 -0
- package/dist/core/debug-protocol.js +19 -0
- package/dist/core/debug.d.ts +103 -0
- package/dist/core/debug.js +276 -0
- package/dist/core/json-value.d.ts +1 -1
- package/dist/core/json-value.js +8 -1
- package/dist/core/model-guard.d.ts +2 -3
- package/dist/core/model-guard.js +4 -4
- package/dist/core/model.d.ts +15 -19
- package/dist/core/model.js +22 -10
- package/dist/core/protocol.d.ts +84 -79
- package/dist/core/protocol.js +18 -12
- package/dist/core/question.js +17 -15
- package/dist/core/repair.js +1 -1
- package/dist/core/session.d.ts +22 -28
- package/dist/core/session.js +16 -7
- package/dist/core/stage-name.d.ts +1 -1
- package/dist/core/stage-name.js +1 -1
- package/dist/core/stage.d.ts +4 -4
- package/dist/core/stage.js +11 -8
- package/dist/core/tool-set.js +6 -6
- package/dist/core/tool.d.ts +36 -39
- package/dist/core/tool.js +58 -31
- package/dist/core/view.d.ts +64 -39
- package/dist/core/view.js +12 -15
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/integrations/assistant-ui-debug.d.ts +25 -0
- package/dist/integrations/assistant-ui-debug.js +1162 -0
- package/dist/integrations/assistant-ui.d.ts +10 -3
- package/dist/integrations/assistant-ui.js +48 -18
- package/dist/testing/in-memory-session-store.js +1 -1
- package/dist/testing/scenario.js +6 -6
- package/examples/answer-modes.ts +60 -49
- package/examples/prompt-injection-policy.ts +20 -20
- package/examples/resource-search.ts +104 -0
- package/package.json +15 -3
- package/examples/agency-search.ts +0 -101
package/dist/core/answer.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import type { ChoiceQuestion, FixedQuestion, QuestionDefinition, QuestionDefinitionContract } from "./question.js";
|
|
3
3
|
/** How strongly a collect-stage answer must be grounded in user messages. */
|
|
4
|
-
export declare const AnswerModeSchema: Schema.
|
|
4
|
+
export declare const AnswerModeSchema: Schema.Literals<readonly ["semantic", "explicit", "confirmed"]>;
|
|
5
5
|
/** How strongly a collect-stage answer must be grounded in user messages. */
|
|
6
6
|
export type AnswerMode = Schema.Schema.Type<typeof AnswerModeSchema>;
|
|
7
7
|
/** Minimum runtime shape retained for every collect-stage answer. */
|
|
8
8
|
export interface AnswerDefinitionContract {
|
|
9
9
|
readonly _tag: "AnswerDefinition";
|
|
10
10
|
readonly mode: AnswerMode;
|
|
11
|
-
readonly schema: Schema.
|
|
11
|
+
readonly schema: Schema.ConstraintCodec<unknown, unknown>;
|
|
12
12
|
readonly description: string;
|
|
13
13
|
readonly question: QuestionDefinitionContract;
|
|
14
14
|
readonly validate?: (value: never) => Effect.Effect<void, unknown, unknown>;
|
|
@@ -20,16 +20,16 @@ export interface AnswerDefinitionContract {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
/** One typed fact required by a collect stage. */
|
|
23
|
-
export interface AnswerDefinition<Mode extends AnswerMode, ValueSchema extends Schema.
|
|
23
|
+
export interface AnswerDefinition<Mode extends AnswerMode, ValueSchema extends Schema.ConstraintCodec<unknown, unknown>, Error = never, Requirements = never> extends AnswerDefinitionContract {
|
|
24
24
|
readonly mode: Mode;
|
|
25
25
|
readonly schema: ValueSchema;
|
|
26
|
-
readonly question: QuestionDefinition<
|
|
27
|
-
readonly validate?: (value:
|
|
26
|
+
readonly question: QuestionDefinition<ValueSchema["Type"]>;
|
|
27
|
+
readonly validate?: (value: ValueSchema["Type"]) => Effect.Effect<void, Error, Requirements>;
|
|
28
28
|
readonly reject?: {
|
|
29
|
-
readonly ask: FixedQuestion | ChoiceQuestion<
|
|
29
|
+
readonly ask: FixedQuestion | ChoiceQuestion<ValueSchema["Type"]>;
|
|
30
30
|
};
|
|
31
31
|
readonly escape?: {
|
|
32
|
-
readonly value:
|
|
32
|
+
readonly value: ValueSchema["Type"];
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
interface DefineAnswerBase<Value> {
|
|
@@ -58,12 +58,12 @@ export interface DefineValidatedAnswerInput<Value, Error, Requirements> extends
|
|
|
58
58
|
}
|
|
59
59
|
/** Configuration shared by all answer grounding modes. */
|
|
60
60
|
export type DefineAnswerInput<Value, Error = never, Requirements = never> = DefineUnvalidatedAnswerInput<Value> | DefineValidatedAnswerInput<Value, Error, Requirements>;
|
|
61
|
-
declare function semantic<ValueSchema extends Schema.
|
|
62
|
-
declare function semantic<ValueSchema extends Schema.
|
|
63
|
-
declare function explicit<ValueSchema extends Schema.
|
|
64
|
-
declare function explicit<ValueSchema extends Schema.
|
|
65
|
-
declare function confirmed<ValueSchema extends Schema.
|
|
66
|
-
declare function confirmed<ValueSchema extends Schema.
|
|
61
|
+
declare function semantic<ValueSchema extends Schema.ConstraintCodec<unknown, unknown>>(schema: ValueSchema, input: DefineUnvalidatedAnswerInput<ValueSchema["Type"]>): AnswerDefinition<"semantic", ValueSchema, never, never>;
|
|
62
|
+
declare function semantic<ValueSchema extends Schema.ConstraintCodec<unknown, unknown>, Error, Requirements>(schema: ValueSchema, input: DefineValidatedAnswerInput<ValueSchema["Type"], Error, Requirements>): AnswerDefinition<"semantic", ValueSchema, Error, Requirements>;
|
|
63
|
+
declare function explicit<ValueSchema extends Schema.ConstraintCodec<unknown, unknown>>(schema: ValueSchema, input: DefineUnvalidatedAnswerInput<ValueSchema["Type"]>): AnswerDefinition<"explicit", ValueSchema, never, never>;
|
|
64
|
+
declare function explicit<ValueSchema extends Schema.ConstraintCodec<unknown, unknown>, Error, Requirements>(schema: ValueSchema, input: DefineValidatedAnswerInput<ValueSchema["Type"], Error, Requirements>): AnswerDefinition<"explicit", ValueSchema, Error, Requirements>;
|
|
65
|
+
declare function confirmed<ValueSchema extends Schema.ConstraintCodec<unknown, unknown>>(schema: ValueSchema, input: DefineUnvalidatedAnswerInput<ValueSchema["Type"]>): AnswerDefinition<"confirmed", ValueSchema, never, never>;
|
|
66
|
+
declare function confirmed<ValueSchema extends Schema.ConstraintCodec<unknown, unknown>, Error, Requirements>(schema: ValueSchema, input: DefineValidatedAnswerInput<ValueSchema["Type"], Error, Requirements>): AnswerDefinition<"confirmed", ValueSchema, Error, Requirements>;
|
|
67
67
|
/** Constructors for semantic, explicit, and explicitly confirmed facts. */
|
|
68
68
|
export declare const Answer: {
|
|
69
69
|
readonly semantic: typeof semantic;
|
package/dist/core/answer.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
/** How strongly a collect-stage answer must be grounded in user messages. */
|
|
3
|
-
export const AnswerModeSchema = Schema.
|
|
4
|
-
|
|
3
|
+
export const AnswerModeSchema = Schema.Literals([
|
|
4
|
+
"semantic",
|
|
5
|
+
"explicit",
|
|
6
|
+
"confirmed",
|
|
7
|
+
]);
|
|
8
|
+
const AnswerDescriptionSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(1_000));
|
|
5
9
|
const defineAnswer = (mode, schema, input) => {
|
|
6
10
|
if (input.ask._tag === "ChoiceQuestion") {
|
|
7
11
|
for (const option of input.ask.options) {
|
|
@@ -27,18 +31,23 @@ const defineAnswer = (mode, schema, input) => {
|
|
|
27
31
|
description: Schema.decodeSync(AnswerDescriptionSchema)(input.description),
|
|
28
32
|
question: input.ask,
|
|
29
33
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (input.escape === undefined) {
|
|
35
|
+
return input.validate === undefined
|
|
36
|
+
? base
|
|
37
|
+
: {
|
|
38
|
+
...base,
|
|
39
|
+
validate: input.validate,
|
|
40
|
+
reject: input.reject,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const escape = {
|
|
44
|
+
value: Schema.decodeSync(Schema.toType(schema))(input.escape.value),
|
|
45
|
+
};
|
|
38
46
|
return input.validate === undefined
|
|
39
|
-
?
|
|
47
|
+
? { ...base, escape }
|
|
40
48
|
: {
|
|
41
|
-
...
|
|
49
|
+
...base,
|
|
50
|
+
escape,
|
|
42
51
|
validate: input.validate,
|
|
43
52
|
reject: input.reject,
|
|
44
53
|
};
|
package/dist/core/chat.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
-
import type * as ParseResult from "effect/ParseResult";
|
|
3
2
|
import type { AcceptedAnswer, CollectAnswers, CollectStage, CollectStageDefinitionContract, CollectStagePrompt, CollectStageState } from "./collect-stage.js";
|
|
4
3
|
import { type UntrustedMessage } from "./model.js";
|
|
5
4
|
import { type CommandStage, type CommandStageDefinitionContract, type ToolStage, type ToolStageDefinitionContract } from "./stage.js";
|
|
@@ -7,17 +6,15 @@ import type { ToolSetExecution } from "./tool-set.js";
|
|
|
7
6
|
import type { StandardRepair } from "./repair.js";
|
|
8
7
|
import { ChatSessionConflict, ChatSessionStore, InvalidChatSession, type ChatSessionStoreUnavailable } from "./session.js";
|
|
9
8
|
/** Stable machine-facing name for one structured chat definition. */
|
|
10
|
-
export declare const ChatNameSchema: Schema.
|
|
9
|
+
export declare const ChatNameSchema: Schema.Trimmed;
|
|
11
10
|
/** Positive persisted-state version for one structured chat definition. */
|
|
12
|
-
export declare const ChatVersionSchema: Schema.
|
|
11
|
+
export declare const ChatVersionSchema: Schema.Number;
|
|
13
12
|
/** Safe reason that a server-owned chat transition was rejected. */
|
|
14
|
-
export declare const InvalidChatTransitionReasonSchema: Schema.
|
|
15
|
-
declare const InvalidChatTransition_base: Schema.
|
|
16
|
-
readonly
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
reason: Schema.Literal<["already_complete", "invalid_state"]>;
|
|
20
|
-
}>;
|
|
13
|
+
export declare const InvalidChatTransitionReasonSchema: Schema.Literals<readonly ["already_complete", "invalid_state"]>;
|
|
14
|
+
declare const InvalidChatTransition_base: Schema.Class<InvalidChatTransition, Schema.TaggedStruct<"InvalidChatTransition", {
|
|
15
|
+
readonly chat: Schema.Trimmed;
|
|
16
|
+
readonly reason: Schema.Literals<readonly ["already_complete", "invalid_state"]>;
|
|
17
|
+
}>, import("effect/Cause").YieldableError>;
|
|
21
18
|
/** A server-owned chat state cannot perform the requested transition. */
|
|
22
19
|
export declare class InvalidChatTransition extends InvalidChatTransition_base {
|
|
23
20
|
}
|
|
@@ -50,12 +47,12 @@ export interface ChatState<Name extends string, Version extends number, Stages e
|
|
|
50
47
|
};
|
|
51
48
|
}
|
|
52
49
|
type ChatQuestion<Stage> = Stage extends CollectStage<infer _Name, infer Fields, infer _Guards> ? CollectStagePrompt<Fields> : never;
|
|
53
|
-
type ChatToolExecution<Stage> = Stage extends ToolStage<infer _Name, infer Tools, infer _Guards> ? ToolSetExecution<Tools> : Stage extends CommandStage<infer _Name, infer _Command, infer _Guards> ? Extract<Effect.
|
|
50
|
+
type ChatToolExecution<Stage> = Stage extends ToolStage<infer _Name, infer Tools, infer _Guards> ? ToolSetExecution<Tools> : Stage extends CommandStage<infer _Name, infer _Command, infer _Guards> ? Extract<Effect.Success<ReturnType<Stage["run"]>>, object> : never;
|
|
54
51
|
type StageEffect<Stage> = Stage extends CollectStage<infer _CollectName, infer _Fields, infer _CollectGuards> ? ReturnType<Stage["run"]> : Stage extends ToolStage<infer _ToolName, infer _Tools, infer _ToolGuards> ? ReturnType<Stage["run"]> : Stage extends CommandStage<infer _CommandName, infer _Command, infer _CommandGuards> ? ReturnType<Stage["run"]> : never;
|
|
55
52
|
/** Failure union produced by any stage in one chat. */
|
|
56
|
-
export type ChatError<Stages extends ChatStageTuple> = InvalidChatTransition | Effect.
|
|
53
|
+
export type ChatError<Stages extends ChatStageTuple> = InvalidChatTransition | Effect.Error<StageEffect<Stages[number]>>;
|
|
57
54
|
/** Effect service union required by any stage in one chat. */
|
|
58
|
-
export type ChatRequirements<Stages extends ChatStageTuple> = Effect.
|
|
55
|
+
export type ChatRequirements<Stages extends ChatStageTuple> = Effect.Services<StageEffect<Stages[number]>>;
|
|
59
56
|
/** Question, ongoing tool result, or terminal result emitted by one turn. */
|
|
60
57
|
export type ChatTurn<Name extends string, Version extends number, Stages extends ChatStageTuple> = {
|
|
61
58
|
readonly _tag: "Question";
|
|
@@ -100,12 +97,12 @@ export interface ChatDefinition<Name extends string, Version extends number, Sta
|
|
|
100
97
|
readonly version: Version;
|
|
101
98
|
readonly stages: Stages;
|
|
102
99
|
readonly repair: StandardRepair | undefined;
|
|
103
|
-
readonly stateSchema: Schema.
|
|
100
|
+
readonly stateSchema: Schema.Codec<ChatState<Name, Version, Stages>, unknown>;
|
|
104
101
|
readonly initialState: ChatState<Name, Version, Stages>;
|
|
105
102
|
/** Read one accepted value together with its supporting transcript data. */
|
|
106
103
|
readonly getAcceptedAnswer: <Stage extends ChatCollectStage<Stages>, Field extends keyof CollectFields<Stage> & string>(state: ChatState<Name, Version, Stages>, stage: Stage, field: Field) => AcceptedAnswer<CollectAnswers<CollectFields<Stage>>[Field]> | undefined;
|
|
107
104
|
/** Strictly parse persisted server-owned chat state. */
|
|
108
|
-
readonly parseState: (input: Schema.
|
|
105
|
+
readonly parseState: (input: Schema.Codec.Encoded<Schema.Codec<ChatState<Name, Version, Stages>, unknown>>) => Effect.Effect<ChatState<Name, Version, Stages>, Schema.SchemaError>;
|
|
109
106
|
/** Run the active stage and any immediately reachable tool stage. */
|
|
110
107
|
readonly run: (input: {
|
|
111
108
|
readonly state: ChatState<Name, Version, Stages>;
|
package/dist/core/chat.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Schema
|
|
1
|
+
import { cast, Effect, Schema } from "effect";
|
|
2
2
|
import { readCollectStageRuntime } from "./collect-stage.js";
|
|
3
3
|
import { countUntrustedMessageCharacters, UntrustedMessageSchema, } from "./model.js";
|
|
4
4
|
import { readCommandStageRuntime, readToolStageRuntime, } from "./stage.js";
|
|
@@ -7,11 +7,14 @@ import { deriveCommandId } from "./command.js";
|
|
|
7
7
|
import { defineTool } from "./tool.js";
|
|
8
8
|
import { ChatSessionConflict, ChatSessionIdSchema, ChatSessionNamespaceSchema, ChatSessionReplacementSchema, ChatSessionRevisionSchema, ChatSessionSnapshotSchema, ChatSessionStore, InvalidChatSession, } from "./session.js";
|
|
9
9
|
/** Stable machine-facing name for one structured chat definition. */
|
|
10
|
-
export const ChatNameSchema = Schema.
|
|
10
|
+
export const ChatNameSchema = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(100), Schema.isPattern(/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/));
|
|
11
11
|
/** Positive persisted-state version for one structured chat definition. */
|
|
12
|
-
export const ChatVersionSchema = Schema.Number.
|
|
12
|
+
export const ChatVersionSchema = Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 2_147_483_647 }));
|
|
13
13
|
/** Safe reason that a server-owned chat transition was rejected. */
|
|
14
|
-
export const InvalidChatTransitionReasonSchema = Schema.
|
|
14
|
+
export const InvalidChatTransitionReasonSchema = Schema.Literals([
|
|
15
|
+
"already_complete",
|
|
16
|
+
"invalid_state",
|
|
17
|
+
]);
|
|
15
18
|
/** A server-owned chat state cannot perform the requested transition. */
|
|
16
19
|
export class InvalidChatTransition extends Schema.TaggedError()("InvalidChatTransition", {
|
|
17
20
|
chat: ChatNameSchema,
|
|
@@ -90,9 +93,9 @@ export const defineChat = (definition) => {
|
|
|
90
93
|
}
|
|
91
94
|
const correctionSchema = remainingSchemas.length === 0
|
|
92
95
|
? firstSchema
|
|
93
|
-
: Schema.Union(firstSchema, ...remainingSchemas);
|
|
96
|
+
: Schema.Union([firstSchema, ...remainingSchemas]);
|
|
94
97
|
const input = Schema.Struct({
|
|
95
|
-
corrections: Schema.NonEmptyArray(correctionSchema).
|
|
98
|
+
corrections: Schema.NonEmptyArray(correctionSchema).check(Schema.isMaxLength(repair.maximumCorrections)),
|
|
96
99
|
});
|
|
97
100
|
return defineTool({
|
|
98
101
|
name: repairToolName,
|
|
@@ -164,8 +167,11 @@ export const defineChat = (definition) => {
|
|
|
164
167
|
const baseStateFields = {
|
|
165
168
|
schemaVersion: Schema.Literal(definition.version),
|
|
166
169
|
chat: Schema.Literal(definition.name),
|
|
167
|
-
stage: Schema.Number.
|
|
168
|
-
|
|
170
|
+
stage: Schema.Number.check(Schema.isInt(), Schema.isBetween({
|
|
171
|
+
minimum: 0,
|
|
172
|
+
maximum: definition.stages.length - 1,
|
|
173
|
+
})),
|
|
174
|
+
status: Schema.Literals(["active", "complete"]),
|
|
169
175
|
stages: Schema.Struct(stateFields),
|
|
170
176
|
};
|
|
171
177
|
const rawStateSchema = repair === undefined
|
|
@@ -173,18 +179,21 @@ export const defineChat = (definition) => {
|
|
|
173
179
|
: Schema.Struct({
|
|
174
180
|
...baseStateFields,
|
|
175
181
|
repair: Schema.Struct({
|
|
176
|
-
pendingStages: Schema.Array(Schema.Number.
|
|
182
|
+
pendingStages: Schema.Array(Schema.Number.check(Schema.isInt(), Schema.isBetween({
|
|
183
|
+
minimum: 0,
|
|
184
|
+
maximum: finalStageIndex - 1,
|
|
185
|
+
}))).check(Schema.isMaxLength(20)),
|
|
177
186
|
}),
|
|
178
187
|
});
|
|
179
188
|
// SAFETY: the conditional repair field is erased only for applying the
|
|
180
189
|
// shared semantic predicate; stateSchema below restores the public type.
|
|
181
|
-
const runtimeStateSchema =
|
|
182
|
-
const refinedStateSchema = runtimeStateSchema.
|
|
190
|
+
const runtimeStateSchema = cast(rawStateSchema);
|
|
191
|
+
const refinedStateSchema = runtimeStateSchema.check(Schema.makeFilter((state) => isValidRuntimeState(cast(state)), {
|
|
183
192
|
description: "semantically valid structured-chat state",
|
|
184
193
|
}));
|
|
185
194
|
// SAFETY: stage state fields are taken directly from the concrete collect
|
|
186
195
|
// stages, and the remaining envelope fields are exact literals or bounds.
|
|
187
|
-
const stateSchema =
|
|
196
|
+
const stateSchema = cast(refinedStateSchema);
|
|
188
197
|
const baseInitialState = {
|
|
189
198
|
schemaVersion: definition.version,
|
|
190
199
|
chat: definition.name,
|
|
@@ -195,7 +204,7 @@ export const defineChat = (definition) => {
|
|
|
195
204
|
const initialStateInput = repair === undefined
|
|
196
205
|
? baseInitialState
|
|
197
206
|
: { ...baseInitialState, repair: { pendingStages: [] } };
|
|
198
|
-
const initialState = Schema.
|
|
207
|
+
const initialState = Schema.decodeUnknownSync(Schema.toType(stateSchema))(initialStateInput);
|
|
199
208
|
const isGroundedInMessages = (state, messages) => definition.stages.every((stage) => {
|
|
200
209
|
if (stage._tag !== "CollectStage") {
|
|
201
210
|
return true;
|
|
@@ -279,7 +288,7 @@ export const defineChat = (definition) => {
|
|
|
279
288
|
}
|
|
280
289
|
// SAFETY: planWith used the generated repair tool schema, and this
|
|
281
290
|
// branch is selected by that tool's unique literal name.
|
|
282
|
-
const proposal =
|
|
291
|
+
const proposal = cast(call.arguments);
|
|
283
292
|
return applyConversationRepairs(state, messages, proposal.corrections).pipe(Effect.flatMap((repairedState) => Effect.suspend(() => runTrustedRuntime(repairedState, messages, commandContext, false))));
|
|
284
293
|
}));
|
|
285
294
|
}
|
|
@@ -348,14 +357,14 @@ export const defineChat = (definition) => {
|
|
|
348
357
|
const run = (input) => {
|
|
349
358
|
// SAFETY: ChatState is generated from the same stage tuple as the sealed
|
|
350
359
|
// runtime state contract; only generic correlations are erased here.
|
|
351
|
-
const runtimeState =
|
|
360
|
+
const runtimeState = cast(input.state);
|
|
352
361
|
const runtime = runCheckedRuntime(runtimeState, input.messages);
|
|
353
362
|
// SAFETY: runtime dispatch follows the exact Stages tuple and each stage
|
|
354
363
|
// retains its own parsing, errors, dependencies, and output constructor.
|
|
355
|
-
return
|
|
364
|
+
return cast(runtime);
|
|
356
365
|
};
|
|
357
366
|
const reply = (input) => Effect.gen(function* () {
|
|
358
|
-
const parsedInput = yield* Schema.
|
|
367
|
+
const parsedInput = yield* Schema.decodeUnknownEffect(ChatReplyBoundaryInputSchema)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidSession("invalid_input")));
|
|
359
368
|
const store = yield* ChatSessionStore;
|
|
360
369
|
const scope = {
|
|
361
370
|
namespace: parsedInput.namespace ?? "",
|
|
@@ -371,7 +380,7 @@ export const defineChat = (definition) => {
|
|
|
371
380
|
}));
|
|
372
381
|
const snapshot = loaded === null
|
|
373
382
|
? null
|
|
374
|
-
: yield* Schema.
|
|
383
|
+
: yield* Schema.decodeUnknownEffect(ChatSessionSnapshotSchema)(loaded, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidSession("invalid_snapshot")));
|
|
375
384
|
if ((snapshot === null &&
|
|
376
385
|
parsedInput.expectedRevision !== undefined) ||
|
|
377
386
|
(snapshot !== null &&
|
|
@@ -380,12 +389,12 @@ export const defineChat = (definition) => {
|
|
|
380
389
|
}
|
|
381
390
|
const state = snapshot === null
|
|
382
391
|
? initialState
|
|
383
|
-
: yield* Schema.
|
|
392
|
+
: yield* Schema.decodeUnknownEffect(stateSchema)(snapshot.state, {
|
|
384
393
|
onExcessProperty: "error",
|
|
385
394
|
}).pipe(Effect.mapError(() => invalidSession("invalid_state")));
|
|
386
395
|
const previousMessages = snapshot?.messages ?? [];
|
|
387
396
|
// SAFETY: stateSchema decoded this definition's exact state envelope.
|
|
388
|
-
const runtimeState =
|
|
397
|
+
const runtimeState = cast(state);
|
|
389
398
|
if (!isGroundedInMessages(runtimeState, previousMessages)) {
|
|
390
399
|
return yield* Effect.fail(invalidSession("invalid_state"));
|
|
391
400
|
}
|
|
@@ -393,7 +402,7 @@ export const defineChat = (definition) => {
|
|
|
393
402
|
maximumPersistedMessages) {
|
|
394
403
|
return yield* Effect.fail(invalidSession("history_limit"));
|
|
395
404
|
}
|
|
396
|
-
const userMessage = yield* Schema.
|
|
405
|
+
const userMessage = yield* Schema.decodeUnknownEffect(UntrustedMessageSchema)({ role: "user", content: parsedInput.message }, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidSession("invalid_input")));
|
|
397
406
|
const messages = [...previousMessages, userMessage];
|
|
398
407
|
const commandContext = finalStage?._tag === "CommandStage"
|
|
399
408
|
? {
|
|
@@ -414,7 +423,7 @@ export const defineChat = (definition) => {
|
|
|
414
423
|
snapshot !== null &&
|
|
415
424
|
state.status === "active" &&
|
|
416
425
|
state.stage === finalStageIndex);
|
|
417
|
-
const turn = yield*
|
|
426
|
+
const turn = yield* cast(trustedTurn);
|
|
418
427
|
const toolModelContext = turn._tag === "Question"
|
|
419
428
|
? undefined
|
|
420
429
|
: readToolExecutionModelContext(turn.result);
|
|
@@ -438,7 +447,7 @@ export const defineChat = (definition) => {
|
|
|
438
447
|
if (persistedMessages.length > maximumPersistedMessages) {
|
|
439
448
|
return yield* Effect.fail(invalidSession("history_limit"));
|
|
440
449
|
}
|
|
441
|
-
const encodedState = yield* Schema.
|
|
450
|
+
const encodedState = yield* Schema.encodeUnknownEffect(stateSchema)(turn.state, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidSession("invalid_state")));
|
|
442
451
|
const replaced = yield* store
|
|
443
452
|
.replace({
|
|
444
453
|
...scope,
|
|
@@ -456,7 +465,7 @@ export const defineChat = (definition) => {
|
|
|
456
465
|
status: turn.state.status,
|
|
457
466
|
},
|
|
458
467
|
}));
|
|
459
|
-
const replacement = yield* Schema.
|
|
468
|
+
const replacement = yield* Schema.decodeUnknownEffect(ChatSessionReplacementSchema)(replaced, { onExcessProperty: "error" }).pipe(Effect.mapError(() => invalidSession("invalid_replacement")));
|
|
460
469
|
return {
|
|
461
470
|
revision: replacement.revision,
|
|
462
471
|
turn,
|
|
@@ -477,11 +486,11 @@ export const defineChat = (definition) => {
|
|
|
477
486
|
}
|
|
478
487
|
// SAFETY: Stage is restricted to this chat's concrete collect stages,
|
|
479
488
|
// Field is restricted to its field keys, and state uses the same tuple.
|
|
480
|
-
const runtimeState =
|
|
489
|
+
const runtimeState = cast(state);
|
|
481
490
|
const accepted = runtimeState.stages[stage.name]?.accepted[field];
|
|
482
|
-
return
|
|
491
|
+
return cast(accepted);
|
|
483
492
|
},
|
|
484
|
-
parseState: (input) => Schema.
|
|
493
|
+
parseState: (input) => Schema.decodeUnknownEffect(stateSchema)(input, {
|
|
485
494
|
onExcessProperty: "error",
|
|
486
495
|
}),
|
|
487
496
|
run,
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
-
import type * as ParseResult from "effect/ParseResult";
|
|
3
2
|
import type { AnswerDefinition, AnswerDefinitionContract, AnswerMode } from "./answer.js";
|
|
4
3
|
import { ChatModelUnavailable, StructuredChatModel, type UnsupportedModelToolSchema, type UntrustedMessage } from "./model.js";
|
|
5
4
|
import type { ModelGuardError, ModelGuardRequirements, ModelGuardTuple } from "./model-guard.js";
|
|
6
5
|
import { InvalidToolCall, type InvalidToolProjection } from "./tool.js";
|
|
7
|
-
import type { AdaptiveChoiceQuestion, ChoiceQuestion, QuestionChoice } from "./question.js";
|
|
6
|
+
import type { AdaptiveChoiceQuestion, ChoiceQuestion, QuestionDefinitionContract, QuestionChoice } from "./question.js";
|
|
8
7
|
import { type StructuredDefinition } from "./definition.js";
|
|
9
8
|
/** Safe reason that a collect-stage model proposal was rejected. */
|
|
10
|
-
export declare const InvalidCollectStageResponseReasonSchema: Schema.
|
|
11
|
-
declare const InvalidCollectStageResponse_base: Schema.
|
|
12
|
-
readonly
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
reason: Schema.Literal<["invalid_evidence", "invalid_repair"]>;
|
|
16
|
-
}>;
|
|
9
|
+
export declare const InvalidCollectStageResponseReasonSchema: Schema.Literals<readonly ["invalid_evidence", "invalid_repair"]>;
|
|
10
|
+
declare const InvalidCollectStageResponse_base: Schema.Class<InvalidCollectStageResponse, Schema.TaggedStruct<"InvalidCollectStageResponse", {
|
|
11
|
+
readonly stage: Schema.Trimmed;
|
|
12
|
+
readonly reason: Schema.Literals<readonly ["invalid_evidence", "invalid_repair"]>;
|
|
13
|
+
}>, import("effect/Cause").YieldableError>;
|
|
17
14
|
/** A collect-stage proposal was not grounded in a user message. */
|
|
18
15
|
export declare class InvalidCollectStageResponse extends InvalidCollectStageResponse_base {
|
|
19
16
|
}
|
|
@@ -104,7 +101,7 @@ export interface CollectStageTurn<Fields extends AnswerFields> {
|
|
|
104
101
|
readonly complete: boolean;
|
|
105
102
|
readonly question: CollectStagePrompt<Fields> | undefined;
|
|
106
103
|
}
|
|
107
|
-
type RuntimeAnswerValue = Schema.Schema.Type<Schema.
|
|
104
|
+
type RuntimeAnswerValue = Schema.Schema.Type<Schema.Codec<unknown, unknown>>;
|
|
108
105
|
type RuntimeAcceptedAnswer = AcceptedAnswer<RuntimeAnswerValue>;
|
|
109
106
|
interface RuntimeCollectStageState {
|
|
110
107
|
readonly accepted: Readonly<Partial<Record<string, RuntimeAcceptedAnswer>>>;
|
|
@@ -139,27 +136,43 @@ interface RuntimeCollectRepairResult {
|
|
|
139
136
|
/** @internal Erased collect-stage behavior consumed by the chat runtime. */
|
|
140
137
|
export interface CollectStageRuntime {
|
|
141
138
|
readonly initialState: RuntimeCollectStageState;
|
|
142
|
-
readonly stateSchema: Schema.
|
|
139
|
+
readonly stateSchema: Schema.Codec<unknown, unknown>;
|
|
143
140
|
readonly isInitial: (state: RuntimeCollectStageState) => boolean;
|
|
144
141
|
readonly isValid: (state: RuntimeCollectStageState) => boolean;
|
|
145
142
|
readonly isGroundedInMessages: (state: RuntimeCollectStageState, messages: ReadonlyArray<UntrustedMessage>) => boolean;
|
|
146
143
|
readonly isComplete: (state: RuntimeCollectStageState) => boolean;
|
|
147
|
-
readonly repairSchema: Schema.
|
|
144
|
+
readonly repairSchema: Schema.Codec<unknown, unknown>;
|
|
148
145
|
readonly applyRepairs: (state: RuntimeCollectStageState, messages: ReadonlyArray<UntrustedMessage>, repairs: ReadonlyArray<RuntimeCollectRepair>) => Effect.Effect<RuntimeCollectRepairResult, unknown, unknown>;
|
|
149
146
|
readonly run: (input: {
|
|
150
147
|
readonly state: RuntimeCollectStageState;
|
|
151
148
|
readonly messages: ReadonlyArray<UntrustedMessage>;
|
|
152
149
|
}) => Effect.Effect<RuntimeCollectStageTurn, unknown, unknown>;
|
|
153
150
|
}
|
|
151
|
+
/** @internal One definition-ordered field exposed to trusted projections. */
|
|
152
|
+
export interface CollectStageInspectionField {
|
|
153
|
+
readonly field: string;
|
|
154
|
+
readonly mode: AnswerMode;
|
|
155
|
+
readonly description: string;
|
|
156
|
+
readonly question: QuestionDefinitionContract;
|
|
157
|
+
readonly encodeValue: (value: RuntimeAnswerValue) => Effect.Effect<unknown, Schema.SchemaError>;
|
|
158
|
+
}
|
|
159
|
+
/** @internal Read-only collect-stage metadata used by trusted projections. */
|
|
160
|
+
export interface CollectStageInspection {
|
|
161
|
+
readonly fields: ReadonlyArray<CollectStageInspectionField>;
|
|
162
|
+
}
|
|
154
163
|
declare const collectStageRuntime: unique symbol;
|
|
164
|
+
declare const collectStageInspection: unique symbol;
|
|
155
165
|
/** Minimum sealed collect-stage shape accepted by a chat definition. */
|
|
156
166
|
export interface CollectStageDefinitionContract extends StructuredDefinition<"collect_stage"> {
|
|
157
167
|
readonly _tag: "CollectStage";
|
|
158
168
|
readonly name: string;
|
|
159
169
|
readonly [collectStageRuntime]: CollectStageRuntime;
|
|
170
|
+
readonly [collectStageInspection]: CollectStageInspection;
|
|
160
171
|
}
|
|
161
172
|
/** @internal Read the erased runtime from an authentic collect stage. */
|
|
162
173
|
export declare const readCollectStageRuntime: (stage: CollectStageDefinitionContract) => CollectStageRuntime;
|
|
174
|
+
/** @internal Read trusted definition metadata from an authentic collect stage. */
|
|
175
|
+
export declare const readCollectStageInspection: (stage: CollectStageDefinitionContract) => CollectStageInspection;
|
|
163
176
|
/** Shared conversational policy for questions in one collect stage. */
|
|
164
177
|
export interface CollectQuestionPolicy {
|
|
165
178
|
/** Trusted style guidance applied to every adaptive question in this stage. */
|
|
@@ -180,12 +193,12 @@ export interface CollectStage<Name extends string, Fields extends AnswerFields,
|
|
|
180
193
|
readonly name: Name;
|
|
181
194
|
readonly fields: Fields;
|
|
182
195
|
readonly questions: CollectQuestionPolicy;
|
|
183
|
-
readonly answersSchema: Schema.
|
|
184
|
-
readonly stateSchema: Schema.
|
|
196
|
+
readonly answersSchema: Schema.Codec<CollectAnswers<Fields>, unknown>;
|
|
197
|
+
readonly stateSchema: Schema.Codec<CollectStageState<Fields>, unknown>;
|
|
185
198
|
readonly initialState: CollectStageState<Fields>;
|
|
186
199
|
readonly guards: Guards;
|
|
187
200
|
/** Strictly parse persisted or client-returned stage state. */
|
|
188
|
-
readonly parseState: (input: Schema.
|
|
201
|
+
readonly parseState: (input: Schema.Codec.Encoded<Schema.Codec<CollectStageState<Fields>, unknown>>) => Effect.Effect<CollectStageState<Fields>, Schema.SchemaError>;
|
|
189
202
|
/** Test whether every schema-defined answer has been populated. */
|
|
190
203
|
readonly isComplete: (state: CollectStageState<Fields>) => boolean;
|
|
191
204
|
/** Select the first missing question in schema declaration order. */
|