@shop-os/modelkit 0.2.10 → 0.2.11

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/dist/index.cjs CHANGED
@@ -63,6 +63,7 @@ __export(index_exports, {
63
63
  generateVideo: () => generateVideo,
64
64
  googleGenaiAdapter: () => googleGenaiAdapter,
65
65
  googleImageAdapter: () => googleImageAdapter,
66
+ hasAwsCredentials: () => hasAwsCredentials,
66
67
  higgsfieldAdapter: () => higgsfieldAdapter,
67
68
  isListed: () => isListed,
68
69
  isListedOn: () => isListedOn,
@@ -100,6 +101,14 @@ __export(index_exports, {
100
101
  module.exports = __toCommonJS(index_exports);
101
102
  __reExport(index_exports, require("ai"), module.exports);
102
103
 
104
+ // src/types.ts
105
+ function hasAwsCredentials(aws) {
106
+ if (!aws || !aws.region) return false;
107
+ if (aws.apiKey) return true;
108
+ if (aws.credentialProvider) return true;
109
+ return Boolean(aws.accessKeyId && aws.secretAccessKey);
110
+ }
111
+
103
112
  // src/errors.ts
104
113
  var ModelKitError = class extends Error {
105
114
  code;
@@ -2136,7 +2145,18 @@ var CatalogModelSchemaBase = import_zod.z.looseObject({
2136
2145
  });
2137
2146
  }
2138
2147
  });
2139
- var CatalogModelSchema = CatalogModelSchemaBase;
2148
+ var CatalogModelSchema = CatalogModelSchemaBase.superRefine(
2149
+ (row, ctx) => {
2150
+ const transport = row.providerOptions?.["modelkit"]?.["transport"];
2151
+ if (transport !== void 0 && transport !== "direct" && transport !== "bedrock") {
2152
+ ctx.addIssue({
2153
+ code: "custom",
2154
+ path: ["providerOptions", "modelkit", "transport"],
2155
+ message: 'transport must be "direct" or "bedrock"'
2156
+ });
2157
+ }
2158
+ }
2159
+ );
2140
2160
  var CatalogSchema = import_zod.z.array(CatalogModelSchemaBase).superRefine((rows, ctx) => {
2141
2161
  const seenIds = /* @__PURE__ */ new Map();
2142
2162
  rows.forEach((row, index) => {
@@ -2741,9 +2761,12 @@ var ADAPTER_KEYS = {
2741
2761
  function hasAll(keys, names) {
2742
2762
  if (!keys) return false;
2743
2763
  return names.every(
2744
- (n) => Boolean(keys[n]) && (n !== "higgsfield" || Boolean(keys.higgsfieldSecret))
2764
+ (n) => n === "aws" ? hasAwsCredentials(keys.aws) : Boolean(keys[n]) && (n !== "higgsfield" || Boolean(keys.higgsfieldSecret))
2745
2765
  );
2746
2766
  }
2767
+ function hasKey(keys, name) {
2768
+ return hasAll(keys, [name]);
2769
+ }
2747
2770
  function missingEnterpriseKeys(config, names) {
2748
2771
  return names.filter((name) => !hasAll(config.keys.enterprise, [name]));
2749
2772
  }
@@ -2780,6 +2803,22 @@ function keysFromEnv(env, prefix = "") {
2780
2803
  set("higgsfield", pick("HIGGSFIELD_API_KEY"));
2781
2804
  set("higgsfieldSecret", pick("HIGGSFIELD_API_SECRET"));
2782
2805
  set("cloudflare", pick("CLOUDFLARE_AI_GATEWAY_TOKEN", "CF_AIG_TOKEN"));
2806
+ const bearer = pick("AWS_BEARER_TOKEN_BEDROCK");
2807
+ const accessKeyId = pick("AWS_ACCESS_KEY_ID");
2808
+ const secretAccessKey = pick("AWS_SECRET_ACCESS_KEY");
2809
+ if (bearer || accessKeyId && secretAccessKey) {
2810
+ const aws = {
2811
+ region: env[`${prefix}BEDROCK_REGION`] || "us-east-1"
2812
+ };
2813
+ if (accessKeyId && secretAccessKey) {
2814
+ aws.accessKeyId = accessKeyId;
2815
+ aws.secretAccessKey = secretAccessKey;
2816
+ }
2817
+ const sessionToken = pick("AWS_SESSION_TOKEN");
2818
+ if (sessionToken) aws.sessionToken = sessionToken;
2819
+ if (bearer) aws.apiKey = bearer;
2820
+ keys.aws = aws;
2821
+ }
2783
2822
  return keys;
2784
2823
  }
2785
2824
 
@@ -3108,7 +3147,8 @@ function toGeneratedImage(image) {
3108
3147
  }
3109
3148
 
3110
3149
  // src/adapters/ai-sdk-text.ts
3111
- var import_anthropic = require("@ai-sdk/anthropic");
3150
+ var import_anthropic = require("@ai-sdk/amazon-bedrock/anthropic");
3151
+ var import_anthropic2 = require("@ai-sdk/anthropic");
3112
3152
  var import_google = require("@ai-sdk/google");
3113
3153
  var import_openai = require("@ai-sdk/openai");
3114
3154
  var import_openai_compatible = require("@ai-sdk/openai-compatible");
@@ -3159,16 +3199,47 @@ function detectTextProvider(modelId) {
3159
3199
  message: `ai-sdk-text cannot detect a provider for "${modelId}". Set providerOptions.provider to one of ${TEXT_PROVIDERS.join(", ")}.`
3160
3200
  });
3161
3201
  }
3162
- function textKeyName(model, _routing) {
3163
- return textProviderOf(model);
3202
+ function textKeyName(model, transport) {
3203
+ const provider = textProviderOf(model);
3204
+ return provider === "anthropic" && transport === "bedrock" ? "aws" : provider;
3164
3205
  }
3165
3206
  function textProviderOf(model) {
3166
3207
  const explicit = model.providerOptions?.["modelkit"]?.["provider"];
3167
3208
  return isTextProvider(explicit) ? explicit : detectTextProvider(model.providerModelId);
3168
3209
  }
3169
- function textUsesCloudflareGateway(model, routing) {
3210
+ function textUsesCloudflareGateway(model, routing, transport) {
3170
3211
  if (routing.text !== "cloudflare") return false;
3171
- return GATEWAY_SLUGS[textProviderOf(model)] !== void 0;
3212
+ const provider = textProviderOf(model);
3213
+ if (provider === "anthropic" && transport === "bedrock") return false;
3214
+ return GATEWAY_SLUGS[provider] !== void 0;
3215
+ }
3216
+ var BEDROCK_DATED_MODEL_IDS = {
3217
+ "claude-haiku-4-5": "anthropic.claude-haiku-4-5-20251001-v1:0",
3218
+ "claude-haiku-4-5-20251001": "anthropic.claude-haiku-4-5-20251001-v1:0",
3219
+ "claude-sonnet-4-5": "anthropic.claude-sonnet-4-5-20250929-v1:0",
3220
+ "claude-sonnet-4-5-20250929": "anthropic.claude-sonnet-4-5-20250929-v1:0"
3221
+ };
3222
+ var BEDROCK_PROFILE_PREFIXES = ["us.", "global.", "eu.", "jp.", "au.", "apac."];
3223
+ function toBedrockModelId(model, routing) {
3224
+ const profile = routing.bedrockProfile ?? "us";
3225
+ const override = model.providerOptions?.["modelkit"]?.["bedrockModelId"];
3226
+ if (typeof override === "string" && override.length > 0) {
3227
+ if (BEDROCK_PROFILE_PREFIXES.some((prefix) => override.startsWith(prefix))) return override;
3228
+ if (override.startsWith("anthropic.")) return `${profile}.${override}`;
3229
+ return override;
3230
+ }
3231
+ const id = model.providerModelId;
3232
+ if (BEDROCK_PROFILE_PREFIXES.some((prefix) => id.startsWith(prefix))) return id;
3233
+ const dated = BEDROCK_DATED_MODEL_IDS[id];
3234
+ if (dated) return `${profile}.${dated}`;
3235
+ if (id.startsWith("claude-")) return `${profile}.anthropic.${id}`;
3236
+ throw new ModelKitError({
3237
+ code: "unsupported_option",
3238
+ message: `No Bedrock model id for "${id}". Set providerOptions.modelkit.bedrockModelId on the catalog row.`,
3239
+ provider: "ai-sdk-text",
3240
+ model: model.id,
3241
+ retryable: false
3242
+ });
3172
3243
  }
3173
3244
  function nativeClientFor(provider) {
3174
3245
  switch (provider) {
@@ -3193,14 +3264,14 @@ function nativeClientFor(provider) {
3193
3264
  function resolveTextRoute(ctx) {
3194
3265
  const provider = textProviderOf(ctx.model);
3195
3266
  const mode = ctx.routing.text ?? "direct";
3196
- if (provider === "anthropic" && ctx.routing.anthropic === "bedrock") {
3197
- throw new ModelKitError({
3198
- code: "unsupported_option",
3199
- message: "ai-sdk-text has no Bedrock transport (@ai-sdk/amazon-bedrock is not installed). Pass the Bedrock model through catalogOverride with providerOptions.bedrockModel and resolve it outside the SDK.",
3200
- provider: "ai-sdk-text",
3201
- model: ctx.model.id,
3202
- retryable: false
3203
- });
3267
+ if (provider === "anthropic" && ctx.anthropicTransport === "bedrock") {
3268
+ return {
3269
+ provider,
3270
+ client: "bedrock-anthropic",
3271
+ modelId: toBedrockModelId(ctx.model, ctx.routing),
3272
+ keyName: "aws",
3273
+ canonicalModelId: ctx.model.id
3274
+ };
3204
3275
  }
3205
3276
  const compatBase = OPENAI_COMPATIBLE_BASE_URLS[provider];
3206
3277
  const route2 = {
@@ -3244,6 +3315,70 @@ function missingKey(ctx, name) {
3244
3315
  retryable: false
3245
3316
  });
3246
3317
  }
3318
+ function bedrockMiddleware(ctx, canonicalModelId) {
3319
+ let warned = false;
3320
+ return {
3321
+ specificationVersion: "v3",
3322
+ transformParams: async ({ params }) => {
3323
+ const anthropic = params.providerOptions?.["anthropic"];
3324
+ if (typeof anthropic !== "object" || anthropic === null || !("cacheControl" in anthropic))
3325
+ return params;
3326
+ if (!warned) {
3327
+ warned = true;
3328
+ ctx.logger.warn(
3329
+ `[modelkit] ${canonicalModelId} drops call-level providerOptions.anthropic.cacheControl: not supported on Bedrock, use message-level cacheControl`
3330
+ );
3331
+ }
3332
+ const { cacheControl: _dropped, ...rest } = anthropic;
3333
+ return {
3334
+ ...params,
3335
+ providerOptions: { ...params.providerOptions, anthropic: rest }
3336
+ };
3337
+ },
3338
+ // Bedrock echoes its own id for dated models (anthropic.claude-haiku-4-5-20251001-v1:0),
3339
+ // and consumers bill on response.modelId.
3340
+ wrapGenerate: async ({ doGenerate }) => {
3341
+ const result = await doGenerate();
3342
+ if (!result.response) return result;
3343
+ return { ...result, response: { ...result.response, modelId: canonicalModelId } };
3344
+ },
3345
+ wrapStream: async ({ doStream }) => {
3346
+ const result = await doStream();
3347
+ return {
3348
+ ...result,
3349
+ stream: result.stream.pipeThrough(
3350
+ new TransformStream({
3351
+ transform(part, controller) {
3352
+ controller.enqueue(
3353
+ part.type === "response-metadata" && part.modelId !== void 0 ? { ...part, modelId: canonicalModelId } : part
3354
+ );
3355
+ }
3356
+ })
3357
+ )
3358
+ };
3359
+ }
3360
+ };
3361
+ }
3362
+ function bedrockLanguageModel(ctx, route2) {
3363
+ const aws = ctx.keys.aws;
3364
+ if (!hasAwsCredentials(aws)) throw missingKey(ctx, "aws");
3365
+ const canonical = route2.canonicalModelId ?? ctx.model.id;
3366
+ const inner = (0, import_anthropic.createBedrockAnthropic)({
3367
+ region: aws.region,
3368
+ fetch: ctx.fetch,
3369
+ ...aws.apiKey ? { apiKey: aws.apiKey } : aws.credentialProvider ? { credentialProvider: aws.credentialProvider } : aws.accessKeyId !== void 0 && aws.secretAccessKey !== void 0 ? {
3370
+ accessKeyId: aws.accessKeyId,
3371
+ secretAccessKey: aws.secretAccessKey,
3372
+ ...aws.sessionToken !== void 0 ? { sessionToken: aws.sessionToken } : {}
3373
+ } : {}
3374
+ })(route2.modelId);
3375
+ return (0, import_ai2.wrapLanguageModel)({
3376
+ model: inner,
3377
+ middleware: bedrockMiddleware(ctx, canonical),
3378
+ modelId: canonical,
3379
+ providerId: "anthropic.messages"
3380
+ });
3381
+ }
3247
3382
  function minimaxDefaults(params) {
3248
3383
  return params.responseFormat?.type === "json" ? { reasoning_split: true, thinking: { type: "disabled" } } : { reasoning_split: true };
3249
3384
  }
@@ -3282,14 +3417,17 @@ var aiSdkTextAdapter = {
3282
3417
  id: "ai-sdk-text",
3283
3418
  languageModel(ctx) {
3284
3419
  const route2 = resolveTextRoute(ctx);
3285
- const apiKey = ctx.keys[route2.keyName];
3286
- if (!apiKey) throw missingKey(ctx, route2.keyName);
3420
+ if (route2.client === "bedrock-anthropic") return bedrockLanguageModel(ctx, route2);
3421
+ const { keyName } = route2;
3422
+ if (keyName === "aws") throw missingKey(ctx, keyName);
3423
+ const apiKey = ctx.keys[keyName];
3424
+ if (!apiKey) throw missingKey(ctx, keyName);
3287
3425
  const settings = { apiKey, fetch: ctx.fetch };
3288
3426
  if (route2.baseURL !== void 0) settings.baseURL = route2.baseURL;
3289
3427
  if (route2.headers !== void 0) settings.headers = route2.headers;
3290
3428
  switch (route2.client) {
3291
3429
  case "anthropic":
3292
- return (0, import_anthropic.createAnthropic)(settings)(route2.modelId);
3430
+ return (0, import_anthropic2.createAnthropic)(settings)(route2.modelId);
3293
3431
  case "google":
3294
3432
  return (0, import_google.createGoogleGenerativeAI)(settings)(route2.modelId);
3295
3433
  case "xai":
@@ -3322,14 +3460,43 @@ var aiSdkTextAdapter = {
3322
3460
  }
3323
3461
  };
3324
3462
 
3463
+ // src/anthropic-transport.ts
3464
+ var ANTHROPIC_TRANSPORTS = ["direct", "bedrock"];
3465
+ function isAnthropicTransport(value) {
3466
+ return ANTHROPIC_TRANSPORTS.some((transport) => transport === value);
3467
+ }
3468
+ function preferredAnthropicTransport(model, routing) {
3469
+ const row = model.providerOptions?.["modelkit"]?.["transport"];
3470
+ if (isAnthropicTransport(row)) return row;
3471
+ return routing.anthropic ?? "direct";
3472
+ }
3473
+ function resolveAnthropicTransport(model, config, tier, warn) {
3474
+ if (model.adapter !== "ai-sdk-text" || textProviderOf(model) !== "anthropic") return void 0;
3475
+ const rowValue = model.providerOptions?.["modelkit"]?.["transport"];
3476
+ if (rowValue !== void 0 && !isAnthropicTransport(rowValue))
3477
+ warn(
3478
+ `[modelkit] ${model.id} has an unknown providerOptions.modelkit.transport "${String(rowValue)}"; ignoring it`
3479
+ );
3480
+ const preferred = preferredAnthropicTransport(model, config.routing ?? {});
3481
+ const usable = (transport) => {
3482
+ const name = transport === "bedrock" ? "aws" : "anthropic";
3483
+ return hasKey(config.keys.pro, name) || tier === "enterprise" && hasKey(config.keys.enterprise, name);
3484
+ };
3485
+ if (usable(preferred)) return preferred;
3486
+ const other = preferred === "bedrock" ? "direct" : "bedrock";
3487
+ if (!usable(other)) return preferred;
3488
+ warn(`[modelkit] ${model.id}: no credentials for the ${preferred} transport; using ${other}`);
3489
+ return other;
3490
+ }
3491
+
3325
3492
  // src/client.ts
3326
- function requiredKeys(model, config) {
3493
+ function requiredKeys(model, config, transport) {
3327
3494
  if (model.adapter === "fal" && config.routing?.fal === "cloudflare-passthrough")
3328
3495
  return ["cloudflare"];
3329
3496
  if (model.adapter === "ai-sdk-text") {
3330
3497
  const routing = config.routing ?? {};
3331
- const names = [textKeyName(model, routing)];
3332
- if (textUsesCloudflareGateway(model, routing)) names.push("cloudflare");
3498
+ const names = [textKeyName(model, transport)];
3499
+ if (textUsesCloudflareGateway(model, routing, transport)) names.push("cloudflare");
3333
3500
  return names;
3334
3501
  }
3335
3502
  return ADAPTER_KEYS[model.adapter];
@@ -3416,6 +3583,12 @@ function createModelKit(config, adapters) {
3416
3583
  const catalog = config.catalogOverride ? liveCatalog.withOverrides(config.catalogOverride) : liveCatalog;
3417
3584
  const logger = config.logger ?? console;
3418
3585
  const fetchImpl = config.fetch ?? globalThis.fetch;
3586
+ const transportWarnings = /* @__PURE__ */ new Set();
3587
+ function warnOnce(message) {
3588
+ if (transportWarnings.has(message)) return;
3589
+ transportWarnings.add(message);
3590
+ logger.warn(message);
3591
+ }
3419
3592
  function context(model, call, warnings) {
3420
3593
  const adapter = adapters[model.adapter];
3421
3594
  if (!adapter)
@@ -3424,7 +3597,8 @@ function createModelKit(config, adapters) {
3424
3597
  message: `No adapter registered for ${model.adapter}`,
3425
3598
  model: model.id
3426
3599
  });
3427
- const names = requiredKeys(model, config);
3600
+ const transport = resolveAnthropicTransport(model, config, call.tier, warnOnce);
3601
+ const names = requiredKeys(model, config, transport);
3428
3602
  if (call.tier === "enterprise") {
3429
3603
  const missing = missingEnterpriseKeys(config, names);
3430
3604
  if (missing.length > 0)
@@ -3443,6 +3617,7 @@ function createModelKit(config, adapters) {
3443
3617
  call,
3444
3618
  warnings
3445
3619
  };
3620
+ if (transport) ctx.anthropicTransport = transport;
3446
3621
  if (config.endpoints) ctx.endpoints = config.endpoints;
3447
3622
  if (call.signal) ctx.signal = call.signal;
3448
3623
  return { adapter, ctx };
@@ -3777,7 +3952,7 @@ function createModelKit(config, adapters) {
3777
3952
  async prepareMediaPart(modelId, ref, mediaType, call = {}) {
3778
3953
  const model = catalog.require(modelId);
3779
3954
  const { adapter, ctx } = context(model, call, []);
3780
- const fileStore = model.adapter === "ai-sdk-text" && textKeyName(model, config.routing ?? {}) === "google" ? adapters["google-genai"] : adapter;
3955
+ const fileStore = model.adapter === "ai-sdk-text" && textKeyName(model) === "google" ? adapters["google-genai"] : adapter;
3781
3956
  if (fileStore?.prepareMediaPart) return fileStore.prepareMediaPart(ref, mediaType, ctx);
3782
3957
  return "url" in ref ? { type: "file", data: ref.url, mediaType } : { type: "file", data: ref.bytes, mediaType };
3783
3958
  },
@@ -7569,6 +7744,7 @@ var describe = (modelId) => createDefaultModelKit().describe(modelId);
7569
7744
  generateVideo,
7570
7745
  googleGenaiAdapter,
7571
7746
  googleImageAdapter,
7747
+ hasAwsCredentials,
7572
7748
  higgsfieldAdapter,
7573
7749
  isListed,
7574
7750
  isListedOn,