Skip to content
Source
Sign inSign up

secretRequest.ts

80 lines3.5 KB

1import * as Schema from "effect/Schema";
2​
3import { ThreadId, TrimmedNonEmptyString, TurnItemId } from "./baseSchemas.ts";
4import { SecretReference } from "./secretReference.ts";
5​
6/** The http(s) page a secret card may open, or null for anything else. */
7export function secretRequestPageUrl(value: string): URL | null {
8 // React Native's URL has no `canParse`.
9 try {
10 const url = new URL(value);
11 return (url.protocol === "https:" || url.protocol === "http:") && url.host !== "" ? url : null;
12 } catch {
13 return null;
14 }
16​
17/** Server stores the value under a one-use SecretRef; the thread learns only status. */
18export const SecretRequestAnswerInput = Schema.Struct({
19 threadId: ThreadId,
20 turnItemId: TurnItemId,
21 answer: Schema.Union([
22 Schema.Struct({ type: Schema.Literal("save"), secret: TrimmedNonEmptyString }),
23 Schema.Struct({ type: Schema.Literal("decline") }),
24 /** Read once through a configured secret provider; only servers advertising `secretRequestReferences` accept it. */
25 Schema.Struct({ type: Schema.Literal("reference"), reference: SecretReference }),
26 ]),
27});
28export type SecretRequestAnswerInput = typeof SecretRequestAnswerInput.Type;
29​
30/** What the agent reads when its card ends without a secret. */
31export const SECRET_REQUEST_UNSAVED_NOTE = {
32 declined:
33 "The user declined. Don't ask for it in chat or another way; say what's blocked and go on without it.",
34 cancelled:
35 "The card closed with the run. Ask again with a new clientRequestId only if the secret is still needed.",
36 timed_out:
37 "No answer in time. Don't ask in chat; ask again with a new clientRequestId only if it is still needed.",
38} as const;
39​
40const SECRET_REQUEST_FAILURE_MESSAGES = {
41 load_failed: "Could not load the secret request.",
42 not_found: "This secret request no longer exists.",
43 already_answered: "This secret request was already answered.",
44 agent_stopped: "The agent that asked has stopped, so this secret can't be used.",
45 store_failed: "Could not store the secret.",
46 reference_unreadable: "Could not read the secret from the secret provider.",
47 record_failed: "Saved the secret, but could not update the request.",
48 invalid_ref: "That secretRef is not valid.",
49 read_failed: "Could not read the secret.",
50 ref_unavailable:
51 "That secretRef was already used or does not exist. Ask the user again with request_secret.",
52 ref_expired: "That secretRef expired. Ask the user again with request_secret.",
53 consume_failed: "Could not use that secretRef. Try again.",
54 not_a_file_request:
55 'The user did not save this secret for shell use. Ask again with request_secret and delivery: "file".',
56} as const;
57​
58export const SecretRequestFailureReason = Schema.Literals(
59 Object.keys(SECRET_REQUEST_FAILURE_MESSAGES) as Array<
60 keyof typeof SECRET_REQUEST_FAILURE_MESSAGES
61 >,
62);
63export type SecretRequestFailureReason = typeof SecretRequestFailureReason.Type;
64​
65export class SecretRequestError extends Schema.TaggedError<SecretRequestError>()(
66 "SecretRequestError",
67 {
68 reason: SecretRequestFailureReason,
69 /** The secret provider a `reference_unreadable` answer named. */
70 provider: Schema.optionalKey(Schema.String),
71 cause: Schema.optionalKey(Schema.Defect()),
72 },
73) {
74 override get message(): string {
75 if (this.reason === "reference_unreadable" && this.provider !== undefined) {
76 return `Could not read the secret from the secret provider "${this.provider}". Unlock it and try again, or enter the value.`;
77 }
78 return SECRET_REQUEST_FAILURE_MESSAGES[this.reason];
79 }