@f5-sales-demo/pi-ai 23.9.4 → 23.10.1
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/package.json +2 -2
- package/src/auth-storage.ts +52 -3
- package/src/index.ts +2 -0
- package/src/model-manager.ts +6 -4
- package/src/provider-models/descriptors.ts +1 -0
- package/src/provider-models/special.ts +13 -0
- package/src/providers/anthropic.ts +1 -1
- package/src/providers/google-gemini-cli.ts +1 -1
- package/src/providers/kiro-LICENSE +21 -0
- package/src/providers/kiro-service.ts +153 -0
- package/src/providers/kiro.ts +357 -0
- package/src/stream.ts +5 -0
- package/src/types.ts +9 -0
- package/src/utils/discovery/codex.ts +2 -1
- package/src/utils/oauth/index.ts +22 -11
- package/src/utils/oauth/kiro-credentials.ts +96 -0
- package/src/utils/oauth/kiro-sources.ts +90 -0
- package/src/utils/oauth/kiro.ts +328 -0
- package/src/utils/oauth/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-ai",
|
|
4
|
-
"version": "23.
|
|
4
|
+
"version": "23.10.1",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@anthropic-ai/sdk": "0.124.0",
|
|
47
47
|
"@aws-sdk/client-bedrock-runtime": "3.1127.0",
|
|
48
48
|
"@bufbuild/protobuf": "2.14.1",
|
|
49
|
-
"@f5-sales-demo/pi-utils": "23.
|
|
49
|
+
"@f5-sales-demo/pi-utils": "23.10.1",
|
|
50
50
|
"@google/genai": "2.21.0",
|
|
51
51
|
"@sinclair/typebox": "0.34.52",
|
|
52
52
|
"@smithy/node-http-handler": "4.12.1",
|
package/src/auth-storage.ts
CHANGED
|
@@ -12,7 +12,7 @@ import * as fs from "node:fs/promises";
|
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { $envExact, getAgentDbPath, logger } from "@f5-sales-demo/pi-utils";
|
|
14
14
|
import { getEnvApiKey } from "./stream";
|
|
15
|
-
import type { Provider } from "./types";
|
|
15
|
+
import type { Model, Provider } from "./types";
|
|
16
16
|
import type {
|
|
17
17
|
CredentialRankingStrategy,
|
|
18
18
|
UsageCredential,
|
|
@@ -44,6 +44,7 @@ import { loginHuggingface } from "./utils/oauth/huggingface";
|
|
|
44
44
|
import { loginKagi } from "./utils/oauth/kagi";
|
|
45
45
|
import { loginKilo } from "./utils/oauth/kilo";
|
|
46
46
|
import { loginKimi } from "./utils/oauth/kimi";
|
|
47
|
+
import type { KiroCredentials } from "./utils/oauth/kiro-credentials";
|
|
47
48
|
import { loginLiteLLM } from "./utils/oauth/litellm";
|
|
48
49
|
import { loginLmStudio } from "./utils/oauth/lm-studio";
|
|
49
50
|
import { loginMiniMaxCode, loginMiniMaxCodeCn } from "./utils/oauth/minimax-code";
|
|
@@ -267,6 +268,7 @@ type UsageRequestDescriptor = {
|
|
|
267
268
|
};
|
|
268
269
|
|
|
269
270
|
type AuthApiKeyOptions = {
|
|
271
|
+
kiroConnection?: Model["kiroConnection"];
|
|
270
272
|
baseUrl?: string;
|
|
271
273
|
modelId?: string;
|
|
272
274
|
};
|
|
@@ -1284,7 +1286,19 @@ export class AuthStorage {
|
|
|
1284
1286
|
break;
|
|
1285
1287
|
}
|
|
1286
1288
|
}
|
|
1287
|
-
const newCredential: OAuthCredential = {
|
|
1289
|
+
const newCredential: OAuthCredential = {
|
|
1290
|
+
type: "oauth",
|
|
1291
|
+
...credentials,
|
|
1292
|
+
...(provider === "kiro"
|
|
1293
|
+
? {
|
|
1294
|
+
selectedAt: Math.max(
|
|
1295
|
+
Date.now(),
|
|
1296
|
+
((this.#store.getLatestKiroSelection()?.credential as Partial<KiroCredentials> | undefined)
|
|
1297
|
+
?.selectedAt ?? 0) + 1,
|
|
1298
|
+
),
|
|
1299
|
+
}
|
|
1300
|
+
: {}),
|
|
1301
|
+
};
|
|
1288
1302
|
await this.#upsertOAuthCredential(credentialProvider, newCredential);
|
|
1289
1303
|
}
|
|
1290
1304
|
|
|
@@ -1971,13 +1985,29 @@ export class AuthStorage {
|
|
|
1971
1985
|
sessionId?: string,
|
|
1972
1986
|
options?: AuthApiKeyOptions,
|
|
1973
1987
|
): Promise<string | undefined> {
|
|
1974
|
-
|
|
1988
|
+
let credentials = this.#getStoredCredentials(provider)
|
|
1975
1989
|
.map((entry, index) => ({ credential: entry.credential, index, credentialId: entry.id }))
|
|
1976
1990
|
.filter(
|
|
1977
1991
|
(entry): entry is { credential: OAuthCredential; index: number; credentialId: number } =>
|
|
1978
1992
|
entry.credential.type === "oauth",
|
|
1979
1993
|
);
|
|
1980
1994
|
|
|
1995
|
+
if (provider === "kiro") {
|
|
1996
|
+
const binding = options?.kiroConnection;
|
|
1997
|
+
const latest = binding ? undefined : this.#store.getLatestKiroSelection();
|
|
1998
|
+
credentials = binding
|
|
1999
|
+
? credentials.filter(entry => {
|
|
2000
|
+
const c = entry.credential as OAuthCredential & Partial<KiroCredentials>;
|
|
2001
|
+
return (
|
|
2002
|
+
c.accountId === binding.accountId &&
|
|
2003
|
+
c.profileArn === binding.profileArn &&
|
|
2004
|
+
c.serviceRegion === binding.serviceRegion
|
|
2005
|
+
);
|
|
2006
|
+
})
|
|
2007
|
+
: credentials.filter(entry => {
|
|
2008
|
+
return latest && !latest.disabledCause && entry.credentialId === latest.id;
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
1981
2011
|
if (credentials.length === 0) return undefined;
|
|
1982
2012
|
|
|
1983
2013
|
const providerKey = this.#getProviderTypeKey(provider, "oauth");
|
|
@@ -2172,6 +2202,8 @@ export class AuthStorage {
|
|
|
2172
2202
|
}
|
|
2173
2203
|
if (!result) return undefined;
|
|
2174
2204
|
const updated: OAuthCredential = {
|
|
2205
|
+
...selection.credential,
|
|
2206
|
+
...result.newCredentials,
|
|
2175
2207
|
type: "oauth",
|
|
2176
2208
|
access: result.newCredentials.access,
|
|
2177
2209
|
refresh: result.newCredentials.refresh,
|
|
@@ -2425,6 +2457,12 @@ function resolveProviderCredentialIdentityKey(provider: string, identifiers: str
|
|
|
2425
2457
|
|
|
2426
2458
|
function resolveCredentialIdentityKey(provider: string, credential: AuthCredential): string | null {
|
|
2427
2459
|
if (credential.type === "api_key") return null;
|
|
2460
|
+
if (provider === "kiro") {
|
|
2461
|
+
const c = credential as OAuthCredential & Partial<KiroCredentials>;
|
|
2462
|
+
return c.accountId && c.profileArn && c.serviceRegion
|
|
2463
|
+
? JSON.stringify([c.accountId, c.profileArn, c.serviceRegion])
|
|
2464
|
+
: null;
|
|
2465
|
+
}
|
|
2428
2466
|
return resolveProviderCredentialIdentityKey(provider, extractOAuthCredentialIdentifiers(credential));
|
|
2429
2467
|
}
|
|
2430
2468
|
|
|
@@ -2836,6 +2874,17 @@ export class AuthCredentialStore {
|
|
|
2836
2874
|
|
|
2837
2875
|
// ─── AuthCredentialStore interface ──────────────────────────────────────
|
|
2838
2876
|
|
|
2877
|
+
/** Keep the most recent explicit native connection selected even after it is disabled. */
|
|
2878
|
+
getLatestKiroSelection(): StoredAuthCredential | undefined {
|
|
2879
|
+
const row = this.#db
|
|
2880
|
+
.prepare(
|
|
2881
|
+
"SELECT id, provider, credential_type, data, disabled_cause, identity_key FROM auth_credentials WHERE provider = 'kiro' AND credential_type = 'oauth' ORDER BY CASE WHEN json_valid(data) THEN COALESCE(json_extract(data, '$.selectedAt'), 0) ELSE 0 END DESC, id DESC LIMIT 1",
|
|
2882
|
+
)
|
|
2883
|
+
.get() as AuthRow | undefined;
|
|
2884
|
+
if (!row) return undefined;
|
|
2885
|
+
const credential = deserializeCredential(row);
|
|
2886
|
+
return credential ? toStoredAuthCredential(row, credential) : undefined;
|
|
2887
|
+
}
|
|
2839
2888
|
listAuthCredentials(provider?: string): StoredAuthCredential[] {
|
|
2840
2889
|
const rows =
|
|
2841
2890
|
(provider
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,8 @@ export * from "./providers/google";
|
|
|
20
20
|
export * from "./providers/google-gemini-cli";
|
|
21
21
|
export * from "./providers/google-vertex";
|
|
22
22
|
export * from "./providers/kimi";
|
|
23
|
+
export * from "./providers/kiro";
|
|
24
|
+
export * from "./providers/kiro-service";
|
|
23
25
|
export type { OpenAICodexResponsesOptions } from "./providers/openai-codex-responses";
|
|
24
26
|
export * from "./providers/openai-completions";
|
|
25
27
|
export * from "./providers/openai-responses";
|
package/src/model-manager.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface ModelsDevFallback<TApi extends Api = Api, TPayload = unknown> {
|
|
|
28
28
|
export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload = unknown> {
|
|
29
29
|
/** Provider id used for static lookup and cache namespacing. */
|
|
30
30
|
providerId: Provider;
|
|
31
|
+
/** Credential/profile-specific namespace; model routing still uses providerId. */
|
|
32
|
+
cacheNamespace?: string;
|
|
31
33
|
/** Optional static list override. When omitted, bundled models.json is used. */
|
|
32
34
|
staticModels?: readonly Model<TApi>[];
|
|
33
35
|
/** Optional override for the cache database path. Default: <agent-dir>/models.db. */
|
|
@@ -98,7 +100,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
98
100
|
const staticModels = normalizeModelList<TApi>(
|
|
99
101
|
options.staticModels ?? getBundledModels(options.providerId as GeneratedProvider),
|
|
100
102
|
);
|
|
101
|
-
const cache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
|
|
103
|
+
const cache = readModelCache<TApi>(options.cacheNamespace ?? options.providerId, ttlMs, now, dbPath);
|
|
102
104
|
const dynamicFetcher = options.fetchDynamicModels;
|
|
103
105
|
const hasDynamicFetcher = typeof dynamicFetcher === "function";
|
|
104
106
|
const hasAuthoritativeCache = (cache?.authoritative ?? false) || !hasDynamicFetcher;
|
|
@@ -129,13 +131,13 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
129
131
|
if (shouldFetchFromNetwork) {
|
|
130
132
|
if (dynamicFetchSucceeded) {
|
|
131
133
|
const snapshotModels = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), dynamicModels);
|
|
132
|
-
writeModelCache(options.providerId, now(), snapshotModels, true, dbPath);
|
|
134
|
+
writeModelCache(options.cacheNamespace ?? options.providerId, now(), snapshotModels, true, dbPath);
|
|
133
135
|
} else {
|
|
134
136
|
// Dynamic fetch failed — update cache with a non-authoritative snapshot so
|
|
135
137
|
// stale state remains visible while retry backoff still applies.
|
|
136
|
-
const latestCache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
|
|
138
|
+
const latestCache = readModelCache<TApi>(options.cacheNamespace ?? options.providerId, ttlMs, now, dbPath);
|
|
137
139
|
writeModelCache(
|
|
138
|
-
options.providerId,
|
|
140
|
+
options.cacheNamespace ?? options.providerId,
|
|
139
141
|
now(),
|
|
140
142
|
mergeDynamicModels(
|
|
141
143
|
mergeModelSources(staticModels, modelsDevModels),
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ModelManagerOptions } from "../model-manager";
|
|
2
|
+
import { discoverKiroModels, kiroCacheNamespace } from "../providers/kiro-service";
|
|
2
3
|
import { fetchCodexModels } from "../utils/discovery/codex";
|
|
3
4
|
import { fetchCursorUsableModels } from "../utils/discovery/cursor";
|
|
5
|
+
import type { KiroCredentials } from "../utils/oauth/kiro-credentials";
|
|
4
6
|
|
|
5
7
|
// ---------------------------------------------------------------------------
|
|
6
8
|
// OpenAI Codex
|
|
@@ -105,3 +107,14 @@ export interface ZaiModelManagerConfig {}
|
|
|
105
107
|
export function zaiModelManagerOptions(_config: ZaiModelManagerConfig = {}): ModelManagerOptions<"anthropic-messages"> {
|
|
106
108
|
return { providerId: "zai" };
|
|
107
109
|
}
|
|
110
|
+
|
|
111
|
+
/** Kiro catalogs are private to the selected native account and profile. */
|
|
112
|
+
export function kiroModelManagerOptions(config: { apiKey: string }): ModelManagerOptions<"kiro-api"> {
|
|
113
|
+
const credentials = JSON.parse(config.apiKey) as KiroCredentials;
|
|
114
|
+
return {
|
|
115
|
+
providerId: "kiro",
|
|
116
|
+
cacheNamespace: kiroCacheNamespace(credentials),
|
|
117
|
+
staticModels: [],
|
|
118
|
+
fetchDynamicModels: async signal => (await discoverKiroModels(credentials, { signal })).models,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -192,7 +192,7 @@ function getCacheControl(
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Stealth mode: Mimic Claude Code headers and tool prefixing.
|
|
195
|
-
export const claudeCodeVersion = "2.1.
|
|
195
|
+
export const claudeCodeVersion = "2.1.311";
|
|
196
196
|
export const claudeToolPrefix: string = "proxy_";
|
|
197
197
|
export const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
198
198
|
|
|
@@ -79,7 +79,7 @@ export function resolveAntigravityServingModelId(requestedModelId: string): stri
|
|
|
79
79
|
* GeminiCLI/VERSION/MODEL (PLATFORM; ARCH; SURFACE)
|
|
80
80
|
*/
|
|
81
81
|
export function getGeminiCliUserAgent(modelId = "gemini-3.1-pro-preview"): string {
|
|
82
|
-
const version = process.env.PI_AI_GEMINI_CLI_VERSION || "0.
|
|
82
|
+
const version = process.env.PI_AI_GEMINI_CLI_VERSION || "0.65.0";
|
|
83
83
|
const platform = process.platform === "win32" ? "win32" : process.platform;
|
|
84
84
|
const arch = process.arch === "x64" ? "x64" : process.arch;
|
|
85
85
|
return `GeminiCLI/${version}/${modelId} (${platform}; ${arch}; terminal)`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hongyi Lyu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { Model } from "../types";
|
|
3
|
+
import { type KiroCredentials, validKiroRegion } from "../utils/oauth/kiro-credentials";
|
|
4
|
+
|
|
5
|
+
export interface KiroServiceOptions {
|
|
6
|
+
fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
regions?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface KiroProfile {
|
|
11
|
+
arn: string;
|
|
12
|
+
name: string;
|
|
13
|
+
region: string;
|
|
14
|
+
}
|
|
15
|
+
export function kiroEndpoint(region: string): string {
|
|
16
|
+
if (!validKiroRegion(region)) throw new Error("Invalid Kiro service region");
|
|
17
|
+
return `https://q.${region}.amazonaws.com`;
|
|
18
|
+
}
|
|
19
|
+
export function kiroCacheNamespace(c: KiroCredentials): string {
|
|
20
|
+
if (!c.accountId || !c.profileArn || !c.serviceRegion)
|
|
21
|
+
throw new Error("Kiro account/profile selection required. Run /login kiro.");
|
|
22
|
+
return `kiro:${createHash("sha256")
|
|
23
|
+
.update(JSON.stringify([c.accountId, c.profileArn, c.serviceRegion]))
|
|
24
|
+
.digest("hex")}`;
|
|
25
|
+
}
|
|
26
|
+
export async function kiroRequest(url: string, init: RequestInit, options: KiroServiceOptions = {}): Promise<Response> {
|
|
27
|
+
const signal = options.signal
|
|
28
|
+
? AbortSignal.any([options.signal, AbortSignal.timeout(30000)])
|
|
29
|
+
: AbortSignal.timeout(30000);
|
|
30
|
+
signal.throwIfAborted();
|
|
31
|
+
return (options.fetch ?? globalThis.fetch)(url, { ...init, signal });
|
|
32
|
+
}
|
|
33
|
+
function headers(c: KiroCredentials): Record<string, string> {
|
|
34
|
+
return { Authorization: `Bearer ${c.access}`, "Content-Type": "application/json", "User-Agent": "xcsh-kiro" };
|
|
35
|
+
}
|
|
36
|
+
async function jsonResponse(response: Response): Promise<Record<string, any>> {
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Kiro service HTTP ${response.status}${response.status === 401 || response.status === 403 ? ". Reconnect with /login kiro." : ". Retry when the service is available."}`,
|
|
40
|
+
);
|
|
41
|
+
const body = await response.json();
|
|
42
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) throw new Error("Malformed Kiro response");
|
|
43
|
+
return body;
|
|
44
|
+
}
|
|
45
|
+
async function pages<T>(
|
|
46
|
+
request: (token?: string) => Promise<Record<string, any>>,
|
|
47
|
+
field: string,
|
|
48
|
+
): Promise<{ items: T[]; defaultModel?: unknown }> {
|
|
49
|
+
const items: T[] = [];
|
|
50
|
+
const seen = new Set<string>();
|
|
51
|
+
let token: string | undefined;
|
|
52
|
+
let defaultModel: unknown;
|
|
53
|
+
for (let page = 0; page < 100; page++) {
|
|
54
|
+
const body = await request(token);
|
|
55
|
+
if (!Array.isArray(body[field])) throw new Error(`Malformed Kiro ${field} response`);
|
|
56
|
+
items.push(...body[field]);
|
|
57
|
+
defaultModel = body.defaultModel ?? defaultModel;
|
|
58
|
+
if (!body.nextToken) return { items, defaultModel };
|
|
59
|
+
if (typeof body.nextToken !== "string" || seen.has(body.nextToken)) throw new Error("Invalid Kiro pagination");
|
|
60
|
+
token = body.nextToken;
|
|
61
|
+
seen.add(token);
|
|
62
|
+
}
|
|
63
|
+
throw new Error("Kiro pagination exceeded limit");
|
|
64
|
+
}
|
|
65
|
+
export async function discoverKiroProfiles(
|
|
66
|
+
c: KiroCredentials,
|
|
67
|
+
options: KiroServiceOptions = {},
|
|
68
|
+
): Promise<KiroProfile[]> {
|
|
69
|
+
const profiles = new Map<string, KiroProfile>();
|
|
70
|
+
// Desktop 1.4.26 maps BuilderId to this service-owned profile; the API
|
|
71
|
+
// explicitly rejects ListAvailableProfiles for Builder ID.
|
|
72
|
+
if (c.authMethod === "builder-id")
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
arn: "arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX",
|
|
76
|
+
name: "AWS Builder ID",
|
|
77
|
+
region: "us-east-1",
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
const regions = options.regions ?? ["us-east-1", "eu-central-1"];
|
|
81
|
+
for (const region of regions) {
|
|
82
|
+
const result = await pages<{ arn: string; name?: string }>(
|
|
83
|
+
async nextToken =>
|
|
84
|
+
jsonResponse(
|
|
85
|
+
await kiroRequest(
|
|
86
|
+
`${kiroEndpoint(region)}/ListAvailableProfiles`,
|
|
87
|
+
{
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: headers(c),
|
|
90
|
+
body: JSON.stringify({ ...(nextToken ? { nextToken } : {}) }),
|
|
91
|
+
},
|
|
92
|
+
options,
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
"profiles",
|
|
96
|
+
);
|
|
97
|
+
for (const p of result.items) {
|
|
98
|
+
if (typeof p.arn !== "string") throw new Error("Malformed Kiro profile");
|
|
99
|
+
const serviceRegion = p.arn.split(":")[3];
|
|
100
|
+
if (!serviceRegion || !validKiroRegion(serviceRegion)) throw new Error("Invalid Kiro profile region");
|
|
101
|
+
profiles.set(p.arn, { arn: p.arn, name: p.name ?? "Kiro profile", region: serviceRegion });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!profiles.size)
|
|
105
|
+
throw new Error("No Kiro profiles available for this account. Check entitlement or choose another account.");
|
|
106
|
+
return [...profiles.values()];
|
|
107
|
+
}
|
|
108
|
+
export async function discoverKiroModels(
|
|
109
|
+
c: KiroCredentials,
|
|
110
|
+
options: KiroServiceOptions = {},
|
|
111
|
+
): Promise<{ models: Model<"kiro-api">[]; defaultModel?: string }> {
|
|
112
|
+
if (!c.profileArn || !c.serviceRegion) throw new Error("Select a Kiro profile with /login kiro");
|
|
113
|
+
const result = await pages<Record<string, any>>(async nextToken => {
|
|
114
|
+
const url = new URL(`${kiroEndpoint(c.serviceRegion!)}/ListAvailableModels`);
|
|
115
|
+
url.searchParams.set("origin", "KIRO_CLI");
|
|
116
|
+
url.searchParams.set("profileArn", c.profileArn!);
|
|
117
|
+
if (nextToken) url.searchParams.set("nextToken", nextToken);
|
|
118
|
+
return jsonResponse(await kiroRequest(url.toString(), { headers: headers(c) }, options));
|
|
119
|
+
}, "models");
|
|
120
|
+
const models = result.items.map(m => {
|
|
121
|
+
const input = m.tokenLimits?.maxInputTokens;
|
|
122
|
+
const output = m.tokenLimits?.maxOutputTokens;
|
|
123
|
+
if (
|
|
124
|
+
typeof m.modelId !== "string" ||
|
|
125
|
+
!m.modelId ||
|
|
126
|
+
!Number.isFinite(input) ||
|
|
127
|
+
input <= 0 ||
|
|
128
|
+
!Number.isFinite(output) ||
|
|
129
|
+
output <= 0
|
|
130
|
+
)
|
|
131
|
+
throw new Error("Kiro model is missing service limits or ID");
|
|
132
|
+
const model: Model<"kiro-api"> = {
|
|
133
|
+
id: m.modelId,
|
|
134
|
+
name: m.modelName ?? m.modelId,
|
|
135
|
+
provider: "kiro",
|
|
136
|
+
kiroConnection: { accountId: c.accountId!, profileArn: c.profileArn!, serviceRegion: c.serviceRegion! },
|
|
137
|
+
api: "kiro-api",
|
|
138
|
+
baseUrl: `${kiroEndpoint(c.serviceRegion!)}/generateAssistantResponse`,
|
|
139
|
+
reasoning: false,
|
|
140
|
+
input: m.supportedInputTypes?.includes("IMAGE") ? ["text", "image"] : ["text"],
|
|
141
|
+
contextWindow: input,
|
|
142
|
+
maxTokens: output,
|
|
143
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, pricingKnown: false },
|
|
144
|
+
};
|
|
145
|
+
return model;
|
|
146
|
+
});
|
|
147
|
+
if (!models.length) throw new Error("No Kiro models available for the selected profile");
|
|
148
|
+
const d = result.defaultModel;
|
|
149
|
+
const defaultModel =
|
|
150
|
+
typeof d === "string" ? d : d && typeof d === "object" ? (d as { modelId?: string }).modelId : undefined;
|
|
151
|
+
for (const model of models) model.providerDefault = model.id === defaultModel;
|
|
152
|
+
return { models, defaultModel: models.some(m => m.id === defaultModel) ? defaultModel : undefined };
|
|
153
|
+
}
|