@popcomputer/structured-chat 0.2.0-rc.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/README.md +53 -0
- package/dist/core/collect-stage.d.ts +17 -1
- package/dist/core/collect-stage.js +18 -0
- 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/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 +32 -9
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -600,6 +600,59 @@ that expose message editing, regeneration, or persistent branching should pair
|
|
|
600
600
|
it with an application-owned branch/session policy rather than treating browser
|
|
601
601
|
history as authoritative.
|
|
602
602
|
|
|
603
|
+
## Inspect development state
|
|
604
|
+
|
|
605
|
+
The optional debug inspector shows the current stage, stage progress, required
|
|
606
|
+
fields, accepted values, issued questions, and supporting evidence. It uses a
|
|
607
|
+
small package-owned panel inspired by DialKit, without adding DialKit or another
|
|
608
|
+
runtime dependency.
|
|
609
|
+
|
|
610
|
+
Debug data uses a separate, explicit response contract. Select it on an
|
|
611
|
+
authenticated development endpoint:
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
import {
|
|
615
|
+
presentChatDebugReply,
|
|
616
|
+
presentChatReply,
|
|
617
|
+
} from "@popcomputer/structured-chat"
|
|
618
|
+
|
|
619
|
+
const response = debugAccessGranted
|
|
620
|
+
? yield* presentChatDebugReply(
|
|
621
|
+
ResourceFinder,
|
|
622
|
+
{ ...reply, sessionId: publicSessionId },
|
|
623
|
+
{ inspection: { evidence: "include" } },
|
|
624
|
+
)
|
|
625
|
+
: yield* presentChatReply({ ...reply, sessionId: publicSessionId })
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Connect that endpoint to the inspector store:
|
|
629
|
+
|
|
630
|
+
```tsx
|
|
631
|
+
import { makeAssistantChatModelAdapter } from "@popcomputer/structured-chat/assistant-ui"
|
|
632
|
+
import {
|
|
633
|
+
createStructuredChatDebugStore,
|
|
634
|
+
StructuredChatDebugPanel,
|
|
635
|
+
} from "@popcomputer/structured-chat/assistant-ui/debug"
|
|
636
|
+
|
|
637
|
+
const debugStore = createStructuredChatDebugStore()
|
|
638
|
+
const model = makeAssistantChatModelAdapter({
|
|
639
|
+
endpoint: "/api/resource-finder/debug/turn",
|
|
640
|
+
onDebugSnapshot: debugStore.receive,
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
export function ResourceFinderDebugPanel() {
|
|
644
|
+
return <StructuredChatDebugPanel store={debugStore} />
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Without `onDebugSnapshot`, the normal adapter continues to reject a response
|
|
649
|
+
containing debug data. Hiding or unmounting the panel is not an authorization
|
|
650
|
+
boundary: the server must decide whether to emit the debug response. Use
|
|
651
|
+
`evidence: "omit"` when transcript quotes should not cross that boundary.
|
|
652
|
+
Create one store per chat runtime; it reflects the most recently completed
|
|
653
|
+
debug response. Observer failures are isolated and never change the outcome of
|
|
654
|
+
the persisted chat turn.
|
|
655
|
+
|
|
603
656
|
## Plan without executing
|
|
604
657
|
|
|
605
658
|
The default remains one call:
|
|
@@ -3,7 +3,7 @@ import type { AnswerDefinition, AnswerDefinitionContract, AnswerMode } from "./a
|
|
|
3
3
|
import { ChatModelUnavailable, StructuredChatModel, type UnsupportedModelToolSchema, type UntrustedMessage } from "./model.js";
|
|
4
4
|
import type { ModelGuardError, ModelGuardRequirements, ModelGuardTuple } from "./model-guard.js";
|
|
5
5
|
import { InvalidToolCall, type InvalidToolProjection } from "./tool.js";
|
|
6
|
-
import type { AdaptiveChoiceQuestion, ChoiceQuestion, QuestionChoice } from "./question.js";
|
|
6
|
+
import type { AdaptiveChoiceQuestion, ChoiceQuestion, QuestionDefinitionContract, QuestionChoice } from "./question.js";
|
|
7
7
|
import { type StructuredDefinition } from "./definition.js";
|
|
8
8
|
/** Safe reason that a collect-stage model proposal was rejected. */
|
|
9
9
|
export declare const InvalidCollectStageResponseReasonSchema: Schema.Literals<readonly ["invalid_evidence", "invalid_repair"]>;
|
|
@@ -148,15 +148,31 @@ export interface CollectStageRuntime {
|
|
|
148
148
|
readonly messages: ReadonlyArray<UntrustedMessage>;
|
|
149
149
|
}) => Effect.Effect<RuntimeCollectStageTurn, unknown, unknown>;
|
|
150
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
|
+
}
|
|
151
163
|
declare const collectStageRuntime: unique symbol;
|
|
164
|
+
declare const collectStageInspection: unique symbol;
|
|
152
165
|
/** Minimum sealed collect-stage shape accepted by a chat definition. */
|
|
153
166
|
export interface CollectStageDefinitionContract extends StructuredDefinition<"collect_stage"> {
|
|
154
167
|
readonly _tag: "CollectStage";
|
|
155
168
|
readonly name: string;
|
|
156
169
|
readonly [collectStageRuntime]: CollectStageRuntime;
|
|
170
|
+
readonly [collectStageInspection]: CollectStageInspection;
|
|
157
171
|
}
|
|
158
172
|
/** @internal Read the erased runtime from an authentic collect stage. */
|
|
159
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;
|
|
160
176
|
/** Shared conversational policy for questions in one collect stage. */
|
|
161
177
|
export interface CollectQuestionPolicy {
|
|
162
178
|
/** Trusted style guidance applied to every adaptive question in this stage. */
|
|
@@ -19,8 +19,11 @@ export class InvalidCollectStageResponse extends Schema.TaggedError()("InvalidCo
|
|
|
19
19
|
export class AnswerValidationRejected extends Data.TaggedError("AnswerValidationRejected") {
|
|
20
20
|
}
|
|
21
21
|
const collectStageRuntime = Symbol("@popcomputer/structured-chat/CollectStageRuntime");
|
|
22
|
+
const collectStageInspection = Symbol("@popcomputer/structured-chat/CollectStageInspection");
|
|
22
23
|
/** @internal Read the erased runtime from an authentic collect stage. */
|
|
23
24
|
export const readCollectStageRuntime = (stage) => stage[collectStageRuntime];
|
|
25
|
+
/** @internal Read trusted definition metadata from an authentic collect stage. */
|
|
26
|
+
export const readCollectStageInspection = (stage) => stage[collectStageInspection];
|
|
24
27
|
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
25
28
|
/** Define one deterministic schema-derived fact collection stage. */
|
|
26
29
|
export const defineCollectStage = (definition) => {
|
|
@@ -171,6 +174,18 @@ export const defineCollectStage = (definition) => {
|
|
|
171
174
|
accepted: {},
|
|
172
175
|
asked: {},
|
|
173
176
|
});
|
|
177
|
+
const inspectionFields = fieldNames.map((field) => {
|
|
178
|
+
const answer = getAnswer(field);
|
|
179
|
+
return {
|
|
180
|
+
field,
|
|
181
|
+
mode: answer.mode,
|
|
182
|
+
description: answer.description,
|
|
183
|
+
question: answer.question,
|
|
184
|
+
encodeValue: (value) => Schema.encodeUnknownEffect(answer.schema)(value, {
|
|
185
|
+
onExcessProperty: "error",
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
});
|
|
174
189
|
// SAFETY: when guards are omitted, Guards uses its readonly [] default; an
|
|
175
190
|
// explicitly supplied tuple is returned unchanged.
|
|
176
191
|
const guards = definition.guards ?? cast([]);
|
|
@@ -630,6 +645,9 @@ export const defineCollectStage = (definition) => {
|
|
|
630
645
|
},
|
|
631
646
|
}),
|
|
632
647
|
run,
|
|
648
|
+
[collectStageInspection]: {
|
|
649
|
+
fields: inspectionFields,
|
|
650
|
+
},
|
|
633
651
|
[collectStageRuntime]: {
|
|
634
652
|
initialState,
|
|
635
653
|
stateSchema,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import type { ChatDefinition, ChatReply, ChatStageTuple } from "./chat.js";
|
|
3
|
+
import { type InspectChatStateOptions, type InvalidChatDebugProjection } from "./debug.js";
|
|
4
|
+
import { presentChatReply, type InvalidChatPresentation, type PresentChatReplyOptions } from "./protocol.js";
|
|
5
|
+
type BrowserPresentableTurn = Parameters<typeof presentChatReply>[0]["turn"];
|
|
6
|
+
type DebugChatTurn<Name extends string, Version extends number, Stages extends ChatStageTuple> = ChatReply<Name, Version, Stages>["turn"] & BrowserPresentableTurn;
|
|
7
|
+
type DebugChatReply<Name extends string, Version extends number, Stages extends ChatStageTuple> = Omit<ChatReply<Name, Version, Stages>, "turn"> & {
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
readonly turn: DebugChatTurn<Name, Version, Stages>;
|
|
10
|
+
};
|
|
11
|
+
/** Explicit opt-in browser response carrying one debug state projection. */
|
|
12
|
+
export declare const StructuredChatDebugTurnResponseSchema: Schema.Struct<{
|
|
13
|
+
readonly debug: Schema.Struct<{
|
|
14
|
+
readonly schemaVersion: Schema.Literal<1>;
|
|
15
|
+
readonly chat: Schema.Struct<{
|
|
16
|
+
readonly name: Schema.Trimmed;
|
|
17
|
+
readonly version: Schema.Number;
|
|
18
|
+
}>;
|
|
19
|
+
readonly status: Schema.Literals<readonly ["active", "complete"]>;
|
|
20
|
+
readonly currentStage: Schema.Struct<{
|
|
21
|
+
readonly index: Schema.Natural;
|
|
22
|
+
readonly name: Schema.Trimmed;
|
|
23
|
+
readonly kind: Schema.Literals<readonly ["collect", "tool", "command"]>;
|
|
24
|
+
}>;
|
|
25
|
+
readonly stages: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
26
|
+
readonly satisfiedFields: Schema.Natural;
|
|
27
|
+
readonly totalFields: Schema.Natural;
|
|
28
|
+
readonly fields: Schema.$Array<Schema.Struct<{
|
|
29
|
+
readonly field: Schema.String;
|
|
30
|
+
readonly mode: Schema.Literals<readonly ["semantic", "explicit", "confirmed"]>;
|
|
31
|
+
readonly description: Schema.String;
|
|
32
|
+
readonly question: Schema.Union<readonly [Schema.Struct<{
|
|
33
|
+
readonly _tag: Schema.Literal<"FixedQuestion">;
|
|
34
|
+
readonly text: Schema.String;
|
|
35
|
+
}>, Schema.Struct<{
|
|
36
|
+
readonly _tag: Schema.Literal<"AdaptiveQuestion">;
|
|
37
|
+
readonly goal: Schema.String;
|
|
38
|
+
readonly fallback: Schema.String;
|
|
39
|
+
}>, Schema.Struct<{
|
|
40
|
+
readonly _tag: Schema.Literal<"AdaptiveChoiceQuestion">;
|
|
41
|
+
readonly prompt: Schema.String;
|
|
42
|
+
readonly minimumOptions: Schema.Natural;
|
|
43
|
+
readonly maximumOptions: Schema.Natural;
|
|
44
|
+
readonly fallbackOptions: Schema.$Array<Schema.String>;
|
|
45
|
+
}>, Schema.Struct<{
|
|
46
|
+
readonly _tag: Schema.Literal<"ChoiceQuestion">;
|
|
47
|
+
readonly text: Schema.String;
|
|
48
|
+
readonly options: Schema.$Array<Schema.Struct<{
|
|
49
|
+
readonly label: Schema.String;
|
|
50
|
+
}>>;
|
|
51
|
+
}>]>;
|
|
52
|
+
readonly state: Schema.Union<readonly [Schema.Struct<{
|
|
53
|
+
readonly _tag: Schema.Literal<"Missing">;
|
|
54
|
+
}>, Schema.Struct<{
|
|
55
|
+
readonly _tag: Schema.Literal<"Asked">;
|
|
56
|
+
readonly issuedQuestion: Schema.Struct<{
|
|
57
|
+
readonly messageIndex: Schema.Natural;
|
|
58
|
+
readonly text: Schema.String;
|
|
59
|
+
}>;
|
|
60
|
+
}>, Schema.Struct<{
|
|
61
|
+
readonly _tag: Schema.Literal<"Accepted">;
|
|
62
|
+
readonly value: Schema.Codec<import("./json-value.js").JsonValue, import("./json-value.js").JsonValue, never, never>;
|
|
63
|
+
readonly evidence: Schema.NullOr<Schema.Struct<{
|
|
64
|
+
readonly messageIndex: Schema.Natural;
|
|
65
|
+
readonly quote: Schema.String;
|
|
66
|
+
}>>;
|
|
67
|
+
readonly issuedQuestion: Schema.NullOr<Schema.Struct<{
|
|
68
|
+
readonly messageIndex: Schema.Natural;
|
|
69
|
+
readonly text: Schema.String;
|
|
70
|
+
}>>;
|
|
71
|
+
}>]>;
|
|
72
|
+
}>>;
|
|
73
|
+
readonly index: Schema.Natural;
|
|
74
|
+
readonly name: Schema.Trimmed;
|
|
75
|
+
readonly status: Schema.Literals<readonly ["complete", "current", "upcoming"]>;
|
|
76
|
+
readonly repairPending: Schema.Boolean;
|
|
77
|
+
readonly _tag: Schema.Literal<"CollectStage">;
|
|
78
|
+
}>, Schema.Struct<{
|
|
79
|
+
readonly tools: Schema.$Array<Schema.Trimmed>;
|
|
80
|
+
readonly afterExecution: Schema.Literals<readonly ["stay", "complete"]>;
|
|
81
|
+
readonly index: Schema.Natural;
|
|
82
|
+
readonly name: Schema.Trimmed;
|
|
83
|
+
readonly status: Schema.Literals<readonly ["complete", "current", "upcoming"]>;
|
|
84
|
+
readonly repairPending: Schema.Boolean;
|
|
85
|
+
readonly _tag: Schema.Literal<"ToolStage">;
|
|
86
|
+
}>, Schema.Struct<{
|
|
87
|
+
readonly command: Schema.Trimmed;
|
|
88
|
+
readonly index: Schema.Natural;
|
|
89
|
+
readonly name: Schema.Trimmed;
|
|
90
|
+
readonly status: Schema.Literals<readonly ["complete", "current", "upcoming"]>;
|
|
91
|
+
readonly repairPending: Schema.Boolean;
|
|
92
|
+
readonly _tag: Schema.Literal<"CommandStage">;
|
|
93
|
+
}>]>>;
|
|
94
|
+
}>;
|
|
95
|
+
readonly schemaVersion: Schema.Literal<1>;
|
|
96
|
+
readonly session: Schema.optional<Schema.Struct<{
|
|
97
|
+
readonly id: Schema.Trimmed;
|
|
98
|
+
readonly revision: Schema.Trimmed;
|
|
99
|
+
}>>;
|
|
100
|
+
readonly message: Schema.Struct<{
|
|
101
|
+
readonly role: Schema.Literal<"assistant">;
|
|
102
|
+
readonly content: Schema.NonEmptyArray<Schema.Union<readonly [Schema.Struct<{
|
|
103
|
+
readonly type: Schema.Literal<"text">;
|
|
104
|
+
readonly text: Schema.Trimmed;
|
|
105
|
+
}>, Schema.Struct<{
|
|
106
|
+
readonly type: Schema.Literal<"data">;
|
|
107
|
+
readonly name: Schema.Trimmed;
|
|
108
|
+
readonly data: Schema.Unknown;
|
|
109
|
+
}>]>>;
|
|
110
|
+
}>;
|
|
111
|
+
}>;
|
|
112
|
+
/** Explicit opt-in browser response carrying one debug state projection. */
|
|
113
|
+
export type StructuredChatDebugTurnResponse = Schema.Schema.Type<typeof StructuredChatDebugTurnResponseSchema>;
|
|
114
|
+
/** Presentation and state-inspection policies for one debug chat reply. */
|
|
115
|
+
export interface PresentChatDebugReplyOptions<Name extends string, Version extends number, Stages extends ChatStageTuple> {
|
|
116
|
+
readonly presentation?: PresentChatReplyOptions<DebugChatTurn<Name, Version, Stages>>;
|
|
117
|
+
readonly inspection?: InspectChatStateOptions;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Project one persisted reply into the explicit debug browser protocol.
|
|
121
|
+
*
|
|
122
|
+
* Applications must select this presenter deliberately and should authorize
|
|
123
|
+
* its endpoint independently from whether a debug panel is visually mounted.
|
|
124
|
+
*/
|
|
125
|
+
export declare const presentChatDebugReply: <const Name extends string, const Version extends number, const Stages extends ChatStageTuple>(chat: ChatDefinition<Name, Version, Stages>, reply: DebugChatReply<Name, Version, Stages>, options?: PresentChatDebugReplyOptions<Name, Version, Stages>) => Effect.Effect<StructuredChatDebugTurnResponse, InvalidChatPresentation | InvalidChatDebugProjection>;
|
|
126
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { inspectChatState, StructuredChatDebugSnapshotSchema, } from "./debug.js";
|
|
3
|
+
import { presentChatReply, StructuredChatTurnResponseSchema, } from "./protocol.js";
|
|
4
|
+
/** Explicit opt-in browser response carrying one debug state projection. */
|
|
5
|
+
export const StructuredChatDebugTurnResponseSchema = Schema.Struct({
|
|
6
|
+
...StructuredChatTurnResponseSchema.fields,
|
|
7
|
+
debug: StructuredChatDebugSnapshotSchema,
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Project one persisted reply into the explicit debug browser protocol.
|
|
11
|
+
*
|
|
12
|
+
* Applications must select this presenter deliberately and should authorize
|
|
13
|
+
* its endpoint independently from whether a debug panel is visually mounted.
|
|
14
|
+
*/
|
|
15
|
+
export const presentChatDebugReply = (chat, reply, options = {}) => Effect.gen(function* () {
|
|
16
|
+
const response = yield* presentChatReply(reply, options.presentation);
|
|
17
|
+
const debug = yield* inspectChatState(chat, reply.turn.state, options.inspection);
|
|
18
|
+
return { ...response, debug };
|
|
19
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { type ChatDefinition, type ChatStageTuple, type ChatState } from "./chat.js";
|
|
3
|
+
/** Runtime schema for one JSON-safe structured-chat debug snapshot. */
|
|
4
|
+
export declare const StructuredChatDebugSnapshotSchema: Schema.Struct<{
|
|
5
|
+
readonly schemaVersion: Schema.Literal<1>;
|
|
6
|
+
readonly chat: Schema.Struct<{
|
|
7
|
+
readonly name: Schema.Trimmed;
|
|
8
|
+
readonly version: Schema.Number;
|
|
9
|
+
}>;
|
|
10
|
+
readonly status: Schema.Literals<readonly ["active", "complete"]>;
|
|
11
|
+
readonly currentStage: Schema.Struct<{
|
|
12
|
+
readonly index: Schema.Natural;
|
|
13
|
+
readonly name: Schema.Trimmed;
|
|
14
|
+
readonly kind: Schema.Literals<readonly ["collect", "tool", "command"]>;
|
|
15
|
+
}>;
|
|
16
|
+
readonly stages: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
17
|
+
readonly satisfiedFields: Schema.Natural;
|
|
18
|
+
readonly totalFields: Schema.Natural;
|
|
19
|
+
readonly fields: Schema.$Array<Schema.Struct<{
|
|
20
|
+
readonly field: Schema.String;
|
|
21
|
+
readonly mode: Schema.Literals<readonly ["semantic", "explicit", "confirmed"]>;
|
|
22
|
+
readonly description: Schema.String;
|
|
23
|
+
readonly question: Schema.Union<readonly [Schema.Struct<{
|
|
24
|
+
readonly _tag: Schema.Literal<"FixedQuestion">;
|
|
25
|
+
readonly text: Schema.String;
|
|
26
|
+
}>, Schema.Struct<{
|
|
27
|
+
readonly _tag: Schema.Literal<"AdaptiveQuestion">;
|
|
28
|
+
readonly goal: Schema.String;
|
|
29
|
+
readonly fallback: Schema.String;
|
|
30
|
+
}>, Schema.Struct<{
|
|
31
|
+
readonly _tag: Schema.Literal<"AdaptiveChoiceQuestion">;
|
|
32
|
+
readonly prompt: Schema.String;
|
|
33
|
+
readonly minimumOptions: Schema.Natural;
|
|
34
|
+
readonly maximumOptions: Schema.Natural;
|
|
35
|
+
readonly fallbackOptions: Schema.$Array<Schema.String>;
|
|
36
|
+
}>, Schema.Struct<{
|
|
37
|
+
readonly _tag: Schema.Literal<"ChoiceQuestion">;
|
|
38
|
+
readonly text: Schema.String;
|
|
39
|
+
readonly options: Schema.$Array<Schema.Struct<{
|
|
40
|
+
readonly label: Schema.String;
|
|
41
|
+
}>>;
|
|
42
|
+
}>]>;
|
|
43
|
+
readonly state: Schema.Union<readonly [Schema.Struct<{
|
|
44
|
+
readonly _tag: Schema.Literal<"Missing">;
|
|
45
|
+
}>, Schema.Struct<{
|
|
46
|
+
readonly _tag: Schema.Literal<"Asked">;
|
|
47
|
+
readonly issuedQuestion: Schema.Struct<{
|
|
48
|
+
readonly messageIndex: Schema.Natural;
|
|
49
|
+
readonly text: Schema.String;
|
|
50
|
+
}>;
|
|
51
|
+
}>, Schema.Struct<{
|
|
52
|
+
readonly _tag: Schema.Literal<"Accepted">;
|
|
53
|
+
readonly value: Schema.Codec<import("./json-value.js").JsonValue, import("./json-value.js").JsonValue, never, never>;
|
|
54
|
+
readonly evidence: Schema.NullOr<Schema.Struct<{
|
|
55
|
+
readonly messageIndex: Schema.Natural;
|
|
56
|
+
readonly quote: Schema.String;
|
|
57
|
+
}>>;
|
|
58
|
+
readonly issuedQuestion: Schema.NullOr<Schema.Struct<{
|
|
59
|
+
readonly messageIndex: Schema.Natural;
|
|
60
|
+
readonly text: Schema.String;
|
|
61
|
+
}>>;
|
|
62
|
+
}>]>;
|
|
63
|
+
}>>;
|
|
64
|
+
readonly index: Schema.Natural;
|
|
65
|
+
readonly name: Schema.Trimmed;
|
|
66
|
+
readonly status: Schema.Literals<readonly ["complete", "current", "upcoming"]>;
|
|
67
|
+
readonly repairPending: Schema.Boolean;
|
|
68
|
+
readonly _tag: Schema.Literal<"CollectStage">;
|
|
69
|
+
}>, Schema.Struct<{
|
|
70
|
+
readonly tools: Schema.$Array<Schema.Trimmed>;
|
|
71
|
+
readonly afterExecution: Schema.Literals<readonly ["stay", "complete"]>;
|
|
72
|
+
readonly index: Schema.Natural;
|
|
73
|
+
readonly name: Schema.Trimmed;
|
|
74
|
+
readonly status: Schema.Literals<readonly ["complete", "current", "upcoming"]>;
|
|
75
|
+
readonly repairPending: Schema.Boolean;
|
|
76
|
+
readonly _tag: Schema.Literal<"ToolStage">;
|
|
77
|
+
}>, Schema.Struct<{
|
|
78
|
+
readonly command: Schema.Trimmed;
|
|
79
|
+
readonly index: Schema.Natural;
|
|
80
|
+
readonly name: Schema.Trimmed;
|
|
81
|
+
readonly status: Schema.Literals<readonly ["complete", "current", "upcoming"]>;
|
|
82
|
+
readonly repairPending: Schema.Boolean;
|
|
83
|
+
readonly _tag: Schema.Literal<"CommandStage">;
|
|
84
|
+
}>]>>;
|
|
85
|
+
}>;
|
|
86
|
+
/** JSON-safe read model rendered by a structured-chat debug inspector. */
|
|
87
|
+
export type StructuredChatDebugSnapshot = Schema.Schema.Type<typeof StructuredChatDebugSnapshotSchema>;
|
|
88
|
+
/** Controls sensitive provenance included in a structured-chat debug snapshot. */
|
|
89
|
+
export interface InspectChatStateOptions {
|
|
90
|
+
readonly evidence?: "include" | "omit";
|
|
91
|
+
}
|
|
92
|
+
declare const InvalidChatDebugProjection_base: Schema.Class<InvalidChatDebugProjection, Schema.TaggedStruct<"InvalidChatDebugProjection", {
|
|
93
|
+
readonly reason: Schema.Literals<readonly ["invalid_options", "invalid_state", "invalid_answer_value", "invalid_snapshot"]>;
|
|
94
|
+
}>, import("effect/Cause").YieldableError>;
|
|
95
|
+
/** A chat state or answer could not be projected into safe debug JSON. */
|
|
96
|
+
export declare class InvalidChatDebugProjection extends InvalidChatDebugProjection_base {
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Project one trusted chat definition and Type-side state into browser-safe
|
|
100
|
+
* inspector data without exposing choice values or raw Effect schemas.
|
|
101
|
+
*/
|
|
102
|
+
export declare const inspectChatState: <const Name extends string, const Version extends number, const Stages extends ChatStageTuple>(chat: ChatDefinition<Name, Version, Stages>, state: ChatState<Name, Version, Stages>, options?: InspectChatStateOptions) => Effect.Effect<StructuredChatDebugSnapshot, InvalidChatDebugProjection>;
|
|
103
|
+
export {};
|
|
@@ -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
|
+
});
|