mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Fix legacy Claude and Gemini auth migration
This commit is contained in:
@@ -14,6 +14,20 @@ AS $$
|
|||||||
END
|
END
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.aether_legacy_raw_api_format_auth_type(value text)
|
||||||
|
RETURNS text
|
||||||
|
LANGUAGE sql
|
||||||
|
IMMUTABLE
|
||||||
|
AS $$
|
||||||
|
SELECT CASE LOWER(BTRIM(COALESCE(value, '')))
|
||||||
|
WHEN 'claude:chat' THEN 'api_key'
|
||||||
|
WHEN 'claude:cli' THEN 'bearer'
|
||||||
|
WHEN 'gemini:chat' THEN 'api_key'
|
||||||
|
WHEN 'gemini:cli' THEN 'bearer'
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
ALTER TABLE IF EXISTS public.provider_endpoints
|
ALTER TABLE IF EXISTS public.provider_endpoints
|
||||||
DROP CONSTRAINT IF EXISTS uq_provider_api_format;
|
DROP CONSTRAINT IF EXISTS uq_provider_api_format;
|
||||||
|
|
||||||
@@ -26,6 +40,181 @@ ALTER TABLE IF EXISTS public.provider_api_keys
|
|||||||
ALTER TABLE IF EXISTS public.provider_api_keys
|
ALTER TABLE IF EXISTS public.provider_api_keys
|
||||||
ALTER COLUMN api_key DROP NOT NULL;
|
ALTER COLUMN api_key DROP NOT NULL;
|
||||||
|
|
||||||
|
WITH legacy_key_formats AS (
|
||||||
|
SELECT
|
||||||
|
pak.id,
|
||||||
|
LOWER(BTRIM(COALESCE(provider.provider_type, ''))) AS provider_type,
|
||||||
|
LOWER(BTRIM(COALESCE(pak.auth_type, ''))) AS current_auth_type,
|
||||||
|
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'claude:chat'), false) AS has_claude_chat,
|
||||||
|
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'claude:cli'), false) AS has_claude_cli,
|
||||||
|
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'gemini:chat'), false) AS has_gemini_chat,
|
||||||
|
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'gemini:cli'), false) AS has_gemini_cli
|
||||||
|
FROM public.provider_api_keys AS pak
|
||||||
|
INNER JOIN public.providers AS provider
|
||||||
|
ON provider.id = pak.provider_id
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT key_format.value
|
||||||
|
FROM json_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN pak.api_formats IS NOT NULL
|
||||||
|
AND json_typeof(pak.api_formats) = 'array'
|
||||||
|
THEN pak.api_formats
|
||||||
|
ELSE '[]'::json
|
||||||
|
END
|
||||||
|
) AS key_format(value)
|
||||||
|
UNION ALL
|
||||||
|
SELECT endpoint.api_format AS value
|
||||||
|
FROM public.provider_endpoints AS endpoint
|
||||||
|
WHERE pak.api_formats IS NULL
|
||||||
|
AND endpoint.provider_id = pak.provider_id
|
||||||
|
) AS format ON true
|
||||||
|
GROUP BY pak.id, provider_type, current_auth_type
|
||||||
|
),
|
||||||
|
inferred_key_auth AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
current_auth_type,
|
||||||
|
CASE
|
||||||
|
WHEN provider_type IN ('claude_code', 'kiro')
|
||||||
|
AND has_claude_cli
|
||||||
|
THEN 'oauth'
|
||||||
|
WHEN provider_type IN ('gemini_cli', 'antigravity')
|
||||||
|
AND has_gemini_cli
|
||||||
|
THEN 'oauth'
|
||||||
|
WHEN provider_type NOT IN ('claude_code', 'codex', 'gemini_cli', 'vertex_ai', 'antigravity', 'kiro')
|
||||||
|
AND (has_claude_chat OR has_gemini_chat)
|
||||||
|
AND NOT (has_claude_cli OR has_gemini_cli)
|
||||||
|
THEN 'api_key'
|
||||||
|
ELSE NULL
|
||||||
|
END AS inferred_auth_type
|
||||||
|
FROM legacy_key_formats
|
||||||
|
)
|
||||||
|
UPDATE public.provider_api_keys AS pak
|
||||||
|
SET
|
||||||
|
auth_type = inferred.inferred_auth_type,
|
||||||
|
updated_at = NOW()
|
||||||
|
FROM inferred_key_auth AS inferred
|
||||||
|
WHERE pak.id = inferred.id
|
||||||
|
AND inferred.inferred_auth_type IS NOT NULL
|
||||||
|
AND inferred.current_auth_type IS DISTINCT FROM inferred.inferred_auth_type
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
inferred.inferred_auth_type = 'oauth'
|
||||||
|
AND inferred.current_auth_type IN ('', 'api_key', 'bearer')
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
inferred.inferred_auth_type = 'api_key'
|
||||||
|
AND inferred.current_auth_type IN ('', 'bearer')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
WITH existing_auth_entries AS (
|
||||||
|
SELECT
|
||||||
|
pak.id,
|
||||||
|
public.aether_canonical_api_format_alias(entry.key) AS api_format,
|
||||||
|
CASE LOWER(BTRIM(COALESCE(entry.value #>> '{}', '')))
|
||||||
|
WHEN 'api_key' THEN 'api_key'
|
||||||
|
WHEN 'apikey' THEN 'api_key'
|
||||||
|
WHEN 'api-key' THEN 'api_key'
|
||||||
|
WHEN 'bearer' THEN 'bearer'
|
||||||
|
WHEN 'bearer_token' THEN 'bearer'
|
||||||
|
WHEN 'bearer-token' THEN 'bearer'
|
||||||
|
WHEN 'authorization' THEN 'bearer'
|
||||||
|
ELSE NULL
|
||||||
|
END AS auth_type,
|
||||||
|
0 AS source_priority,
|
||||||
|
entry.ordinality
|
||||||
|
FROM public.provider_api_keys AS pak
|
||||||
|
CROSS JOIN LATERAL json_each(
|
||||||
|
CASE
|
||||||
|
WHEN json_typeof(pak.auth_type_by_format) = 'object' THEN pak.auth_type_by_format
|
||||||
|
ELSE '{}'::json
|
||||||
|
END
|
||||||
|
) WITH ORDINALITY AS entry(key, value, ordinality)
|
||||||
|
WHERE pak.auth_type_by_format IS NOT NULL
|
||||||
|
),
|
||||||
|
legacy_auth_entries AS (
|
||||||
|
SELECT
|
||||||
|
pak.id,
|
||||||
|
public.aether_canonical_api_format_alias(format.value) AS api_format,
|
||||||
|
MIN(public.aether_legacy_raw_api_format_auth_type(format.value)) AS auth_type,
|
||||||
|
1 AS source_priority,
|
||||||
|
MIN(format.ordinality) AS ordinality
|
||||||
|
FROM public.provider_api_keys AS pak
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT key_format.value, key_format.ordinality
|
||||||
|
FROM json_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN pak.api_formats IS NOT NULL
|
||||||
|
AND json_typeof(pak.api_formats) = 'array'
|
||||||
|
THEN pak.api_formats
|
||||||
|
ELSE '[]'::json
|
||||||
|
END
|
||||||
|
) WITH ORDINALITY AS key_format(value, ordinality)
|
||||||
|
UNION ALL
|
||||||
|
SELECT endpoint.api_format AS value, endpoint.ordinality
|
||||||
|
FROM (
|
||||||
|
SELECT endpoint.api_format, ROW_NUMBER() OVER (ORDER BY endpoint.id) AS ordinality
|
||||||
|
FROM public.provider_endpoints AS endpoint
|
||||||
|
WHERE pak.api_formats IS NULL
|
||||||
|
AND endpoint.provider_id = pak.provider_id
|
||||||
|
) AS endpoint
|
||||||
|
) AS format ON true
|
||||||
|
WHERE LOWER(BTRIM(COALESCE(pak.auth_type, ''))) IN ('api_key', 'bearer')
|
||||||
|
AND public.aether_legacy_raw_api_format_auth_type(format.value) IS NOT NULL
|
||||||
|
GROUP BY
|
||||||
|
pak.id,
|
||||||
|
public.aether_canonical_api_format_alias(format.value)
|
||||||
|
HAVING COUNT(DISTINCT public.aether_legacy_raw_api_format_auth_type(format.value)) = 1
|
||||||
|
),
|
||||||
|
auth_entries AS (
|
||||||
|
SELECT id, api_format, auth_type, source_priority, ordinality
|
||||||
|
FROM existing_auth_entries
|
||||||
|
WHERE api_format <> ''
|
||||||
|
AND auth_type IS NOT NULL
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
legacy.id,
|
||||||
|
legacy.api_format,
|
||||||
|
legacy.auth_type,
|
||||||
|
legacy.source_priority,
|
||||||
|
legacy.ordinality
|
||||||
|
FROM legacy_auth_entries AS legacy
|
||||||
|
INNER JOIN public.provider_api_keys AS pak
|
||||||
|
ON pak.id = legacy.id
|
||||||
|
WHERE legacy.api_format <> ''
|
||||||
|
AND legacy.auth_type IS NOT NULL
|
||||||
|
AND legacy.auth_type IS DISTINCT FROM LOWER(BTRIM(COALESCE(pak.auth_type, '')))
|
||||||
|
),
|
||||||
|
ranked AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
api_format,
|
||||||
|
auth_type,
|
||||||
|
source_priority,
|
||||||
|
ordinality,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY id, api_format
|
||||||
|
ORDER BY source_priority, ordinality
|
||||||
|
) AS rank
|
||||||
|
FROM auth_entries
|
||||||
|
),
|
||||||
|
rebuilt AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
jsonb_object_agg(api_format, to_jsonb(auth_type) ORDER BY source_priority, ordinality) AS auth_type_by_format
|
||||||
|
FROM ranked
|
||||||
|
WHERE rank = 1
|
||||||
|
GROUP BY id
|
||||||
|
)
|
||||||
|
UPDATE public.provider_api_keys AS pak
|
||||||
|
SET
|
||||||
|
auth_type_by_format = rebuilt.auth_type_by_format::json,
|
||||||
|
updated_at = NOW()
|
||||||
|
FROM rebuilt
|
||||||
|
WHERE pak.id = rebuilt.id
|
||||||
|
AND pak.auth_type_by_format::jsonb IS DISTINCT FROM rebuilt.auth_type_by_format;
|
||||||
|
|
||||||
WITH normalized AS (
|
WITH normalized AS (
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
@@ -307,4 +496,5 @@ FROM rebuilt
|
|||||||
WHERE pak.id = rebuilt.id
|
WHERE pak.id = rebuilt.id
|
||||||
AND pak.circuit_breaker_by_format IS DISTINCT FROM rebuilt.circuit_breaker_by_format;
|
AND pak.circuit_breaker_by_format IS DISTINCT FROM rebuilt.circuit_breaker_by_format;
|
||||||
|
|
||||||
|
DROP FUNCTION public.aether_legacy_raw_api_format_auth_type(text);
|
||||||
DROP FUNCTION public.aether_canonical_api_format_alias(text);
|
DROP FUNCTION public.aether_canonical_api_format_alias(text);
|
||||||
|
|||||||
@@ -790,6 +790,11 @@ SELECT EXISTS (
|
|||||||
|
|
||||||
sqlx::raw_sql(
|
sqlx::raw_sql(
|
||||||
r#"
|
r#"
|
||||||
|
CREATE TABLE public.providers (
|
||||||
|
id text PRIMARY KEY,
|
||||||
|
provider_type text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE public.provider_endpoints (
|
CREATE TABLE public.provider_endpoints (
|
||||||
id text PRIMARY KEY,
|
id text PRIMARY KEY,
|
||||||
provider_id text NOT NULL,
|
provider_id text NOT NULL,
|
||||||
@@ -814,6 +819,10 @@ ALTER TABLE ONLY public.provider_endpoints
|
|||||||
|
|
||||||
CREATE TABLE public.provider_api_keys (
|
CREATE TABLE public.provider_api_keys (
|
||||||
id text PRIMARY KEY,
|
id text PRIMARY KEY,
|
||||||
|
provider_id text NOT NULL,
|
||||||
|
api_key text,
|
||||||
|
auth_type text DEFAULT 'api_key' NOT NULL,
|
||||||
|
auth_type_by_format json,
|
||||||
api_formats json,
|
api_formats json,
|
||||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
rate_multipliers json,
|
rate_multipliers json,
|
||||||
@@ -840,6 +849,12 @@ CREATE TABLE public.models (
|
|||||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
INSERT INTO public.providers (id, provider_type)
|
||||||
|
VALUES
|
||||||
|
('provider-conflict', 'custom'),
|
||||||
|
('provider-claude-code', 'claude_code'),
|
||||||
|
('provider-gemini-cli', 'gemini_cli');
|
||||||
|
|
||||||
INSERT INTO public.provider_endpoints (
|
INSERT INTO public.provider_endpoints (
|
||||||
id,
|
id,
|
||||||
provider_id,
|
provider_id,
|
||||||
@@ -884,23 +899,126 @@ INSERT INTO public.provider_endpoints (
|
|||||||
'{"transport":"cli"}'::json,
|
'{"transport":"cli"}'::json,
|
||||||
'{"url":"http://proxy-cli"}'::jsonb,
|
'{"url":"http://proxy-cli"}'::jsonb,
|
||||||
'{"accept":"cli"}'::json
|
'{"accept":"cli"}'::json
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'endpoint-claude-oauth-cli',
|
||||||
|
'provider-claude-code',
|
||||||
|
'claude:cli',
|
||||||
|
'claude',
|
||||||
|
'cli',
|
||||||
|
'https://claude-oauth.example',
|
||||||
|
'/v1/messages',
|
||||||
|
3,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'endpoint-gemini-oauth-cli',
|
||||||
|
'provider-gemini-cli',
|
||||||
|
'gemini:cli',
|
||||||
|
'gemini',
|
||||||
|
'cli',
|
||||||
|
'https://gemini-oauth.example',
|
||||||
|
'/v1beta/models',
|
||||||
|
3,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO public.provider_api_keys (
|
INSERT INTO public.provider_api_keys (
|
||||||
id,
|
id,
|
||||||
|
provider_id,
|
||||||
|
auth_type,
|
||||||
|
auth_type_by_format,
|
||||||
api_formats,
|
api_formats,
|
||||||
rate_multipliers,
|
rate_multipliers,
|
||||||
global_priority_by_format,
|
global_priority_by_format,
|
||||||
health_by_format,
|
health_by_format,
|
||||||
circuit_breaker_by_format
|
circuit_breaker_by_format
|
||||||
) VALUES (
|
) VALUES
|
||||||
'provider-key',
|
(
|
||||||
'["claude:chat","claude:cli","openai:cli","openai:responses"]'::json,
|
'provider-key',
|
||||||
'{"claude:chat":1,"openai:compact":2}'::json,
|
'provider-conflict',
|
||||||
'{"gemini:cli":3}'::json,
|
'api_key',
|
||||||
'{"openai:cli":{"health_score":0.9}}'::jsonb,
|
'{"claude:cli":"bearer","gemini:chat":"api-key"}'::json,
|
||||||
'{"openai:compact":{"open":false}}'::jsonb
|
'["claude:chat","claude:cli","openai:cli","openai:responses"]'::json,
|
||||||
);
|
'{"claude:chat":1,"openai:compact":2}'::json,
|
||||||
|
'{"gemini:cli":3}'::json,
|
||||||
|
'{"openai:cli":{"health_score":0.9}}'::jsonb,
|
||||||
|
'{"openai:compact":{"open":false}}'::jsonb
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'provider-raw-cli-key',
|
||||||
|
'provider-conflict',
|
||||||
|
'api_key',
|
||||||
|
NULL,
|
||||||
|
'["claude:cli"]'::json,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'provider-chat-key',
|
||||||
|
'provider-conflict',
|
||||||
|
'bearer',
|
||||||
|
NULL,
|
||||||
|
'["gemini:chat"]'::json,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'provider-claude-oauth-key',
|
||||||
|
'provider-claude-code',
|
||||||
|
'api_key',
|
||||||
|
NULL,
|
||||||
|
'["claude:cli"]'::json,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'provider-claude-oauth-null-key',
|
||||||
|
'provider-claude-code',
|
||||||
|
'api_key',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'provider-gemini-oauth-key',
|
||||||
|
'provider-gemini-cli',
|
||||||
|
'api_key',
|
||||||
|
NULL,
|
||||||
|
'["gemini:cli"]'::json,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'provider-gemini-oauth-null-key',
|
||||||
|
'provider-gemini-cli',
|
||||||
|
'api_key',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
INSERT INTO public.api_keys (id, allowed_api_formats)
|
INSERT INTO public.api_keys (id, allowed_api_formats)
|
||||||
VALUES ('api-key', '["gemini:chat","gemini:cli"]'::json);
|
VALUES ('api-key', '["gemini:chat","gemini:cli"]'::json);
|
||||||
@@ -986,6 +1104,66 @@ ORDER BY id
|
|||||||
serde_json::json!(["claude:messages", "openai:responses"])
|
serde_json::json!(["claude:messages", "openai:responses"])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let provider_key_auth_rows =
|
||||||
|
sqlx::query_as::<_, (String, String, Option<serde_json::Value>)>(
|
||||||
|
r#"
|
||||||
|
SELECT id, auth_type, auth_type_by_format::jsonb
|
||||||
|
FROM public.provider_api_keys
|
||||||
|
WHERE id IN (
|
||||||
|
'provider-chat-key',
|
||||||
|
'provider-claude-oauth-key',
|
||||||
|
'provider-claude-oauth-null-key',
|
||||||
|
'provider-gemini-oauth-key',
|
||||||
|
'provider-gemini-oauth-null-key',
|
||||||
|
'provider-key',
|
||||||
|
'provider-raw-cli-key'
|
||||||
|
)
|
||||||
|
ORDER BY id
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await
|
||||||
|
.expect("provider key auth rows should be readable");
|
||||||
|
assert_eq!(
|
||||||
|
provider_key_auth_rows,
|
||||||
|
vec![
|
||||||
|
("provider-chat-key".to_string(), "api_key".to_string(), None),
|
||||||
|
(
|
||||||
|
"provider-claude-oauth-key".to_string(),
|
||||||
|
"oauth".to_string(),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"provider-claude-oauth-null-key".to_string(),
|
||||||
|
"oauth".to_string(),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"provider-gemini-oauth-key".to_string(),
|
||||||
|
"oauth".to_string(),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"provider-gemini-oauth-null-key".to_string(),
|
||||||
|
"oauth".to_string(),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"provider-key".to_string(),
|
||||||
|
"api_key".to_string(),
|
||||||
|
Some(serde_json::json!({
|
||||||
|
"claude:messages": "bearer",
|
||||||
|
"gemini:generate_content": "api_key"
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"provider-raw-cli-key".to_string(),
|
||||||
|
"api_key".to_string(),
|
||||||
|
Some(serde_json::json!({"claude:messages": "bearer"}))
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
let provider_format_constraint_count: i64 = query_scalar(
|
let provider_format_constraint_count: i64 = query_scalar(
|
||||||
"SELECT COUNT(*)::BIGINT FROM pg_constraint WHERE conname = 'uq_provider_api_format'",
|
"SELECT COUNT(*)::BIGINT FROM pg_constraint WHERE conname = 'uq_provider_api_format'",
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user