@braedonsaunders/appkit-ai 1.0.2 → 1.0.3
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/client.js +1 -1
- package/client.js.map +1 -1
- package/models.js +1 -1
- package/models.js.map +1 -1
- package/package.json +2 -2
package/client.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createAnthropic } from '@ai-sdk/anthropic';
|
|
|
4
4
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
5
5
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
6
6
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
7
|
-
import { resolvePublicHost, secureFetch, validateOutboundRequestConfiguration, } from '@braedonsaunders/appkit-
|
|
7
|
+
import { resolvePublicHost, secureFetch, validateOutboundRequestConfiguration, } from '@braedonsaunders/appkit-egress-proxy/secure-fetch';
|
|
8
8
|
import { generateText } from 'ai';
|
|
9
9
|
const MAX_AI_REQUEST_BYTES = 16 * 1024 * 1024;
|
|
10
10
|
const MAX_AI_RESPONSE_BYTES = 16 * 1024 * 1024;
|
package/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kFAAkF;AAElF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,oCAAoC,GACrC,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,YAAY,EAAsB,MAAM,IAAI,CAAA;AAErD,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAC7C,MAAM,qBAAqB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAC9C;;;;;;;;;;;;GAYG;AACH,MAAM,qBAAqB,GAAG,OAAO,CAAA;AAsErC;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAmB;IAC/C;QACE,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,2BAA2B;QACjC,KAAK,EAAE,mBAAmB;QAC1B,OAAO,EAAE,UAAU;QACnB,iBAAiB,EAAE,IAAI;KACxB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,QAAQ;QACf,OAAO,EAAE,MAAM;QACf,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,iBAAiB;QACxB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,gBAAgB;QACvB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,iCAAiC;QACxC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,8BAA8B;QACvC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,4BAA4B;QAClC,KAAK,EAAE,6BAA6B;QACpC,OAAO,EAAE,SAAS;QAClB,SAAS,EAAE,4EAA4E;QACvF,2EAA2E;QAC3E,yEAAyE;QACzE,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,gCAAgC;QACzC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,yBAAyB;QAChC,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,qBAAqB;QAC9B,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,0BAA0B;QACnC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,eAAe;QACtB,OAAO,EAAE,MAAM;QACf,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,2BAA2B;QACpC,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,sBAAsB;QAC7B,OAAO,EAAE,sBAAsB;QAC/B,iBAAiB,EAAE,KAAK;KACzB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,4BAA4B;QACnC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,IAAI;QACrB,IAAI,EAAE,EAAE;QACR,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,uBAAuB;QAChC,SAAS,EACP,yIAAyI;QAC3I,iBAAiB,EAAE,KAAK;KACzB;CACF,CAAA;AAED,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAGlF,CAAA;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAoB;IAC/C,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,IAAI,GAAG,GAAG,KAAK,CAAC,MAAM,CAAA;IACtB,OAAO,GAAG,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE;QAAE,GAAG,IAAI,CAAC,CAAA;IAC5D,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAoB,EACpB,UAAqC;IAErC,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;IACnC,MAAM,GAAG,GAAG,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACpC,IAAI,IAAI,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;QACtC,IAAI,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,sCAAsC,CAAC,CAAA;QAC7E,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,IAAI,CAAC,eAAe;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;QACjF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,SAAc,CAAA;IAClB,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;IAClD,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAA;IACjF,CAAC;IACD,MAAM,SAAS,GAAG,oCAAoC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAA;IACrE,MAAM,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IAE3C,MAAM,IAAI,GAAG,sBAAsB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IACvD,SAAS,CAAC,QAAQ,GAAG,IAAI,IAAI,GAAG,CAAA;IAChC,MAAM,SAAS,GAAG,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IACxD,OAAO,SAAS,IAAI,SAAS,CAAC,MAAM,CAAA;AACtC,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,OAAgB;IACpD,IAAI,CAAC,OAAO,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IACnC,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAA;IACpE,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,cAAc,GAAG,oBAAoB,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,4BAA4B,oBAAoB,SAAS,CAAC,CAAA;IAC5E,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAA;IACvC,MAAM,MAAM,GAAiB,EAAE,CAAA;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO;gBAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAA;YACvD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YAC3C,IAAI,IAAI;gBAAE,MAAK;YACf,KAAK,IAAI,KAAK,CAAC,UAAU,CAAA;YACzB,IAAI,KAAK,GAAG,oBAAoB,EAAE,CAAC;gBACjC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAA;gBACrB,MAAM,IAAI,KAAK,CAAC,4BAA4B,oBAAoB,SAAS,CAAC,CAAA;YAC5E,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAA;IACtB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;IAClC,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QACvB,MAAM,IAAI,KAAK,CAAC,UAAU,CAAA;IAC5B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,MAAM,aAAa,GAA4B,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAC1E,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;IAC3C,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,oBAAoB,CAAC,CAAA;IAClE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,sBAAsB,CAAC,OAAO,CAAC,CAAA;IAClD,OAAO,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9B,MAAM,EAAE,MAA8D;QACtE,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI;QACJ,SAAS,EAAE,qBAAqB;QAChC,eAAe,EAAE,oBAAoB;QACrC,gBAAgB,EAAE,qBAAqB;QACvC,YAAY,EAAE,CAAC;QACf,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,UAAU,YAAY,CAAC,QAAoB,EAAE,IAAe;IAChE,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,SAAS,CAAA;IAC/D,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gCAAgC,CAAC,MAAmC;IAClF,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACzB,OAAO,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,iBAAiB,IAAI,KAAK,CAAA;AACnE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAmC;IAChE,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAC3C,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IACvB,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE;QAAE,OAAO,KAAK,CAAA;IACjE,OAAO,IAAI,CAAA;AACb,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CACtB,MAAmC,EACnC,OAAkB,MAAM;IAExB,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC3C,MAAM,OAAO,GACX,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAClG,8EAA8E;IAC9E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAEzB,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,WAAW;YACd,OAAO,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QAC5D,KAAK,QAAQ;YACX,OAAO,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QACzD,KAAK,QAAQ;YACX,OAAO,wBAAwB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QACrE,KAAK,mBAAmB,CAAC,CAAC,CAAC;YACzB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,CAAA;YACtD,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAA;YACzB,OAAO,sBAAsB,CAAC;gBAC5B,IAAI,EAAE,IAAI,CAAC,KAAK;gBAChB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO;gBACP,KAAK,EAAE,aAAa;aACrB,CAAC,CAAC,OAAO,CAAC,CAAA;QACb,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtB,IAAI,GAAG,iBAAiB,CAAA;IAC1C;QACE,KAAK,CAAC,+CAA+C,CAAC,CAAA;IACxD,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAmC;IAEnC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,uEAAuE;SACjF,CAAA;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,gCAAgC,EAAE,CAAC,CAAA;QACxF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,kCAAkC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;IAC9F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAA;IACjG,CAAC;AACH,CAAC","sourcesContent":["// Config-driven AI client. Provider + API key + models (+ base URL) are passed\n// in per call (resolved from per-tenant settings), NOT read from the environment.\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport {\n resolvePublicHost,\n secureFetch,\n validateOutboundRequestConfiguration,\n} from '@braedonsaunders/appkit-sync/egress'\nimport { generateText, type LanguageModel } from 'ai'\n\nconst MAX_AI_REQUEST_BYTES = 16 * 1024 * 1024\nconst MAX_AI_RESPONSE_BYTES = 16 * 1024 * 1024\n/**\n * How long one model call may take before the transport gives up on it.\n *\n * This is not a budget — the caller's own loop governs how much work an agent\n * may do — it is only the point past which a socket is assumed dead. Two\n * minutes turned out to be inside the normal range for a reasoning model\n * answering with tool calls: measured runs on several hosted models spent\n * longer than that on a single step, and each one died on this timeout having\n * produced nothing, billed nothing, and lost every step that came before it.\n *\n * Ten minutes is comfortably past the slowest observed step while still\n * catching a connection that has genuinely gone away.\n */\nconst AI_REQUEST_TIMEOUT_MS = 600_000\n\nexport type AiProvider =\n | 'anthropic'\n | 'openai'\n | 'google'\n | 'openrouter'\n | 'groq'\n | 'xai'\n | 'deepseek'\n | 'mistral'\n | 'custom'\n\nexport type ModelTier = 'fast' | 'smart'\n\n/**\n * Platform-wide AI policy governing per-tenant overrides (mirrors the email/SMS\n * policy modes). 'disabled' is a global kill switch; 'global_only' forces the\n * platform provider for every tenant; 'tenant_optional' lets each tenant use its\n * own provider and falls back to the platform default.\n */\nexport type AiPolicyMode = 'tenant_optional' | 'global_only' | 'disabled'\n\nexport type AiConfig = {\n provider: AiProvider\n apiKey: string\n modelFast?: string | null\n modelSmart?: string | null\n /**\n * Endpoint for OpenAI-compatible providers. Required for `custom`; for the\n * named compatible providers it is an optional override of the built-in URL.\n */\n baseUrl?: string | null\n /**\n * Organization (tenant) identity for prompt grounding, so generated content\n * uses the real org name instead of a placeholder. Populated by\n * `getTenantAiConfig`; content-generation paths inject it into the system\n * prompt, analysis/vision paths ignore it.\n */\n org?: { name: string } | null\n}\n\n// How a provider's language model is constructed. The OpenAI-compatible kind\n// covers OpenRouter, Groq, xAI, DeepSeek, Mistral and any user `custom` endpoint.\ntype ProviderKind = 'anthropic' | 'openai' | 'google' | 'openai-compatible'\n\nexport type ProviderSpec = {\n value: AiProvider\n label: string\n kind: ProviderKind\n /** Built-in endpoint for a named OpenAI-compatible provider (null otherwise). */\n baseUrl: string | null\n /** True when the tenant MUST supply their own base URL (i.e. `custom`). */\n requiresBaseUrl: boolean\n /** Default fast/smart model ids (placeholders + fallbacks). Empty for `custom`. */\n fast: string\n smart: string\n /** Placeholder shown in the API-key field. */\n keyHint: string\n /** Optional note about the model-id format for this provider. */\n modelHint?: string\n /**\n * True when this provider's API accepts IMAGE content inside a tool result\n * (Anthropic does; OpenAI's and Google's function/tool results are text/JSON\n * only). Gates vision tools that return rendered page images to the model —\n * see `providerSupportsImageToolResults`.\n */\n visionToolResults: boolean\n}\n\n/**\n * Provider catalogue — single source of truth for the settings UI, the model\n * factory and config validation. Add a provider here and it lights up everywhere.\n */\nexport const AI_PROVIDER_SPECS: ProviderSpec[] = [\n {\n value: 'anthropic',\n label: 'Anthropic — Claude',\n kind: 'anthropic',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'claude-haiku-4-5-20251001',\n smart: 'claude-sonnet-4-6',\n keyHint: 'sk-ant-…',\n visionToolResults: true,\n },\n {\n value: 'openai',\n label: 'OpenAI — GPT',\n kind: 'openai',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gpt-4o-mini',\n smart: 'gpt-4o',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'google',\n label: 'Google — Gemini',\n kind: 'google',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gemini-2.5-flash',\n smart: 'gemini-2.5-pro',\n keyHint: 'AIza…',\n visionToolResults: false,\n },\n {\n value: 'openrouter',\n label: 'OpenRouter — any model, one key',\n kind: 'openai-compatible',\n baseUrl: 'https://openrouter.ai/api/v1',\n requiresBaseUrl: false,\n fast: 'anthropic/claude-3.5-haiku',\n smart: 'anthropic/claude-3.5-sonnet',\n keyHint: 'sk-or-…',\n modelHint: 'Use vendor/model slugs, e.g. anthropic/claude-3.5-sonnet or openai/gpt-4o.',\n // OpenRouter proxies many vendors over an OpenAI-compatible surface; image\n // tool-results aren't reliably supported, so keep vision tools off here.\n visionToolResults: false,\n },\n {\n value: 'groq',\n label: 'Groq — fast inference',\n kind: 'openai-compatible',\n baseUrl: 'https://api.groq.com/openai/v1',\n requiresBaseUrl: false,\n fast: 'llama-3.1-8b-instant',\n smart: 'llama-3.3-70b-versatile',\n keyHint: 'gsk_…',\n visionToolResults: false,\n },\n {\n value: 'xai',\n label: 'xAI — Grok',\n kind: 'openai-compatible',\n baseUrl: 'https://api.x.ai/v1',\n requiresBaseUrl: false,\n fast: 'grok-2-1212',\n smart: 'grok-2-vision-1212',\n keyHint: 'xai-…',\n visionToolResults: false,\n },\n {\n value: 'deepseek',\n label: 'DeepSeek',\n kind: 'openai-compatible',\n baseUrl: 'https://api.deepseek.com',\n requiresBaseUrl: false,\n fast: 'deepseek-chat',\n smart: 'deepseek-chat',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'mistral',\n label: 'Mistral',\n kind: 'openai-compatible',\n baseUrl: 'https://api.mistral.ai/v1',\n requiresBaseUrl: false,\n fast: 'mistral-small-latest',\n smart: 'mistral-large-latest',\n keyHint: 'Your Mistral API key',\n visionToolResults: false,\n },\n {\n value: 'custom',\n label: 'Custom (OpenAI-compatible)',\n kind: 'openai-compatible',\n baseUrl: null,\n requiresBaseUrl: true,\n fast: '',\n smart: '',\n keyHint: 'Your provider API key',\n modelHint:\n 'Use a public HTTPS OpenAI-compatible endpoint (for example Together, Fireworks, Perplexity, or hosted vLLM) and set explicit model ids.',\n visionToolResults: false,\n },\n]\n\nconst SPEC_BY_VALUE = Object.fromEntries(AI_PROVIDER_SPECS.map((s) => [s.value, s])) as Record<\n AiProvider,\n ProviderSpec\n>\n\nexport function isAiProvider(value: unknown): value is AiProvider {\n return typeof value === 'string' && Object.hasOwn(SPEC_BY_VALUE, value)\n}\n\nexport function providerSpec(provider: AiProvider): ProviderSpec {\n return SPEC_BY_VALUE[provider]\n}\n\nfunction withoutTrailingSlashes(value: string): string {\n let end = value.length\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1\n return value.slice(0, end)\n}\n\n/**\n * Validate and canonicalize a persisted AI endpoint override. Runtime requests\n * repeat the public-DNS check immediately before opening each socket.\n */\nexport async function validateAiBaseUrl(\n provider: AiProvider,\n rawBaseUrl: string | null | undefined,\n): Promise<string | null> {\n const spec = providerSpec(provider)\n const raw = rawBaseUrl?.trim() ?? ''\n if (spec.kind !== 'openai-compatible') {\n if (raw) throw new Error(`${spec.label} does not support a custom base URL.`)\n return null\n }\n if (!raw) {\n if (spec.requiresBaseUrl) throw new Error('A public HTTPS base URL is required.')\n return null\n }\n\n let submitted: URL\n try {\n submitted = new URL(raw)\n } catch {\n throw new Error('The AI base URL is not valid.')\n }\n if (submitted.search || submitted.hash) {\n throw new Error('The AI base URL must not include a query string or fragment.')\n }\n const validated = validateOutboundRequestConfiguration(submitted).url\n await resolvePublicHost(validated.hostname)\n\n const path = withoutTrailingSlashes(validated.pathname)\n validated.pathname = path || '/'\n const canonical = withoutTrailingSlashes(validated.href)\n return canonical || validated.origin\n}\n\nasync function readBoundedRequestBody(request: Request): Promise<Uint8Array | undefined> {\n if (!request.body) return undefined\n const declaredLength = Number(request.headers.get('content-length'))\n if (Number.isFinite(declaredLength) && declaredLength > MAX_AI_REQUEST_BYTES) {\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n try {\n for (;;) {\n if (request.signal.aborted) throw request.signal.reason\n const { done, value } = await reader.read()\n if (done) break\n total += value.byteLength\n if (total > MAX_AI_REQUEST_BYTES) {\n await reader.cancel()\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n const body = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n body.set(chunk, offset)\n offset += chunk.byteLength\n }\n return body\n}\n\n/** Socket-pinned transport for tenant-configurable OpenAI-compatible endpoints. */\nexport const secureAiFetch: typeof globalThis.fetch = async (input, init) => {\n const request = new Request(input, init)\n const method = request.method.toUpperCase()\n if (!['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n throw new Error(`AI request method ${method} is not supported.`)\n }\n const body = await readBoundedRequestBody(request)\n return secureFetch(request.url, {\n method: method as 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n headers: request.headers,\n body,\n timeoutMs: AI_REQUEST_TIMEOUT_MS,\n maxRequestBytes: MAX_AI_REQUEST_BYTES,\n maxResponseBytes: MAX_AI_RESPONSE_BYTES,\n maxRedirects: 2,\n signal: request.signal,\n })\n}\n\nexport function defaultModel(provider: AiProvider, tier: ModelTier): string {\n const spec = SPEC_BY_VALUE[provider] ?? SPEC_BY_VALUE.anthropic\n return tier === 'smart' ? spec.smart : spec.fast\n}\n\n/**\n * Whether the configured provider accepts IMAGE content in a tool result — the\n * capability that lets a tool hand rendered PDF pages back to the model for\n * vision reading. Currently Anthropic only; other providers' tool/function\n * results are text/JSON only, so exposing such a tool to them would break the\n * agent turn. Used to gate the assistant's `view_document_pages` tool.\n */\nexport function providerSupportsImageToolResults(config: AiConfig | null | undefined): boolean {\n if (!config) return false\n return SPEC_BY_VALUE[config.provider]?.visionToolResults ?? false\n}\n\nexport function isAiConfigured(config: AiConfig | null | undefined): config is AiConfig {\n if (!config || !config.apiKey) return false\n const spec = SPEC_BY_VALUE[config.provider]\n if (!spec) return false\n if (spec.requiresBaseUrl && !config.baseUrl?.trim()) return false\n return true\n}\n\n/** Resolve a language model from a tenant's config, or null when not configured. */\nexport function getModel(\n config: AiConfig | null | undefined,\n tier: ModelTier = 'fast',\n): LanguageModel | null {\n if (!isAiConfigured(config)) return null\n const spec = SPEC_BY_VALUE[config.provider]\n const modelId =\n (tier === 'smart' ? config.modelSmart : config.modelFast) || defaultModel(config.provider, tier)\n // `custom` has no default model — without an explicit one, AI stays disabled.\n if (!modelId) return null\n\n switch (spec.kind) {\n case 'anthropic':\n return createAnthropic({ apiKey: config.apiKey })(modelId)\n case 'openai':\n return createOpenAI({ apiKey: config.apiKey })(modelId)\n case 'google':\n return createGoogleGenerativeAI({ apiKey: config.apiKey })(modelId)\n case 'openai-compatible': {\n const baseURL = config.baseUrl?.trim() || spec.baseUrl\n if (!baseURL) return null\n return createOpenAICompatible({\n name: spec.value,\n apiKey: config.apiKey,\n baseURL,\n fetch: secureAiFetch,\n })(modelId)\n }\n }\n}\n\nexport class AIDisabledError extends Error {\n override readonly name = 'AIDisabledError'\n constructor() {\n super('No AI provider is available for this request.')\n }\n}\n\n/** Live test of a config — sends a tiny prompt and reports success/failure. */\nexport async function pingModel(\n config: AiConfig | null | undefined,\n): Promise<{ ok: boolean; message: string }> {\n const model = getModel(config, 'fast')\n if (!model) {\n return {\n ok: false,\n message: 'Not configured yet — check the provider, API key, model and base URL.',\n }\n }\n try {\n const { text } = await generateText({ model, prompt: 'Reply with the single word: ok' })\n return { ok: true, message: `Connected — the model replied “${text.trim().slice(0, 24)}”.` }\n } catch (e) {\n return { ok: false, message: e instanceof Error ? e.message.slice(0, 180) : 'Request failed.' }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["// Config-driven AI client. Provider + API key + models (+ base URL) are passed\n// in per call (resolved from per-tenant settings), NOT read from the environment.\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport {\n resolvePublicHost,\n secureFetch,\n validateOutboundRequestConfiguration,\n} from '@braedonsaunders/appkit-egress-proxy/secure-fetch'\nimport { generateText, type LanguageModel } from 'ai'\n\nconst MAX_AI_REQUEST_BYTES = 16 * 1024 * 1024\nconst MAX_AI_RESPONSE_BYTES = 16 * 1024 * 1024\n/**\n * How long one model call may take before the transport gives up on it.\n *\n * This is not a budget — the caller's own loop governs how much work an agent\n * may do — it is only the point past which a socket is assumed dead. Two\n * minutes turned out to be inside the normal range for a reasoning model\n * answering with tool calls: measured runs on several hosted models spent\n * longer than that on a single step, and each one died on this timeout having\n * produced nothing, billed nothing, and lost every step that came before it.\n *\n * Ten minutes is comfortably past the slowest observed step while still\n * catching a connection that has genuinely gone away.\n */\nconst AI_REQUEST_TIMEOUT_MS = 600_000\n\nexport type AiProvider =\n | 'anthropic'\n | 'openai'\n | 'google'\n | 'openrouter'\n | 'groq'\n | 'xai'\n | 'deepseek'\n | 'mistral'\n | 'custom'\n\nexport type ModelTier = 'fast' | 'smart'\n\n/**\n * Platform-wide AI policy governing per-tenant overrides (mirrors the email/SMS\n * policy modes). 'disabled' is a global kill switch; 'global_only' forces the\n * platform provider for every tenant; 'tenant_optional' lets each tenant use its\n * own provider and falls back to the platform default.\n */\nexport type AiPolicyMode = 'tenant_optional' | 'global_only' | 'disabled'\n\nexport type AiConfig = {\n provider: AiProvider\n apiKey: string\n modelFast?: string | null\n modelSmart?: string | null\n /**\n * Endpoint for OpenAI-compatible providers. Required for `custom`; for the\n * named compatible providers it is an optional override of the built-in URL.\n */\n baseUrl?: string | null\n /**\n * Organization (tenant) identity for prompt grounding, so generated content\n * uses the real org name instead of a placeholder. Populated by\n * `getTenantAiConfig`; content-generation paths inject it into the system\n * prompt, analysis/vision paths ignore it.\n */\n org?: { name: string } | null\n}\n\n// How a provider's language model is constructed. The OpenAI-compatible kind\n// covers OpenRouter, Groq, xAI, DeepSeek, Mistral and any user `custom` endpoint.\ntype ProviderKind = 'anthropic' | 'openai' | 'google' | 'openai-compatible'\n\nexport type ProviderSpec = {\n value: AiProvider\n label: string\n kind: ProviderKind\n /** Built-in endpoint for a named OpenAI-compatible provider (null otherwise). */\n baseUrl: string | null\n /** True when the tenant MUST supply their own base URL (i.e. `custom`). */\n requiresBaseUrl: boolean\n /** Default fast/smart model ids (placeholders + fallbacks). Empty for `custom`. */\n fast: string\n smart: string\n /** Placeholder shown in the API-key field. */\n keyHint: string\n /** Optional note about the model-id format for this provider. */\n modelHint?: string\n /**\n * True when this provider's API accepts IMAGE content inside a tool result\n * (Anthropic does; OpenAI's and Google's function/tool results are text/JSON\n * only). Gates vision tools that return rendered page images to the model —\n * see `providerSupportsImageToolResults`.\n */\n visionToolResults: boolean\n}\n\n/**\n * Provider catalogue — single source of truth for the settings UI, the model\n * factory and config validation. Add a provider here and it lights up everywhere.\n */\nexport const AI_PROVIDER_SPECS: ProviderSpec[] = [\n {\n value: 'anthropic',\n label: 'Anthropic — Claude',\n kind: 'anthropic',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'claude-haiku-4-5-20251001',\n smart: 'claude-sonnet-4-6',\n keyHint: 'sk-ant-…',\n visionToolResults: true,\n },\n {\n value: 'openai',\n label: 'OpenAI — GPT',\n kind: 'openai',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gpt-4o-mini',\n smart: 'gpt-4o',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'google',\n label: 'Google — Gemini',\n kind: 'google',\n baseUrl: null,\n requiresBaseUrl: false,\n fast: 'gemini-2.5-flash',\n smart: 'gemini-2.5-pro',\n keyHint: 'AIza…',\n visionToolResults: false,\n },\n {\n value: 'openrouter',\n label: 'OpenRouter — any model, one key',\n kind: 'openai-compatible',\n baseUrl: 'https://openrouter.ai/api/v1',\n requiresBaseUrl: false,\n fast: 'anthropic/claude-3.5-haiku',\n smart: 'anthropic/claude-3.5-sonnet',\n keyHint: 'sk-or-…',\n modelHint: 'Use vendor/model slugs, e.g. anthropic/claude-3.5-sonnet or openai/gpt-4o.',\n // OpenRouter proxies many vendors over an OpenAI-compatible surface; image\n // tool-results aren't reliably supported, so keep vision tools off here.\n visionToolResults: false,\n },\n {\n value: 'groq',\n label: 'Groq — fast inference',\n kind: 'openai-compatible',\n baseUrl: 'https://api.groq.com/openai/v1',\n requiresBaseUrl: false,\n fast: 'llama-3.1-8b-instant',\n smart: 'llama-3.3-70b-versatile',\n keyHint: 'gsk_…',\n visionToolResults: false,\n },\n {\n value: 'xai',\n label: 'xAI — Grok',\n kind: 'openai-compatible',\n baseUrl: 'https://api.x.ai/v1',\n requiresBaseUrl: false,\n fast: 'grok-2-1212',\n smart: 'grok-2-vision-1212',\n keyHint: 'xai-…',\n visionToolResults: false,\n },\n {\n value: 'deepseek',\n label: 'DeepSeek',\n kind: 'openai-compatible',\n baseUrl: 'https://api.deepseek.com',\n requiresBaseUrl: false,\n fast: 'deepseek-chat',\n smart: 'deepseek-chat',\n keyHint: 'sk-…',\n visionToolResults: false,\n },\n {\n value: 'mistral',\n label: 'Mistral',\n kind: 'openai-compatible',\n baseUrl: 'https://api.mistral.ai/v1',\n requiresBaseUrl: false,\n fast: 'mistral-small-latest',\n smart: 'mistral-large-latest',\n keyHint: 'Your Mistral API key',\n visionToolResults: false,\n },\n {\n value: 'custom',\n label: 'Custom (OpenAI-compatible)',\n kind: 'openai-compatible',\n baseUrl: null,\n requiresBaseUrl: true,\n fast: '',\n smart: '',\n keyHint: 'Your provider API key',\n modelHint:\n 'Use a public HTTPS OpenAI-compatible endpoint (for example Together, Fireworks, Perplexity, or hosted vLLM) and set explicit model ids.',\n visionToolResults: false,\n },\n]\n\nconst SPEC_BY_VALUE = Object.fromEntries(AI_PROVIDER_SPECS.map((s) => [s.value, s])) as Record<\n AiProvider,\n ProviderSpec\n>\n\nexport function isAiProvider(value: unknown): value is AiProvider {\n return typeof value === 'string' && Object.hasOwn(SPEC_BY_VALUE, value)\n}\n\nexport function providerSpec(provider: AiProvider): ProviderSpec {\n return SPEC_BY_VALUE[provider]\n}\n\nfunction withoutTrailingSlashes(value: string): string {\n let end = value.length\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1\n return value.slice(0, end)\n}\n\n/**\n * Validate and canonicalize a persisted AI endpoint override. Runtime requests\n * repeat the public-DNS check immediately before opening each socket.\n */\nexport async function validateAiBaseUrl(\n provider: AiProvider,\n rawBaseUrl: string | null | undefined,\n): Promise<string | null> {\n const spec = providerSpec(provider)\n const raw = rawBaseUrl?.trim() ?? ''\n if (spec.kind !== 'openai-compatible') {\n if (raw) throw new Error(`${spec.label} does not support a custom base URL.`)\n return null\n }\n if (!raw) {\n if (spec.requiresBaseUrl) throw new Error('A public HTTPS base URL is required.')\n return null\n }\n\n let submitted: URL\n try {\n submitted = new URL(raw)\n } catch {\n throw new Error('The AI base URL is not valid.')\n }\n if (submitted.search || submitted.hash) {\n throw new Error('The AI base URL must not include a query string or fragment.')\n }\n const validated = validateOutboundRequestConfiguration(submitted).url\n await resolvePublicHost(validated.hostname)\n\n const path = withoutTrailingSlashes(validated.pathname)\n validated.pathname = path || '/'\n const canonical = withoutTrailingSlashes(validated.href)\n return canonical || validated.origin\n}\n\nasync function readBoundedRequestBody(request: Request): Promise<Uint8Array | undefined> {\n if (!request.body) return undefined\n const declaredLength = Number(request.headers.get('content-length'))\n if (Number.isFinite(declaredLength) && declaredLength > MAX_AI_REQUEST_BYTES) {\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n try {\n for (;;) {\n if (request.signal.aborted) throw request.signal.reason\n const { done, value } = await reader.read()\n if (done) break\n total += value.byteLength\n if (total > MAX_AI_REQUEST_BYTES) {\n await reader.cancel()\n throw new Error(`AI request body exceeded ${MAX_AI_REQUEST_BYTES} bytes.`)\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n const body = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n body.set(chunk, offset)\n offset += chunk.byteLength\n }\n return body\n}\n\n/** Socket-pinned transport for tenant-configurable OpenAI-compatible endpoints. */\nexport const secureAiFetch: typeof globalThis.fetch = async (input, init) => {\n const request = new Request(input, init)\n const method = request.method.toUpperCase()\n if (!['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n throw new Error(`AI request method ${method} is not supported.`)\n }\n const body = await readBoundedRequestBody(request)\n return secureFetch(request.url, {\n method: method as 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n headers: request.headers,\n body,\n timeoutMs: AI_REQUEST_TIMEOUT_MS,\n maxRequestBytes: MAX_AI_REQUEST_BYTES,\n maxResponseBytes: MAX_AI_RESPONSE_BYTES,\n maxRedirects: 2,\n signal: request.signal,\n })\n}\n\nexport function defaultModel(provider: AiProvider, tier: ModelTier): string {\n const spec = SPEC_BY_VALUE[provider] ?? SPEC_BY_VALUE.anthropic\n return tier === 'smart' ? spec.smart : spec.fast\n}\n\n/**\n * Whether the configured provider accepts IMAGE content in a tool result — the\n * capability that lets a tool hand rendered PDF pages back to the model for\n * vision reading. Currently Anthropic only; other providers' tool/function\n * results are text/JSON only, so exposing such a tool to them would break the\n * agent turn. Used to gate the assistant's `view_document_pages` tool.\n */\nexport function providerSupportsImageToolResults(config: AiConfig | null | undefined): boolean {\n if (!config) return false\n return SPEC_BY_VALUE[config.provider]?.visionToolResults ?? false\n}\n\nexport function isAiConfigured(config: AiConfig | null | undefined): config is AiConfig {\n if (!config || !config.apiKey) return false\n const spec = SPEC_BY_VALUE[config.provider]\n if (!spec) return false\n if (spec.requiresBaseUrl && !config.baseUrl?.trim()) return false\n return true\n}\n\n/** Resolve a language model from a tenant's config, or null when not configured. */\nexport function getModel(\n config: AiConfig | null | undefined,\n tier: ModelTier = 'fast',\n): LanguageModel | null {\n if (!isAiConfigured(config)) return null\n const spec = SPEC_BY_VALUE[config.provider]\n const modelId =\n (tier === 'smart' ? config.modelSmart : config.modelFast) || defaultModel(config.provider, tier)\n // `custom` has no default model — without an explicit one, AI stays disabled.\n if (!modelId) return null\n\n switch (spec.kind) {\n case 'anthropic':\n return createAnthropic({ apiKey: config.apiKey })(modelId)\n case 'openai':\n return createOpenAI({ apiKey: config.apiKey })(modelId)\n case 'google':\n return createGoogleGenerativeAI({ apiKey: config.apiKey })(modelId)\n case 'openai-compatible': {\n const baseURL = config.baseUrl?.trim() || spec.baseUrl\n if (!baseURL) return null\n return createOpenAICompatible({\n name: spec.value,\n apiKey: config.apiKey,\n baseURL,\n fetch: secureAiFetch,\n })(modelId)\n }\n }\n}\n\nexport class AIDisabledError extends Error {\n override readonly name = 'AIDisabledError'\n constructor() {\n super('No AI provider is available for this request.')\n }\n}\n\n/** Live test of a config — sends a tiny prompt and reports success/failure. */\nexport async function pingModel(\n config: AiConfig | null | undefined,\n): Promise<{ ok: boolean; message: string }> {\n const model = getModel(config, 'fast')\n if (!model) {\n return {\n ok: false,\n message: 'Not configured yet — check the provider, API key, model and base URL.',\n }\n }\n try {\n const { text } = await generateText({ model, prompt: 'Reply with the single word: ok' })\n return { ok: true, message: `Connected — the model replied “${text.trim().slice(0, 24)}”.` }\n } catch (e) {\n return { ok: false, message: e instanceof Error ? e.message.slice(0, 180) : 'Request failed.' }\n }\n}\n"]}
|
package/models.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// settings UI can offer dynamic dropdowns instead of free-text model ids.
|
|
3
3
|
// Runs server-side only (the API key never leaves the server).
|
|
4
4
|
import { providerSpec, validateAiBaseUrl } from './client.js';
|
|
5
|
-
import { secureFetch } from '@braedonsaunders/appkit-
|
|
5
|
+
import { secureFetch } from '@braedonsaunders/appkit-egress-proxy/secure-fetch';
|
|
6
6
|
function trimSlash(u) {
|
|
7
7
|
let end = u.length;
|
|
8
8
|
while (end > 0 && u.charCodeAt(end - 1) === 47)
|
package/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,0EAA0E;AAC1E,+DAA+D;AAE/D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAiB,MAAM,UAAU,CAAA;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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","sourcesContent":["// Model discovery — queries each provider's \"list models\" endpoint so the\n// settings UI can offer dynamic dropdowns instead of free-text model ids.\n// Runs server-side only (the API key never leaves the server).\n\nimport { providerSpec, validateAiBaseUrl, type AiConfig } from './client'\nimport { secureFetch } from '@braedonsaunders/appkit-egress-proxy/secure-fetch'\n\nexport type ModelListItem = { id: string; label?: string }\n\nfunction trimSlash(u: string): string {\n let end = u.length\n while (end > 0 && u.charCodeAt(end - 1) === 47) end -= 1\n return u.slice(0, end)\n}\n\nfunction dedupeSort(items: ModelListItem[]): ModelListItem[] {\n const map = new Map<string, ModelListItem>()\n for (const it of items) {\n if (it.id && !map.has(it.id)) map.set(it.id, it)\n }\n return [...map.values()].sort((a, b) => a.id.localeCompare(b.id))\n}\n\nasync function fetchJson(url: string, headers: Record<string, string>): Promise<unknown> {\n // Custom OpenAI-compatible endpoints are tenant-configured. Use the shared\n // HTTPS/public-DNS transport so redirects, DNS rebinding, private networks,\n // response size, and timeouts are enforced at the socket boundary.\n const res = await secureFetch(url, {\n headers,\n timeoutMs: 15_000,\n maxResponseBytes: 2 * 1024 * 1024,\n })\n if (!res.ok) {\n const body = await res.text().catch(() => '')\n throw new Error(`${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 160)}` : ''}`)\n }\n return res.json()\n}\n\nfunction asArray(value: unknown): Record<string, unknown>[] {\n return Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n}\n\nfunction str(value: unknown): string {\n return typeof value === 'string' ? value : value == null ? '' : String(value)\n}\n\n/** OpenAI returns every model (embeddings, tts, image…) — keep chat-capable text ones. */\nfunction isOpenAiChatModel(id: string): boolean {\n if (\n /embedding|whisper|tts|audio|dall-e|image|moderation|realtime|transcribe|search|similarity|edit|babbage|davinci/i.test(\n id,\n )\n ) {\n return false\n }\n return /^(gpt-|o1|o3|o4|chatgpt)/i.test(id)\n}\n\n/**\n * Fetch the available model ids for a provider config. Throws on HTTP / auth\n * errors so the caller can surface the provider's message.\n */\nexport async function listModels(config: AiConfig): Promise<ModelListItem[]> {\n const spec = providerSpec(config.provider)\n const key = config.apiKey\n\n switch (spec.kind) {\n case 'anthropic': {\n const json = await fetchJson('https://api.anthropic.com/v1/models?limit=1000', {\n 'x-api-key': key,\n 'anthropic-version': '2023-06-01',\n })\n const data = asArray((json as Record<string, unknown>)?.data)\n return dedupeSort(\n data.map((m) => ({\n id: str(m.id),\n label: m.display_name ? str(m.display_name) : undefined,\n })),\n )\n }\n case 'openai': {\n const json = await fetchJson('https://api.openai.com/v1/models', {\n Authorization: `Bearer ${key}`,\n })\n const data = asArray((json as Record<string, unknown>)?.data)\n return dedupeSort(data.map((m) => ({ id: str(m.id) })).filter((m) => isOpenAiChatModel(m.id)))\n }\n case 'google': {\n const json = await fetchJson(\n `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key)}&pageSize=1000`,\n {},\n )\n const models = asArray((json as Record<string, unknown>)?.models)\n return dedupeSort(\n models\n .filter((m) => {\n const methods = m.supportedGenerationMethods\n return Array.isArray(methods) && methods.includes('generateContent')\n })\n .map((m) => ({\n id: str(m.name).replace(/^models\\//, ''),\n label: m.displayName ? str(m.displayName) : undefined,\n })),\n )\n }\n case 'openai-compatible': {\n const baseURL = (await validateAiBaseUrl(config.provider, config.baseUrl)) || spec.baseUrl\n if (!baseURL) throw new Error('A base URL is required to list models.')\n const json = await fetchJson(`${trimSlash(baseURL)}/models`, {\n Authorization: `Bearer ${key}`,\n })\n const root = json as Record<string, unknown>\n const data = asArray(root?.data).length ? asArray(root?.data) : asArray(root?.models)\n return dedupeSort(\n data.map((m) => {\n const id = str(m.id || m.name)\n const name = str(m.name)\n return { id, label: name && name !== id ? name : undefined }\n }),\n )\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braedonsaunders/appkit-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "App-agnostic bounded agent loop and streaming assistant interface, with provider policy supplied by the host app.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@ai-sdk/google": "^3.0.91",
|
|
25
25
|
"@ai-sdk/openai": "^3.0.84",
|
|
26
26
|
"@ai-sdk/openai-compatible": "^2.0.59",
|
|
27
|
-
"@braedonsaunders/appkit-
|
|
27
|
+
"@braedonsaunders/appkit-egress-proxy": "^0.1.1",
|
|
28
28
|
"ai": "^6.0.224",
|
|
29
29
|
"zod": "^4.4.3"
|
|
30
30
|
},
|