@omg-dev/ai 0.4.24

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.mjs ADDED
@@ -0,0 +1,51 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { createAnthropic } from "@ai-sdk/anthropic";
3
+ import { createOpenAI } from "@ai-sdk/openai";
4
+ import { convertToModelMessages, generateObject, generateText, stepCountIs, streamObject, streamText, tool } from "ai";
5
+ //#region src/index.ts
6
+ const endUserStore = new AsyncLocalStorage();
7
+ /**
8
+ * Run `fn` with the given end-user identifier in scope. Any LLM call made
9
+ * (synchronously or asynchronously, transitively) inside `fn` will have
10
+ * `X-OMG-User: <userId>` stamped on the upstream request unless an
11
+ * explicit `headers` argument overrides it.
12
+ *
13
+ * Pass an empty/undefined userId to opt out (calls inside will be
14
+ * anonymous / unattributed).
15
+ */
16
+ function runWithEndUser(userId, fn) {
17
+ if (!userId) return fn();
18
+ return endUserStore.run(userId, fn);
19
+ }
20
+ /**
21
+ * Returns the end-user id currently in scope, or `undefined` if no
22
+ * `runWithEndUser` is on the stack.
23
+ */
24
+ function getEndUser() {
25
+ return endUserStore.getStore();
26
+ }
27
+ const attributedFetch = async (input, init) => {
28
+ const userId = endUserStore.getStore();
29
+ if (!userId) return fetch(input, init);
30
+ const headers = new Headers(init?.headers);
31
+ if (!headers.has("x-omg-user")) headers.set("X-OMG-User", userId);
32
+ return fetch(input, {
33
+ ...init,
34
+ headers
35
+ });
36
+ };
37
+ const ANTHROPIC_BASE = process.env.ANTHROPIC_BASE_URL || void 0;
38
+ const OPENAI_BASE = process.env.OPENAI_BASE_URL || void 0;
39
+ const fetchForSdk = attributedFetch;
40
+ const anthropic = createAnthropic({
41
+ baseURL: ANTHROPIC_BASE,
42
+ apiKey: process.env.ANTHROPIC_API_KEY || "omg-sandbox",
43
+ fetch: fetchForSdk
44
+ });
45
+ const openai = createOpenAI({
46
+ baseURL: OPENAI_BASE,
47
+ apiKey: process.env.OPENAI_API_KEY || "omg-sandbox",
48
+ fetch: fetchForSdk
49
+ });
50
+ //#endregion
51
+ export { anthropic, convertToModelMessages, generateObject, generateText, getEndUser, openai, runWithEndUser, stepCountIs, streamObject, streamText, tool };
package/dist/react.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { experimental_useObject as useObject, useChat, useCompletion } from "@ai-sdk/react";
2
+ export { useChat, useCompletion, useObject };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@omg-dev/ai",
3
+ "version": "0.4.24",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./src/index.ts",
8
+ "default": "./dist/index.mjs"
9
+ },
10
+ "./react": {
11
+ "types": "./src/react.ts",
12
+ "default": "./dist/react.mjs"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "ai": "^6.0.0",
17
+ "@ai-sdk/anthropic": "^3.0.0",
18
+ "@ai-sdk/openai": "^2.0.0",
19
+ "@ai-sdk/react": "^3.0.0"
20
+ },
21
+ "peerDependencies": {
22
+ "react": "^18 || ^19"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "react": {
26
+ "optional": true
27
+ }
28
+ },
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/BennyKok/vibes.git"
33
+ },
34
+ "homepage": "https://docs.omg.dev",
35
+ "files": [
36
+ "dist",
37
+ "src"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "registry": "https://registry.npmjs.org/"
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,122 @@
1
+ // @omg-dev/ai — zero-config AI SDK (`ai` package) for omg-deployed apps.
2
+ //
3
+ // What this gives you:
4
+ // - `anthropic` and `openai` providers pre-pointed at the per-VM omg LLM
5
+ // proxy. No baseURL, no apiKey from your code.
6
+ // - `runWithEndUser(userId, fn)` — wraps any async handler so that LLM
7
+ // calls inside auto-stamp `X-OMG-User: <userId>` on the upstream request.
8
+ // Used by @omg-dev/auth's middleware to attribute every call to the
9
+ // end-user who triggered the request, with no per-call wiring.
10
+ //
11
+ // How attribution flows:
12
+ // 1. End-user request hits your server.
13
+ // 2. @omg-dev/auth middleware verifies their session, extracts userId.
14
+ // 3. Middleware calls `runWithEndUser(userId, () => handler(req))`.
15
+ // 4. Inside the handler, `streamText({ model: anthropic('...'), ... })`
16
+ // runs. The provider's fetch interceptor reads the ALS context and
17
+ // adds `X-OMG-User: <userId>` to the upstream call.
18
+ // 5. The omg LLM proxy reads that header and writes user_id into
19
+ // usage_logs / usage_aggregates.
20
+ //
21
+ // The header argument on individual `streamText` / `generateText` calls
22
+ // remains an escape hatch — if you pass `headers: { 'X-OMG-User': '...' }`
23
+ // explicitly, that wins over the ALS context. Useful for bots, cron jobs,
24
+ // or labeling on behalf of a user other than the request subject.
25
+
26
+ import { AsyncLocalStorage } from "node:async_hooks"
27
+ import { createAnthropic } from "@ai-sdk/anthropic"
28
+ import { createOpenAI } from "@ai-sdk/openai"
29
+
30
+ // ── End-user context ────────────────────────────────────────────────────────
31
+
32
+ const endUserStore = new AsyncLocalStorage<string>()
33
+
34
+ /**
35
+ * Run `fn` with the given end-user identifier in scope. Any LLM call made
36
+ * (synchronously or asynchronously, transitively) inside `fn` will have
37
+ * `X-OMG-User: <userId>` stamped on the upstream request unless an
38
+ * explicit `headers` argument overrides it.
39
+ *
40
+ * Pass an empty/undefined userId to opt out (calls inside will be
41
+ * anonymous / unattributed).
42
+ */
43
+ export function runWithEndUser<T>(
44
+ userId: string | undefined | null,
45
+ fn: () => T | Promise<T>,
46
+ ): T | Promise<T> {
47
+ if (!userId) return fn()
48
+ return endUserStore.run(userId, fn)
49
+ }
50
+
51
+ /**
52
+ * Returns the end-user id currently in scope, or `undefined` if no
53
+ * `runWithEndUser` is on the stack.
54
+ */
55
+ export function getEndUser(): string | undefined {
56
+ return endUserStore.getStore()
57
+ }
58
+
59
+ // ── Fetch interceptor ───────────────────────────────────────────────────────
60
+
61
+ // Wraps the global fetch so every upstream LLM call carries the end-user
62
+ // from ALS. The provider SDKs accept a `fetch` option; we hand them this.
63
+ type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
64
+
65
+ const attributedFetch: FetchFn = async (input, init) => {
66
+ const userId = endUserStore.getStore()
67
+ if (!userId) return fetch(input, init)
68
+
69
+ // If caller already set X-OMG-User explicitly, leave it alone.
70
+ const headers = new Headers(init?.headers)
71
+ if (!headers.has("x-omg-user")) {
72
+ headers.set("X-OMG-User", userId)
73
+ }
74
+ return fetch(input, { ...init, headers })
75
+ }
76
+
77
+ // ── Providers ───────────────────────────────────────────────────────────────
78
+
79
+ // Read base URLs from env vars injected by the omg infra orchestrator. Falls
80
+ // back to the AI SDK defaults if the env isn't set (e.g. running locally
81
+ // outside an omg sandbox), so this package is safe to import anywhere.
82
+ const ANTHROPIC_BASE = process.env.ANTHROPIC_BASE_URL || undefined
83
+ const OPENAI_BASE = process.env.OPENAI_BASE_URL || undefined
84
+
85
+ // The AI SDK's `fetch` option types as `typeof fetch`, which (in newer
86
+ // Node/TS lib versions) includes `preconnect` — never actually called by the
87
+ // SDK. Cast at the boundary instead of polyfilling something we don't need.
88
+ const fetchForSdk = attributedFetch as unknown as typeof fetch
89
+
90
+ export const anthropic = createAnthropic({
91
+ baseURL: ANTHROPIC_BASE,
92
+ apiKey: process.env.ANTHROPIC_API_KEY || "omg-sandbox",
93
+ fetch: fetchForSdk,
94
+ })
95
+
96
+ export const openai = createOpenAI({
97
+ baseURL: OPENAI_BASE,
98
+ apiKey: process.env.OPENAI_API_KEY || "omg-sandbox",
99
+ fetch: fetchForSdk,
100
+ })
101
+
102
+ // ── Re-exports ──────────────────────────────────────────────────────────────
103
+ // Re-export the pieces of `ai` that 80%+ of apps use directly. Importing
104
+ // from `@omg-dev/ai` becomes the one-line setup; no separate `import { ... }
105
+ // from "ai"` is needed for common cases.
106
+
107
+ export {
108
+ streamText,
109
+ generateText,
110
+ streamObject,
111
+ generateObject,
112
+ tool,
113
+ stepCountIs,
114
+ // Convert UIMessage[] from the client (useChat sends `parts`-shaped
115
+ // messages) into the ModelMessage[] streamText accepts. Always run on
116
+ // the server side of a useChat-based app — passing UIMessages
117
+ // directly to streamText silently produces empty completions.
118
+ convertToModelMessages,
119
+ type ModelMessage,
120
+ type UIMessage,
121
+ type Tool,
122
+ } from "ai"
package/src/react.ts ADDED
@@ -0,0 +1,27 @@
1
+ // Browser-safe entry — React hooks for chat / completion UIs.
2
+ //
3
+ // `@omg-dev/ai` (the root entry) is server-only because it imports
4
+ // `node:async_hooks` for the `runWithEndUser` ALS attribution. Browser
5
+ // bundlers (Vite, esbuild) refuse to bundle Node built-ins, so any
6
+ // component that imports from `@omg-dev/ai` directly fails to build.
7
+ //
8
+ // This subpath re-exports just the browser-safe surface from
9
+ // `@ai-sdk/react` — no Node imports, no fetch interceptor, no ALS. The
10
+ // hooks talk to your `functions/api/<name>.ts` routes via plain HTTP.
11
+ //
12
+ // Usage:
13
+ // import { useChat } from "@omg-dev/ai/react"
14
+ //
15
+ // Server side keeps using `from "@omg-dev/ai"` for the proxy-bound
16
+ // providers + ALS. The two halves never load each other; bundlers stay
17
+ // happy in both environments.
18
+
19
+ export {
20
+ useChat,
21
+ useCompletion,
22
+ // The useObject hook is experimental in @ai-sdk/react@3 and ships under
23
+ // `experimental_useObject`. Re-export under the documented name so app
24
+ // code can stay future-proof when it stabilizes.
25
+ experimental_useObject as useObject,
26
+ type UseChatOptions,
27
+ } from "@ai-sdk/react"