@earendil-works/pi-ai 1.0.4 → 1.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 (93) hide show
  1. package/README.md +14 -3
  2. package/dist/api/bedrock-converse-stream.js +33 -2
  3. package/dist/api/bedrock-converse-stream.js.map +1 -1
  4. package/dist/api/classifier-shared.d.ts +21 -0
  5. package/dist/api/classifier-shared.d.ts.map +1 -0
  6. package/dist/api/classifier-shared.js +98 -0
  7. package/dist/api/classifier-shared.js.map +1 -0
  8. package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -1
  9. package/dist/api/cloudflare-workers-ai-system-one.js +2 -1
  10. package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -1
  11. package/dist/api/lazy.d.ts.map +1 -1
  12. package/dist/api/lazy.js +4 -3
  13. package/dist/api/lazy.js.map +1 -1
  14. package/dist/api/llama-cpp-classify.d.ts.map +1 -1
  15. package/dist/api/llama-cpp-classify.js +2 -0
  16. package/dist/api/llama-cpp-classify.js.map +1 -1
  17. package/dist/api/mistral-conversations.js +2 -1
  18. package/dist/api/mistral-conversations.js.map +1 -1
  19. package/dist/api/openai-codex-responses.js +5 -3
  20. package/dist/api/openai-codex-responses.js.map +1 -1
  21. package/dist/api/openai-decisions.d.ts +4 -0
  22. package/dist/api/openai-decisions.d.ts.map +1 -0
  23. package/dist/api/openai-decisions.js +162 -0
  24. package/dist/api/openai-decisions.js.map +1 -0
  25. package/dist/api/openai-decisions.lazy.d.ts +3 -0
  26. package/dist/api/openai-decisions.lazy.d.ts.map +1 -0
  27. package/dist/api/openai-decisions.lazy.js +4 -0
  28. package/dist/api/openai-decisions.lazy.js.map +1 -0
  29. package/dist/api/system-one-shared.d.ts +0 -1
  30. package/dist/api/system-one-shared.d.ts.map +1 -1
  31. package/dist/api/system-one-shared.js +5 -89
  32. package/dist/api/system-one-shared.js.map +1 -1
  33. package/dist/api/typesafe-system-one.d.ts.map +1 -1
  34. package/dist/api/typesafe-system-one.js +2 -1
  35. package/dist/api/typesafe-system-one.js.map +1 -1
  36. package/dist/auth/oauth/anthropic.d.ts.map +1 -1
  37. package/dist/auth/oauth/anthropic.js +14 -6
  38. package/dist/auth/oauth/anthropic.js.map +1 -1
  39. package/dist/auth/oauth/openai-chatgpt.js +1 -1
  40. package/dist/auth/oauth/openai-chatgpt.js.map +1 -1
  41. package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
  42. package/dist/auth/oauth/openai-codex.js +4 -4
  43. package/dist/auth/oauth/openai-codex.js.map +1 -1
  44. package/dist/auth/types.d.ts +5 -0
  45. package/dist/auth/types.d.ts.map +1 -1
  46. package/dist/auth/types.js.map +1 -1
  47. package/dist/models.d.ts.map +1 -1
  48. package/dist/models.js +2 -1
  49. package/dist/models.js.map +1 -1
  50. package/dist/providers/data/.manifest.json +1 -1
  51. package/dist/providers/data/amazon-bedrock.json +1 -1
  52. package/dist/providers/data/anthropic.json +1 -1
  53. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  54. package/dist/providers/data/github-copilot.json +1 -1
  55. package/dist/providers/data/google.json +1 -1
  56. package/dist/providers/data/minimax-cn.json +1 -1
  57. package/dist/providers/data/minimax.json +1 -1
  58. package/dist/providers/data/mistral.json +1 -1
  59. package/dist/providers/data/openai.json +1 -1
  60. package/dist/providers/data/opencode-go.json +1 -1
  61. package/dist/providers/data/opencode.json +1 -1
  62. package/dist/providers/data/openrouter.json +1 -1
  63. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  64. package/dist/providers/faux.d.ts.map +1 -1
  65. package/dist/providers/faux.js +25 -8
  66. package/dist/providers/faux.js.map +1 -1
  67. package/dist/providers/openai.d.ts.map +1 -1
  68. package/dist/providers/openai.js +12 -3
  69. package/dist/providers/openai.js.map +1 -1
  70. package/dist/providers/radius.d.ts.map +1 -1
  71. package/dist/providers/radius.js +4 -12
  72. package/dist/providers/radius.js.map +1 -1
  73. package/dist/types.d.ts +16 -1
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/types.js.map +1 -1
  76. package/dist/utils/estimate.js +1 -1
  77. package/dist/utils/estimate.js.map +1 -1
  78. package/dist/utils/event-stream.d.ts +10 -1
  79. package/dist/utils/event-stream.d.ts.map +1 -1
  80. package/dist/utils/event-stream.js +25 -0
  81. package/dist/utils/event-stream.js.map +1 -1
  82. package/dist/utils/model-operations.d.ts +3 -1
  83. package/dist/utils/model-operations.d.ts.map +1 -1
  84. package/dist/utils/model-operations.js +6 -0
  85. package/dist/utils/model-operations.js.map +1 -1
  86. package/dist/utils/provider-retry.d.ts +2 -0
  87. package/dist/utils/provider-retry.d.ts.map +1 -1
  88. package/dist/utils/provider-retry.js +2 -0
  89. package/dist/utils/provider-retry.js.map +1 -1
  90. package/dist/utils/retry.d.ts.map +1 -1
  91. package/dist/utils/retry.js +2 -0
  92. package/dist/utils/retry.js.map +1 -1
  93. package/package.json +2 -2
@@ -0,0 +1,162 @@
1
+ import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
2
+ import { isRecord, parseClassifierUsage, postClassifierRequest, requiredNumber, } from "./classifier-shared.js";
3
+ /**
4
+ * OpenAI's Decisions API: `POST /v1/decisions` with `{ model, input, questions }`.
5
+ * https://developers.openai.com/api/docs/guides/decisions
6
+ *
7
+ * The state is sent as JSON text. With images, the input becomes one user message with the state
8
+ * as `input_text` followed by `input_image` data URLs. Questions map to Decisions types:
9
+ * `choice` to `choice`, `score` to `score`, and `bool` to `predicate`. Predicates have no
10
+ * criteria field, so the meanings of true and false are appended to the instructions.
11
+ *
12
+ * Only OpenAI API keys work: Sign in with ChatGPT tokens are rejected on this route.
13
+ */
14
+ const LABEL = "OpenAI Decisions";
15
+ /** The endpoint accepts at most this many image parts per request. */
16
+ const MAX_IMAGES = 128;
17
+ function predicateInstructions(question) {
18
+ const meanings = [
19
+ question.criteria.true ? `True means: ${question.criteria.true}` : "",
20
+ question.criteria.false ? `False means: ${question.criteria.false}` : "",
21
+ ].filter(Boolean);
22
+ return meanings.length > 0 ? `${question.instructions}\n\n${meanings.join("\n")}` : question.instructions;
23
+ }
24
+ function wireQuestion(name, question) {
25
+ if (question.type === "choice") {
26
+ return {
27
+ type: "choice",
28
+ name,
29
+ instructions: question.instructions,
30
+ choices: Object.entries(question.criteria).map(([value, description]) => description ? { value, description } : { value }),
31
+ };
32
+ }
33
+ if (question.type === "score") {
34
+ return {
35
+ type: "score",
36
+ name,
37
+ instructions: question.instructions,
38
+ levels: question.criteria.map((label) => ({ label })),
39
+ };
40
+ }
41
+ return { type: "predicate", name, instructions: predicateInstructions(question) };
42
+ }
43
+ function wireInput(context) {
44
+ const state = JSON.stringify(context.state);
45
+ const images = context.images ?? [];
46
+ if (images.length === 0)
47
+ return state;
48
+ if (images.length > MAX_IMAGES) {
49
+ throw new Error(`${LABEL} accepts at most ${MAX_IMAGES} images, got ${images.length}`);
50
+ }
51
+ return [
52
+ {
53
+ role: "user",
54
+ content: [
55
+ { type: "input_text", text: state },
56
+ ...images.map((image) => ({
57
+ type: "input_image",
58
+ image_url: `data:${image.mimeType};base64,${image.data}`,
59
+ })),
60
+ ],
61
+ },
62
+ ];
63
+ }
64
+ function choiceProbabilities(value, id) {
65
+ if (!Array.isArray(value))
66
+ throw new Error(`${LABEL} returned invalid probabilities for ${id}`);
67
+ return Object.fromEntries(value.map((entry) => {
68
+ if (!isRecord(entry) || typeof entry.value !== "string") {
69
+ throw new Error(`${LABEL} returned invalid probabilities for ${id}`);
70
+ }
71
+ return [entry.value, requiredNumber(LABEL, entry.probability, `probability for ${id}.${entry.value}`)];
72
+ }));
73
+ }
74
+ function parseAnswer(id, question, answer) {
75
+ if (answer.type === "refusal")
76
+ throw new Error(`${LABEL} refused to answer ${id}`);
77
+ if (question.type === "choice") {
78
+ if (answer.type !== "choice" || typeof answer.choice !== "string") {
79
+ throw new Error(`${LABEL} did not return a choice answer for ${id}`);
80
+ }
81
+ return {
82
+ type: "choice",
83
+ choice: answer.choice,
84
+ probabilities: choiceProbabilities(answer.probabilities, id),
85
+ confidence: requiredNumber(LABEL, answer.confidence, `confidence for ${id}`),
86
+ };
87
+ }
88
+ if (question.type === "score") {
89
+ if (answer.type !== "score")
90
+ throw new Error(`${LABEL} did not return a score answer for ${id}`);
91
+ return {
92
+ type: "score",
93
+ score: requiredNumber(LABEL, answer.score, `score for ${id}`),
94
+ confidence: requiredNumber(LABEL, answer.confidence, `confidence for ${id}`),
95
+ };
96
+ }
97
+ if (answer.type !== "predicate")
98
+ throw new Error(`${LABEL} did not return a predicate answer for ${id}`);
99
+ return { type: "bool", probability: requiredNumber(LABEL, answer.probability, `probability for ${id}`) };
100
+ }
101
+ function parseAnswers(value, context) {
102
+ if (!Array.isArray(value))
103
+ throw new Error(`${LABEL} returned an unexpected response`);
104
+ const byName = new Map();
105
+ for (const answer of value) {
106
+ if (isRecord(answer) && typeof answer.name === "string")
107
+ byName.set(answer.name, answer);
108
+ }
109
+ return Object.fromEntries(Object.entries(context.questions).map(([id, question]) => {
110
+ const answer = byName.get(id);
111
+ if (!answer)
112
+ throw new Error(`${LABEL} did not return an answer for ${id}`);
113
+ return [id, parseAnswer(id, question, answer)];
114
+ }));
115
+ }
116
+ /**
117
+ * Cloudflare in front of api.openai.com answers 504 with an HTML page when a request runs longer
118
+ * than about five seconds. Large inputs, currently above roughly 600K tokens, hit this limit, and
119
+ * retrying the same input runs into it again, so 504 is not retried.
120
+ */
121
+ const NO_RETRY_STATUSES = [504];
122
+ function errorMessage(error) {
123
+ if (error.status === 504) {
124
+ return `${LABEL} error (504): the request timed out at the gateway. Very large inputs (above roughly 600K tokens) currently exceed its time limit.`;
125
+ }
126
+ return formatProviderError(normalizeProviderError(error), `${LABEL} error`);
127
+ }
128
+ /** Classification through OpenAI's Decisions API. */
129
+ export const classify = async (model, context, options) => {
130
+ const output = {
131
+ api: model.api,
132
+ provider: model.provider,
133
+ model: model.id,
134
+ answers: {},
135
+ stopReason: "stop",
136
+ timestamp: Date.now(),
137
+ };
138
+ try {
139
+ if (model.api !== "openai-decisions")
140
+ throw new Error(`Unsupported classifier API: ${model.api}`);
141
+ const body = await postClassifierRequest(LABEL, new URL("decisions", `${model.baseUrl.replace(/\/+$/u, "")}/`), model, {
142
+ model: model.id,
143
+ input: wireInput(context),
144
+ questions: Object.entries(context.questions).map(([id, question]) => wireQuestion(id, question)),
145
+ }, options, NO_RETRY_STATUSES);
146
+ if (!isRecord(body))
147
+ throw new Error(`${LABEL} returned an unexpected response`);
148
+ // Set before parsing answers: a request with malformed or refused answers was still billed.
149
+ const usage = parseClassifierUsage(body.usage, model);
150
+ if (usage)
151
+ output.usage = usage;
152
+ output.answers = parseAnswers(body.answers, context);
153
+ return output;
154
+ }
155
+ catch (error) {
156
+ output.answers = {};
157
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
158
+ output.errorMessage = errorMessage(error);
159
+ return output;
160
+ }
161
+ };
162
+ //# sourceMappingURL=openai-decisions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-decisions.js","sourceRoot":"","sources":["../../src/api/openai-decisions.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAEN,QAAQ,EACR,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,GACd,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;GAUG;AAEH,MAAM,KAAK,GAAG,kBAAkB,CAAC;AAEjC,sEAAsE;AACtE,MAAM,UAAU,GAAG,GAAG,CAAC;AAUvB,SAAS,qBAAqB,CAAC,QAAuD;IACrF,MAAM,QAAQ,GAAG;QAChB,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;QACrE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE;KACxE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAClB,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,YAAY,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;AAC3G,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,QAA4B;IAC/D,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO;YACN,IAAI,EAAE,QAAQ;YACd,IAAI;YACJ,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,EAAE,CACvE,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAChD;SACD,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC/B,OAAO;YACN,IAAI,EAAE,OAAO;YACb,IAAI;YACJ,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;SACrD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC;AACnF,CAAC;AAED,SAAS,SAAS,CAAC,OAA0B;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACpC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,UAAU,gBAAgB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO;QACN;YACC,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE;gBACR,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE;gBACnC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;oBACzB,IAAI,EAAE,aAAa;oBACnB,SAAS,EAAE,QAAQ,KAAK,CAAC,QAAQ,WAAW,KAAK,CAAC,IAAI,EAAE;iBACxD,CAAC,CAAC;aACH;SACD;KACD,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc,EAAE,EAAU;IACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uCAAuC,EAAE,EAAE,CAAC,CAAC;IAChG,OAAO,MAAM,CAAC,WAAW,CACxB,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACnB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uCAAuC,EAAE,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,mBAAmB,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACxG,CAAC,CAAC,CACF,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,EAAU,EAAE,QAA4B,EAAE,MAA+B;IAC7F,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,sBAAsB,EAAE,EAAE,CAAC,CAAC;IACnF,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACnE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uCAAuC,EAAE,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,OAAO;YACN,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,aAAa,EAAE,mBAAmB,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC;YAC5D,UAAU,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC;SAC5E,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC/B,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,sCAAsC,EAAE,EAAE,CAAC,CAAC;QACjG,OAAO;YACN,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,CAAC;YAC7D,UAAU,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC;SAC5E,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,0CAA0C,EAAE,EAAE,CAAC,CAAC;IACzG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,mBAAmB,EAAE,EAAE,CAAC,EAAE,CAAC;AAC1G,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,OAA0B;IAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,kCAAkC,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmC,CAAC;IAC1D,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;QAC5B,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CACxB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE;QACxD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,iCAAiC,EAAE,EAAE,CAAC,CAAC;QAC5E,OAAO,CAAC,EAAE,EAAE,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CACF,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,CAAC,GAAG,CAAC,CAAC;AAEhC,SAAS,YAAY,CAAC,KAAc;IACnC,IAAK,KAAsC,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5D,OAAO,GAAG,KAAK,oIAAoI,CAAC;IACrJ,CAAC;IACD,OAAO,mBAAmB,CAAC,sBAAsB,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC;AAC7E,CAAC;AAED,qDAAqD;AACrD,MAAM,CAAC,MAAM,QAAQ,GAA0C,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAChG,MAAM,MAAM,GAAqB;QAChC,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,EAAE;QACf,OAAO,EAAE,EAAE;QACX,UAAU,EAAE,MAAM;QAClB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACrB,CAAC;IAEF,IAAI,CAAC;QACJ,IAAI,KAAK,CAAC,GAAG,KAAK,kBAAkB;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAClG,MAAM,IAAI,GAAG,MAAM,qBAAqB,CACvC,KAAK,EACL,IAAI,GAAG,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,EAC9D,KAAK,EACL;YACC,KAAK,EAAE,KAAK,CAAC,EAAE;YACf,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC;YACzB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;SAChG,EACD,OAAO,EACP,iBAAiB,CACjB,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,kCAAkC,CAAC,CAAC;QACjF,4FAA4F;QAC5F,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACtD,IAAI,KAAK;YAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAChC,MAAM,CAAC,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;QACpB,MAAM,CAAC,UAAU,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;QACnE,MAAM,CAAC,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;IACf,CAAC;AACF,CAAC,CAAC","sourcesContent":["import type {\n\tClassifierAnswer,\n\tClassifierContext,\n\tClassifierFunction,\n\tClassifierOptions,\n\tClassifierQuestion,\n\tClassifierResult,\n} from \"../types.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport {\n\ttype ClassifierHttpError,\n\tisRecord,\n\tparseClassifierUsage,\n\tpostClassifierRequest,\n\trequiredNumber,\n} from \"./classifier-shared.ts\";\n\n/**\n * OpenAI's Decisions API: `POST /v1/decisions` with `{ model, input, questions }`.\n * https://developers.openai.com/api/docs/guides/decisions\n *\n * The state is sent as JSON text. With images, the input becomes one user message with the state\n * as `input_text` followed by `input_image` data URLs. Questions map to Decisions types:\n * `choice` to `choice`, `score` to `score`, and `bool` to `predicate`. Predicates have no\n * criteria field, so the meanings of true and false are appended to the instructions.\n *\n * Only OpenAI API keys work: Sign in with ChatGPT tokens are rejected on this route.\n */\n\nconst LABEL = \"OpenAI Decisions\";\n\n/** The endpoint accepts at most this many image parts per request. */\nconst MAX_IMAGES = 128;\n\ninterface DecisionQuestion {\n\ttype: \"predicate\" | \"choice\" | \"score\";\n\tname: string;\n\tinstructions: string;\n\tchoices?: Array<{ value: string; description?: string }>;\n\tlevels?: Array<{ label: string }>;\n}\n\nfunction predicateInstructions(question: Extract<ClassifierQuestion, { type: \"bool\" }>): string {\n\tconst meanings = [\n\t\tquestion.criteria.true ? `True means: ${question.criteria.true}` : \"\",\n\t\tquestion.criteria.false ? `False means: ${question.criteria.false}` : \"\",\n\t].filter(Boolean);\n\treturn meanings.length > 0 ? `${question.instructions}\\n\\n${meanings.join(\"\\n\")}` : question.instructions;\n}\n\nfunction wireQuestion(name: string, question: ClassifierQuestion): DecisionQuestion {\n\tif (question.type === \"choice\") {\n\t\treturn {\n\t\t\ttype: \"choice\",\n\t\t\tname,\n\t\t\tinstructions: question.instructions,\n\t\t\tchoices: Object.entries(question.criteria).map(([value, description]) =>\n\t\t\t\tdescription ? { value, description } : { value },\n\t\t\t),\n\t\t};\n\t}\n\tif (question.type === \"score\") {\n\t\treturn {\n\t\t\ttype: \"score\",\n\t\t\tname,\n\t\t\tinstructions: question.instructions,\n\t\t\tlevels: question.criteria.map((label) => ({ label })),\n\t\t};\n\t}\n\treturn { type: \"predicate\", name, instructions: predicateInstructions(question) };\n}\n\nfunction wireInput(context: ClassifierContext): unknown {\n\tconst state = JSON.stringify(context.state);\n\tconst images = context.images ?? [];\n\tif (images.length === 0) return state;\n\tif (images.length > MAX_IMAGES) {\n\t\tthrow new Error(`${LABEL} accepts at most ${MAX_IMAGES} images, got ${images.length}`);\n\t}\n\treturn [\n\t\t{\n\t\t\trole: \"user\",\n\t\t\tcontent: [\n\t\t\t\t{ type: \"input_text\", text: state },\n\t\t\t\t...images.map((image) => ({\n\t\t\t\t\ttype: \"input_image\",\n\t\t\t\t\timage_url: `data:${image.mimeType};base64,${image.data}`,\n\t\t\t\t})),\n\t\t\t],\n\t\t},\n\t];\n}\n\nfunction choiceProbabilities(value: unknown, id: string): Record<string, number> {\n\tif (!Array.isArray(value)) throw new Error(`${LABEL} returned invalid probabilities for ${id}`);\n\treturn Object.fromEntries(\n\t\tvalue.map((entry) => {\n\t\t\tif (!isRecord(entry) || typeof entry.value !== \"string\") {\n\t\t\t\tthrow new Error(`${LABEL} returned invalid probabilities for ${id}`);\n\t\t\t}\n\t\t\treturn [entry.value, requiredNumber(LABEL, entry.probability, `probability for ${id}.${entry.value}`)];\n\t\t}),\n\t);\n}\n\nfunction parseAnswer(id: string, question: ClassifierQuestion, answer: Record<string, unknown>): ClassifierAnswer {\n\tif (answer.type === \"refusal\") throw new Error(`${LABEL} refused to answer ${id}`);\n\tif (question.type === \"choice\") {\n\t\tif (answer.type !== \"choice\" || typeof answer.choice !== \"string\") {\n\t\t\tthrow new Error(`${LABEL} did not return a choice answer for ${id}`);\n\t\t}\n\t\treturn {\n\t\t\ttype: \"choice\",\n\t\t\tchoice: answer.choice,\n\t\t\tprobabilities: choiceProbabilities(answer.probabilities, id),\n\t\t\tconfidence: requiredNumber(LABEL, answer.confidence, `confidence for ${id}`),\n\t\t};\n\t}\n\tif (question.type === \"score\") {\n\t\tif (answer.type !== \"score\") throw new Error(`${LABEL} did not return a score answer for ${id}`);\n\t\treturn {\n\t\t\ttype: \"score\",\n\t\t\tscore: requiredNumber(LABEL, answer.score, `score for ${id}`),\n\t\t\tconfidence: requiredNumber(LABEL, answer.confidence, `confidence for ${id}`),\n\t\t};\n\t}\n\tif (answer.type !== \"predicate\") throw new Error(`${LABEL} did not return a predicate answer for ${id}`);\n\treturn { type: \"bool\", probability: requiredNumber(LABEL, answer.probability, `probability for ${id}`) };\n}\n\nfunction parseAnswers(value: unknown, context: ClassifierContext): Record<string, ClassifierAnswer> {\n\tif (!Array.isArray(value)) throw new Error(`${LABEL} returned an unexpected response`);\n\tconst byName = new Map<string, Record<string, unknown>>();\n\tfor (const answer of value) {\n\t\tif (isRecord(answer) && typeof answer.name === \"string\") byName.set(answer.name, answer);\n\t}\n\treturn Object.fromEntries(\n\t\tObject.entries(context.questions).map(([id, question]) => {\n\t\t\tconst answer = byName.get(id);\n\t\t\tif (!answer) throw new Error(`${LABEL} did not return an answer for ${id}`);\n\t\t\treturn [id, parseAnswer(id, question, answer)];\n\t\t}),\n\t);\n}\n\n/**\n * Cloudflare in front of api.openai.com answers 504 with an HTML page when a request runs longer\n * than about five seconds. Large inputs, currently above roughly 600K tokens, hit this limit, and\n * retrying the same input runs into it again, so 504 is not retried.\n */\nconst NO_RETRY_STATUSES = [504];\n\nfunction errorMessage(error: unknown): string {\n\tif ((error as Partial<ClassifierHttpError>).status === 504) {\n\t\treturn `${LABEL} error (504): the request timed out at the gateway. Very large inputs (above roughly 600K tokens) currently exceed its time limit.`;\n\t}\n\treturn formatProviderError(normalizeProviderError(error), `${LABEL} error`);\n}\n\n/** Classification through OpenAI's Decisions API. */\nexport const classify: ClassifierFunction<ClassifierOptions> = async (model, context, options) => {\n\tconst output: ClassifierResult = {\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tanswers: {},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\n\ttry {\n\t\tif (model.api !== \"openai-decisions\") throw new Error(`Unsupported classifier API: ${model.api}`);\n\t\tconst body = await postClassifierRequest(\n\t\t\tLABEL,\n\t\t\tnew URL(\"decisions\", `${model.baseUrl.replace(/\\/+$/u, \"\")}/`),\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tmodel: model.id,\n\t\t\t\tinput: wireInput(context),\n\t\t\t\tquestions: Object.entries(context.questions).map(([id, question]) => wireQuestion(id, question)),\n\t\t\t},\n\t\t\toptions,\n\t\t\tNO_RETRY_STATUSES,\n\t\t);\n\t\tif (!isRecord(body)) throw new Error(`${LABEL} returned an unexpected response`);\n\t\t// Set before parsing answers: a request with malformed or refused answers was still billed.\n\t\tconst usage = parseClassifierUsage(body.usage, model);\n\t\tif (usage) output.usage = usage;\n\t\toutput.answers = parseAnswers(body.answers, context);\n\t\treturn output;\n\t} catch (error) {\n\t\toutput.answers = {};\n\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\toutput.errorMessage = errorMessage(error);\n\t\treturn output;\n\t}\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ProviderClassifier } from "../types.ts";
2
+ export declare const openAIDecisionsApi: () => ProviderClassifier;
3
+ //# sourceMappingURL=openai-decisions.lazy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-decisions.lazy.d.ts","sourceRoot":"","sources":["../../src/api/openai-decisions.lazy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,eAAO,MAAM,kBAAkB,QAAO,kBAGpC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export const openAIDecisionsApi = () => ({
2
+ classify: async (model, context, options) => (await import("./openai-decisions.js")).classify(model, context, options),
3
+ });
4
+ //# sourceMappingURL=openai-decisions.lazy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-decisions.lazy.js","sourceRoot":"","sources":["../../src/api/openai-decisions.lazy.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAuB,EAAE,CAAC,CAAC;IAC5D,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAC3C,CAAC,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC;CAC1E,CAAC,CAAC","sourcesContent":["import type { ProviderClassifier } from \"../types.ts\";\n\nexport const openAIDecisionsApi = (): ProviderClassifier => ({\n\tclassify: async (model, context, options) =>\n\t\t(await import(\"./openai-decisions.ts\")).classify(model, context, options),\n});\n"]}
@@ -17,7 +17,6 @@ export interface SystemOneTransport {
17
17
  /** Extracts the System One output (`{ answers, usage }`) from the service's response envelope. */
18
18
  output(body: unknown): Record<string, unknown>;
19
19
  }
20
- export declare function isRecord(value: unknown): value is Record<string, unknown>;
21
20
  /** Runs one System One classification over the given transport. */
22
21
  export declare function classifySystemOne(transport: SystemOneTransport, model: ClassifierModel<ClassifierApi>, context: ClassifierContext, options: ClassifierOptions | undefined): Promise<ClassifierResult>;
23
22
  //# sourceMappingURL=system-one-shared.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"system-one-shared.d.ts","sourceRoot":"","sources":["../../src/api/system-one-shared.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEX,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAGhB,MAAM,aAAa,CAAC;AAKrB,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IAClC,oDAAoD;IACpD,GAAG,EAAE,aAAa,CAAC;IACnB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;IAChD,sEAAsE;IACtE,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC;IACvF,kGAAkG;IAClG,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAyBD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAiHD,mEAAmE;AACnE,wBAAsB,iBAAiB,CACtC,SAAS,EAAE,kBAAkB,EAC7B,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,EACrC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,iBAAiB,GAAG,SAAS,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAyD3B"}
1
+ {"version":3,"file":"system-one-shared.d.ts","sourceRoot":"","sources":["../../src/api/system-one-shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,aAAa,CAAC;AAIrB,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IAClC,oDAAoD;IACpD,GAAG,EAAE,aAAa,CAAC;IACnB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;IAChD,sEAAsE;IACtE,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC;IACvF,kGAAkG;IAClG,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAoED,mEAAmE;AACnE,wBAAsB,iBAAiB,CACtC,SAAS,EAAE,kBAAkB,EAC7B,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,EACrC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,iBAAiB,GAAG,SAAS,GACpC,OAAO,CAAC,gBAAgB,CAAC,CA+B3B"}
@@ -1,31 +1,5 @@
1
- import { calculateCost } from "../models.js";
2
1
  import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
3
- import { headersToRecord, providerHeadersToRecord } from "../utils/headers.js";
4
- import { retryProviderRequest } from "../utils/provider-retry.js";
5
- function httpError(label, response, body) {
6
- const error = new Error(`${label} returned ${response.status}`);
7
- error.status = response.status;
8
- error.headers = response.headers;
9
- error.body = body;
10
- return error;
11
- }
12
- function timeoutError(timeoutMs) {
13
- const error = new Error(`Request timed out after ${timeoutMs}ms`);
14
- error.name = "TimeoutError";
15
- error.status = undefined;
16
- error.headers = undefined;
17
- error.body = "";
18
- return error;
19
- }
20
- export function isRecord(value) {
21
- return typeof value === "object" && value !== null && !Array.isArray(value);
22
- }
23
- function requiredNumber(label, value, field) {
24
- if (typeof value !== "number" || !Number.isFinite(value)) {
25
- throw new Error(`${label} returned an invalid ${field}`);
26
- }
27
- return value;
28
- }
2
+ import { isRecord, parseClassifierUsage, postClassifierRequest, requiredNumber } from "./classifier-shared.js";
29
3
  function probabilities(label, value, id) {
30
4
  if (!isRecord(value))
31
5
  throw new Error(`${label} returned invalid probabilities for ${id}`);
@@ -82,29 +56,6 @@ function parseAnswers(label, value, context) {
82
56
  }
83
57
  return Object.fromEntries(answers);
84
58
  }
85
- function tokenCount(value) {
86
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
87
- }
88
- /**
89
- * Usage from System One's `{ input_tokens, output_tokens }`, priced from the model catalog like chat
90
- * usage. A missing or malformed usage object leaves the result without usage instead of failing it.
91
- */
92
- function parseUsage(value, model) {
93
- if (!isRecord(value) || (value.input_tokens === undefined && value.output_tokens === undefined))
94
- return undefined;
95
- const input = tokenCount(value.input_tokens);
96
- const output = tokenCount(value.output_tokens);
97
- const usage = {
98
- input,
99
- output,
100
- cacheRead: 0,
101
- cacheWrite: 0,
102
- totalTokens: input + output,
103
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
104
- };
105
- calculateCost(model, usage);
106
- return usage;
107
- }
108
59
  /** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */
109
60
  function wireRequest(context) {
110
61
  return {
@@ -115,9 +66,6 @@ function wireRequest(context) {
115
66
  ])),
116
67
  };
117
68
  }
118
- function requestHeaders(model, apiKey, optionsHeaders) {
119
- return (providerHeadersToRecord({ authorization: `Bearer ${apiKey}`, "content-type": "application/json" }, model.headers, optionsHeaders) ?? {});
120
- }
121
69
  /** Runs one System One classification over the given transport. */
122
70
  export async function classifySystemOne(transport, model, context, options) {
123
71
  const output = {
@@ -131,44 +79,12 @@ export async function classifySystemOne(transport, model, context, options) {
131
79
  try {
132
80
  if (model.api !== transport.api)
133
81
  throw new Error(`Unsupported classifier API: ${model.api}`);
134
- if (!options?.apiKey)
135
- throw new Error(`No API key for provider: ${model.provider}`);
136
- const apiKey = options.apiKey;
137
- let payload = transport.payload(model, wireRequest(context));
138
- const transformed = await options.onPayload?.(payload, model);
139
- if (transformed !== undefined)
140
- payload = transformed;
141
- const requestFetch = options.fetch ?? globalThis.fetch;
142
- const { response, body } = await retryProviderRequest(async () => {
143
- const timeoutSignal = options.timeoutMs !== undefined ? AbortSignal.timeout(options.timeoutMs) : undefined;
144
- const signal = options.signal && timeoutSignal
145
- ? AbortSignal.any([options.signal, timeoutSignal])
146
- : (options.signal ?? timeoutSignal);
147
- try {
148
- const next = await requestFetch(transport.url(model), {
149
- method: "POST",
150
- headers: requestHeaders(model, apiKey, options.headers),
151
- body: JSON.stringify(payload),
152
- signal,
153
- });
154
- if (!next.ok)
155
- throw httpError(transport.label, next, await next.text());
156
- return { response: next, body: (await next.json()) };
157
- }
158
- catch (error) {
159
- if (timeoutSignal?.aborted && !options.signal?.aborted)
160
- throw timeoutError(options.timeoutMs);
161
- throw error;
162
- }
163
- }, {
164
- maxRetries: options.maxRetries ?? 2,
165
- maxRetryDelayMs: options.maxRetryDelayMs,
166
- signal: options.signal,
167
- });
168
- await options.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
82
+ if (context.images?.length)
83
+ throw new Error(`${transport.label} does not support image input`);
84
+ const body = await postClassifierRequest(transport.label, transport.url(model), model, transport.payload(model, wireRequest(context)), options);
169
85
  const result = transport.output(body);
170
86
  // Set before parsing answers: a request with malformed answers was still billed.
171
- const usage = parseUsage(result.usage, model);
87
+ const usage = parseClassifierUsage(result.usage, model);
172
88
  if (usage)
173
89
  output.usage = usage;
174
90
  output.answers = parseAnswers(transport.label, result.answers, context);
@@ -1 +1 @@
1
- {"version":3,"file":"system-one-shared.js","sourceRoot":"","sources":["../../src/api/system-one-shared.ts"],"names":[],"mappings":"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","sourcesContent":["import { calculateCost } from \"../models.ts\";\nimport type {\n\tClassifierAnswer,\n\tClassifierApi,\n\tClassifierContext,\n\tClassifierModel,\n\tClassifierOptions,\n\tClassifierResult,\n\tProviderHeaders,\n\tUsage,\n} from \"../types.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { headersToRecord, providerHeadersToRecord } from \"../utils/headers.ts\";\nimport { retryProviderRequest } from \"../utils/provider-retry.ts\";\n\n/** TypeSafe System One request body without the transport-specific envelope. */\nexport interface SystemOneWireRequest {\n\tstate: ClassifierContext[\"state\"];\n\tquestions: Record<string, unknown>;\n}\n\n/** Differences between services that serve System One models. */\nexport interface SystemOneTransport {\n\t/** Classifier API implemented by this transport. */\n\tapi: ClassifierApi;\n\t/** Service name used in error messages. */\n\tlabel: string;\n\t/** Absolute request URL. */\n\turl(model: ClassifierModel<ClassifierApi>): URL;\n\t/** Wraps the System One request in the service's request envelope. */\n\tpayload(model: ClassifierModel<ClassifierApi>, request: SystemOneWireRequest): unknown;\n\t/** Extracts the System One output (`{ answers, usage }`) from the service's response envelope. */\n\toutput(body: unknown): Record<string, unknown>;\n}\n\ninterface SystemOneHttpError extends Error {\n\tstatus: number | undefined;\n\theaders: Headers | undefined;\n\tbody: string;\n}\n\nfunction httpError(label: string, response: Response, body: string): SystemOneHttpError {\n\tconst error = new Error(`${label} returned ${response.status}`) as SystemOneHttpError;\n\terror.status = response.status;\n\terror.headers = response.headers;\n\terror.body = body;\n\treturn error;\n}\n\nfunction timeoutError(timeoutMs: number): SystemOneHttpError {\n\tconst error = new Error(`Request timed out after ${timeoutMs}ms`) as SystemOneHttpError;\n\terror.name = \"TimeoutError\";\n\terror.status = undefined;\n\terror.headers = undefined;\n\terror.body = \"\";\n\treturn error;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction requiredNumber(label: string, value: unknown, field: string): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new Error(`${label} returned an invalid ${field}`);\n\t}\n\treturn value;\n}\n\nfunction probabilities(label: string, value: unknown, id: string): Record<string, number> {\n\tif (!isRecord(value)) throw new Error(`${label} returned invalid probabilities for ${id}`);\n\treturn Object.fromEntries(\n\t\tObject.entries(value).map(([key, probability]) => [\n\t\t\tkey,\n\t\t\trequiredNumber(label, probability, `probability for ${id}.${key}`),\n\t\t]),\n\t);\n}\n\nfunction parseAnswers(label: string, value: unknown, context: ClassifierContext): Record<string, ClassifierAnswer> {\n\tif (!isRecord(value)) throw new Error(`${label} returned an unexpected response`);\n\tconst answers: Array<[string, ClassifierAnswer]> = [];\n\tfor (const [id, question] of Object.entries(context.questions)) {\n\t\tconst answer = value[id];\n\t\tif (!isRecord(answer)) throw new Error(`${label} did not return an answer for ${id}`);\n\t\tif (question.type === \"choice\") {\n\t\t\tif (answer.type !== \"choice\" || typeof answer.choice !== \"string\") {\n\t\t\t\tthrow new Error(`${label} did not return a choice answer for ${id}`);\n\t\t\t}\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"choice\",\n\t\t\t\t\tchoice: answer.choice,\n\t\t\t\t\tprobabilities: probabilities(label, answer.probabilities, id),\n\t\t\t\t\tconfidence: requiredNumber(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else if (question.type === \"score\") {\n\t\t\tif (answer.type !== \"score\") throw new Error(`${label} did not return a score answer for ${id}`);\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"score\",\n\t\t\t\t\tscore: requiredNumber(label, answer.score, `score for ${id}`),\n\t\t\t\t\tconfidence: requiredNumber(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else {\n\t\t\tif (answer.type !== \"noul\") throw new Error(`${label} did not return a bool answer for ${id}`);\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"bool\",\n\t\t\t\t\tprobability: requiredNumber(label, answer.noul, `probability for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t}\n\t}\n\treturn Object.fromEntries(answers);\n}\n\nfunction tokenCount(value: unknown): number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : 0;\n}\n\n/**\n * Usage from System One's `{ input_tokens, output_tokens }`, priced from the model catalog like chat\n * usage. A missing or malformed usage object leaves the result without usage instead of failing it.\n */\nfunction parseUsage(value: unknown, model: ClassifierModel<ClassifierApi>): Usage | undefined {\n\tif (!isRecord(value) || (value.input_tokens === undefined && value.output_tokens === undefined)) return undefined;\n\tconst input = tokenCount(value.input_tokens);\n\tconst output = tokenCount(value.output_tokens);\n\tconst usage: Usage = {\n\t\tinput,\n\t\toutput,\n\t\tcacheRead: 0,\n\t\tcacheWrite: 0,\n\t\ttotalTokens: input + output,\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t};\n\tcalculateCost(model, usage);\n\treturn usage;\n}\n\n/** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */\nfunction wireRequest(context: ClassifierContext): SystemOneWireRequest {\n\treturn {\n\t\tstate: context.state,\n\t\tquestions: Object.fromEntries(\n\t\t\tObject.entries(context.questions).map(([id, question]) => [\n\t\t\t\tid,\n\t\t\t\tquestion.type === \"bool\" ? { ...question, type: \"noul\" } : question,\n\t\t\t]),\n\t\t),\n\t};\n}\n\nfunction requestHeaders(\n\tmodel: ClassifierModel<ClassifierApi>,\n\tapiKey: string,\n\toptionsHeaders?: ProviderHeaders,\n): Record<string, string> {\n\treturn (\n\t\tproviderHeadersToRecord(\n\t\t\t{ authorization: `Bearer ${apiKey}`, \"content-type\": \"application/json\" },\n\t\t\tmodel.headers,\n\t\t\toptionsHeaders,\n\t\t) ?? {}\n\t);\n}\n\n/** Runs one System One classification over the given transport. */\nexport async function classifySystemOne(\n\ttransport: SystemOneTransport,\n\tmodel: ClassifierModel<ClassifierApi>,\n\tcontext: ClassifierContext,\n\toptions: ClassifierOptions | undefined,\n): Promise<ClassifierResult> {\n\tconst output: ClassifierResult = {\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tanswers: {},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\n\ttry {\n\t\tif (model.api !== transport.api) throw new Error(`Unsupported classifier API: ${model.api}`);\n\t\tif (!options?.apiKey) throw new Error(`No API key for provider: ${model.provider}`);\n\t\tconst apiKey = options.apiKey;\n\t\tlet payload = transport.payload(model, wireRequest(context));\n\t\tconst transformed = await options.onPayload?.(payload, model);\n\t\tif (transformed !== undefined) payload = transformed;\n\t\tconst requestFetch = options.fetch ?? globalThis.fetch;\n\t\tconst { response, body } = await retryProviderRequest(\n\t\t\tasync () => {\n\t\t\t\tconst timeoutSignal = options.timeoutMs !== undefined ? AbortSignal.timeout(options.timeoutMs) : undefined;\n\t\t\t\tconst signal =\n\t\t\t\t\toptions.signal && timeoutSignal\n\t\t\t\t\t\t? AbortSignal.any([options.signal, timeoutSignal])\n\t\t\t\t\t\t: (options.signal ?? timeoutSignal);\n\t\t\t\ttry {\n\t\t\t\t\tconst next = await requestFetch(transport.url(model), {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: requestHeaders(model, apiKey, options.headers),\n\t\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t});\n\t\t\t\t\tif (!next.ok) throw httpError(transport.label, next, await next.text());\n\t\t\t\t\treturn { response: next, body: (await next.json()) as unknown };\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (timeoutSignal?.aborted && !options.signal?.aborted) throw timeoutError(options.timeoutMs!);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tmaxRetries: options.maxRetries ?? 2,\n\t\t\t\tmaxRetryDelayMs: options.maxRetryDelayMs,\n\t\t\t\tsignal: options.signal,\n\t\t\t},\n\t\t);\n\t\tawait options.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\tconst result = transport.output(body);\n\t\t// Set before parsing answers: a request with malformed answers was still billed.\n\t\tconst usage = parseUsage(result.usage, model);\n\t\tif (usage) output.usage = usage;\n\t\toutput.answers = parseAnswers(transport.label, result.answers, context);\n\t\treturn output;\n\t} catch (error) {\n\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\toutput.errorMessage = formatProviderError(normalizeProviderError(error), `${transport.label} error`);\n\t\treturn output;\n\t}\n}\n"]}
1
+ {"version":3,"file":"system-one-shared.js","sourceRoot":"","sources":["../../src/api/system-one-shared.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAsB/G,SAAS,aAAa,CAAC,KAAa,EAAE,KAAc,EAAE,EAAU;IAC/D,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uCAAuC,EAAE,EAAE,CAAC,CAAC;IAC3F,OAAO,MAAM,CAAC,WAAW,CACxB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC;QACjD,GAAG;QACH,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,IAAI,GAAG,EAAE,CAAC;KAClE,CAAC,CACF,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,KAAc,EAAE,OAA0B;IAC9E,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,kCAAkC,CAAC,CAAC;IAClF,MAAM,OAAO,GAAsC,EAAE,CAAC;IACtD,KAAK,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAChE,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,iCAAiC,EAAE,EAAE,CAAC,CAAC;QACtF,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACnE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uCAAuC,EAAE,EAAE,CAAC,CAAC;YACtE,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACZ,EAAE;gBACF;oBACC,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,aAAa,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC;oBAC7D,UAAU,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC;iBAC5E;aACD,CAAC,CAAC;QACJ,CAAC;aAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACtC,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,sCAAsC,EAAE,EAAE,CAAC,CAAC;YACjG,OAAO,CAAC,IAAI,CAAC;gBACZ,EAAE;gBACF;oBACC,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,CAAC;oBAC7D,UAAU,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC;iBAC5E;aACD,CAAC,CAAC;QACJ,CAAC;aAAM,CAAC;YACP,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,qCAAqC,EAAE,EAAE,CAAC,CAAC;YAC/F,OAAO,CAAC,IAAI,CAAC;gBACZ,EAAE;gBACF;oBACC,IAAI,EAAE,MAAM;oBACZ,WAAW,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,EAAE,CAAC;iBACxE;aACD,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AACpC,CAAC;AAED,yEAAyE;AACzE,SAAS,WAAW,CAAC,OAA0B;IAC9C,OAAO;QACN,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,MAAM,CAAC,WAAW,CAC5B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;YACzD,EAAE;YACF,QAAQ,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ;SACnE,CAAC,CACF;KACD,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,SAA6B,EAC7B,KAAqC,EACrC,OAA0B,EAC1B,OAAsC;IAEtC,MAAM,MAAM,GAAqB;QAChC,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,EAAE;QACf,OAAO,EAAE,EAAE;QACX,UAAU,EAAE,MAAM;QAClB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACrB,CAAC;IAEF,IAAI,CAAC;QACJ,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7F,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,CAAC,KAAK,+BAA+B,CAAC,CAAC;QAC/F,MAAM,IAAI,GAAG,MAAM,qBAAqB,CACvC,SAAS,CAAC,KAAK,EACf,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EACpB,KAAK,EACL,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,EAC9C,OAAO,CACP,CAAC;QACF,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtC,iFAAiF;QACjF,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACxD,IAAI,KAAK;YAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAChC,MAAM,CAAC,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,CAAC,UAAU,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;QACnE,MAAM,CAAC,YAAY,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC;QACrG,OAAO,MAAM,CAAC;IACf,CAAC;AACF,CAAC","sourcesContent":["import type {\n\tClassifierAnswer,\n\tClassifierApi,\n\tClassifierContext,\n\tClassifierModel,\n\tClassifierOptions,\n\tClassifierResult,\n} from \"../types.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { isRecord, parseClassifierUsage, postClassifierRequest, requiredNumber } from \"./classifier-shared.ts\";\n\n/** TypeSafe System One request body without the transport-specific envelope. */\nexport interface SystemOneWireRequest {\n\tstate: ClassifierContext[\"state\"];\n\tquestions: Record<string, unknown>;\n}\n\n/** Differences between services that serve System One models. */\nexport interface SystemOneTransport {\n\t/** Classifier API implemented by this transport. */\n\tapi: ClassifierApi;\n\t/** Service name used in error messages. */\n\tlabel: string;\n\t/** Absolute request URL. */\n\turl(model: ClassifierModel<ClassifierApi>): URL;\n\t/** Wraps the System One request in the service's request envelope. */\n\tpayload(model: ClassifierModel<ClassifierApi>, request: SystemOneWireRequest): unknown;\n\t/** Extracts the System One output (`{ answers, usage }`) from the service's response envelope. */\n\toutput(body: unknown): Record<string, unknown>;\n}\n\nfunction probabilities(label: string, value: unknown, id: string): Record<string, number> {\n\tif (!isRecord(value)) throw new Error(`${label} returned invalid probabilities for ${id}`);\n\treturn Object.fromEntries(\n\t\tObject.entries(value).map(([key, probability]) => [\n\t\t\tkey,\n\t\t\trequiredNumber(label, probability, `probability for ${id}.${key}`),\n\t\t]),\n\t);\n}\n\nfunction parseAnswers(label: string, value: unknown, context: ClassifierContext): Record<string, ClassifierAnswer> {\n\tif (!isRecord(value)) throw new Error(`${label} returned an unexpected response`);\n\tconst answers: Array<[string, ClassifierAnswer]> = [];\n\tfor (const [id, question] of Object.entries(context.questions)) {\n\t\tconst answer = value[id];\n\t\tif (!isRecord(answer)) throw new Error(`${label} did not return an answer for ${id}`);\n\t\tif (question.type === \"choice\") {\n\t\t\tif (answer.type !== \"choice\" || typeof answer.choice !== \"string\") {\n\t\t\t\tthrow new Error(`${label} did not return a choice answer for ${id}`);\n\t\t\t}\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"choice\",\n\t\t\t\t\tchoice: answer.choice,\n\t\t\t\t\tprobabilities: probabilities(label, answer.probabilities, id),\n\t\t\t\t\tconfidence: requiredNumber(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else if (question.type === \"score\") {\n\t\t\tif (answer.type !== \"score\") throw new Error(`${label} did not return a score answer for ${id}`);\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"score\",\n\t\t\t\t\tscore: requiredNumber(label, answer.score, `score for ${id}`),\n\t\t\t\t\tconfidence: requiredNumber(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else {\n\t\t\tif (answer.type !== \"noul\") throw new Error(`${label} did not return a bool answer for ${id}`);\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"bool\",\n\t\t\t\t\tprobability: requiredNumber(label, answer.noul, `probability for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t}\n\t}\n\treturn Object.fromEntries(answers);\n}\n\n/** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */\nfunction wireRequest(context: ClassifierContext): SystemOneWireRequest {\n\treturn {\n\t\tstate: context.state,\n\t\tquestions: Object.fromEntries(\n\t\t\tObject.entries(context.questions).map(([id, question]) => [\n\t\t\t\tid,\n\t\t\t\tquestion.type === \"bool\" ? { ...question, type: \"noul\" } : question,\n\t\t\t]),\n\t\t),\n\t};\n}\n\n/** Runs one System One classification over the given transport. */\nexport async function classifySystemOne(\n\ttransport: SystemOneTransport,\n\tmodel: ClassifierModel<ClassifierApi>,\n\tcontext: ClassifierContext,\n\toptions: ClassifierOptions | undefined,\n): Promise<ClassifierResult> {\n\tconst output: ClassifierResult = {\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tanswers: {},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\n\ttry {\n\t\tif (model.api !== transport.api) throw new Error(`Unsupported classifier API: ${model.api}`);\n\t\tif (context.images?.length) throw new Error(`${transport.label} does not support image input`);\n\t\tconst body = await postClassifierRequest(\n\t\t\ttransport.label,\n\t\t\ttransport.url(model),\n\t\t\tmodel,\n\t\t\ttransport.payload(model, wireRequest(context)),\n\t\t\toptions,\n\t\t);\n\t\tconst result = transport.output(body);\n\t\t// Set before parsing answers: a request with malformed answers was still billed.\n\t\tconst usage = parseClassifierUsage(result.usage, model);\n\t\tif (usage) output.usage = usage;\n\t\toutput.answers = parseAnswers(transport.label, result.answers, context);\n\t\treturn output;\n\t} catch (error) {\n\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\toutput.errorMessage = formatProviderError(normalizeProviderError(error), `${transport.label} error`);\n\t\treturn output;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"typesafe-system-one.d.ts","sourceRoot":"","sources":["../../src/api/typesafe-system-one.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkBzE,gGAAgG;AAChG,eAAO,MAAM,QAAQ,EAAE,kBAAkB,CAAC,iBAAiB,CACL,CAAC"}
1
+ {"version":3,"file":"typesafe-system-one.d.ts","sourceRoot":"","sources":["../../src/api/typesafe-system-one.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAmBzE,gGAAgG;AAChG,eAAO,MAAM,QAAQ,EAAE,kBAAkB,CAAC,iBAAiB,CACL,CAAC"}
@@ -1,4 +1,5 @@
1
- import { classifySystemOne, isRecord } from "./system-one-shared.js";
1
+ import { isRecord } from "./classifier-shared.js";
2
+ import { classifySystemOne } from "./system-one-shared.js";
2
3
  /**
3
4
  * TypeSafe's native System One protocol. OpenRouter serves the same protocol,
4
5
  * so both providers use this API with different base URLs.
@@ -1 +1 @@
1
- {"version":3,"file":"typesafe-system-one.js","sourceRoot":"","sources":["../../src/api/typesafe-system-one.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAA2B,MAAM,wBAAwB,CAAC;AAE9F;;;GAGG;AACH,MAAM,SAAS,GAAuB;IACrC,GAAG,EAAE,qBAAqB;IAC1B,KAAK,EAAE,gBAAgB;IACvB,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC;IAC9E,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;IAC9D,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QAChB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC;IACb,CAAC;CACD,CAAC;AAEF,gGAAgG;AAChG,MAAM,CAAC,MAAM,QAAQ,GAA0C,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAC1F,iBAAiB,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC","sourcesContent":["import type { ClassifierFunction, ClassifierOptions } from \"../types.ts\";\nimport { classifySystemOne, isRecord, type SystemOneTransport } from \"./system-one-shared.ts\";\n\n/**\n * TypeSafe's native System One protocol. OpenRouter serves the same protocol,\n * so both providers use this API with different base URLs.\n */\nconst transport: SystemOneTransport = {\n\tapi: \"typesafe-system-one\",\n\tlabel: \"System One API\",\n\turl: (model) => new URL(\"systemone\", `${model.baseUrl.replace(/\\/+$/u, \"\")}/`),\n\tpayload: (model, request) => ({ model: model.id, ...request }),\n\toutput: (body) => {\n\t\tif (!isRecord(body)) throw new Error(\"System One API returned an unexpected response\");\n\t\treturn body;\n\t},\n};\n\n/** TypeSafe System One classification with public `bool` values mapped to wire-level `noul`. */\nexport const classify: ClassifierFunction<ClassifierOptions> = (model, context, options) =>\n\tclassifySystemOne(transport, model, context, options);\n"]}
1
+ {"version":3,"file":"typesafe-system-one.js","sourceRoot":"","sources":["../../src/api/typesafe-system-one.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAA2B,MAAM,wBAAwB,CAAC;AAEpF;;;GAGG;AACH,MAAM,SAAS,GAAuB;IACrC,GAAG,EAAE,qBAAqB;IAC1B,KAAK,EAAE,gBAAgB;IACvB,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC;IAC9E,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;IAC9D,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QAChB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC;IACb,CAAC;CACD,CAAC;AAEF,gGAAgG;AAChG,MAAM,CAAC,MAAM,QAAQ,GAA0C,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAC1F,iBAAiB,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC","sourcesContent":["import type { ClassifierFunction, ClassifierOptions } from \"../types.ts\";\nimport { isRecord } from \"./classifier-shared.ts\";\nimport { classifySystemOne, type SystemOneTransport } from \"./system-one-shared.ts\";\n\n/**\n * TypeSafe's native System One protocol. OpenRouter serves the same protocol,\n * so both providers use this API with different base URLs.\n */\nconst transport: SystemOneTransport = {\n\tapi: \"typesafe-system-one\",\n\tlabel: \"System One API\",\n\turl: (model) => new URL(\"systemone\", `${model.baseUrl.replace(/\\/+$/u, \"\")}/`),\n\tpayload: (model, request) => ({ model: model.id, ...request }),\n\toutput: (body) => {\n\t\tif (!isRecord(body)) throw new Error(\"System One API returned an unexpected response\");\n\t\treturn body;\n\t},\n};\n\n/** TypeSafe System One classification with public `bool` values mapped to wire-level `noul`. */\nexport const classify: ClassifierFunction<ClassifierOptions> = (model, context, options) =>\n\tclassifySystemOne(transport, model, context, options);\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAA4C,MAAM,aAAa,CAAC;AAoQvF,eAAO,MAAM,cAAc,EAAE,SA6B5B,CAAC"}
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAA4C,MAAM,aAAa,CAAC;AA6QvF,eAAO,MAAM,cAAc,EAAE,SA6B5B,CAAC"}
@@ -12,6 +12,8 @@ const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
12
12
  const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
13
13
  const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
14
14
  const CALLBACK_HOST = getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
15
+ // Preferred so the port can be forwarded into containers or over SSH. Anthropic accepts any loopback port,
16
+ // so login falls back to a free port when this one cannot be bound (#10571).
15
17
  const CALLBACK_PORT = 53692;
16
18
  const CALLBACK_PATH = "/callback";
17
19
  const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
@@ -111,21 +113,27 @@ async function exchangeAuthorizationCode(code, state, verifier, redirectUri, sig
111
113
  }
112
114
  async function loginAnthropic(interaction) {
113
115
  const { verifier, challenge } = await generatePKCE();
114
- const callback = await startOAuthCallbackServer({
116
+ const startCallbackServer = (port) => startOAuthCallbackServer({
115
117
  providerName: "Anthropic",
116
118
  host: CALLBACK_HOST,
117
- port: CALLBACK_PORT,
119
+ port,
118
120
  path: CALLBACK_PATH,
121
+ redirectHost: "localhost",
119
122
  state: verifier,
120
123
  complete: async (code) => code,
121
124
  signal: interaction.signal,
122
- }).catch(() => undefined);
125
+ });
126
+ // Without a callback server, login continues with the pasted redirect URL.
127
+ const callback = await startCallbackServer(CALLBACK_PORT)
128
+ .catch(() => startCallbackServer(0))
129
+ .catch(() => undefined);
130
+ const redirectUri = callback?.redirectUri ?? REDIRECT_URI;
123
131
  try {
124
132
  const authParams = new URLSearchParams({
125
133
  code: "true",
126
134
  client_id: CLIENT_ID,
127
135
  response_type: "code",
128
- redirect_uri: REDIRECT_URI,
136
+ redirect_uri: redirectUri,
129
137
  scope: SCOPES,
130
138
  code_challenge: challenge,
131
139
  code_challenge_method: "S256",
@@ -138,7 +146,7 @@ async function loginAnthropic(interaction) {
138
146
  });
139
147
  const result = await waitForCallbackOrManualInput(interaction, callback, {
140
148
  message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
141
- placeholder: REDIRECT_URI,
149
+ placeholder: redirectUri,
142
150
  });
143
151
  let code;
144
152
  let state = verifier;
@@ -155,7 +163,7 @@ async function loginAnthropic(interaction) {
155
163
  if (!code)
156
164
  throw new Error("Missing authorization code");
157
165
  interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
158
- return await exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI, interaction.signal);
166
+ return await exchangeAuthorizationCode(code, state, verifier, redirectUri, interaction.signal);
159
167
  }
160
168
  finally {
161
169
  callback?.close();
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/auth/oauth/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Anthropic OAuth flow (Claude Pro/Max)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback server.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport { getProviderEnvValue } from \"../../utils/provider-env.ts\";\nimport type { OAuthAuth, OAuthCredential, ProviderAuthInteraction } from \"../types.ts\";\nimport { startOAuthCallbackServer, waitForCallbackOrManualInput } from \"./callback-server.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl\");\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://platform.claude.com/v1/oauth/token\";\nconst CALLBACK_HOST = getProviderEnvValue(\"PI_OAUTH_CALLBACK_HOST\") || \"127.0.0.1\";\nconst CALLBACK_PORT = 53692;\nconst CALLBACK_PATH = \"/callback\";\nconst REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;\nconst COPY_CODE_REDIRECT_URI = \"https://platform.claude.com/oauth/code/callback\";\nconst ANTHROPIC_BROWSER_LOGIN_METHOD = \"browser\";\nconst ANTHROPIC_COPY_CODE_LOGIN_METHOD = \"copy_code\";\nconst SCOPES =\n\t\"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload\";\n\nfunction parseAuthorizationInput(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// not a URL\n\t}\n\n\tif (value.includes(\"#\")) {\n\t\tconst [code, state] = value.split(\"#\", 2);\n\t\treturn { code, state };\n\t}\n\n\tif (value.includes(\"code=\")) {\n\t\tconst params = new URLSearchParams(value);\n\t\treturn {\n\t\t\tcode: params.get(\"code\") ?? undefined,\n\t\t\tstate: params.get(\"state\") ?? undefined,\n\t\t};\n\t}\n\n\treturn { code: value };\n}\n\nfunction formatErrorDetails(error: unknown): string {\n\tif (error instanceof Error) {\n\t\tconst details: string[] = [`${error.name}: ${error.message}`];\n\t\tconst errorWithCode = error as Error & { code?: string; errno?: number | string; cause?: unknown };\n\t\tif (errorWithCode.code) details.push(`code=${errorWithCode.code}`);\n\t\tif (typeof errorWithCode.errno !== \"undefined\") details.push(`errno=${String(errorWithCode.errno)}`);\n\t\tif (typeof error.cause !== \"undefined\") {\n\t\t\tdetails.push(`cause=${formatErrorDetails(error.cause)}`);\n\t\t}\n\t\tif (error.stack) {\n\t\t\tdetails.push(`stack=${error.stack}`);\n\t\t}\n\t\treturn details.join(\"; \");\n\t}\n\treturn String(error);\n}\n\nasync function postJson(url: string, body: Record<string, string | number>, signal: AbortSignal): Promise<string> {\n\tconst response = await fetch(url, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.any([signal, AbortSignal.timeout(30_000)]),\n\t});\n\n\tconst responseBody = await response.text();\n\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);\n\t}\n\n\treturn responseBody;\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tstate: string,\n\tverifier: string,\n\tredirectUri: string,\n\tsignal: AbortSignal,\n): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(\n\t\t\tTOKEN_URL,\n\t\t\t{\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tcode,\n\t\t\t\tstate,\n\t\t\t\tredirect_uri: redirectUri,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\tlet tokenData: { access_token: string; refresh_token: string; expires_in: number };\n\ttry {\n\t\ttokenData = JSON.parse(responseBody) as { access_token: string; refresh_token: string; expires_in: number };\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nasync function loginAnthropic(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst callback = await startOAuthCallbackServer({\n\t\tproviderName: \"Anthropic\",\n\t\thost: CALLBACK_HOST,\n\t\tport: CALLBACK_PORT,\n\t\tpath: CALLBACK_PATH,\n\t\tstate: verifier,\n\t\tcomplete: async (code) => code,\n\t\tsignal: interaction.signal,\n\t}).catch(() => undefined);\n\n\ttry {\n\t\tconst authParams = new URLSearchParams({\n\t\t\tcode: \"true\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tscope: SCOPES,\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t});\n\t\tinteraction.notify({\n\t\t\ttype: \"auth_url\",\n\t\t\turl: `${AUTHORIZE_URL}?${authParams.toString()}`,\n\t\t\tinstructions:\n\t\t\t\t\"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.\",\n\t\t});\n\n\t\tconst result = await waitForCallbackOrManualInput(interaction, callback, {\n\t\t\tmessage: \"Complete login in your browser, or paste the authorization code / redirect URL here:\",\n\t\t\tplaceholder: REDIRECT_URI,\n\t\t});\n\t\tlet code: string | undefined;\n\t\tlet state = verifier;\n\t\tif (result.type === \"callback\") {\n\t\t\tcode = result.value;\n\t\t} else {\n\t\t\tconst parsed = parseAuthorizationInput(result.input);\n\t\t\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\t\t\tcode = parsed.code;\n\t\t\tstate = parsed.state ?? verifier;\n\t\t}\n\n\t\tif (!code) throw new Error(\"Missing authorization code\");\n\t\tinteraction.notify({ type: \"progress\", message: \"Exchanging authorization code for tokens...\" });\n\t\treturn await exchangeAuthorizationCode(code, state, verifier, REDIRECT_URI, interaction.signal);\n\t} finally {\n\t\tcallback?.close();\n\t}\n}\n\nasync function loginAnthropicCopyCode(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: COPY_CODE_REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\tinteraction.notify({\n\t\ttype: \"auth_url\",\n\t\turl: `${AUTHORIZE_URL}?${authParams.toString()}`,\n\t\tinstructions: \"Complete login in your browser, then copy the code Anthropic shows and paste it here.\",\n\t});\n\n\tconst input = await interaction.prompt({\n\t\ttype: \"manual_code\",\n\t\tmessage: \"Paste the code Anthropic shows after you sign in:\",\n\t\tplaceholder: \"code#state\",\n\t\tsignal: interaction.signal,\n\t});\n\tconst parsed = parseAuthorizationInput(input);\n\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\tif (!parsed.code) throw new Error(\"Missing authorization code\");\n\tinteraction.notify({ type: \"progress\", message: \"Exchanging authorization code for tokens...\" });\n\treturn await exchangeAuthorizationCode(\n\t\tparsed.code,\n\t\tparsed.state ?? verifier,\n\t\tverifier,\n\t\tCOPY_CODE_REDIRECT_URI,\n\t\tinteraction.signal,\n\t);\n}\n\n/**\n * Refresh Anthropic OAuth token\n */\nasync function refreshAnthropicToken(refreshToken: string, signal: AbortSignal): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(\n\t\t\tTOKEN_URL,\n\t\t\t{\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);\n\t}\n\n\tlet data: { access_token: string; refresh_token: string; expires_in: number; scope?: string };\n\ttry {\n\t\tdata = JSON.parse(responseBody) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t\tscope?: string;\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: data.refresh_token,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nexport const anthropicOAuth: OAuthAuth = {\n\tname: \"Anthropic (Claude Pro/Max)\",\n\tisSubscription: true,\n\n\tasync login(interaction) {\n\t\tconst method = await interaction.prompt({\n\t\t\ttype: \"select\",\n\t\t\tmessage: \"Select Anthropic login method:\",\n\t\t\toptions: [\n\t\t\t\t{ id: ANTHROPIC_BROWSER_LOGIN_METHOD, label: \"Browser login (default)\" },\n\t\t\t\t{ id: ANTHROPIC_COPY_CODE_LOGIN_METHOD, label: \"Copy code login (headless)\" },\n\t\t\t],\n\t\t});\n\n\t\tif (method === ANTHROPIC_COPY_CODE_LOGIN_METHOD) {\n\t\t\treturn loginAnthropicCopyCode(interaction);\n\t\t}\n\t\tif (method !== ANTHROPIC_BROWSER_LOGIN_METHOD) {\n\t\t\tthrow new Error(`Unknown Anthropic login method: ${method}`);\n\t\t}\n\n\t\treturn loginAnthropic(interaction);\n\t},\n\n\trefresh: (credential, signal) => refreshAnthropicToken(credential.refresh, signal),\n\n\tasync toAuth(credential) {\n\t\treturn { apiKey: credential.access };\n\t},\n};\n"]}
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/auth/oauth/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Anthropic OAuth flow (Claude Pro/Max)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callback server.\n * It is only intended for CLI use, not browser environments.\n */\n\nimport { getProviderEnvValue } from \"../../utils/provider-env.ts\";\nimport type { OAuthAuth, OAuthCredential, ProviderAuthInteraction } from \"../types.ts\";\nimport { startOAuthCallbackServer, waitForCallbackOrManualInput } from \"./callback-server.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl\");\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://platform.claude.com/v1/oauth/token\";\nconst CALLBACK_HOST = getProviderEnvValue(\"PI_OAUTH_CALLBACK_HOST\") || \"127.0.0.1\";\n// Preferred so the port can be forwarded into containers or over SSH. Anthropic accepts any loopback port,\n// so login falls back to a free port when this one cannot be bound (#10571).\nconst CALLBACK_PORT = 53692;\nconst CALLBACK_PATH = \"/callback\";\nconst REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;\nconst COPY_CODE_REDIRECT_URI = \"https://platform.claude.com/oauth/code/callback\";\nconst ANTHROPIC_BROWSER_LOGIN_METHOD = \"browser\";\nconst ANTHROPIC_COPY_CODE_LOGIN_METHOD = \"copy_code\";\nconst SCOPES =\n\t\"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload\";\n\nfunction parseAuthorizationInput(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// not a URL\n\t}\n\n\tif (value.includes(\"#\")) {\n\t\tconst [code, state] = value.split(\"#\", 2);\n\t\treturn { code, state };\n\t}\n\n\tif (value.includes(\"code=\")) {\n\t\tconst params = new URLSearchParams(value);\n\t\treturn {\n\t\t\tcode: params.get(\"code\") ?? undefined,\n\t\t\tstate: params.get(\"state\") ?? undefined,\n\t\t};\n\t}\n\n\treturn { code: value };\n}\n\nfunction formatErrorDetails(error: unknown): string {\n\tif (error instanceof Error) {\n\t\tconst details: string[] = [`${error.name}: ${error.message}`];\n\t\tconst errorWithCode = error as Error & { code?: string; errno?: number | string; cause?: unknown };\n\t\tif (errorWithCode.code) details.push(`code=${errorWithCode.code}`);\n\t\tif (typeof errorWithCode.errno !== \"undefined\") details.push(`errno=${String(errorWithCode.errno)}`);\n\t\tif (typeof error.cause !== \"undefined\") {\n\t\t\tdetails.push(`cause=${formatErrorDetails(error.cause)}`);\n\t\t}\n\t\tif (error.stack) {\n\t\t\tdetails.push(`stack=${error.stack}`);\n\t\t}\n\t\treturn details.join(\"; \");\n\t}\n\treturn String(error);\n}\n\nasync function postJson(url: string, body: Record<string, string | number>, signal: AbortSignal): Promise<string> {\n\tconst response = await fetch(url, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.any([signal, AbortSignal.timeout(30_000)]),\n\t});\n\n\tconst responseBody = await response.text();\n\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);\n\t}\n\n\treturn responseBody;\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tstate: string,\n\tverifier: string,\n\tredirectUri: string,\n\tsignal: AbortSignal,\n): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(\n\t\t\tTOKEN_URL,\n\t\t\t{\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tcode,\n\t\t\t\tstate,\n\t\t\t\tredirect_uri: redirectUri,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\tlet tokenData: { access_token: string; refresh_token: string; expires_in: number };\n\ttry {\n\t\ttokenData = JSON.parse(responseBody) as { access_token: string; refresh_token: string; expires_in: number };\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nasync function loginAnthropic(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst startCallbackServer = (port: number) =>\n\t\tstartOAuthCallbackServer({\n\t\t\tproviderName: \"Anthropic\",\n\t\t\thost: CALLBACK_HOST,\n\t\t\tport,\n\t\t\tpath: CALLBACK_PATH,\n\t\t\tredirectHost: \"localhost\",\n\t\t\tstate: verifier,\n\t\t\tcomplete: async (code) => code,\n\t\t\tsignal: interaction.signal,\n\t\t});\n\t// Without a callback server, login continues with the pasted redirect URL.\n\tconst callback = await startCallbackServer(CALLBACK_PORT)\n\t\t.catch(() => startCallbackServer(0))\n\t\t.catch(() => undefined);\n\tconst redirectUri = callback?.redirectUri ?? REDIRECT_URI;\n\n\ttry {\n\t\tconst authParams = new URLSearchParams({\n\t\t\tcode: \"true\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tresponse_type: \"code\",\n\t\t\tredirect_uri: redirectUri,\n\t\t\tscope: SCOPES,\n\t\t\tcode_challenge: challenge,\n\t\t\tcode_challenge_method: \"S256\",\n\t\t\tstate: verifier,\n\t\t});\n\t\tinteraction.notify({\n\t\t\ttype: \"auth_url\",\n\t\t\turl: `${AUTHORIZE_URL}?${authParams.toString()}`,\n\t\t\tinstructions:\n\t\t\t\t\"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.\",\n\t\t});\n\n\t\tconst result = await waitForCallbackOrManualInput(interaction, callback, {\n\t\t\tmessage: \"Complete login in your browser, or paste the authorization code / redirect URL here:\",\n\t\t\tplaceholder: redirectUri,\n\t\t});\n\t\tlet code: string | undefined;\n\t\tlet state = verifier;\n\t\tif (result.type === \"callback\") {\n\t\t\tcode = result.value;\n\t\t} else {\n\t\t\tconst parsed = parseAuthorizationInput(result.input);\n\t\t\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\t\t\tcode = parsed.code;\n\t\t\tstate = parsed.state ?? verifier;\n\t\t}\n\n\t\tif (!code) throw new Error(\"Missing authorization code\");\n\t\tinteraction.notify({ type: \"progress\", message: \"Exchanging authorization code for tokens...\" });\n\t\treturn await exchangeAuthorizationCode(code, state, verifier, redirectUri, interaction.signal);\n\t} finally {\n\t\tcallback?.close();\n\t}\n}\n\nasync function loginAnthropicCopyCode(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: COPY_CODE_REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\tinteraction.notify({\n\t\ttype: \"auth_url\",\n\t\turl: `${AUTHORIZE_URL}?${authParams.toString()}`,\n\t\tinstructions: \"Complete login in your browser, then copy the code Anthropic shows and paste it here.\",\n\t});\n\n\tconst input = await interaction.prompt({\n\t\ttype: \"manual_code\",\n\t\tmessage: \"Paste the code Anthropic shows after you sign in:\",\n\t\tplaceholder: \"code#state\",\n\t\tsignal: interaction.signal,\n\t});\n\tconst parsed = parseAuthorizationInput(input);\n\tif (parsed.state && parsed.state !== verifier) throw new Error(\"OAuth state mismatch\");\n\tif (!parsed.code) throw new Error(\"Missing authorization code\");\n\tinteraction.notify({ type: \"progress\", message: \"Exchanging authorization code for tokens...\" });\n\treturn await exchangeAuthorizationCode(\n\t\tparsed.code,\n\t\tparsed.state ?? verifier,\n\t\tverifier,\n\t\tCOPY_CODE_REDIRECT_URI,\n\t\tinteraction.signal,\n\t);\n}\n\n/**\n * Refresh Anthropic OAuth token\n */\nasync function refreshAnthropicToken(refreshToken: string, signal: AbortSignal): Promise<OAuthCredential> {\n\tlet responseBody: string;\n\ttry {\n\t\tresponseBody = await postJson(\n\t\t\tTOKEN_URL,\n\t\t\t{\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);\n\t}\n\n\tlet data: { access_token: string; refresh_token: string; expires_in: number; scope?: string };\n\ttry {\n\t\tdata = JSON.parse(responseBody) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token: string;\n\t\t\texpires_in: number;\n\t\t\tscope?: string;\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: data.refresh_token,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nexport const anthropicOAuth: OAuthAuth = {\n\tname: \"Anthropic (Claude Pro/Max)\",\n\tisSubscription: true,\n\n\tasync login(interaction) {\n\t\tconst method = await interaction.prompt({\n\t\t\ttype: \"select\",\n\t\t\tmessage: \"Select Anthropic login method:\",\n\t\t\toptions: [\n\t\t\t\t{ id: ANTHROPIC_BROWSER_LOGIN_METHOD, label: \"Browser login (default)\" },\n\t\t\t\t{ id: ANTHROPIC_COPY_CODE_LOGIN_METHOD, label: \"Copy code login (headless)\" },\n\t\t\t],\n\t\t});\n\n\t\tif (method === ANTHROPIC_COPY_CODE_LOGIN_METHOD) {\n\t\t\treturn loginAnthropicCopyCode(interaction);\n\t\t}\n\t\tif (method !== ANTHROPIC_BROWSER_LOGIN_METHOD) {\n\t\t\tthrow new Error(`Unknown Anthropic login method: ${method}`);\n\t\t}\n\n\t\treturn loginAnthropic(interaction);\n\t},\n\n\trefresh: (credential, signal) => refreshAnthropicToken(credential.refresh, signal),\n\n\tasync toAuth(credential) {\n\t\treturn { apiKey: credential.access };\n\t},\n};\n"]}
@@ -204,7 +204,7 @@ async function loginOpenAIChatGPT(interaction, options) {
204
204
  const authorizationUrl = new URL(AUTHORIZE_URL);
205
205
  authorizationUrl.search = new URLSearchParams({
206
206
  client_id: DYNAMIC_CLIENT_ID,
207
- agent_name_hint: AGENT_NAME_HINT,
207
+ agent_name_hint: options?.agentName ?? AGENT_NAME_HINT,
208
208
  ext_agent_host_id: hostId,
209
209
  response_type: "code",
210
210
  redirect_uri: REDIRECT_URI,