@popcomputer/structured-chat 0.1.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 (52) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +780 -0
  3. package/dist/adapters/openai-compatible-model.d.ts +89 -0
  4. package/dist/adapters/openai-compatible-model.js +231 -0
  5. package/dist/core/answer.d.ts +73 -0
  6. package/dist/core/answer.js +60 -0
  7. package/dist/core/chat.d.ts +124 -0
  8. package/dist/core/chat.js +490 -0
  9. package/dist/core/collect-stage.d.ts +203 -0
  10. package/dist/core/collect-stage.js +645 -0
  11. package/dist/core/command.d.ts +16 -0
  12. package/dist/core/command.js +17 -0
  13. package/dist/core/definition.d.ts +10 -0
  14. package/dist/core/definition.js +14 -0
  15. package/dist/core/json-value.d.ts +11 -0
  16. package/dist/core/json-value.js +3 -0
  17. package/dist/core/model-guard.d.ts +52 -0
  18. package/dist/core/model-guard.js +37 -0
  19. package/dist/core/model.d.ts +99 -0
  20. package/dist/core/model.js +109 -0
  21. package/dist/core/protocol.d.ts +257 -0
  22. package/dist/core/protocol.js +153 -0
  23. package/dist/core/question.d.ts +52 -0
  24. package/dist/core/question.js +62 -0
  25. package/dist/core/repair.d.ts +14 -0
  26. package/dist/core/repair.js +9 -0
  27. package/dist/core/session.d.ts +78 -0
  28. package/dist/core/session.js +34 -0
  29. package/dist/core/stage-name.d.ts +3 -0
  30. package/dist/core/stage-name.js +3 -0
  31. package/dist/core/stage.d.ts +88 -0
  32. package/dist/core/stage.js +104 -0
  33. package/dist/core/tool-set.d.ts +49 -0
  34. package/dist/core/tool-set.js +66 -0
  35. package/dist/core/tool.d.ts +149 -0
  36. package/dist/core/tool.js +215 -0
  37. package/dist/core/view.d.ts +95 -0
  38. package/dist/core/view.js +69 -0
  39. package/dist/index.d.ts +16 -0
  40. package/dist/index.js +16 -0
  41. package/dist/integrations/assistant-ui.d.ts +95 -0
  42. package/dist/integrations/assistant-ui.js +114 -0
  43. package/dist/testing/in-memory-session-store.d.ts +4 -0
  44. package/dist/testing/in-memory-session-store.js +38 -0
  45. package/dist/testing/scenario.d.ts +59 -0
  46. package/dist/testing/scenario.js +147 -0
  47. package/dist/testing.d.ts +4 -0
  48. package/dist/testing.js +2 -0
  49. package/examples/agency-search.ts +101 -0
  50. package/examples/answer-modes.ts +92 -0
  51. package/examples/prompt-injection-policy.ts +66 -0
  52. package/package.json +89 -0
@@ -0,0 +1,10 @@
1
+ /** Package-owned kinds for executable structured-chat definitions. */
2
+ export type StructuredDefinitionKind = "tool" | "collect_stage" | "tool_stage" | "command_stage" | "repair" | "model_guard";
3
+ declare const structuredDefinitionKind: unique symbol;
4
+ /** @internal Nominal proof that a definition came from this package. */
5
+ export interface StructuredDefinition<Kind extends StructuredDefinitionKind> {
6
+ readonly [structuredDefinitionKind]: Kind;
7
+ }
8
+ /** @internal Attach a non-copying nominal identity in a public builder. */
9
+ export declare const structuredDefinition: <const Kind extends StructuredDefinitionKind>(kind: Kind) => <Value extends object>(value: Omit<Value, keyof StructuredDefinition<Kind>>) => Value & StructuredDefinition<Kind>;
10
+ export {};
@@ -0,0 +1,14 @@
1
+ const structuredDefinitionKind = Symbol("@popcomputer/structured-chat/StructuredDefinitionKind");
2
+ /** @internal Attach a non-copying nominal identity in a public builder. */
3
+ export const structuredDefinition = (kind) => (value) => {
4
+ Object.defineProperty(value, structuredDefinitionKind, {
5
+ value: kind,
6
+ enumerable: false,
7
+ configurable: false,
8
+ writable: false,
9
+ });
10
+ // SAFETY: defineProperty attached the exact private symbol and literal
11
+ // kind to this freshly constructed package definition. The non-enumerable
12
+ // proof cannot be copied by ordinary object spread.
13
+ return value;
14
+ };
@@ -0,0 +1,11 @@
1
+ import { Schema } from "effect";
2
+ /** Primitive value representable by JSON. */
3
+ export type JsonPrimitive = string | number | boolean | null;
4
+ /** Object value representable by JSON. */
5
+ export interface JsonObject {
6
+ readonly [key: string]: JsonValue;
7
+ }
8
+ /** Recursively typed value accepted at serialized JSON boundaries. */
9
+ export type JsonValue = JsonPrimitive | JsonObject | ReadonlyArray<JsonValue>;
10
+ /** Runtime parser for recursively JSON-serializable values. */
11
+ export declare const JsonValueSchema: Schema.Schema<JsonValue>;
@@ -0,0 +1,3 @@
1
+ import { Schema } from "effect";
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 })));
@@ -0,0 +1,52 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { type StructuredDefinition } from "./definition.js";
3
+ import type { UntrustedMessage } from "./model.js";
4
+ import type { JsonValue } from "./json-value.js";
5
+ /** Stable machine-facing name for one model-boundary policy guard. */
6
+ export declare const ModelGuardNameSchema: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
7
+ /** Safe context supplied before a structured model request begins. */
8
+ export interface ModelGuardContext {
9
+ readonly messages: ReadonlyArray<UntrustedMessage>;
10
+ readonly toolNames: ReadonlyArray<string>;
11
+ }
12
+ /** Strictly parsed model proposal supplied before application execution. */
13
+ export interface ModelGuardCall {
14
+ readonly name: string;
15
+ readonly arguments: JsonValue;
16
+ }
17
+ /** Safe context supplied after parsing and before a tool executes. */
18
+ export interface ModelGuardCallContext extends ModelGuardContext {
19
+ readonly call: ModelGuardCall;
20
+ }
21
+ /** Minimum runtime identity retained for every model-boundary guard. */
22
+ export interface ModelGuardDefinitionContract extends StructuredDefinition<"model_guard"> {
23
+ readonly _tag: "ModelGuard";
24
+ readonly name: string;
25
+ }
26
+ /** Composable policy checks around one structured model request. */
27
+ export interface ModelGuard<Name extends string, Error, Requirements> extends ModelGuardDefinitionContract {
28
+ readonly name: Name;
29
+ readonly check: (context: ModelGuardContext) => Effect.Effect<void, Error, Requirements>;
30
+ readonly checkCall?: (context: ModelGuardCallContext) => Effect.Effect<void, Error, Requirements>;
31
+ }
32
+ /** Definition input for one model-boundary policy guard. */
33
+ export interface DefineModelGuardInput<Name extends string, Error, Requirements> {
34
+ readonly name: Name;
35
+ readonly check: (context: ModelGuardContext) => Effect.Effect<void, Error, Requirements>;
36
+ readonly checkCall?: (context: ModelGuardCallContext) => Effect.Effect<void, Error, Requirements>;
37
+ }
38
+ /** Readonly tuple of optional guards applied to one model step. */
39
+ export type ModelGuardTuple = ReadonlyArray<ModelGuardDefinitionContract>;
40
+ type ModelGuardErrorOf<Guard> = Guard extends ModelGuard<infer _Name, infer Error, infer _Requirements> ? Error : never;
41
+ type ModelGuardRequirementsOf<Guard> = Guard extends ModelGuard<infer _Name, infer _Error, infer Requirements> ? Requirements : never;
42
+ /** Failure union produced by a tuple of model guards. */
43
+ export type ModelGuardError<Guards extends ModelGuardTuple> = ModelGuardErrorOf<Guards[number]>;
44
+ /** Effect service union required by a tuple of model guards. */
45
+ export type ModelGuardRequirements<Guards extends ModelGuardTuple> = ModelGuardRequirementsOf<Guards[number]>;
46
+ /** Define Effect-native policy checks around a structured model step. */
47
+ export declare const defineModelGuard: <const Name extends string, Error, Requirements>(definition: DefineModelGuardInput<Name, Error, Requirements>) => ModelGuard<Name, Error, Requirements>;
48
+ /** @internal */
49
+ export declare const runModelGuards: <Guards extends ModelGuardTuple>(guards: Guards, context: ModelGuardContext) => Effect.Effect<void, ModelGuardError<Guards>, ModelGuardRequirements<Guards>>;
50
+ /** @internal Run optional semantic checks on one parsed tool proposal. */
51
+ export declare const runModelCallGuards: <Guards extends ModelGuardTuple>(guards: Guards, context: ModelGuardCallContext) => Effect.Effect<void, ModelGuardError<Guards>, ModelGuardRequirements<Guards>>;
52
+ export {};
@@ -0,0 +1,37 @@
1
+ import { Effect, Schema, unsafeCoerce } from "effect";
2
+ import { structuredDefinition, } from "./definition.js";
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]+)*$/));
5
+ /** Define Effect-native policy checks around a structured model step. */
6
+ export const defineModelGuard = (definition) => {
7
+ Schema.decodeSync(ModelGuardNameSchema)(definition.name);
8
+ const base = {
9
+ _tag: "ModelGuard",
10
+ name: definition.name,
11
+ check: definition.check,
12
+ };
13
+ return structuredDefinition("model_guard")(definition.checkCall === undefined
14
+ ? base
15
+ : { ...base, checkCall: definition.checkCall });
16
+ };
17
+ const runtimeModelGuard = (guard) => {
18
+ // SAFETY: ModelGuardDefinitionContract carries the package-owned nominal
19
+ // identity and can only be constructed by defineModelGuard.
20
+ return unsafeCoerce(guard);
21
+ };
22
+ const runGuardPhase = (guards, phase, run) => {
23
+ const execution = Effect.forEach(guards, (guard) => (run(runtimeModelGuard(guard)) ?? Effect.void).pipe(Effect.withSpan("popcomputer.structured_chat.model_guard.check", {
24
+ attributes: { guard: guard.name, phase },
25
+ })), { concurrency: 1, discard: true });
26
+ // SAFETY: guards run sequentially without recovering failures, so the
27
+ // erased Effect has exactly the conditional error and requirement unions.
28
+ return execution;
29
+ };
30
+ /** @internal */
31
+ export const runModelGuards = (guards, context) => {
32
+ return runGuardPhase(guards, "before_model", (guard) => guard.check(context));
33
+ };
34
+ /** @internal Run optional semantic checks on one parsed tool proposal. */
35
+ export const runModelCallGuards = (guards, context) => {
36
+ return runGuardPhase(guards, "before_tool", (guard) => guard.checkCall?.(context));
37
+ };
@@ -0,0 +1,99 @@
1
+ import { Context, Effect, Schema } from "effect";
2
+ import type { InvalidToolCall, ModelToolDefinition } from "./tool.js";
3
+ import { type ModelGuardError, type ModelGuardRequirements, type ModelGuardTuple } from "./model-guard.js";
4
+ import type { ToolSet, ToolSetCall, ToolSetError, ToolSetExecution, ToolSetRequirements, ModelToolTuple, ToolCallPlanner, ToolTuple } from "./tool-set.js";
5
+ import type { JsonValue } from "./json-value.js";
6
+ /** Bounded application-authored instruction supplied to a model adapter. */
7
+ export declare const TrustedInstructionSchema: Schema.brand<Schema.filter<typeof Schema.NonEmptyTrimmedString>, "TrustedInstruction">;
8
+ /** Bounded application-authored instruction supplied to a model adapter. */
9
+ export type TrustedInstruction = Schema.Schema.Type<typeof TrustedInstructionSchema>;
10
+ /** Conversation role accepted as untrusted model context. */
11
+ export declare const ConversationRoleSchema: Schema.Literal<["user", "assistant"]>;
12
+ /** One bounded conversation message treated as untrusted model context. */
13
+ export declare const UntrustedMessageSchema: Schema.Struct<{
14
+ role: Schema.Literal<["user", "assistant"]>;
15
+ content: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
16
+ }>;
17
+ /** One bounded conversation message treated as untrusted model context. */
18
+ export type UntrustedMessage = Schema.Schema.Type<typeof UntrustedMessageSchema>;
19
+ /** @internal Exact content-character count for bounded message arrays. */
20
+ export declare const countUntrustedMessageCharacters: (messages: ReadonlyArray<UntrustedMessage>) => number;
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
+ }>;
28
+ /** A configured chat model could not complete a structured step. */
29
+ export declare class ChatModelUnavailable extends ChatModelUnavailable_base {
30
+ }
31
+ /** 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
+ }>;
40
+ /** A model tool schema is incompatible with strict provider decoding. */
41
+ export declare class UnsupportedModelToolSchema extends UnsupportedModelToolSchema_base {
42
+ }
43
+ /** Provider-neutral request for exactly one model-authored tool call. */
44
+ export interface ToolModelRequest {
45
+ readonly instructions: ReadonlyArray<TrustedInstruction>;
46
+ readonly untrustedMessages: ReadonlyArray<UntrustedMessage>;
47
+ readonly tools: ReadonlyArray<ModelToolDefinition>;
48
+ readonly toolChoice: "required";
49
+ readonly maximumToolCalls: 1;
50
+ readonly parallelToolCalls: false;
51
+ }
52
+ /** Narrow provider seam used by structured chat model steps. */
53
+ export interface StructuredChatModelService {
54
+ /** Return one untrusted provider tool call for runtime validation. */
55
+ readonly requestTool: (request: ToolModelRequest) => Effect.Effect<JsonValue, ChatModelUnavailable | UnsupportedModelToolSchema>;
56
+ }
57
+ declare const StructuredChatModel_base: Context.TagClass<StructuredChatModel, "@popcomputer/structured-chat/StructuredChatModel", StructuredChatModelService>;
58
+ /** Effect service for the configured structured chat model adapter. */
59
+ export declare class StructuredChatModel extends StructuredChatModel_base {
60
+ }
61
+ /** Input for one required, stage-scoped tool step. */
62
+ export interface RunToolStepInput<Tools extends ToolTuple, Guards extends ModelGuardTuple = readonly []> {
63
+ readonly instructions: ReadonlyArray<TrustedInstruction>;
64
+ readonly messages: ReadonlyArray<UntrustedMessage>;
65
+ readonly tools: ToolSet<Tools>;
66
+ readonly guards?: Guards;
67
+ }
68
+ interface PlanToolCallInput<Tools extends ModelToolTuple, Guards extends ModelGuardTuple> {
69
+ readonly instructions: ReadonlyArray<TrustedInstruction>;
70
+ readonly messages: ReadonlyArray<UntrustedMessage>;
71
+ readonly tools: ToolCallPlanner<Tools>;
72
+ readonly guards?: Guards;
73
+ }
74
+ /** @internal Plan one strictly parsed and guarded call to a closed tool set. */
75
+ export declare const planToolCall: <const Tools extends ModelToolTuple, const Guards extends ModelGuardTuple = readonly []>(input: PlanToolCallInput<Tools, Guards>) => Effect.Effect<ToolSetCall<Tools>, ChatModelUnavailable | UnsupportedModelToolSchema | InvalidToolCall | ModelGuardError<Guards>, StructuredChatModel | ModelGuardRequirements<Guards>>;
76
+ /**
77
+ * Ask the configured model for one call to a closed tool set, then execute it.
78
+ *
79
+ * One contract-invalid model output may trigger a bounded repair request
80
+ * before any application tool executes. Model-visible results are returned to
81
+ * the application and are never sent through another model request.
82
+ */
83
+ export declare const runToolStep: <const Tools extends ToolTuple, const Guards extends ModelGuardTuple = readonly []>(input: RunToolStepInput<Tools, Guards>) => Effect.Effect<ToolSetExecution<Tools>, ChatModelUnavailable | UnsupportedModelToolSchema | ToolSetError<Tools> | ModelGuardError<Guards>, StructuredChatModel | ToolSetRequirements<Tools> | ModelGuardRequirements<Guards>>;
84
+ /** Constructors that explicitly mark static application instructions. */
85
+ export declare const Instruction: {
86
+ readonly make: (value: string) => TrustedInstruction;
87
+ };
88
+ /** Constructors that explicitly mark conversation text as untrusted. */
89
+ export declare const Message: {
90
+ readonly user: (content: string) => {
91
+ readonly role: "user" | "assistant";
92
+ readonly content: string;
93
+ };
94
+ readonly assistant: (content: string) => {
95
+ readonly role: "user" | "assistant";
96
+ readonly content: string;
97
+ };
98
+ };
99
+ export {};
@@ -0,0 +1,109 @@
1
+ import { Context, Effect, Schema } from "effect";
2
+ import { runModelCallGuards, runModelGuards, } from "./model-guard.js";
3
+ /** Bounded application-authored instruction supplied to a model adapter. */
4
+ export const TrustedInstructionSchema = Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(20_000), Schema.brand("TrustedInstruction"));
5
+ /** Conversation role accepted as untrusted model context. */
6
+ export const ConversationRoleSchema = Schema.Literal("user", "assistant");
7
+ /** One bounded conversation message treated as untrusted model context. */
8
+ export const UntrustedMessageSchema = Schema.Struct({
9
+ role: ConversationRoleSchema,
10
+ content: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(50_000)),
11
+ });
12
+ /** @internal Exact content-character count for bounded message arrays. */
13
+ export const countUntrustedMessageCharacters = (messages) => messages.reduce((total, message) => total + message.content.length, 0);
14
+ /** 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");
16
+ /** A configured chat model could not complete a structured step. */
17
+ export class ChatModelUnavailable extends Schema.TaggedError()("ChatModelUnavailable", { reason: ChatModelUnavailableReasonSchema }) {
18
+ }
19
+ /** 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");
21
+ /** A model tool schema is incompatible with strict provider decoding. */
22
+ export class UnsupportedModelToolSchema extends Schema.TaggedError()("UnsupportedModelToolSchema", {
23
+ tool: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(100)),
24
+ path: Schema.NonEmptyTrimmedString.pipe(Schema.maxLength(2_000)),
25
+ reason: UnsupportedModelToolSchemaReasonSchema,
26
+ }) {
27
+ }
28
+ /** Effect service for the configured structured chat model adapter. */
29
+ export class StructuredChatModel extends Context.Tag("@popcomputer/structured-chat/StructuredChatModel")() {
30
+ }
31
+ 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
+ const isRepairableModelOutput = (error) => error._tag === "InvalidToolCall" ||
33
+ (error._tag === "ChatModelUnavailable" &&
34
+ error.reason === "invalid_response");
35
+ /** @internal Plan one strictly parsed and guarded call to a closed tool set. */
36
+ export const planToolCall = (input) => runModelGuards(input.guards ?? [], {
37
+ messages: input.messages,
38
+ toolNames: input.tools.models.map(({ name }) => name),
39
+ }).pipe(Effect.zipRight(StructuredChatModel), Effect.flatMap((model) => {
40
+ const requestParsedCall = (instructions, attempt) => model
41
+ .requestTool({
42
+ instructions,
43
+ untrustedMessages: input.messages,
44
+ tools: input.tools.models,
45
+ toolChoice: "required",
46
+ maximumToolCalls: 1,
47
+ parallelToolCalls: false,
48
+ })
49
+ .pipe(Effect.withSpan("popcomputer.structured_chat.model.request", {
50
+ attributes: {
51
+ attempt,
52
+ messageCount: input.messages.length,
53
+ messageCharacterCount: countUntrustedMessageCharacters(input.messages),
54
+ instructionCount: instructions.length,
55
+ toolCount: input.tools.models.length,
56
+ },
57
+ }), Effect.flatMap(input.tools.parseCall));
58
+ return requestParsedCall(input.instructions, 1).pipe(Effect.catchIf(isRepairableModelOutput, (error) => {
59
+ const annotations = error._tag === "InvalidToolCall" && error.path !== null
60
+ ? {
61
+ attempt: 2,
62
+ errorTag: error._tag,
63
+ errorReason: error.reason,
64
+ errorPath: error.path,
65
+ }
66
+ : {
67
+ attempt: 2,
68
+ errorTag: error._tag,
69
+ errorReason: error.reason,
70
+ };
71
+ return Effect.logWarning("Retrying structured model output").pipe(Effect.annotateLogs(annotations), Effect.zipRight(requestParsedCall([
72
+ ...input.instructions,
73
+ invalidOutputRepairInstruction,
74
+ ], 2)));
75
+ }));
76
+ }), Effect.tap((call) => runModelCallGuards(input.guards ?? [], {
77
+ messages: input.messages,
78
+ toolNames: input.tools.models.map(({ name }) => name),
79
+ call,
80
+ })), Effect.withSpan("popcomputer.structured_chat.tool_step.plan", {
81
+ attributes: {
82
+ messageCount: input.messages.length,
83
+ toolCount: input.tools.models.length,
84
+ },
85
+ }));
86
+ /**
87
+ * Ask the configured model for one call to a closed tool set, then execute it.
88
+ *
89
+ * One contract-invalid model output may trigger a bounded repair request
90
+ * before any application tool executes. Model-visible results are returned to
91
+ * the application and are never sent through another model request.
92
+ */
93
+ export const runToolStep = (input) => planToolCall(input).pipe(Effect.flatMap(input.tools.execute), Effect.withSpan("popcomputer.structured_chat.tool_step.run", {
94
+ attributes: {
95
+ messageCount: input.messages.length,
96
+ toolCount: input.tools.models.length,
97
+ },
98
+ }));
99
+ const makeInstruction = (value) => Schema.decodeSync(TrustedInstructionSchema)(value);
100
+ const makeMessage = (role, content) => Schema.decodeSync(UntrustedMessageSchema)({ role, content });
101
+ /** Constructors that explicitly mark static application instructions. */
102
+ export const Instruction = {
103
+ make: makeInstruction,
104
+ };
105
+ /** Constructors that explicitly mark conversation text as untrusted. */
106
+ export const Message = {
107
+ user: (content) => makeMessage("user", content),
108
+ assistant: (content) => makeMessage("assistant", content),
109
+ };
@@ -0,0 +1,257 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { ChatSessionIdSchema, ChatSessionRevisionSchema } from "./session.js";
3
+ /** Bounded plain text emitted by a structured chat presenter. */
4
+ export declare const AssistantTextPartSchema: Schema.Struct<{
5
+ type: Schema.Literal<["text"]>;
6
+ text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
7
+ }>;
8
+ /** Provider-neutral named data emitted by a structured chat presenter. */
9
+ export declare const AssistantDataPartSchema: Schema.Struct<{
10
+ type: Schema.Literal<["data"]>;
11
+ name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
12
+ data: typeof Schema.Unknown;
13
+ }>;
14
+ /** Message parts transported from a structured chat action to a browser. */
15
+ export declare const AssistantMessagePartSchema: Schema.Union<[Schema.Struct<{
16
+ type: Schema.Literal<["text"]>;
17
+ text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
18
+ }>, Schema.Struct<{
19
+ type: Schema.Literal<["data"]>;
20
+ name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
21
+ data: typeof Schema.Unknown;
22
+ }>]>;
23
+ /** Message parts transported from a structured chat action to a browser. */
24
+ export type AssistantMessagePart = Schema.Schema.Type<typeof AssistantMessagePartSchema>;
25
+ /** Strict assistant message returned by one structured chat action. */
26
+ export declare const StructuredChatAssistantMessageSchema: Schema.Struct<{
27
+ role: Schema.Literal<["assistant"]>;
28
+ content: Schema.filter<Schema.NonEmptyArray<Schema.Union<[Schema.Struct<{
29
+ type: Schema.Literal<["text"]>;
30
+ text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
31
+ }>, Schema.Struct<{
32
+ type: Schema.Literal<["data"]>;
33
+ name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
34
+ data: typeof Schema.Unknown;
35
+ }>]>>>;
36
+ }>;
37
+ /** Strict assistant message returned by one structured chat action. */
38
+ export type StructuredChatAssistantMessage = Schema.Schema.Type<typeof StructuredChatAssistantMessageSchema>;
39
+ /** Opaque browser-held reference to one server-owned chat session. */
40
+ export declare const StructuredChatSessionReferenceSchema: Schema.Struct<{
41
+ id: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
42
+ revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
43
+ }>;
44
+ /** Opaque browser-held reference to one server-owned chat session. */
45
+ export type StructuredChatSessionReference = Schema.Schema.Type<typeof StructuredChatSessionReferenceSchema>;
46
+ /** Browser request carrying no server-owned chat state. */
47
+ export declare const StructuredChatTurnRequestSchema: Schema.Struct<{
48
+ session: Schema.optional<Schema.Struct<{
49
+ id: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
50
+ revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
51
+ }>>;
52
+ message: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
53
+ }>;
54
+ /** Browser request carrying no server-owned chat state. */
55
+ export type StructuredChatTurnRequest = Schema.Schema.Type<typeof StructuredChatTurnRequestSchema>;
56
+ /** Versioned browser response for one persisted structured chat turn. */
57
+ export declare const StructuredChatTurnResponseSchema: Schema.Struct<{
58
+ schemaVersion: Schema.Literal<[1]>;
59
+ session: Schema.optional<Schema.Struct<{
60
+ id: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
61
+ revision: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
62
+ }>>;
63
+ message: Schema.Struct<{
64
+ role: Schema.Literal<["assistant"]>;
65
+ content: Schema.filter<Schema.NonEmptyArray<Schema.Union<[Schema.Struct<{
66
+ type: Schema.Literal<["text"]>;
67
+ text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
68
+ }>, Schema.Struct<{
69
+ type: Schema.Literal<["data"]>;
70
+ name: Schema.filter<Schema.filter<typeof Schema.NonEmptyTrimmedString>>;
71
+ data: typeof Schema.Unknown;
72
+ }>]>>>;
73
+ }>;
74
+ }>;
75
+ /** Versioned browser response for one persisted structured chat turn. */
76
+ export type StructuredChatTurnResponse = Schema.Schema.Type<typeof StructuredChatTurnResponseSchema>;
77
+ /** Built-in browser contract for one collect-stage question. */
78
+ export declare const CollectQuestionView: {
79
+ name: "collect_question";
80
+ version: 1;
81
+ inputSchema: Schema.Struct<{
82
+ stage: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
83
+ field: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
84
+ text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
85
+ options: Schema.filter<Schema.Array$<Schema.Struct<{
86
+ label: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
87
+ }>>>;
88
+ }>;
89
+ dataSchema: Schema.Struct<{
90
+ readonly schemaVersion: Schema.Literal<[1]>;
91
+ } & {
92
+ stage: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
93
+ field: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
94
+ text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
95
+ options: Schema.filter<Schema.Array$<Schema.Struct<{
96
+ label: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
97
+ }>>>;
98
+ }>;
99
+ partSchema: Schema.Struct<{
100
+ readonly type: Schema.Literal<["data"]>;
101
+ readonly name: Schema.Literal<["collect_question"]>;
102
+ readonly data: Schema.Struct<{
103
+ readonly schemaVersion: Schema.Literal<[1]>;
104
+ } & {
105
+ stage: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
106
+ field: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
107
+ text: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
108
+ options: Schema.filter<Schema.Array$<Schema.Struct<{
109
+ label: Schema.filter<typeof Schema.NonEmptyTrimmedString>;
110
+ }>>>;
111
+ }>;
112
+ }>;
113
+ make: (input: {
114
+ readonly text: string;
115
+ readonly stage: string;
116
+ readonly field: string;
117
+ readonly options: readonly {
118
+ readonly label: string;
119
+ }[];
120
+ }) => {
121
+ readonly name: "collect_question";
122
+ readonly type: "data";
123
+ readonly data: {
124
+ readonly schemaVersion: 1;
125
+ readonly text: string;
126
+ readonly stage: string;
127
+ readonly field: string;
128
+ readonly options: readonly {
129
+ readonly label: string;
130
+ }[];
131
+ };
132
+ };
133
+ parseData: (input: import("./json-value.js").JsonValue) => Effect.Effect<{
134
+ readonly name: "collect_question";
135
+ readonly type: "data";
136
+ readonly data: {
137
+ readonly schemaVersion: 1;
138
+ readonly text: string;
139
+ readonly stage: string;
140
+ readonly field: string;
141
+ readonly options: readonly {
142
+ readonly label: string;
143
+ }[];
144
+ };
145
+ }, import("effect/ParseResult").ParseError, never>;
146
+ decode: (input: import("./json-value.js").JsonValue) => Effect.Effect<{
147
+ readonly name: "collect_question";
148
+ readonly type: "data";
149
+ readonly data: {
150
+ readonly schemaVersion: 1;
151
+ readonly text: string;
152
+ readonly stage: string;
153
+ readonly field: string;
154
+ readonly options: readonly {
155
+ readonly label: string;
156
+ }[];
157
+ };
158
+ }, import("effect/ParseResult").ParseError, never>;
159
+ decodeEither: (input: import("./json-value.js").JsonValue) => import("effect/Either").Either<{
160
+ readonly name: "collect_question";
161
+ readonly type: "data";
162
+ readonly data: {
163
+ readonly schemaVersion: 1;
164
+ readonly text: string;
165
+ readonly stage: string;
166
+ readonly field: string;
167
+ readonly options: readonly {
168
+ readonly label: string;
169
+ }[];
170
+ };
171
+ }, import("effect/ParseResult").ParseError>;
172
+ };
173
+ declare const InvalidChatPresentation_base: Schema.TaggedErrorClass<InvalidChatPresentation, "InvalidChatPresentation", {
174
+ readonly _tag: Schema.tag<"InvalidChatPresentation">;
175
+ } & {
176
+ reason: Schema.Literal<["invalid_message"]>;
177
+ }>;
178
+ /** Safe reason that application-owned message presentation was rejected. */
179
+ export declare class InvalidChatPresentation extends InvalidChatPresentation_base {
180
+ }
181
+ interface PresentableQuestionTurn {
182
+ readonly _tag: "Question";
183
+ readonly stage: string;
184
+ readonly question: {
185
+ readonly field: string;
186
+ readonly text: string;
187
+ readonly options: ReadonlyArray<{
188
+ readonly label: string;
189
+ }>;
190
+ readonly escape?: {
191
+ readonly label: string;
192
+ };
193
+ };
194
+ }
195
+ interface PresentableToolTurn {
196
+ readonly _tag: "ToolResult" | "Complete";
197
+ readonly stage: string;
198
+ readonly result: {
199
+ readonly views: ReadonlyArray<AssistantMessagePart>;
200
+ };
201
+ }
202
+ type PresentableTurn = PresentableQuestionTurn | PresentableToolTurn;
203
+ /** Optional application projections for question and tool-result messages. */
204
+ export interface PresentChatReplyOptions<Turn extends PresentableTurn> {
205
+ readonly question?: (turn: Extract<Turn, PresentableQuestionTurn>) => ReadonlyArray<AssistantMessagePart>;
206
+ readonly result?: (turn: Extract<Turn, PresentableToolTurn>) => ReadonlyArray<AssistantMessagePart>;
207
+ }
208
+ /** Constructors for deterministic assistant message parts. */
209
+ export declare const Text: {
210
+ readonly make: (text: string) => AssistantMessagePart;
211
+ };
212
+ /**
213
+ * Present a safe non-progressing notice after a rejected or unavailable turn.
214
+ *
215
+ * Supplying the prior session reference lets the browser retry from the same
216
+ * server-owned state. An initial rejected turn may omit it entirely.
217
+ */
218
+ export declare const presentChatNotice: (input: {
219
+ readonly text: string;
220
+ readonly session?: StructuredChatSessionReference | undefined;
221
+ }) => Effect.Effect<StructuredChatTurnResponse, InvalidChatPresentation>;
222
+ /**
223
+ * Present an application-authored retry question after answer validation.
224
+ *
225
+ * The rejection is non-progressing: pass the browser's prior session
226
+ * reference, if any, so its next answer retries from the same revision.
227
+ */
228
+ export declare const presentAnswerValidationRejection: (input: {
229
+ readonly rejection: {
230
+ readonly stage: string;
231
+ readonly question: {
232
+ readonly field: string;
233
+ readonly text: string;
234
+ readonly options: ReadonlyArray<{
235
+ readonly label: string;
236
+ readonly value?: unknown;
237
+ }>;
238
+ readonly escape?: {
239
+ readonly label: string;
240
+ };
241
+ };
242
+ };
243
+ readonly session?: StructuredChatSessionReference | undefined;
244
+ }) => Effect.Effect<StructuredChatTurnResponse, InvalidChatPresentation>;
245
+ /**
246
+ * Project one persisted chat reply into the strict browser protocol.
247
+ *
248
+ * Questions use the built-in CollectQuestionView unless overridden. Tool
249
+ * results use their validated views unless the application adds text or a
250
+ * different ordered composition.
251
+ */
252
+ export declare const presentChatReply: <Turn extends PresentableTurn>(reply: {
253
+ readonly sessionId: Schema.Schema.Type<typeof ChatSessionIdSchema> | string;
254
+ readonly revision: Schema.Schema.Type<typeof ChatSessionRevisionSchema> | string;
255
+ readonly turn: Turn;
256
+ }, options?: PresentChatReplyOptions<Turn>) => Effect.Effect<StructuredChatTurnResponse, InvalidChatPresentation>;
257
+ export {};