@bastani/pi-ai 0.9.40 → 0.9.41-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +42 -2
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +7 -10
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/azure-openai-responses.js +2 -2
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +12 -0
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/classifier-shared.d.ts.map +1 -1
- package/dist/api/classifier-shared.js +4 -3
- package/dist/api/classifier-shared.js.map +1 -1
- package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -1
- package/dist/api/cloudflare-workers-ai-system-one.js +8 -0
- package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -1
- package/dist/api/google-generative-ai.js +4 -4
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-vertex.d.ts.map +1 -1
- package/dist/api/google-vertex.js +9 -9
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/llama-cpp-classify.d.ts.map +1 -1
- package/dist/api/llama-cpp-classify.js +2 -1
- package/dist/api/llama-cpp-classify.js.map +1 -1
- package/dist/api/mistral-conversations.js +2 -2
- package/dist/api/mistral-conversations.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +12 -9
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +4 -4
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +15 -7
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/openrouter-images.d.ts.map +1 -1
- package/dist/api/openrouter-images.js +4 -3
- package/dist/api/openrouter-images.js.map +1 -1
- package/dist/api/pi-messages.d.ts.map +1 -1
- package/dist/api/pi-messages.js +3 -3
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js +1 -0
- package/dist/api/simple-options.js.map +1 -1
- package/dist/api/system-one-shared.d.ts +20 -4
- package/dist/api/system-one-shared.d.ts.map +1 -1
- package/dist/api/system-one-shared.js +49 -19
- package/dist/api/system-one-shared.js.map +1 -1
- package/dist/api/typesafe-system-one.d.ts.map +1 -1
- package/dist/api/typesafe-system-one.js +10 -3
- package/dist/api/typesafe-system-one.js.map +1 -1
- package/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/dist/auth/oauth/anthropic.js +15 -2
- package/dist/auth/oauth/anthropic.js.map +1 -1
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/dist/auth/oauth/github-copilot.js +7 -2
- package/dist/auth/oauth/github-copilot.js.map +1 -1
- package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
- package/dist/auth/oauth/kimi-coding.js +24 -21
- package/dist/auth/oauth/kimi-coding.js.map +1 -1
- package/dist/auth/oauth/meta.d.ts.map +1 -1
- package/dist/auth/oauth/meta.js +7 -2
- package/dist/auth/oauth/meta.js.map +1 -1
- package/dist/auth/oauth/openai-chatgpt.d.ts.map +1 -1
- package/dist/auth/oauth/openai-chatgpt.js +10 -0
- package/dist/auth/oauth/openai-chatgpt.js.map +1 -1
- package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
- package/dist/auth/oauth/openai-codex.js +11 -2
- package/dist/auth/oauth/openai-codex.js.map +1 -1
- package/dist/auth/oauth/radius.d.ts.map +1 -1
- package/dist/auth/oauth/radius.js +24 -8
- package/dist/auth/oauth/radius.js.map +1 -1
- package/dist/auth/oauth/xai.d.ts.map +1 -1
- package/dist/auth/oauth/xai.js +11 -0
- package/dist/auth/oauth/xai.js.map +1 -1
- package/dist/auth/oauth-refresh-errors.d.ts +46 -0
- package/dist/auth/oauth-refresh-errors.d.ts.map +1 -0
- package/dist/auth/oauth-refresh-errors.js +150 -0
- package/dist/auth/oauth-refresh-errors.js.map +1 -0
- package/dist/auth/resolve.d.ts +23 -0
- package/dist/auth/resolve.d.ts.map +1 -1
- package/dist/auth/resolve.js +194 -5
- package/dist/auth/resolve.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +4 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +15 -4
- package/dist/models.js.map +1 -1
- package/dist/providers/compat-schema.d.ts +18 -0
- package/dist/providers/compat-schema.d.ts.map +1 -1
- package/dist/providers/compat-schema.js +10 -0
- package/dist/providers/compat-schema.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/baseten.json +1 -1
- package/dist/providers/data/cerebras.json +1 -1
- package/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/providers/data/github-copilot.json +1 -1
- package/dist/providers/data/groq.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/radius.json +1 -1
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/typesafe.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/identity.d.ts +65 -0
- package/dist/utils/identity.d.ts.map +1 -0
- package/dist/utils/identity.js +142 -0
- package/dist/utils/identity.js.map +1 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +9 -8
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +3 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +2 -2
- package/dist/utils/pi-user-agent.d.ts +0 -2
- package/dist/utils/pi-user-agent.d.ts.map +0 -1
- package/dist/utils/pi-user-agent.js +0 -12
- package/dist/utils/pi-user-agent.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClassifierApi, ClassifierContext, ClassifierModel, ClassifierOptions, ClassifierResult } from "../types.ts";
|
|
1
|
+
import type { ClassifierApi, ClassifierContext, ClassifierModel, ClassifierOptions, ClassifierResult, SystemOneCompat } from "../types.ts";
|
|
2
2
|
/** Base64 image in the System One request's top-level `assets` map. */
|
|
3
3
|
export interface SystemOneImageAsset {
|
|
4
4
|
type: "image";
|
|
@@ -7,25 +7,41 @@ export interface SystemOneImageAsset {
|
|
|
7
7
|
}
|
|
8
8
|
/** TypeSafe System One request body without the transport-specific envelope. */
|
|
9
9
|
export interface SystemOneWireRequest {
|
|
10
|
-
state
|
|
10
|
+
/** The context state, or with images in OpenRouter's form a list of image parts followed by the state. */
|
|
11
|
+
state: unknown;
|
|
11
12
|
questions: Record<string, unknown>;
|
|
12
13
|
/** Images keyed by the references listed in `state.images`. */
|
|
13
14
|
assets?: Record<string, SystemOneImageAsset>;
|
|
15
|
+
/** Images as data URLs, Clef's extension to System One. */
|
|
16
|
+
images?: string[];
|
|
14
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* How a service takes images (`SystemOneCompat.imageInput`):
|
|
20
|
+
* - `assets`: TypeSafe's `assets` map, keyed by a fresh UUID per image and listed in order under
|
|
21
|
+
* `state.images`. PNG and JPEG only.
|
|
22
|
+
* - `images`: Clef's `images` list of data URLs, which the model reads before the state.
|
|
23
|
+
* PNG, JPEG, and WebP, at most four.
|
|
24
|
+
* - `state-image-urls`: OpenRouter's form, data URL parts placed before the state:
|
|
25
|
+
* `state: [{ type: "image_url", image_url: { url } }, state]`. OpenRouter accepts this for every
|
|
26
|
+
* model and drops `assets` silently.
|
|
27
|
+
*/
|
|
28
|
+
export type SystemOneImageInput = NonNullable<SystemOneCompat["imageInput"]>;
|
|
29
|
+
/** System One compatibility settings with every field resolved. */
|
|
30
|
+
export type ResolvedSystemOneCompat = Required<SystemOneCompat>;
|
|
15
31
|
/** Differences between services that serve System One models. */
|
|
16
32
|
export interface SystemOneTransport {
|
|
17
33
|
/** Classifier API implemented by this transport. */
|
|
18
34
|
api: ClassifierApi;
|
|
19
35
|
/** Service name used in error messages. */
|
|
20
36
|
label: string;
|
|
37
|
+
/** Compatibility settings for models that do not set them in `compat`. */
|
|
38
|
+
detectCompat(model: ClassifierModel<ClassifierApi>): ResolvedSystemOneCompat;
|
|
21
39
|
/** Absolute request URL. */
|
|
22
40
|
url(model: ClassifierModel<ClassifierApi>): URL;
|
|
23
41
|
/** Wraps the System One request in the service's request envelope. */
|
|
24
42
|
payload(model: ClassifierModel<ClassifierApi>, request: SystemOneWireRequest): unknown;
|
|
25
43
|
/** Extracts the System One output, including answers and usage. */
|
|
26
44
|
output(body: unknown): Record<string, unknown>;
|
|
27
|
-
/** Whether the service accepts `context.images` as `assets` for this model. Images are rejected when omitted. */
|
|
28
|
-
acceptsImages?(model: ClassifierModel<ClassifierApi>): boolean;
|
|
29
45
|
}
|
|
30
46
|
/** Runs one System One classification over the given transport. */
|
|
31
47
|
export declare function classifySystemOne(transport: SystemOneTransport, model: ClassifierModel<ClassifierApi>, context: ClassifierContext, options: ClassifierOptions | undefined): Promise<ClassifierResult>;
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,EAEhB,eAAe,EACf,MAAM,aAAa,CAAC;AAIrB,uEAAuE;AACvE,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,KAAK,GAAG,KAAK,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACpB;AAQD,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACpC,0GAA0G;IAC1G,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC7C,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC;AAE7E,mEAAmE;AACnE,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;AAKhE,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IAClC,oDAAoD;IACpD,GAAG,EAAE,aAAa,CAAC;IACnB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,GAAG,uBAAuB,CAAC;IAC7E,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,mEAAmE;IACnE,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAkJD,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,CA0C3B"}
|
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
|
|
2
2
|
import { isRecord, parseClassifierUsage, postClassifierRequest, requiredNumber } from "./classifier-shared.js";
|
|
3
|
+
/** TypeSafe's opt-in header for its experimental image input. */
|
|
4
|
+
const IMAGE_OPT_IN_HEADERS = { "TypeSafe-Danger-Zone-Images": "true" };
|
|
3
5
|
const IMAGES_STATE_FIELD = "images";
|
|
4
6
|
const IMAGE_FORMATS = new Map([
|
|
5
7
|
["image/png", "png"],
|
|
6
8
|
["image/jpeg", "jpg"],
|
|
7
9
|
["image/jpg", "jpg"],
|
|
8
10
|
]);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const DATA_URL_TYPES = new Set(["image/png", "image/jpeg", "image/webp"]);
|
|
12
|
+
/** Clef accepts at most this many images per request. */
|
|
13
|
+
const MAX_DATA_URL_IMAGES = 4;
|
|
14
|
+
function base64Data(image) {
|
|
15
|
+
return image.data.replace(/^data:[^,]*,/u, "");
|
|
16
|
+
}
|
|
17
|
+
/** Adds the context images to `request` in the service's form. */
|
|
18
|
+
function addImages(label, model, input, images, request) {
|
|
19
|
+
if (input === "images") {
|
|
20
|
+
if (images.length > MAX_DATA_URL_IMAGES) {
|
|
21
|
+
throw new Error(`${label} accepts at most ${MAX_DATA_URL_IMAGES} images for ${model.id}, got ${images.length}`);
|
|
22
|
+
}
|
|
23
|
+
request.images = images.map((image) => {
|
|
24
|
+
if (!DATA_URL_TYPES.has(image.mimeType)) {
|
|
25
|
+
throw new Error(`${label} accepts PNG, JPEG, and WebP images for ${model.id}, got ${image.mimeType}`);
|
|
26
|
+
}
|
|
27
|
+
return `data:${image.mimeType};base64,${base64Data(image)}`;
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (input === "state-image-urls") {
|
|
32
|
+
const parts = images.map((image) => ({
|
|
33
|
+
type: "image_url",
|
|
34
|
+
image_url: { url: `data:${image.mimeType};base64,${base64Data(image)}` },
|
|
35
|
+
}));
|
|
36
|
+
request.state = [...parts, request.state];
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const state = request.state;
|
|
40
|
+
if (Object.hasOwn(state, IMAGES_STATE_FIELD)) {
|
|
15
41
|
throw new Error(`${label} lists images in state.${IMAGES_STATE_FIELD}; rename that state field to send images`);
|
|
16
42
|
}
|
|
17
43
|
const assets = {};
|
|
@@ -19,13 +45,10 @@ function imageAssets(label, context) {
|
|
|
19
45
|
const format = IMAGE_FORMATS.get(image.mimeType.toLowerCase());
|
|
20
46
|
if (!format)
|
|
21
47
|
throw new Error(`${label} accepts only PNG or JPEG images, got ${image.mimeType}`);
|
|
22
|
-
assets[crypto.randomUUID()] = {
|
|
23
|
-
type: "image",
|
|
24
|
-
format,
|
|
25
|
-
data_base64: image.data.replace(/^data:[^,]*,/u, ""),
|
|
26
|
-
};
|
|
48
|
+
assets[crypto.randomUUID()] = { type: "image", format, data_base64: base64Data(image) };
|
|
27
49
|
}
|
|
28
|
-
|
|
50
|
+
request.state = { ...state, [IMAGES_STATE_FIELD]: Object.keys(assets) };
|
|
51
|
+
request.assets = assets;
|
|
29
52
|
}
|
|
30
53
|
function unitInterval(label, value, field) {
|
|
31
54
|
const number = requiredNumber(label, value, field);
|
|
@@ -100,15 +123,13 @@ function parseAnswers(label, value, context) {
|
|
|
100
123
|
return Object.fromEntries(answers);
|
|
101
124
|
}
|
|
102
125
|
/** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */
|
|
103
|
-
function wireRequest(
|
|
104
|
-
const { state, assets } = imageAssets(label, context);
|
|
126
|
+
function wireRequest(context) {
|
|
105
127
|
return {
|
|
106
|
-
state,
|
|
128
|
+
state: context.state,
|
|
107
129
|
questions: Object.fromEntries(Object.entries(context.questions).map(([id, question]) => [
|
|
108
130
|
id,
|
|
109
131
|
question.type === "bool" ? { ...question, type: "noul" } : question,
|
|
110
132
|
])),
|
|
111
|
-
...(assets ? { assets } : {}),
|
|
112
133
|
};
|
|
113
134
|
}
|
|
114
135
|
/** Runs one System One classification over the given transport. */
|
|
@@ -124,10 +145,19 @@ export async function classifySystemOne(transport, model, context, options) {
|
|
|
124
145
|
try {
|
|
125
146
|
if (model.api !== transport.api)
|
|
126
147
|
throw new Error(`Unsupported classifier API: ${model.api}`);
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
const request = wireRequest(context);
|
|
149
|
+
let requestOptions = options;
|
|
150
|
+
if (context.images?.length) {
|
|
151
|
+
if (!model.input.includes("image")) {
|
|
152
|
+
throw new Error(`${transport.label} does not support image input for ${model.id}`);
|
|
153
|
+
}
|
|
154
|
+
const detected = transport.detectCompat(model);
|
|
155
|
+
addImages(transport.label, model, model.compat?.imageInput ?? detected.imageInput, context.images, request);
|
|
156
|
+
if (model.compat?.sendImageOptInHeader ?? detected.sendImageOptInHeader) {
|
|
157
|
+
requestOptions = { ...options, headers: { ...IMAGE_OPT_IN_HEADERS, ...options?.headers } };
|
|
158
|
+
}
|
|
129
159
|
}
|
|
130
|
-
const body = await postClassifierRequest(transport.label, transport.url(model), model, transport.payload(model,
|
|
160
|
+
const body = await postClassifierRequest(transport.label, transport.url(model), model, transport.payload(model, request), requestOptions);
|
|
131
161
|
const result = transport.output(body);
|
|
132
162
|
// Malformed answers may still be billed, so retain usage before parsing them.
|
|
133
163
|
const usage = parseClassifierUsage(result.usage, model);
|
|
@@ -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 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/** Base64 image in the System One request's top-level `assets` map. */\nexport interface SystemOneImageAsset {\n\ttype: \"image\";\n\tformat: \"png\" | \"jpg\";\n\tdata_base64: string;\n}\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\t/** Images keyed by the references listed in `state.images`. */\n\tassets?: Record<string, SystemOneImageAsset>;\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, including answers and usage. */\n\toutput(body: unknown): Record<string, unknown>;\n\t/** Whether the service accepts `context.images` as `assets` for this model. Images are rejected when omitted. */\n\tacceptsImages?(model: ClassifierModel<ClassifierApi>): boolean;\n}\n\nconst IMAGES_STATE_FIELD = \"images\";\nconst IMAGE_FORMATS = new Map<string, SystemOneImageAsset[\"format\"]>([\n\t[\"image/png\", \"png\"],\n\t[\"image/jpeg\", \"jpg\"],\n\t[\"image/jpg\", \"jpg\"],\n]);\n\n/** Lists each image once in `assets` under a fresh UUID and writes the UUIDs into `state.images`, in order. */\nfunction imageAssets(label: string, context: ClassifierContext): Pick<SystemOneWireRequest, \"state\" | \"assets\"> {\n\tconst images = context.images ?? [];\n\tif (images.length === 0) return { state: context.state };\n\tif (Object.hasOwn(context.state, IMAGES_STATE_FIELD)) {\n\t\tthrow new Error(`${label} lists images in state.${IMAGES_STATE_FIELD}; rename that state field to send images`);\n\t}\n\tconst assets: Record<string, SystemOneImageAsset> = {};\n\tfor (const image of images) {\n\t\tconst format = IMAGE_FORMATS.get(image.mimeType.toLowerCase());\n\t\tif (!format) throw new Error(`${label} accepts only PNG or JPEG images, got ${image.mimeType}`);\n\t\tassets[crypto.randomUUID()] = {\n\t\t\ttype: \"image\",\n\t\t\tformat,\n\t\t\tdata_base64: image.data.replace(/^data:[^,]*,/u, \"\"),\n\t\t};\n\t}\n\treturn { state: { ...context.state, [IMAGES_STATE_FIELD]: Object.keys(assets) }, assets };\n}\n\n\nfunction unitInterval(label: string, value: unknown, field: string): number {\n\tconst number = requiredNumber(label, value, field);\n\tif (number < 0 || number > 1) throw new Error(`${label} returned an invalid ${field}`);\n\treturn number;\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\tunitInterval(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 (\n\t\t\t\tanswer.type !== \"choice\" ||\n\t\t\t\ttypeof answer.choice !== \"string\" ||\n\t\t\t\t!Object.hasOwn(question.criteria, answer.choice)\n\t\t\t) {\n\t\t\t\tthrow new Error(`${label} did not return a valid choice answer for ${id}`);\n\t\t\t}\n\t\t\tconst choiceProbabilities = probabilities(label, answer.probabilities, id);\n\t\t\tif (Object.keys(choiceProbabilities).some((key) => !Object.hasOwn(question.criteria, key))) {\n\t\t\t\tthrow new Error(`${label} returned an unknown choice probability 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: choiceProbabilities,\n\t\t\t\t\tconfidence: unitInterval(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\tconst score = requiredNumber(label, answer.score, `score for ${id}`);\n\t\t\tif (score < 0 || score > question.criteria.length - 1) {\n\t\t\t\tthrow new Error(`${label} returned an invalid score 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: \"score\",\n\t\t\t\t\tscore,\n\t\t\t\t\tconfidence: unitInterval(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: unitInterval(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\n/** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */\nfunction wireRequest(label: string, context: ClassifierContext): SystemOneWireRequest {\n\tconst { state, assets } = imageAssets(label, context);\n\treturn {\n\t\tstate,\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\t...(assets ? { assets } : {}),\n\t};\n}\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 && !transport.acceptsImages?.(model)) {\n\t\t\tthrow new Error(`${transport.label} does not support image input`);\n\t\t}\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(transport.label, context)),\n\t\t\toptions,\n\t\t);\n\t\tconst result = transport.output(body);\n\t\t// Malformed answers may still be billed, so retain usage before parsing them.\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
|
+
{"version":3,"file":"system-one-shared.js","sourceRoot":"","sources":["../../src/api/system-one-shared.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tClassifierAnswer,\n\tClassifierApi,\n\tClassifierContext,\n\tClassifierModel,\n\tClassifierOptions,\n\tClassifierResult,\n\tImageContent,\n\tSystemOneCompat,\n} from \"../types.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { isRecord, parseClassifierUsage, postClassifierRequest, requiredNumber } from \"./classifier-shared.ts\";\n\n/** Base64 image in the System One request's top-level `assets` map. */\nexport interface SystemOneImageAsset {\n\ttype: \"image\";\n\tformat: \"png\" | \"jpg\";\n\tdata_base64: string;\n}\n\n/** An image in the state, OpenRouter's form. */\ninterface SystemOneImageUrlPart {\n\ttype: \"image_url\";\n\timage_url: { url: string };\n}\n\n/** TypeSafe System One request body without the transport-specific envelope. */\nexport interface SystemOneWireRequest {\n\t/** The context state, or with images in OpenRouter's form a list of image parts followed by the state. */\n\tstate: unknown;\n\tquestions: Record<string, unknown>;\n\t/** Images keyed by the references listed in `state.images`. */\n\tassets?: Record<string, SystemOneImageAsset>;\n\t/** Images as data URLs, Clef's extension to System One. */\n\timages?: string[];\n}\n\n/**\n * How a service takes images (`SystemOneCompat.imageInput`):\n * - `assets`: TypeSafe's `assets` map, keyed by a fresh UUID per image and listed in order under\n * `state.images`. PNG and JPEG only.\n * - `images`: Clef's `images` list of data URLs, which the model reads before the state.\n * PNG, JPEG, and WebP, at most four.\n * - `state-image-urls`: OpenRouter's form, data URL parts placed before the state:\n * `state: [{ type: \"image_url\", image_url: { url } }, state]`. OpenRouter accepts this for every\n * model and drops `assets` silently.\n */\nexport type SystemOneImageInput = NonNullable<SystemOneCompat[\"imageInput\"]>;\n\n/** System One compatibility settings with every field resolved. */\nexport type ResolvedSystemOneCompat = Required<SystemOneCompat>;\n\n/** TypeSafe's opt-in header for its experimental image input. */\nconst IMAGE_OPT_IN_HEADERS = { \"TypeSafe-Danger-Zone-Images\": \"true\" };\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/** Compatibility settings for models that do not set them in `compat`. */\n\tdetectCompat(model: ClassifierModel<ClassifierApi>): ResolvedSystemOneCompat;\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, including answers and usage. */\n\toutput(body: unknown): Record<string, unknown>;\n}\n\nconst IMAGES_STATE_FIELD = \"images\";\nconst IMAGE_FORMATS = new Map<string, SystemOneImageAsset[\"format\"]>([\n\t[\"image/png\", \"png\"],\n\t[\"image/jpeg\", \"jpg\"],\n\t[\"image/jpg\", \"jpg\"],\n]);\nconst DATA_URL_TYPES = new Set([\"image/png\", \"image/jpeg\", \"image/webp\"]);\n/** Clef accepts at most this many images per request. */\nconst MAX_DATA_URL_IMAGES = 4;\n\nfunction base64Data(image: ImageContent): string {\n\treturn image.data.replace(/^data:[^,]*,/u, \"\");\n}\n\n/** Adds the context images to `request` in the service's form. */\nfunction addImages(\n\tlabel: string,\n\tmodel: ClassifierModel<ClassifierApi>,\n\tinput: SystemOneImageInput,\n\timages: ImageContent[],\n\trequest: SystemOneWireRequest,\n): void {\n\tif (input === \"images\") {\n\t\tif (images.length > MAX_DATA_URL_IMAGES) {\n\t\t\tthrow new Error(\n\t\t\t\t`${label} accepts at most ${MAX_DATA_URL_IMAGES} images for ${model.id}, got ${images.length}`,\n\t\t\t);\n\t\t}\n\t\trequest.images = images.map((image) => {\n\t\t\tif (!DATA_URL_TYPES.has(image.mimeType)) {\n\t\t\t\tthrow new Error(`${label} accepts PNG, JPEG, and WebP images for ${model.id}, got ${image.mimeType}`);\n\t\t\t}\n\t\t\treturn `data:${image.mimeType};base64,${base64Data(image)}`;\n\t\t});\n\t\treturn;\n\t}\n\tif (input === \"state-image-urls\") {\n\t\tconst parts = images.map(\n\t\t\t(image): SystemOneImageUrlPart => ({\n\t\t\t\ttype: \"image_url\",\n\t\t\t\timage_url: { url: `data:${image.mimeType};base64,${base64Data(image)}` },\n\t\t\t}),\n\t\t);\n\t\trequest.state = [...parts, request.state];\n\t\treturn;\n\t}\n\tconst state = request.state as ClassifierContext[\"state\"];\n\tif (Object.hasOwn(state, IMAGES_STATE_FIELD)) {\n\t\tthrow new Error(`${label} lists images in state.${IMAGES_STATE_FIELD}; rename that state field to send images`);\n\t}\n\tconst assets: Record<string, SystemOneImageAsset> = {};\n\tfor (const image of images) {\n\t\tconst format = IMAGE_FORMATS.get(image.mimeType.toLowerCase());\n\t\tif (!format) throw new Error(`${label} accepts only PNG or JPEG images, got ${image.mimeType}`);\n\t\tassets[crypto.randomUUID()] = { type: \"image\", format, data_base64: base64Data(image) };\n\t}\n\trequest.state = { ...state, [IMAGES_STATE_FIELD]: Object.keys(assets) };\n\trequest.assets = assets;\n}\n\nfunction unitInterval(label: string, value: unknown, field: string): number {\n\tconst number = requiredNumber(label, value, field);\n\tif (number < 0 || number > 1) throw new Error(`${label} returned an invalid ${field}`);\n\treturn number;\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\tunitInterval(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 (\n\t\t\t\tanswer.type !== \"choice\" ||\n\t\t\t\ttypeof answer.choice !== \"string\" ||\n\t\t\t\t!Object.hasOwn(question.criteria, answer.choice)\n\t\t\t) {\n\t\t\t\tthrow new Error(`${label} did not return a valid choice answer for ${id}`);\n\t\t\t}\n\t\t\tconst choiceProbabilities = probabilities(label, answer.probabilities, id);\n\t\t\tif (Object.keys(choiceProbabilities).some((key) => !Object.hasOwn(question.criteria, key))) {\n\t\t\t\tthrow new Error(`${label} returned an unknown choice probability 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: choiceProbabilities,\n\t\t\t\t\tconfidence: unitInterval(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\tconst score = requiredNumber(label, answer.score, `score for ${id}`);\n\t\t\tif (score < 0 || score > question.criteria.length - 1) {\n\t\t\t\tthrow new Error(`${label} returned an invalid score 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: \"score\",\n\t\t\t\t\tscore,\n\t\t\t\t\tconfidence: unitInterval(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: unitInterval(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\tconst request = wireRequest(context);\n\t\tlet requestOptions = options;\n\t\tif (context.images?.length) {\n\t\t\tif (!model.input.includes(\"image\")) {\n\t\t\t\tthrow new Error(`${transport.label} does not support image input for ${model.id}`);\n\t\t\t}\n\t\t\tconst detected = transport.detectCompat(model);\n\t\t\taddImages(transport.label, model, model.compat?.imageInput ?? detected.imageInput, context.images, request);\n\t\t\tif (model.compat?.sendImageOptInHeader ?? detected.sendImageOptInHeader) {\n\t\t\t\trequestOptions = { ...options, headers: { ...IMAGE_OPT_IN_HEADERS, ...options?.headers } };\n\t\t\t}\n\t\t}\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, request),\n\t\t\trequestOptions,\n\t\t);\n\t\tconst result = transport.output(body);\n\t\t// Malformed answers may still be billed, so retain usage before parsing them.\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;
|
|
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;AA6BzE,gGAAgG;AAChG,eAAO,MAAM,QAAQ,EAAE,kBAAkB,CAAC,iBAAiB,CACL,CAAC"}
|
|
@@ -2,12 +2,20 @@ import { isRecord } from "./classifier-shared.js";
|
|
|
2
2
|
import { classifySystemOne } from "./system-one-shared.js";
|
|
3
3
|
/**
|
|
4
4
|
* TypeSafe's native System One protocol. OpenRouter serves the same protocol,
|
|
5
|
-
* so both providers use this API with different base URLs.
|
|
6
|
-
*
|
|
5
|
+
* so both providers use this API with different base URLs.
|
|
6
|
+
*
|
|
7
|
+
* TypeSafe takes images as referenced `assets`. They are experimental and need the
|
|
8
|
+
* `TypeSafe-Danger-Zone-Images` opt-in header; without it the API answers 400.
|
|
9
|
+
* OpenRouter drops `assets` silently and takes images as `image_url` parts in the state instead.
|
|
10
|
+
* Models behind a proxy that hides both the provider ID and the OpenRouter host must set
|
|
11
|
+
* `compat.imageInput`.
|
|
7
12
|
*/
|
|
8
13
|
const transport = {
|
|
9
14
|
api: "typesafe-system-one",
|
|
10
15
|
label: "System One API",
|
|
16
|
+
detectCompat: (model) => model.provider === "openrouter" || model.baseUrl.includes("openrouter.ai")
|
|
17
|
+
? { imageInput: "state-image-urls", sendImageOptInHeader: false }
|
|
18
|
+
: { imageInput: "assets", sendImageOptInHeader: true },
|
|
11
19
|
url: (model) => new URL("systemone", `${model.baseUrl.replace(/\/+$/u, "")}/`),
|
|
12
20
|
payload: (model, request) => ({ model: model.id, ...request }),
|
|
13
21
|
output: (body) => {
|
|
@@ -15,7 +23,6 @@ const transport = {
|
|
|
15
23
|
throw new Error("System One API returned an unexpected response");
|
|
16
24
|
return body;
|
|
17
25
|
},
|
|
18
|
-
acceptsImages: (model) => model.provider === "typesafe" && model.input.includes("image"),
|
|
19
26
|
};
|
|
20
27
|
/** TypeSafe System One classification with public `bool` values mapped to wire-level `noul`. */
|
|
21
28
|
export const classify = (model, context, options) => classifySystemOne(transport, model, context, options);
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;;GASG;AACH,MAAM,SAAS,GAAuB;IACrC,GAAG,EAAE,qBAAqB;IAC1B,KAAK,EAAE,gBAAgB;IACvB,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CACvB,KAAK,CAAC,QAAQ,KAAK,YAAY,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;QACzE,CAAC,CAAC,EAAE,UAAU,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,EAAE;QACjE,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE;IACxD,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 *\n * TypeSafe takes images as referenced `assets`. They are experimental and need the\n * `TypeSafe-Danger-Zone-Images` opt-in header; without it the API answers 400.\n * OpenRouter drops `assets` silently and takes images as `image_url` parts in the state instead.\n * Models behind a proxy that hides both the provider ID and the OpenRouter host must set\n * `compat.imageInput`.\n */\nconst transport: SystemOneTransport = {\n\tapi: \"typesafe-system-one\",\n\tlabel: \"System One API\",\n\tdetectCompat: (model) =>\n\t\tmodel.provider === \"openrouter\" || model.baseUrl.includes(\"openrouter.ai\")\n\t\t\t? { imageInput: \"state-image-urls\", sendImageOptInHeader: false }\n\t\t\t: { imageInput: \"assets\", sendImageOptInHeader: true },\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;
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,SAAS,EAA4C,MAAM,aAAa,CAAC;AAqRvF,eAAO,MAAM,cAAc,EAAE,SA8B5B,CAAC"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* It is only intended for CLI use, not browser environments.
|
|
6
6
|
*/
|
|
7
7
|
import { getProviderEnvValue } from "../../utils/provider-env.js";
|
|
8
|
+
import { isPermanentRefreshRejection, OAuthRefreshRejectedError, parseOAuthErrorBody } from "../oauth-refresh-errors.js";
|
|
8
9
|
import { startOAuthCallbackServer, waitForCallbackOrManualInput } from "./callback-server.js";
|
|
9
10
|
import { generatePKCE } from "./pkce.js";
|
|
10
11
|
const decode = (s) => atob(s);
|
|
@@ -14,6 +15,8 @@ const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
|
14
15
|
const CALLBACK_HOST = getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
|
|
15
16
|
const CALLBACK_PORT = 53692;
|
|
16
17
|
const CALLBACK_PATH = "/callback";
|
|
18
|
+
/** Login gives up on a stalled authorization-code exchange; a refresh exchange is never cut off (it may have rotated). */
|
|
19
|
+
const AUTHORIZATION_CODE_EXCHANGE_TIMEOUT_MS = 30_000;
|
|
17
20
|
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
18
21
|
const COPY_CODE_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
|
|
19
22
|
const ANTHROPIC_BROWSER_LOGIN_METHOD = "browser";
|
|
@@ -72,10 +75,18 @@ async function postJson(url, body, signal) {
|
|
|
72
75
|
Accept: "application/json",
|
|
73
76
|
},
|
|
74
77
|
body: JSON.stringify(body),
|
|
75
|
-
signal
|
|
78
|
+
signal,
|
|
76
79
|
});
|
|
77
80
|
const responseBody = await response.text();
|
|
78
81
|
if (!response.ok) {
|
|
82
|
+
if (body.grant_type === "refresh_token" && typeof body.refresh_token === "string") {
|
|
83
|
+
const rejection = { status: response.status, ...parseOAuthErrorBody(responseBody) };
|
|
84
|
+
if (isPermanentRefreshRejection(rejection)) {
|
|
85
|
+
throw new OAuthRefreshRejectedError(`Anthropic token refresh failed (HTTP ${response.status})`, rejection, [
|
|
86
|
+
body.refresh_token,
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
79
90
|
throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);
|
|
80
91
|
}
|
|
81
92
|
return responseBody;
|
|
@@ -90,7 +101,7 @@ async function exchangeAuthorizationCode(code, state, verifier, redirectUri, sig
|
|
|
90
101
|
state,
|
|
91
102
|
redirect_uri: redirectUri,
|
|
92
103
|
code_verifier: verifier,
|
|
93
|
-
}, signal);
|
|
104
|
+
}, AbortSignal.any([signal, AbortSignal.timeout(AUTHORIZATION_CODE_EXCHANGE_TIMEOUT_MS)]));
|
|
94
105
|
}
|
|
95
106
|
catch (error) {
|
|
96
107
|
throw new Error(`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`);
|
|
@@ -210,6 +221,8 @@ async function refreshAnthropicToken(refreshToken, signal) {
|
|
|
210
221
|
}, signal);
|
|
211
222
|
}
|
|
212
223
|
catch (error) {
|
|
224
|
+
if (error instanceof OAuthRefreshRejectedError)
|
|
225
|
+
throw error;
|
|
213
226
|
throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);
|
|
214
227
|
}
|
|
215
228
|
let data;
|
|
@@ -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 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\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\tusesCallbackServer: true,\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 { isPermanentRefreshRejection, OAuthRefreshRejectedError, parseOAuthErrorBody } from \"../oauth-refresh-errors.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\";\n/** Login gives up on a stalled authorization-code exchange; a refresh exchange is never cut off (it may have rotated). */\nconst AUTHORIZATION_CODE_EXCHANGE_TIMEOUT_MS = 30_000;\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,\n\t});\n\n\tconst responseBody = await response.text();\n\n\tif (!response.ok) {\n\t\tif (body.grant_type === \"refresh_token\" && typeof body.refresh_token === \"string\") {\n\t\t\tconst rejection = { status: response.status, ...parseOAuthErrorBody(responseBody) };\n\t\t\tif (isPermanentRefreshRejection(rejection)) {\n\t\t\t\tthrow new OAuthRefreshRejectedError(`Anthropic token refresh failed (HTTP ${response.status})`, rejection, [\n\t\t\t\t\tbody.refresh_token,\n\t\t\t\t]);\n\t\t\t}\n\t\t}\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\tAbortSignal.any([signal, AbortSignal.timeout(AUTHORIZATION_CODE_EXCHANGE_TIMEOUT_MS)]),\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\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\tif (error instanceof OAuthRefreshRejectedError) throw 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\tusesCallbackServer: true,\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/github-copilot.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/github-copilot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,SAAS,EAA4C,MAAM,aAAa,CAAC;AA8fvF,eAAO,MAAM,kBAAkB,EAAE,SAchC,CAAC"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { GITHUB_COPILOT_MODELS } from "../../providers/github-copilot.models.js";
|
|
5
5
|
import { sleep } from "../../utils/sleep.js";
|
|
6
|
+
import { OAuthRefreshRejectedError, parseOAuthErrorBody } from "../oauth-refresh-errors.js";
|
|
6
7
|
import { pollOAuthDeviceCodeFlow } from "./device-code.js";
|
|
7
8
|
const decode = (s) => atob(s);
|
|
8
9
|
const CLIENT_ID = decode("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
|
|
@@ -148,10 +149,14 @@ async function fetchGitHubCopilotModels(copilotToken, enterpriseDomain, signal,
|
|
|
148
149
|
}
|
|
149
150
|
return parseGitHubCopilotModelCatalog(await response.json(), allowPolicyFallback);
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
+
/** `presentedToken` marks a request authenticated by the stored login: a 401 then means that login was revoked. */
|
|
153
|
+
async function fetchJson(url, init, presentedToken) {
|
|
152
154
|
const response = await fetch(url, init);
|
|
153
155
|
if (!response.ok) {
|
|
154
156
|
const text = await response.text();
|
|
157
|
+
if (presentedToken !== undefined && response.status === 401) {
|
|
158
|
+
throw new OAuthRefreshRejectedError(`GitHub Copilot token request failed (${response.status})`, { status: response.status, ...parseOAuthErrorBody(text) }, [presentedToken]);
|
|
159
|
+
}
|
|
155
160
|
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
156
161
|
}
|
|
157
162
|
return response.json();
|
|
@@ -256,7 +261,7 @@ async function refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain, s
|
|
|
256
261
|
...COPILOT_HEADERS,
|
|
257
262
|
},
|
|
258
263
|
signal,
|
|
259
|
-
});
|
|
264
|
+
}, refreshToken);
|
|
260
265
|
if (!raw || typeof raw !== "object") {
|
|
261
266
|
throw new Error("Invalid Copilot token response");
|
|
262
267
|
}
|