@urun-sh/openai 0.2.49 → 0.2.51
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/ResponsesClient-CQ3l8Wft.d.cts +62 -0
- package/dist/ResponsesClient-CQ3l8Wft.d.ts +56 -0
- package/dist/chunk-FSS2A7IU.js +1 -0
- package/dist/chunk-KJ25VEMG.js +21 -0
- package/dist/chunk-OLE2YJO3.js +1 -0
- package/dist/chunk-PS4BLX7J.js +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -64
- package/dist/index.d.ts +6 -57
- package/dist/index.js +1 -1
- package/dist/pi-extension/index.cjs +3 -0
- package/dist/pi-extension/index.d.cts +189 -0
- package/dist/pi-extension/index.d.ts +70 -0
- package/dist/pi-extension/index.js +3 -0
- package/dist/proxy/cli.cjs +21 -15
- package/dist/proxy/cli.js +11 -5
- package/dist/proxy/index.cjs +11 -11
- package/dist/proxy/index.d.cts +32 -1
- package/dist/proxy/index.d.ts +12 -1
- package/dist/proxy/index.js +1 -1
- package/package.json +12 -2
- package/dist/chunk-GUQC3NSG.js +0 -1
- package/dist/chunk-YDXG2QGW.js +0 -21
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { Api, Model, Context, SimpleStreamOptions, AssistantMessageEventStream } from '@earendil-works/pi-ai';
|
|
3
|
+
import { a as UrunSessionLike, U as UrunResponses } from '../ResponsesClient-CQ3l8Wft.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pi coding-agent extension: register the `urun` model provider, with one pi
|
|
7
|
+
* model PER DEPLOYED SERVE APP discovered from the org API, streaming over
|
|
8
|
+
* this package's OWN Responses lane (`UrunResponses` — the SAME transport the
|
|
9
|
+
* compat proxy uses; one canonical transport, no shim).
|
|
10
|
+
*
|
|
11
|
+
* HOW PI LOADS THIS (verified against @earendil-works/pi-coding-agent 0.74.2,
|
|
12
|
+
* `dist/core/extensions/loader.js`):
|
|
13
|
+
* - Extensions are modules loaded with jiti (`loadExtensionModule`), which
|
|
14
|
+
* handles TS and JS (ESM or CJS). The published entry is the BUILT dist
|
|
15
|
+
* file: `pi -e node_modules/@urun-sh/openai/dist/pi-extension/index.js`
|
|
16
|
+
* (or `.cjs`); in this repo's worktree the same path under
|
|
17
|
+
* `packages/openai/dist/` after `pnpm build`.
|
|
18
|
+
* - The default export must be the ExtensionFactory; the loader `await`s it
|
|
19
|
+
* (`await factory(api)`), so an async factory that discovers BEFORE
|
|
20
|
+
* registering is the canonical shape (`registerProvider` during load is
|
|
21
|
+
* queued and flushed at runner bind — `pendingProviderRegistrations`).
|
|
22
|
+
* - Imports of pi packages are resolved BY THE LOADER: jiti `alias` (Node
|
|
23
|
+
* mode) / `virtualModules` (compiled Bun binary) map `@earendil-works/
|
|
24
|
+
* pi-ai` and `@earendil-works/pi-coding-agent` (and the legacy
|
|
25
|
+
* `@mariozechner/*` names) to pi's bundled copies. So the ONE value import
|
|
26
|
+
* below (`createAssistantMessageEventStream`) is left external by tsup and
|
|
27
|
+
* resolves at pi runtime without pi being a dependency of this package;
|
|
28
|
+
* `pi-types.d.ts` supplies the types for tsc/tsup, and vitest aliases the
|
|
29
|
+
* module to a local stub.
|
|
30
|
+
* - `@urun-sh/core` stays external in dist too (tsup config) and resolves
|
|
31
|
+
* from the consumer's node_modules — it is this package's peerDependency.
|
|
32
|
+
*
|
|
33
|
+
* WHAT IT DOES:
|
|
34
|
+
* 1. factory (async): discovers the org's deployed serve apps via
|
|
35
|
+
* `GET {URUN_API_URL}/apps` (Authorization: Bearer URUN_API_KEY — the
|
|
36
|
+
* canonical `proxy/routing.ts` helper) and registers provider "urun"
|
|
37
|
+
* with one model per active app exposing the serve function (model id =
|
|
38
|
+
* app slug). Missing URUN_API_KEY or a failed/empty discovery THROWS,
|
|
39
|
+
* which pi surfaces as "Failed to load extension: …" — never a silent
|
|
40
|
+
* empty provider.
|
|
41
|
+
* 2. `streamSimple` (pi's custom-API hook, keyed by `api: "urun-serve"`):
|
|
42
|
+
* opens ONE uRun session per app slug, POOLED and reused across turns,
|
|
43
|
+
* translates pi's Context into Responses `input` items, drives
|
|
44
|
+
* `UrunResponses.responses.create({stream: true})`, and maps
|
|
45
|
+
* `response.output_text.delta` events into pi `text_delta` events.
|
|
46
|
+
* Text-only, matching the precedent extension's fidelity (no tool-call
|
|
47
|
+
* streaming through the serve lane; pi's toolResult turns are folded
|
|
48
|
+
* into user-role input items — the Responses `function_call_output` item
|
|
49
|
+
* form needs call ids this lane never emits).
|
|
50
|
+
* 3. ABORT/CANCEL (loud contract): the Responses lane has NO request-cancel
|
|
51
|
+
* primitive (`SdkTransport.sendResponses` writes an envelope and the
|
|
52
|
+
* decode loop just reads the reply lane), so Ctrl-C / the stall watchdog
|
|
53
|
+
* stop consuming AND evict+close the pooled session — tearing down the
|
|
54
|
+
* WebRTC transport is what stops server-side generation. A clean turn
|
|
55
|
+
* keeps its session for the next turn; a failed/aborted/stalled turn
|
|
56
|
+
* always reopens.
|
|
57
|
+
* 4. cleanup: pooled sessions are released (`session.close()`) on pi's
|
|
58
|
+
* `session_shutdown` lifecycle event.
|
|
59
|
+
*
|
|
60
|
+
* RUNTIME DEPENDENCY STORY (owner requirement — stated honestly): token
|
|
61
|
+
* streams are named-DATA session streams, i.e. SCTP data channels over
|
|
62
|
+
* WebRTC (core session contract). Under pi's Node runtime `@urun-sh/core`
|
|
63
|
+
* installs its Node WebRTC backend from `node-webrtc.ts`: **werift** (pure-TS,
|
|
64
|
+
* H.264-capable; explicitly NOT @roamhq/wrtc, whose prebuilt libwebrtc lacks
|
|
65
|
+
* H.264) plus `ws` for Node < 22 — both declared as OPTIONAL dependencies of
|
|
66
|
+
* `@urun-sh/core`, and `werift` is ALSO declared in this package's
|
|
67
|
+
* `optionalDependencies` (same story the proxy relies on; its `build:bin`
|
|
68
|
+
* marks them external). Optional deps install by default, but a
|
|
69
|
+
* `--no-optional` install would leave core to swallow the missing backend
|
|
70
|
+
* with a warning and the extension would hang waiting for a data channel —
|
|
71
|
+
* so `defaultSessionFactory` PREFLIGHTS the backend and fails loudly instead.
|
|
72
|
+
*
|
|
73
|
+
* ENV CONTRACT (discovery env is read at FACTORY time because pi models must
|
|
74
|
+
* exist at load; session env is read at STREAM time so a registered model
|
|
75
|
+
* fails loudly on first use, not at load):
|
|
76
|
+
* URUN_API_KEY org API key — REQUIRED at load (discovery) and used to
|
|
77
|
+
* mint per-app client tokens at stream time (api-key lane)
|
|
78
|
+
* URUN_API_URL org control-plane base (default DEFAULT_ORG_API_URL)
|
|
79
|
+
* URUN_FUNCTION serve function name (default "serve")
|
|
80
|
+
* URUN_BASE_URL session-gateway base — required at stream time
|
|
81
|
+
* URUN_ORG_ID org id — required at stream time
|
|
82
|
+
* URUN_JWT optional pre-vended session token; WINS over the api-key
|
|
83
|
+
* lane when set (same precedence as the compat proxy)
|
|
84
|
+
* URUN_GATEWAY_URL optional createClientToken baseUrl override
|
|
85
|
+
*
|
|
86
|
+
* CLI (verified against dist/core/model-resolver.js resolveCliModel):
|
|
87
|
+
* pi -e node_modules/@urun-sh/openai/dist/pi-extension/index.js \
|
|
88
|
+
* --provider urun --model <app-slug> -p "..."
|
|
89
|
+
* or equivalently `--model urun/<app-slug>` (provider inferred from the
|
|
90
|
+
* slash prefix), e.g. `--model urun/qwen3-6-27b-bf16`.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Custom `api` id. Pi dispatches the stream function by `model.api`, so a
|
|
95
|
+
* unique id keeps our in-process `streamSimple` scoped to this provider and
|
|
96
|
+
* never overrides a built-in API provider.
|
|
97
|
+
*/
|
|
98
|
+
declare const URUN_API: Api;
|
|
99
|
+
/** Session-target env, read at stream time. Loud on anything missing. */
|
|
100
|
+
declare function resolveSessionEnv(env: NodeJS.ProcessEnv): {
|
|
101
|
+
baseUrl: string;
|
|
102
|
+
orgId: string;
|
|
103
|
+
auth: {
|
|
104
|
+
lane: 'jwt';
|
|
105
|
+
jwt: string;
|
|
106
|
+
} | {
|
|
107
|
+
lane: 'api-key';
|
|
108
|
+
apiKey: string;
|
|
109
|
+
gatewayUrl?: string;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* How a uRun session is opened for one app slug. Injectable so tests drive
|
|
114
|
+
* `streamSimple` against the package's MockSession with no network and no
|
|
115
|
+
* `@urun-sh/core`.
|
|
116
|
+
*/
|
|
117
|
+
interface SessionFactory {
|
|
118
|
+
(env: NodeJS.ProcessEnv, appSlug: string, fnName: string): Promise<UrunSessionLike> | UrunSessionLike;
|
|
119
|
+
}
|
|
120
|
+
/** One pooled backhaul: the session plus its Responses client (cli.ts shape). */
|
|
121
|
+
interface PoolEntry {
|
|
122
|
+
session: UrunSessionLike;
|
|
123
|
+
responses: UrunResponses;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The session pool: one uRun session per app slug, opened lazily on the first
|
|
127
|
+
* turn that uses the model and REUSED across turns (same shape as the compat
|
|
128
|
+
* proxy's ModelRouter pool). A failed open never poisons the slot; a turn
|
|
129
|
+
* that ends in error/abort/stall evicts its entry — closing the session is
|
|
130
|
+
* ALSO the cancel mechanism (see the header's abort contract).
|
|
131
|
+
*/
|
|
132
|
+
declare class SessionPool {
|
|
133
|
+
private readonly open;
|
|
134
|
+
private readonly env;
|
|
135
|
+
private readonly fnName;
|
|
136
|
+
private readonly pool;
|
|
137
|
+
constructor(open: SessionFactory, env: NodeJS.ProcessEnv, fnName: string);
|
|
138
|
+
acquire(appSlug: string): Promise<PoolEntry>;
|
|
139
|
+
/** Drop + release one entry (turn ended badly / user aborted). */
|
|
140
|
+
evict(appSlug: string): void;
|
|
141
|
+
/** Release everything (pi session_shutdown). */
|
|
142
|
+
closeAll(): void;
|
|
143
|
+
}
|
|
144
|
+
/** A Responses input item (message form; content stays a plain string). */
|
|
145
|
+
interface ResponsesInputItem {
|
|
146
|
+
role: 'system' | 'user' | 'assistant';
|
|
147
|
+
content: string;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Pi `Context` -> Responses `input` items. Pi models tool results as
|
|
151
|
+
* `role:"toolResult"`; the Responses item form for those
|
|
152
|
+
* (`{type:'function_call_output', call_id, …}`) needs call ids this text-only
|
|
153
|
+
* lane never emits, so tool results are folded into `user`-role items, then
|
|
154
|
+
* any consecutive same-role runs are coalesced into one item so a chat
|
|
155
|
+
* template that assumes strict role alternation never sees two turns of the
|
|
156
|
+
* same role back-to-back (same rationale as the precedent extension).
|
|
157
|
+
*/
|
|
158
|
+
declare function toResponsesInput(context: Context): ResponsesInputItem[];
|
|
159
|
+
/** Fold consecutive same-role items into one, joining with a blank line. */
|
|
160
|
+
declare function coalesceSameRole(items: ResponsesInputItem[]): ResponsesInputItem[];
|
|
161
|
+
/** Tunable timeouts (overridable so tests can drive them deterministically). */
|
|
162
|
+
interface StreamTiming {
|
|
163
|
+
connectDeadlineMs?: number;
|
|
164
|
+
stallTimeoutMs?: number;
|
|
165
|
+
phaseHeartbeatMs?: number;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Build the `streamSimple` handler over a {@link SessionPool}. Returns the
|
|
169
|
+
* event stream synchronously (pi consumes it as an async iterable) and drives
|
|
170
|
+
* the pooled session's Responses lane on a microtask.
|
|
171
|
+
*/
|
|
172
|
+
declare function makeStreamSimple(pool: SessionPool, timing?: StreamTiming, pi?: ExtensionAPI): (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
173
|
+
/** Injectable seams for tests; production uses the defaults. */
|
|
174
|
+
interface UrunExtensionOptions {
|
|
175
|
+
env?: NodeJS.ProcessEnv;
|
|
176
|
+
fetchImpl?: typeof fetch;
|
|
177
|
+
openSession?: SessionFactory;
|
|
178
|
+
timing?: StreamTiming;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Build the extension factory. Async: pi's loader `await`s the factory, and
|
|
182
|
+
* `registerProvider` calls during load are queued and applied once the runner
|
|
183
|
+
* binds, so discovering BEFORE registering is the canonical shape.
|
|
184
|
+
*/
|
|
185
|
+
declare function createUrunExtension(opts?: UrunExtensionOptions): (pi: ExtensionAPI) => Promise<void>;
|
|
186
|
+
/** The pi extension factory (default export — what pi's loader invokes). */
|
|
187
|
+
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
|
188
|
+
|
|
189
|
+
export { type ResponsesInputItem, type SessionFactory, SessionPool, type StreamTiming, URUN_API, type UrunExtensionOptions, coalesceSameRole, createUrunExtension, _default as default, makeStreamSimple, resolveSessionEnv, toResponsesInput };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { Api, Model, Context, SimpleStreamOptions, AssistantMessageEventStream } from '@earendil-works/pi-ai';
|
|
3
|
+
import { a as UrunSessionLike, U as UrunResponses } from '../ResponsesClient-CQ3l8Wft.js';
|
|
4
|
+
|
|
5
|
+
declare const URUN_API: Api;
|
|
6
|
+
|
|
7
|
+
declare function resolveSessionEnv(env: NodeJS.ProcessEnv): {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
orgId: string;
|
|
10
|
+
auth: {
|
|
11
|
+
lane: 'jwt';
|
|
12
|
+
jwt: string;
|
|
13
|
+
} | {
|
|
14
|
+
lane: 'api-key';
|
|
15
|
+
apiKey: string;
|
|
16
|
+
gatewayUrl?: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
interface SessionFactory {
|
|
21
|
+
(env: NodeJS.ProcessEnv, appSlug: string, fnName: string): Promise<UrunSessionLike> | UrunSessionLike;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface PoolEntry {
|
|
25
|
+
session: UrunSessionLike;
|
|
26
|
+
responses: UrunResponses;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class SessionPool {
|
|
30
|
+
private readonly open;
|
|
31
|
+
private readonly env;
|
|
32
|
+
private readonly fnName;
|
|
33
|
+
private readonly pool;
|
|
34
|
+
constructor(open: SessionFactory, env: NodeJS.ProcessEnv, fnName: string);
|
|
35
|
+
acquire(appSlug: string): Promise<PoolEntry>;
|
|
36
|
+
|
|
37
|
+
evict(appSlug: string): void;
|
|
38
|
+
|
|
39
|
+
closeAll(): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ResponsesInputItem {
|
|
43
|
+
role: 'system' | 'user' | 'assistant';
|
|
44
|
+
content: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare function toResponsesInput(context: Context): ResponsesInputItem[];
|
|
48
|
+
|
|
49
|
+
declare function coalesceSameRole(items: ResponsesInputItem[]): ResponsesInputItem[];
|
|
50
|
+
|
|
51
|
+
interface StreamTiming {
|
|
52
|
+
connectDeadlineMs?: number;
|
|
53
|
+
stallTimeoutMs?: number;
|
|
54
|
+
phaseHeartbeatMs?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
declare function makeStreamSimple(pool: SessionPool, timing?: StreamTiming, pi?: ExtensionAPI): (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
58
|
+
|
|
59
|
+
interface UrunExtensionOptions {
|
|
60
|
+
env?: NodeJS.ProcessEnv;
|
|
61
|
+
fetchImpl?: typeof fetch;
|
|
62
|
+
openSession?: SessionFactory;
|
|
63
|
+
timing?: StreamTiming;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare function createUrunExtension(opts?: UrunExtensionOptions): (pi: ExtensionAPI) => Promise<void>;
|
|
67
|
+
|
|
68
|
+
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
|
69
|
+
|
|
70
|
+
export { type ResponsesInputItem, type SessionFactory, SessionPool, type StreamTiming, URUN_API, type UrunExtensionOptions, coalesceSameRole, createUrunExtension, _default as default, makeStreamSimple, resolveSessionEnv, toResponsesInput };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{c as L}from"../chunk-FSS2A7IU.js";import{a as x,c as D}from"../chunk-PS4BLX7J.js";import{createAssistantMessageEventStream as J}from"@earendil-works/pi-ai";async function $(t){let e=await D(t),n=e.filter(o=>o.function_name===t.fnName&&o.deployment_status==="active").map(o=>o.app_slug);if(n.length===0)throw new Error(`urun pi extension: the org has no active deployed app exposing "${t.fnName}" (GET ${t.apiUrl}/apps returned ${e.length} app(s), none servable) \u2014 deploy one with \`urun serve <model>\` first`);return n.sort()}var O="urun-serve",G="serve",H=131072,Y=16384,F=3,B=42e4,X=6e4,z=15e3;function Q(t){let e=(t.URUN_API_KEY??"").trim();if(!e)throw new Error("urun pi extension: URUN_API_KEY is required \u2014 model discovery lists the org's deployed serve apps via the org API (URUN_JWT alone cannot list apps)");return{apiKey:e,apiUrl:(t.URUN_API_URL??"").trim()||x,fnName:(t.URUN_FUNCTION??"").trim()||G}}function V(t){let e=(t.URUN_BASE_URL??"").trim(),n=(t.URUN_ORG_ID??"").trim();if(!e)throw new Error("urun pi extension: URUN_BASE_URL is required to open a session");if(/\/v1\/*$/.test(new URL(e).pathname))throw new Error(`urun pi extension: URUN_BASE_URL must be the session-gateway base (e.g. https://api.urun.sh), got ${e} \u2014 a trailing /v1 is the org API form (URUN_API_URL); session allocation 404s against it. Drop the /v1.`);if(!n)throw new Error("urun pi extension: URUN_ORG_ID is required to open a session");let o=(t.URUN_JWT??"").trim();if(o)return{baseUrl:e,orgId:n,auth:{lane:"jwt",jwt:o}};let s=(t.URUN_API_KEY??"").trim();if(!s)throw new Error("urun pi extension: URUN_JWT or URUN_API_KEY is required to open a session");let i=(t.URUN_GATEWAY_URL??"").trim()||void 0;return{baseUrl:e,orgId:n,auth:{lane:"api-key",apiKey:s,gatewayUrl:i}}}var Z=async(t,e,n)=>{let{baseUrl:o,orgId:s,auth:i}=V(t),{createRequire:a}=await import("module"),p=a(import.meta.url);if(typeof globalThis.RTCPeerConnection>"u")try{p.resolve("werift")}catch{throw new Error('urun pi extension: token streams require a WebRTC data channel, and this Node runtime has no RTCPeerConnection and no "werift" backend installed (@urun-sh/core\'s optional Node WebRTC backend \u2014 it was probably stripped by a --no-optional install). Install werift (`npm i werift`) and retry.')}let{App:d,createClientToken:c}=p("@urun-sh/core"),g=(i.lane==="jwt"?d(e,{baseUrl:o,orgId:s,jwt:i.jwt}):d(e,{baseUrl:o,orgId:s,getAccessToken:async()=>(await c(i.apiKey,{baseUrl:i.gatewayUrl,expiresIn:300,allowedFunctions:[`${e}/${n}`]})).token}))[n];if(typeof g!="function")throw new Error(`urun pi extension: app "${e}" has no function "${n}"`);let u=g(),l=u.connect;return typeof l=="function"&&await l.call(u),u};function ee(t){try{Promise.resolve(t.close?.()).catch(()=>{})}catch{}}var P=class{constructor(e,n,o){this.open=e;this.env=n;this.fnName=o}open;env;fnName;pool=new Map;acquire(e){let n=this.pool.get(e);if(!n){let o=Promise.resolve(this.open(this.env,e,this.fnName)).then(s=>({session:s,responses:new L(s)}));n=o,this.pool.set(e,o),o.catch(()=>{this.pool.get(e)===o&&this.pool.delete(e)})}return n}evict(e){let n=this.pool.get(e);n&&(this.pool.delete(e),n.then(o=>ee(o.session),()=>{}))}closeAll(){for(let e of[...this.pool.keys()])this.evict(e)}};function te(t){let e=[];t.systemPrompt&&e.push({role:"system",content:t.systemPrompt});for(let n of t.messages){let o=n.role==="assistant"?"assistant":"user";e.push({role:o,content:oe(n.content)})}return ne(e)}function ne(t){let e=[];for(let n of t){let o=e[e.length-1];o&&o.role===n.role?o.content=[o.content,n.content].filter(s=>s.length>0).join(`
|
|
2
|
+
|
|
3
|
+
`):e.push({...n})}return e}function oe(t){return typeof t=="string"?t:Array.isArray(t)?t.map(e=>e&&typeof e=="object"&&"text"in e?String(e.text):"").join(""):""}function re(t){let e=t instanceof Error?t.message:String(t),n=t?.code;return/\b529\b/.test(e)||/overloaded/i.test(e)||n===529||n==="529"}function se(t){return new Promise(e=>setTimeout(e,t))}function ie(t,e,n,o){return new Promise((s,i)=>{let a=!1,p=r=>{a||(a=!0,clearTimeout(d),o?.removeEventListener("abort",c),r())},d=setTimeout(()=>p(()=>i(n())),e),c=()=>p(()=>i(new Error("aborted")));if(o?.aborted){c();return}o?.addEventListener("abort",c,{once:!0}),Promise.resolve(t).then(r=>p(()=>s(r)),r=>p(()=>i(r)))})}async function*ae(t,e){let n=t[Symbol.asyncIterator](),o,s=new Promise((i,a)=>{o=()=>a(new Error("aborted")),e.addEventListener("abort",o,{once:!0})});try{for(;;){if(e.aborted)throw new Error("aborted");let i=await Promise.race([n.next(),s]);if(i.done)return;yield i.value}}finally{o&&e.removeEventListener("abort",o),n.return?.(void 0)}}function ce(t,e){let n=t?.sendMessage;if(typeof n=="function")try{n.call(t,{customType:"urun-status",content:e,display:!0},{triggerTurn:!1})}catch{}}function pe(t,e={},n){let o=e.connectDeadlineMs??B,s=e.stallTimeoutMs??X,i=e.phaseHeartbeatMs??z;return(a,p,d)=>{let c=J(),r={role:"assistant",content:[{type:"text",text:""}],api:a.api,provider:a.provider,model:a.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"stop",timestamp:Date.now()},g=r.content[0],u=new AbortController,l=d?.signal,b=!1,E=()=>{b=!0,u.abort()};l&&(l.aborted?E():l.addEventListener("abort",E,{once:!0}));let T=!1,y,I=()=>{y&&clearTimeout(y),y=setTimeout(()=>{T=!0,u.abort()},s)},A=()=>{y&&clearTimeout(y),y=void 0};return(async()=>{c.push({type:"start",partial:r}),c.push({type:"text_start",contentIndex:0,partial:r});let S=!1,v,k=()=>{v&&clearInterval(v),v=void 0},K=Date.now();v=setInterval(()=>{let m=Math.round((Date.now()-K)/1e3);ce(n,`uRun: still opening the "${a.id}" session\u2026 (${m}s \u2014 serve apps scale to zero, first turn can cold-start ~6min)`)},i);try{let{responses:m}=await ie(t.acquire(a.id),o,()=>new Error(`uRun session for "${a.id}" did not connect within ${Math.round(o/1e3)}s \u2014 likely cold-starting (scaled to zero) or queued behind capacity. Retry shortly.`),u.signal);k();let _=te(p),N,U;for(let R=1;R<=F;R++)try{let h=await m.responses.create({model:a.id,input:_,stream:!0,max_output_tokens:a.maxTokens});I();for await(let W of ae(h,u.signal)){I();let f=W;if(f.type==="response.output_text.delta"){let w=typeof f.delta=="string"?f.delta:"";w&&(g.text+=w,c.push({type:"text_delta",contentIndex:0,delta:w,partial:r}))}else if(f.type==="response.completed"){N=f.response;break}else if(f.type==="error"){let w=f.error,C=new Error(`uRun serve error: ${w?.message??"unknown error"}`);throw C.code=w?.code??null,C}}U=void 0;break}catch(h){if(U=h,u.signal.aborted)throw h;if(R<F&&re(h)&&g.text.length===0){await se(R*500);continue}throw h}if(U)throw U;A(),c.push({type:"text_end",contentIndex:0,content:g.text,partial:r});let M=N?.status==="incomplete"?"length":"stop";r.stopReason=M,c.push({type:"done",reason:M,message:r}),c.end(r)}catch(m){if(S=!0,A(),b||l?.aborted&&!T)r.stopReason="aborted",r.errorMessage="aborted by user",c.push({type:"error",reason:"aborted",error:r});else{let _=T?`uRun stream stalled \u2014 no output for ${Math.round(s/1e3)}s; the model may be hung. Retry, or Ctrl-C to abort.`:m instanceof Error?m.message:String(m);r.stopReason="error",r.errorMessage=_,c.push({type:"error",reason:"error",error:r})}c.end(r)}finally{A(),k(),l&&l.removeEventListener("abort",E),S&&t.evict(a.id)}})(),c}}function ue(t){return{id:t,name:`uRun ${t}`,api:O,reasoning:!1,input:["text"],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:H,maxTokens:Y}}function le(t={}){return async e=>{let n=t.env??process.env,{apiKey:o,apiUrl:s,fnName:i}=Q(n),a=await $({apiUrl:s,apiKey:o,fnName:i,fetchImpl:t.fetchImpl}),p=new P(t.openSession??Z,n,i);e.on("session_shutdown",()=>p.closeAll());let d={name:"uRun",api:O,baseUrl:"urun://session",apiKey:"$URUN_API_KEY",models:a.map(ue),streamSimple:pe(p,t.timing??{},e)};e.registerProvider("urun",d)}}var ve=le();export{P as SessionPool,O as URUN_API,ne as coalesceSameRole,le as createUrunExtension,ve as default,pe as makeStreamSimple,V as resolveSessionEnv,te as toResponsesInput};
|
package/dist/proxy/cli.cjs
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var pe=Object.create;var x=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var me=Object.getPrototypeOf,fe=Object.prototype.hasOwnProperty;var ge=(e,n)=>{for(var t in n)x(e,t,{get:n[t],enumerable:!0})},j=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of de(n))!fe.call(e,o)&&o!==t&&x(e,o,{get:()=>n[o],enumerable:!(r=le(n,o))||r.enumerable});return e};var ye=(e,n,t)=>(t=e!=null?pe(me(e)):{},j(n||!e||!e.__esModule?x(t,"default",{value:e,enumerable:!0}):t,e)),_e=e=>j(x({},"__esModule",{value:!0}),e);var Me={};ge(Me,{buildClients:()=>ae,resolveSessionFromEnv:()=>ie});module.exports=_e(Me);var se=require("child_process"),m=ye(require("process"),1),R=require("@urun-sh/core");function q(e,n,t,r){let o=e.response??{},s={request_id:t,consumer_id:r,stream:!0,kind:"chat",messages:n};return typeof o.instructions=="string"&&(s.instructions=o.instructions),Array.isArray(o.modalities)&&(s.modalities=o.modalities),typeof o.temperature=="number"&&(s.temperature=o.temperature),typeof o.max_output_tokens=="number"&&(s.max_output_tokens=o.max_output_tokens),o.tools!==void 0&&(s.tools=o.tools),s}function G(e,n,t){let r={request_id:n,consumer_id:t,stream:!!e.stream,kind:"responses",input:e.input};return e.model&&(r.model=e.model),e.tools!==void 0&&(r.tools=e.tools),typeof e.temperature=="number"&&(r.temperature=e.temperature),typeof e.max_output_tokens=="number"&&(r.max_output_tokens=e.max_output_tokens),r}function D(e){if(e instanceof Error)return{type:"error",error:{type:"urun_error",code:e.name||null,message:e.message}};if(e&&typeof e=="object"&&e.t==="error"){let n=e,t=n.body??{};return{type:"error",error:{type:"urun_error",code:n.code??null,message:t.message??"unknown error"}}}return{type:"error",error:{type:"urun_error",code:null,message:String(e)}}}var he="llm-resp";async function*P(e,n){let t=`${he}:${n}`,r=`resp_${n}`;yield{type:"response.created",response:{id:r,status:"in_progress"}};for await(let o of e.stream(t).messages()){let s=o;if(s.t==="delta")yield{type:"response.output_text.delta",item_id:r,delta:s.delta};else if(s.t==="response"){yield{type:"response.completed",response:{id:r,status:"completed",...s.body}};return}else if(s.t==="error"){yield D(s);return}}}var we="llm",A=class{constructor(n){this.session=n}session;get consumerId(){return this.session.consumerId}write(n){this.session.doc(we).set({requests:{[n.request_id]:{payload:n,consumer_id:n.consumer_id,stream:n.stream}}})}sendResponses(n,t){let r=G(n,t,this.consumerId);return this.write(r),P(this.session,t)}sendResponseCreate(n,t,r){let o=q(n,t,r,this.consumerId);return this.write(o),P(this.session,r)}};var K=0;function ke(){return K+=1,`req_${Date.now().toString(36)}_${K.toString(36)}`}var S=class{transport;constructor(n){this.transport=new A(n)}responses={create:async n=>{let t=ke(),r=this.transport.sendResponses(n,t);return Object.assign((async function*(){yield*r})(),{requestId:t})}}};async function F(e){let t=await(e.fetchImpl??fetch)(`${e.catalogUrl}/model_catalog?select=model_id,variant`,{headers:{apikey:e.anonKey,Authorization:`Bearer ${e.anonKey}`}});if(!t.ok)throw new Error(`model_catalog fetch failed: ${t.status}`);return{object:"list",data:(await t.json()).map(o=>({id:`${o.model_id}:${o.variant}`,object:"model",created:0,owned_by:"urun"}))}}var X=require("http"),$=class e{startedAt=Date.now();requests={};ttftMs=[];tokRates=[];tokensOut=0;charsOut=0;localInBytes=0;naiveInBytes=0;request(n,t,r){this.requests[n]=(this.requests[n]??0)+1,this.localInBytes+=t,this.naiveInBytes+=r}track(n){let t=this,r=performance.now(),o=null,s=0;return(async function*(){for await(let a of n)a.type==="response.output_text.delta"&&typeof a.delta=="string"&&(o===null&&(o=performance.now(),t.ttftMs.push(o-r),t.ttftMs.length>512&&t.ttftMs.shift()),s+=1,t.tokensOut+=1,t.charsOut+=a.delta.length),yield a;let i=(performance.now()-(o??r))/1e3;s>0&&i>0&&(t.tokRates.push(s/i),t.tokRates.length>512&&t.tokRates.shift())})()}static p50(n){if(!n.length)return null;let t=[...n].sort((r,o)=>r-o);return t[Math.floor(t.length/2)]}snapshot(){let n=Object.values(this.requests).reduce((t,r)=>t+r,0);return{uptime_s:Math.round((Date.now()-this.startedAt)/1e3),requests:this.requests,tokens_out:this.tokensOut,chars_out:this.charsOut,ttft_ms:{p50:e.p50(this.ttftMs),last:this.ttftMs.at(-1)??null},tok_per_s:{p50:e.p50(this.tokRates),last:this.tokRates.at(-1)??null},traffic:{local_request_bytes:this.localInBytes,naive_baseline:{request_bytes:this.naiveInBytes,connections:n},persistent_connections:1,history_bytes_saved:Math.max(0,this.naiveInBytes-this.localInBytes),history_bytes_saved_pct:this.naiveInBytes>0?Math.round((1-this.localInBytes/this.naiveInBytes)*1e3)/10:null}}}};function L(e){e.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive"})}function w(e,n,t){e.writeHead(n,{"content-type":"application/json"}),e.end(JSON.stringify(t))}function g(e,n,t,r="invalid_request_error"){w(e,n,{error:{message:t,type:r}})}var Y=64*1024*1024,E=class extends Error{};async function ve(e){let n=[],t=0;for await(let o of e){if(t+=o.length,t>Y)throw new E(`request body exceeds ${Y} bytes`);n.push(o)}let r=Buffer.concat(n).toString("utf8");return r?JSON.parse(r):{}}function b(e,n,t,r){return{id:e,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:n,choices:[{index:0,delta:t,finish_reason:r}]}}var Re=256;function xe(e){if(typeof e=="string")return[{role:"user",content:e}];if(Array.isArray(e))return e;throw new Error("responses input must be a string or an array of messages/items")}var T=class{conversations=new Map;thread(n,t){let r=xe(t);if(n==null)return r;let o=this.conversations.get(String(n));if(!o)throw new Error(`unknown previous_response_id ${String(n)} (proxy restarts drop stored responses)`);return[...o,...r]}remember(n,t,r){this.conversations.set(n,[...t,{role:"assistant",content:r}]);for(let o of this.conversations.keys()){if(this.conversations.size<=Re)break;this.conversations.delete(o)}}};function V(e){let n=e?.output;return Array.isArray(n)?n.filter(t=>t.type==="function_call"):[]}function v(e){return e.find(n=>n.type==="response.completed")?.response??null}function Ae(e){if(Array.isArray(e))return e.map(n=>{let t=n.function;return!t||typeof t!="object"?n:{type:"function",name:t.name,description:t.description,parameters:t.parameters,strict:t.strict}})}var O=class extends Error{};function Se(e,n){return Array.isArray(n)?n.map(t=>{let r=t;if(r.type==="text")return{type:e==="assistant"?"output_text":"input_text",text:r.text};if(r.type==="image_url"){let o=r.image_url??{};return{type:"input_image",image_url:o.url,detail:o.detail}}throw new O(`unsupported chat content part type "${String(r.type)}" \u2014 the proxy translates text and image_url parts`)}):n}function be(e){let n=[];for(let t of e){if(t.role==="tool"){n.push({type:"function_call_output",call_id:t.tool_call_id,output:typeof t.content=="string"?t.content:JSON.stringify(t.content??"")});continue}if(t.role==="assistant"&&Array.isArray(t.tool_calls)){typeof t.content=="string"&&t.content&&n.push({role:"assistant",content:t.content});for(let r of t.tool_calls){let o=r.function??{};n.push({type:"function_call",call_id:r.id,name:o.name,arguments:o.arguments})}continue}n.push({...t,content:Se(t.role,t.content)})}return n}function z(e){return V(e).map((n,t)=>({index:t,id:n.call_id??n.id??`call_${t}`,type:"function",function:{name:n.name??"",arguments:n.arguments??"{}"}}))}function W(e,n){let t=n?.output_text;return typeof t=="string"?t:e.filter(r=>r.type==="response.output_text.delta"&&typeof r.delta=="string").map(r=>r.delta).join("")}async function Ee(e,n,t,r,o){let s=e.stream===!0,i;try{i=t.thread(e.previous_response_id,e.input)}catch(u){g(r,400,String(u instanceof Error?u.message:u));return}e.previous_response_id!=null&&(o.naiveInBytes+=Buffer.byteLength(JSON.stringify(i))-Buffer.byteLength(JSON.stringify(e.input)));let a=`resp_${Math.random().toString(36).slice(2,14)}`,f=e.store!==!1,c=await n.createResponse({model:e.model,input:i,stream:s,tools:e.tools,temperature:e.temperature,max_output_tokens:e.max_output_tokens}),l=[];if(s){L(r);for await(let u of o.track(c)){l.push(u);let p=u.response&&typeof u.response=="object"?{...u,response:{...u.response,id:a}}:u;r.write(`event: ${String(u.type??"message")}
|
|
3
|
-
data: ${JSON.stringify(
|
|
2
|
+
"use strict";var He=Object.create;var j=Object.defineProperty;var Xe=Object.getOwnPropertyDescriptor;var Ve=Object.getOwnPropertyNames;var Qe=Object.getPrototypeOf,Ze=Object.prototype.hasOwnProperty;var et=(e,n)=>{for(var t in n)j(e,t,{get:n[t],enumerable:!0})},ce=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of Ve(n))!Ze.call(e,o)&&o!==t&&j(e,o,{get:()=>n[o],enumerable:!(r=Xe(n,o))||r.enumerable});return e};var D=(e,n,t)=>(t=e!=null?He(Qe(e)):{},ce(n||!e||!e.__esModule?j(t,"default",{value:e,enumerable:!0}):t,e)),tt=e=>ce(j({},"__esModule",{value:!0}),e);var It={};et(It,{buildClients:()=>Ye,buildRouter:()=>Ge});module.exports=tt(It);var nt=()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,v=nt();var pe=require("child_process"),I=require("fs"),Ke=require("url"),qe=D(require("path"),1),f=D(require("process"),1),T=require("@urun-sh/core");function le(e,n,t,r){let o=e.response??{},s={request_id:t,consumer_id:r,stream:!0,kind:"chat",messages:n};return typeof o.instructions=="string"&&(s.instructions=o.instructions),Array.isArray(o.modalities)&&(s.modalities=o.modalities),typeof o.temperature=="number"&&(s.temperature=o.temperature),typeof o.max_output_tokens=="number"&&(s.max_output_tokens=o.max_output_tokens),o.tools!==void 0&&(s.tools=o.tools),s}function de(e,n,t){let r={request_id:n,consumer_id:t,stream:!!e.stream,kind:"responses",input:e.input};return e.model&&(r.model=e.model),e.tools!==void 0&&(r.tools=e.tools),typeof e.temperature=="number"&&(r.temperature=e.temperature),typeof e.max_output_tokens=="number"&&(r.max_output_tokens=e.max_output_tokens),r}function me(e){if(e instanceof Error)return{type:"error",error:{type:"urun_error",code:e.name||null,message:e.message}};if(e&&typeof e=="object"&&e.t==="error"){let n=e,t=n.body??{};return{type:"error",error:{type:"urun_error",code:n.code??null,message:t.message??"unknown error"}}}return{type:"error",error:{type:"urun_error",code:null,message:String(e)}}}var rt="llm-resp";async function*z(e,n){let t=`${rt}:${n}`,r=`resp_${n}`;yield{type:"response.created",response:{id:r,status:"in_progress"}};for await(let o of e.stream(t).messages()){let s=o;if(s.t==="delta")yield{type:"response.output_text.delta",item_id:r,delta:s.delta};else if(s.t==="response"){yield{type:"response.completed",response:{id:r,status:"completed",...s.body}};return}else if(s.t==="error"){yield me(s);return}}}var ot="llm",B=class{constructor(n){this.session=n}session;get consumerId(){return this.session.consumerId}write(n){this.session.doc(ot).set({requests:{[n.request_id]:{payload:n,consumer_id:n.consumer_id,stream:n.stream}}})}sendResponses(n,t){let r=de(n,t,this.consumerId);return this.write(r),z(this.session,t)}sendResponseCreate(n,t,r){let o=le(n,t,r,this.consumerId);return this.write(o),z(this.session,r)}};var fe=0;function st(){return fe+=1,`req_${Date.now().toString(36)}_${fe.toString(36)}`}var N=class{transport;constructor(n){this.transport=new B(n)}responses={create:async n=>{let t=st(),r=this.transport.sendResponses(n,t);return Object.assign((async function*(){yield*r})(),{requestId:t})}}};async function ye(e){let t=await(e.fetchImpl??fetch)(`${e.catalogUrl}/model_catalog?select=model_id,variant`,{headers:{apikey:e.anonKey,Authorization:`Bearer ${e.anonKey}`}});if(!t.ok)throw new Error(`model_catalog fetch failed: ${t.status}`);return await t.json()}var ke=require("http");var ge="https://api.urun.sh/v1";function H(e){return e.toLowerCase().replace(/[^\p{L}\p{N}-]/gu,"-").replace(/^-+|-+$/g,"")}var k=class extends Error{};async function he(e){let n=e.fetchImpl??fetch,t=`${e.apiUrl.replace(/\/+$/,"")}/apps`,r=await n(t,{headers:{Authorization:`Bearer ${e.apiKey}`,Accept:"application/json"}});if(!r.ok)throw new Error(`org apps listing failed: GET ${t} \u2192 ${r.status}`);let o=await r.json();if(!Array.isArray(o.apps))throw new Error(`org apps listing returned no "apps" array (GET ${t})`);if(o.truncated===!0)throw new Error(`org apps listing was truncated (GET ${t} returned ${o.apps.length} of more) \u2014 model discovery would silently omit deployed apps`);return o.apps}var it=6e4,J=class{constructor(n){this.opts=n}opts;pool=new Map;appsCache=null;seed(n,t){this.pool.set(n,Promise.resolve(t))}async deployedApps(){if(!this.opts.listApps)return[];let n=this.opts.appsTtlMs??it;if(this.appsCache&&Date.now()-this.appsCache.at<n)return this.appsCache.apps;let t=await this.opts.listApps();return this.appsCache={at:Date.now(),apps:t},t}servable(n){return n.filter(t=>t.function_name===this.opts.fnName&&t.deployment_status==="active").map(t=>t.app_slug)}async availableIds(){let n=new Set([this.opts.defaultApp]);if(this.opts.listApps)for(let t of this.servable(await this.deployedApps()))n.add(t);for(let t of this.pool.keys())n.add(t);return[this.opts.defaultApp,...[...n].filter(t=>t!==this.opts.defaultApp).sort()]}async resolveApp(n){let t=(n??"").trim();if(!t||t==="urun")return this.opts.defaultApp;let r=H(t);if(r===this.opts.defaultApp)return this.opts.defaultApp;if(this.pool.has(r))return r;if(this.opts.listApps){let o=await this.deployedApps(),s=this.servable(o);if(s.includes(r))return r;let a=s.filter(u=>u.startsWith(`${r}-`));if(a.length===1)return a[0];if(a.length>1)throw new k(`model "${t}" is ambiguous across deployed apps ${a.join(", ")} \u2014 name the exact app slug (or catalog id:variant)`);let i=o.find(u=>u.app_slug===r||u.app_slug.startsWith(`${r}-`));if(i)throw new k(`model "${t}" maps to app "${i.app_slug}", which is not an active "${this.opts.fnName}" app \u2014 deploy it with \`urun serve ${t}\`; available models: ${(await this.availableIds()).join(", ")}`)}if(this.opts.listCatalog){let s=(await this.opts.listCatalog()).find(a=>t===a.model_id||t===`${a.model_id}:${a.variant}`||r===H(a.model_id)||r===H(`${a.model_id}-${a.variant}`));if(s)throw new k(`model "${t}" is in the uRun catalog but not deployed \u2014 deploy it with \`urun serve ${s.model_id}\`; available models: ${(await this.availableIds()).join(", ")}`)}return this.opts.defaultApp}async sessionFor(n){let t=await this.resolveApp(n),r=this.pool.get(t);return r||(r=Promise.resolve(this.opts.openSession(t)),this.pool.set(t,r),r.catch(()=>this.pool.delete(t))),{app:t,entry:await r}}async modelList(){return{object:"list",data:(await this.availableIds()).map(t=>({id:t,object:"model",created:0,owned_by:"urun"}))}}async closeAll(){let n=[...this.pool.values()];this.pool.clear();let t=[];if(await Promise.all(n.map(async r=>{try{await this.opts.closeSession(await r)}catch(o){t.push(String(o instanceof Error?o.message:o))}})),t.length>0)throw new Error(`failed to close ${t.length} pooled session(s): ${t.join("; ")}`)}};var X=class e{startedAt=Date.now();requests={};models={};ttftMs=[];tokRates=[];tokensOut=0;charsOut=0;localInBytes=0;naiveInBytes=0;request(n,t,r){this.requests[n]=(this.requests[n]??0)+1,this.localInBytes+=t,this.naiveInBytes+=r}modelEntry(n){let t=this.models[n];if(t)return t;let r=Object.keys(this.models).length>=at?"(other)":n;return this.models[r]??={requests:0,tokens_out:0}}model(n){this.modelEntry(n).requests+=1}track(n,t){let r=this,o=performance.now(),s=null,a=0;return(async function*(){for await(let u of n)u.type==="response.output_text.delta"&&typeof u.delta=="string"&&(s===null&&(s=performance.now(),r.ttftMs.push(s-o),r.ttftMs.length>512&&r.ttftMs.shift()),a+=1,r.tokensOut+=1,r.charsOut+=u.delta.length,t!==void 0&&(r.modelEntry(t).tokens_out+=1)),yield u;let i=(performance.now()-(s??o))/1e3;a>0&&i>0&&(r.tokRates.push(a/i),r.tokRates.length>512&&r.tokRates.shift())})()}static p50(n){if(!n.length)return null;let t=[...n].sort((r,o)=>r-o);return t[Math.floor(t.length/2)]}snapshot(){let n=Object.values(this.requests).reduce((t,r)=>t+r,0);return{uptime_s:Math.round((Date.now()-this.startedAt)/1e3),requests:this.requests,models:this.models,tokens_out:this.tokensOut,chars_out:this.charsOut,ttft_ms:{p50:e.p50(this.ttftMs),last:this.ttftMs.at(-1)??null},tok_per_s:{p50:e.p50(this.tokRates),last:this.tokRates.at(-1)??null},traffic:{local_request_bytes:this.localInBytes,naive_baseline:{request_bytes:this.naiveInBytes,connections:n},persistent_connections:1,history_bytes_saved:Math.max(0,this.naiveInBytes-this.localInBytes),history_bytes_saved_pct:this.naiveInBytes>0?Math.round((1-this.localInBytes/this.naiveInBytes)*1e3)/10:null}}}};function Q(e){e.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive"})}function R(e,n,t){e.writeHead(n,{"content-type":"application/json"}),e.end(JSON.stringify(t))}function _(e,n,t,r="invalid_request_error"){R(e,n,{error:{message:t,type:r}})}function Z(e,n,t){if(n==="anthropic"){R(e,404,{type:"error",error:{type:"not_found_error",message:t.message}});return}R(e,404,{error:{message:t.message,type:"invalid_request_error",code:"model_not_found"}})}var at=256,pt=128;function ee(e){return typeof e.model!="string"||!e.model.trim()?"(default)":e.model.trim().slice(0,pt)}var _e=64*1024*1024,q=class extends Error{};async function ut(e){let n=[],t=0;for await(let o of e){if(t+=o.length,t>_e)throw new q(`request body exceeds ${_e} bytes`);n.push(o)}let r=Buffer.concat(n).toString("utf8");return r?JSON.parse(r):{}}function K(e,n,t,r){return{id:e,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:n,choices:[{index:0,delta:t,finish_reason:r}]}}var ct=256;function lt(e){if(typeof e=="string")return[{role:"user",content:e}];if(Array.isArray(e))return e;throw new Error("responses input must be a string or an array of messages/items")}var V=class{conversations=new Map;thread(n,t){let r=lt(t);if(n==null)return r;let o=this.conversations.get(String(n));if(!o)throw new Error(`unknown previous_response_id ${String(n)} (proxy restarts drop stored responses)`);return[...o,...r]}remember(n,t,r){this.conversations.set(n,[...t,{role:"assistant",content:r}]);for(let o of this.conversations.keys()){if(this.conversations.size<=ct)break;this.conversations.delete(o)}}};function te(e){let n=e?.output;return Array.isArray(n)?n.filter(t=>t.type==="function_call"):[]}function L(e){return e.find(n=>n.type==="response.completed")?.response??null}function dt(e){if(Array.isArray(e))return e.map(n=>{let t=n.function;return!t||typeof t!="object"?n:{type:"function",name:t.name,description:t.description,parameters:t.parameters,strict:t.strict}})}var G=class extends Error{};function mt(e,n){return Array.isArray(n)?n.map(t=>{let r=t;if(r.type==="text")return{type:e==="assistant"?"output_text":"input_text",text:r.text};if(r.type==="image_url"){let o=r.image_url??{};return{type:"input_image",image_url:o.url,detail:o.detail}}throw new G(`unsupported chat content part type "${String(r.type)}" \u2014 the proxy translates text and image_url parts`)}):n}function ft(e){let n=[];for(let t of e){if(t.role==="tool"){n.push({type:"function_call_output",call_id:t.tool_call_id,output:typeof t.content=="string"?t.content:JSON.stringify(t.content??"")});continue}if(t.role==="assistant"&&Array.isArray(t.tool_calls)){typeof t.content=="string"&&t.content&&n.push({role:"assistant",content:t.content});for(let r of t.tool_calls){let o=r.function??{};n.push({type:"function_call",call_id:r.id,name:o.name,arguments:o.arguments})}continue}n.push({...t,content:mt(t.role,t.content)})}return n}function we(e){return te(e).map((n,t)=>({index:t,id:n.call_id??n.id??`call_${t}`,type:"function",function:{name:n.name??"",arguments:n.arguments??"{}"}}))}function xe(e,n){let t=n?.output_text;return typeof t=="string"?t:e.filter(r=>r.type==="response.output_text.delta"&&typeof r.delta=="string").map(r=>r.delta).join("")}async function yt(e,n,t,r,o){let s=e.stream===!0,a;try{a=t.thread(e.previous_response_id,e.input)}catch(p){_(r,400,String(p instanceof Error?p.message:p));return}e.previous_response_id!=null&&(o.naiveInBytes+=Buffer.byteLength(JSON.stringify(a))-Buffer.byteLength(JSON.stringify(e.input)));let i=`resp_${Math.random().toString(36).slice(2,14)}`,u=e.store!==!1,d=ee(e);o.model(d);let l;try{l=await n.createResponse({model:e.model,input:a,stream:s,tools:e.tools,temperature:e.temperature,max_output_tokens:e.max_output_tokens})}catch(p){if(p instanceof k){Z(r,"openai",p);return}throw p}let m=[];if(s){Q(r);let p=y=>{r.write(`event: ${String(y.type??"message")}
|
|
3
|
+
data: ${JSON.stringify(y)}
|
|
4
4
|
|
|
5
|
-
`)}
|
|
5
|
+
`)},c=y=>y.response&&typeof y.response=="object"?{...y,response:{...y.response,id:i}}:y,h=`item_${i.slice(5)}`,S=!1,A=!1,g=!1,E="",P=y=>{g&&(g=!1,p({type:"response.output_text.done",item_id:h,output_index:0,content_index:0,text:E}),p({type:"response.content_part.done",item_id:h,output_index:0,content_index:0,part:{type:"output_text",text:E}}),p({type:"response.output_item.done",output_index:0,item:{id:h,type:"message",role:"assistant",status:y,content:[{type:"output_text",text:E}]}}))};for await(let y of o.track(l,d)){m.push(y);let U=String(y.type??"");if(U==="response.created"||U==="response.in_progress"){S=!0,p(c(y));continue}if(U==="response.output_item.added"){A=!0,p(c(y));continue}if(U==="response.output_text.delta"&&!A){S||(S=!0,p({type:"response.created",response:{id:i,object:"response",status:"in_progress"}})),g||(g=!0,p({type:"response.output_item.added",output_index:0,item:{id:h,type:"message",role:"assistant",status:"in_progress",content:[]}}),p({type:"response.content_part.added",item_id:h,output_index:0,content_index:0,part:{type:"output_text",text:""}})),E+=String(y.delta??""),p({type:U,item_id:h,output_index:0,content_index:0,delta:y.delta});continue}if(U==="response.completed"&&!A){let Fe=g;P("completed"),te(y.response).forEach((O,ze)=>{let $=(Fe?1:0)+ze,M=O.id??O.call_id??`fc_${$}`,F=O.arguments??"",ue={id:M,type:"function_call",call_id:O.call_id??M,name:O.name??""};p({type:"response.output_item.added",output_index:$,item:{...ue,arguments:"",status:"in_progress"}}),p({type:"response.function_call_arguments.delta",item_id:M,output_index:$,delta:F}),p({type:"response.function_call_arguments.done",item_id:M,output_index:$,arguments:F}),p({type:"response.output_item.done",output_index:$,item:{...ue,arguments:F,status:"completed"}})}),p(c(y));continue}p(c(y))}P("incomplete"),u&&t.remember(i,a,xe(m,L(m))),r.write(`data: [DONE]
|
|
6
6
|
|
|
7
|
-
`),r.end();return}let
|
|
7
|
+
`),r.end();return}let w=null;for await(let p of o.track(l,d)){if(m.push(p),p.type==="error"){_(r,502,JSON.stringify(p),"upstream_error");return}p.type==="response.completed"&&(w=p.response)}if(w==null){_(r,502,"upstream produced no response.completed event","upstream_error");return}let x=xe(m,w);u&&t.remember(i,a,x),R(r,200,{...w,id:i})}async function gt(e,n,t,r){let o=e.messages;if(!Array.isArray(o)){_(t,400,"chat/completions requires a messages array");return}let s=String(e.model??"urun"),a=`chatcmpl-${Math.random().toString(36).slice(2,14)}`,i;try{i=ft(o)}catch(p){if(p instanceof G){_(t,400,p.message);return}throw p}let u=ee(e);r.model(u);let d;try{d=await n.createResponse({model:e.model,input:i,stream:!0,tools:dt(e.tools),temperature:e.temperature,max_output_tokens:e.max_completion_tokens??e.max_tokens})}catch(p){if(p instanceof k){Z(t,"openai",p);return}throw p}if(e.stream===!0){Q(t);let p=[];t.write(`data: ${JSON.stringify(K(a,s,{role:"assistant"},null))}
|
|
8
8
|
|
|
9
|
-
`);for await(let h of r.track(
|
|
9
|
+
`);for await(let h of r.track(d,u))if(p.push(h),h.type==="response.output_text.delta"&&typeof h.delta=="string")t.write(`data: ${JSON.stringify(K(a,s,{content:h.delta},null))}
|
|
10
10
|
|
|
11
11
|
`);else if(h.type==="error"){t.write(`data: ${JSON.stringify({error:h})}
|
|
12
12
|
|
|
13
|
-
`),t.end();return}let
|
|
13
|
+
`),t.end();return}let c=we(L(p));c.length>0&&t.write(`data: ${JSON.stringify(K(a,s,{tool_calls:c},null))}
|
|
14
14
|
|
|
15
|
-
`),t.write(`data: ${JSON.stringify(
|
|
15
|
+
`),t.write(`data: ${JSON.stringify(K(a,s,{},c.length>0?"tool_calls":"stop"))}
|
|
16
16
|
|
|
17
17
|
`),t.write(`data: [DONE]
|
|
18
18
|
|
|
19
|
-
`),t.end();return}let
|
|
20
|
-
data: ${JSON.stringify({type:
|
|
21
|
-
|
|
22
|
-
`)};if(e.stream===!0){let
|
|
23
|
-
`)}
|
|
24
|
-
`)
|
|
25
|
-
`),
|
|
26
|
-
`),
|
|
19
|
+
`),t.end();return}let l="",m=[];for await(let p of r.track(d,u))if(m.push(p),p.type==="response.output_text.delta"&&typeof p.delta=="string"&&(l+=p.delta),p.type==="error"){_(t,502,JSON.stringify(p),"upstream_error");return}let w=we(L(m)).map(({index:p,...c})=>c),x={role:"assistant",content:l||null};w.length>0&&(x.tool_calls=w),R(t,200,{id:a,object:"chat.completion",created:Math.floor(Date.now()/1e3),model:s,choices:[{index:0,message:x,finish_reason:w.length>0?"tool_calls":"stop"}],usage:{prompt_tokens:0,completion_tokens:0,total_tokens:0}})}function ht(e){return typeof e=="string"?e:Array.isArray(e)?e.filter(n=>n.type==="text").map(n=>String(n.text??"")).join(""):""}var b=class extends Error{constructor(n){super(`unsupported anthropic content block type "${n}" \u2014 the proxy translates text, tool_use and tool_result blocks`)}};function _t(e){if(Array.isArray(e))return e.map(n=>{let t=n;return{type:"function",name:t.name,description:t.description,parameters:t.input_schema}})}function wt(e){if(typeof e=="string")return e;if(e==null)return"";if(!Array.isArray(e))throw new b(typeof e);let n="";for(let t of e){let r=String(t.type??"");if(r!=="text")throw new b(`tool_result > ${r}`);n+=String(t.text??"")}return n}function xt(e){let{role:n,content:t}=e;if(typeof t=="string")return[{role:n,content:t}];if(t==null)return[];if(!Array.isArray(t))throw new b(typeof t);let r=[],o="",s=()=>{o&&(r.push({role:n,content:o}),o="")};for(let a of t){let i=String(a.type??"");if(i==="text")o+=String(a.text??"");else if(i==="tool_use")s(),r.push({type:"function_call",call_id:a.id,name:a.name,arguments:JSON.stringify(a.input??{})});else if(i==="tool_result")s(),r.push({type:"function_call_output",call_id:a.tool_use_id,output:wt(a.content)});else{if(i==="thinking"||i==="redacted_thinking")continue;throw new b(i)}}return s(),r}function ve(e){return te(e).map((n,t)=>{let r;try{r=JSON.parse(n.arguments||"{}")}catch{throw new Error(`upstream function_call "${n.name??""}" arguments are not valid JSON`)}return{type:"tool_use",id:n.call_id??n.id??`toolu_${t}`,name:n.name??"",input:r}})}async function vt(e,n,t,r){let o=e.messages;if(!Array.isArray(o)){_(t,400,"messages requires a messages array");return}let s=String(e.model??"urun"),a=`msg_${Math.random().toString(36).slice(2,14)}`,i=[];try{e.system&&i.push({role:"system",content:ht(e.system)});for(let c of o)i.push(...xt(c))}catch(c){if(c instanceof b){_(t,400,c.message);return}throw c}let u=ee(e);r.model(u);let d;try{d=await n.createResponse({model:e.model,input:i,stream:!0,tools:_t(e.tools),temperature:e.temperature,max_output_tokens:e.max_tokens})}catch(c){if(c instanceof k){Z(t,"anthropic",c);return}throw c}let l=(c,h)=>{t.write(`event: ${c}
|
|
20
|
+
data: ${JSON.stringify({type:c,...h})}
|
|
21
|
+
|
|
22
|
+
`)};if(e.stream===!0){let c=r.track(d,u);Q(t),l("message_start",{message:{id:a,type:"message",role:"assistant",content:[],model:s,stop_reason:null,usage:{input_tokens:0,output_tokens:0}}}),l("content_block_start",{index:0,content_block:{type:"text",text:""}});let h=0,S=[];for await(let g of c)if(S.push(g),g.type==="response.output_text.delta"&&typeof g.delta=="string")h+=1,l("content_block_delta",{index:0,delta:{type:"text_delta",text:g.delta}});else if(g.type==="error"){l("error",{error:{type:"api_error",message:JSON.stringify(g)}}),t.end();return}let A;try{A=ve(L(S))}catch(g){l("error",{error:{type:"api_error",message:String(g instanceof Error?g.message:g)}}),t.end();return}l("content_block_stop",{index:0}),A.forEach((g,E)=>{let P=1+E;l("content_block_start",{index:P,content_block:{...g,input:{}}}),l("content_block_delta",{index:P,delta:{type:"input_json_delta",partial_json:JSON.stringify(g.input??{})}}),l("content_block_stop",{index:P})}),l("message_delta",{delta:{stop_reason:A.length>0?"tool_use":"end_turn"},usage:{output_tokens:h}}),l("message_stop",{}),t.end();return}let m="",w=[];for await(let c of r.track(d,u))if(w.push(c),c.type==="response.output_text.delta"&&typeof c.delta=="string"&&(m+=c.delta),c.type==="error"){_(t,502,JSON.stringify(c),"upstream_error");return}let x;try{x=ve(L(w))}catch(c){_(t,502,String(c instanceof Error?c.message:c),"upstream_error");return}let p=[];(m||x.length===0)&&p.push({type:"text",text:m}),p.push(...x),R(t,200,{id:a,type:"message",role:"assistant",content:p,model:s,stop_reason:x.length>0?"tool_use":"end_turn",usage:{input_tokens:0,output_tokens:0}})}function Re(e){let{clients:n,apiKey:t,identity:r}=e,o=new V,s=new X;return(0,ke.createServer)((a,i)=>{(async()=>{let u=new URL(a.url??"/","http://localhost");if(a.method==="GET"&&u.pathname==="/healthz"){R(i,200,{ok:!0});return}if(a.method==="GET"&&u.pathname==="/stats"){R(i,200,r?{identity:r,...s.snapshot()}:s.snapshot());return}if(t&&(a.headers.authorization??"")!==`Bearer ${t}`){_(i,401,"invalid local proxy api key","authentication_error");return}if(a.method==="GET"&&u.pathname==="/v1/models"){R(i,200,await n.listModels());return}if(a.method!=="POST"){_(i,404,`no route for ${a.method} ${u.pathname}`);return}let d;try{d=await ut(a)}catch(m){if(m instanceof q){_(i,413,m.message,"request_too_large");return}_(i,400,"request body is not valid JSON");return}let l=Buffer.byteLength(JSON.stringify(d));if(u.pathname==="/v1/responses"){s.request("responses",l,l),await yt(d,n,o,i,s);return}if(u.pathname==="/v1/chat/completions"){s.request("chat_completions",l,l),await gt(d,n,i,s);return}if(u.pathname==="/v1/messages"){s.request("messages",l,l),await vt(d,n,i,s);return}_(i,404,`no route for POST ${u.pathname}`)})().catch(u=>{i.headersSent?i.end():_(i,500,String(u),"proxy_error")})})}var be=D(require("os"),1),Se=D(require("path"),1),Ee=require("util");var Ae="0.2.51";var Pe=4141,Rt=Ae,W={claude:{bin:"claude",env:"anthropic"},codex:{bin:"codex",env:"openai"},pi:{bin:"pi",env:"openai"},aider:{bin:"aider",env:"openai"},opencode:{bin:"opencode",env:"openai"},crush:{bin:"crush",env:"openai"}};function oe(e,n="openai"){if(n==="anthropic")return{ANTHROPIC_BASE_URL:`http://127.0.0.1:${e}`,ANTHROPIC_API_KEY:"urun-local"};let t=`http://127.0.0.1:${e}/v1`;return{OPENAI_BASE_URL:t,OPENAI_API_BASE:t,OPENAI_API_KEY:"urun-local"}}function Ue(e,n,t){let r={...e,...oe(n,t)};return delete r.URUN_API_KEY,delete r.URUN_JWT,r}function Ie(e,n,t){let r=["-c","model_providers.urun.name=urun","-c",`model_providers.urun.base_url="http://127.0.0.1:${e}/v1"`,"-c",'model_providers.urun.wire_api="responses"',"-c",'model_provider="urun"'],o=t.indexOf("--"),a=(o===-1?t:t.slice(0,o)).some(i=>i==="-m"||i==="--model"||i.startsWith("--model="));return[...r,...a?[]:["-m",n],...t]}function Oe(e,n){return n.some(r=>r==="--model"||r==="--provider"||r.startsWith("--model=")||r.startsWith("--provider="))?n:["--provider","urun","--model",e,...n]}function $e(e){let n=Number(e);if(!Number.isInteger(n)||n<0)throw new Error(`--port needs a non-negative integer (0 = OS-assigned ephemeral), got ${e}`);return n}function Ne(e){if(e[0]!=="--port")return null;let n=$e(e[1]);return e.splice(0,2),n}function se(e){let n=e.indexOf("--port");if(n===-1)return Pe;let t=$e(e[n+1]);return e.splice(n,2),t}function Le(e){if(e===0)throw new Error("`env` cannot use --port 0: it starts no proxy, so the OS never assigns the ephemeral port \u2014 pass the actual port of the running proxy");return e}function ne(e,n){let t=(e[n]??"").trim();if(!t)throw new Error(`${n} is not set. The proxy needs URUN_BASE_URL, URUN_ORG_ID, URUN_APP and either URUN_API_KEY (the org key; the proxy vends short-lived function-scoped client tokens from it via the SDK) or URUN_JWT (an explicit pre-vended token override) \u2014 plus optional URUN_FUNCTION, default "serve" \u2014 to open the backhaul session.`);return t}function C(e){let n=At(ne(e,"URUN_BASE_URL")),t=ne(e,"URUN_ORG_ID"),r=ne(e,"URUN_APP"),o=(e.URUN_FUNCTION??"serve").trim()||"serve",s=(e.URUN_API_URL??"").trim(),a=s?Y(s,"URUN_API_URL"):ge,i=(e.URUN_JWT??"").trim();if(i)return{baseUrl:n,orgId:t,appId:r,fnName:o,apiUrl:a,auth:{lane:"jwt",jwt:i}};let u=(e.URUN_API_KEY??"").trim();if(u){let d=(e.URUN_GATEWAY_URL??"").trim(),l=d?Y(d):void 0;return{baseUrl:n,orgId:t,appId:r,fnName:o,apiUrl:a,auth:{lane:"api-key",apiKey:u,gatewayUrl:l}}}throw new Error("neither URUN_API_KEY nor URUN_JWT is set. Set URUN_API_KEY (the org API key \u2014 the proxy vends short-lived, function-scoped client tokens from it via the SDK, so the key never reaches the agent process) or URUN_JWT (an explicit pre-vended token; when set it always wins). URUN_BASE_URL, URUN_ORG_ID and URUN_APP are also required.")}function At(e,n="URUN_BASE_URL"){if(/\/v1\/*$/.test(new URL(e).pathname))throw new Error(`${n} must be the session-gateway base (e.g. https://api.urun.sh), got ${e} \u2014 a trailing /v1 is the org control-plane API form (that belongs in URUN_API_URL); session allocation 404s against it. Drop the /v1.`);return e}function Y(e,n="URUN_GATEWAY_URL"){let t=new URL(e),r=t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="[::1]";if(t.protocol!=="https:"&&!r)throw new Error(`${n} must be https (got ${e}) \u2014 the org API key rides the request's Authorization header`);return e}function ie(e){return{app:e.appId,org:e.orgId,fn:e.fnName,base_url:e.baseUrl,proxy_version:Rt}}function bt(e,n){return e.app===n.app&&e.org===n.org&&e.fn===n.fn&&e.base_url===n.base_url&&e.proxy_version===n.proxy_version}var St=250;async function ae(e,n=St){try{let t=await fetch(`http://127.0.0.1:${e}/stats`,{signal:AbortSignal.timeout(n)});if(!t.ok)return null;let r=(await t.json()).identity;if(r==null||typeof r!="object")return null;let{app:o,org:s,fn:a,base_url:i,proxy_version:u}=r;return typeof o!="string"||typeof s!="string"||typeof a!="string"||typeof i!="string"||typeof u!="string"?null:{app:o,org:s,fn:a,base_url:i,proxy_version:u}}catch{return null}}async function Ce(e,n,t=Pe){if(e!=null)return{mode:"spawn",port:e};let r=await ae(t);return r!==null&&bt(r,n)?{mode:"reuse",port:t,identity:r}:{mode:"spawn",port:0}}function Te(e,n=be.default.homedir()){return Se.default.join(n,".urun","logs",`compat-proxy-${e}.log`)}var re=["log","info","warn","error","debug","trace"];function Me(e,n=process,t=console){let r=n.stdout.write,o=n.stderr.write,s=(i,...u)=>e.write(i,...u);n.stdout.write=s,n.stderr.write=s;let a=Object.fromEntries(re.map(i=>[i,t[i]]));for(let i of re)t[i]=(...u)=>{e.write(`${(0,Ee.format)(...u)}
|
|
23
|
+
`)};return()=>{n.stdout.write=r,n.stderr.write=o;for(let i of re)t[i]=a[i]}}function je(e,n){let t="pass --port 0 for an OS-assigned ephemeral port";return n?`port ${e} is held by another urun-openai proxy serving app ${n.app} (org ${n.org}, fn ${n.fn}, version ${n.proxy_version}) \u2014 reuse it by pointing your agent at http://127.0.0.1:${e}, or ${t}`:`port ${e} is already in use and does not answer the urun-openai /stats identity probe \u2014 ${t}`}function De(e,n,t){let r=e[t];if(typeof r!="function")throw new Error(`app "${n}" has no function "${t}" (set URUN_FUNCTION)`);let o=r();if(typeof o.end!="function")throw new Error(`app "${n}" function "${t}" returned a session without end()`);return o}function Be(e,n){let{baseUrl:t,orgId:r,fnName:o,auth:s}=e;if(s.lane==="jwt"){let l=(0,T.App)(n,{baseUrl:t,orgId:r,jwt:s.jwt}),m=De(l,n,o);return{session:m,responses:new N(m)}}let{apiKey:a,gatewayUrl:i}=s,u=(0,T.App)(n,{baseUrl:t,orgId:r,getAccessToken:async()=>(await(0,T.createClientToken)(a,{baseUrl:i,expiresIn:300,allowedFunctions:[`${n}/${o}`]})).token}),d=De(u,n,o);return{session:d,responses:new N(d)}}function Ge(e){let n=(f.default.env.URUN_CATALOG_URL??"").trim(),t=n?Y(n,"URUN_CATALOG_URL"):"",r=(f.default.env.URUN_CATALOG_ANON_KEY??"").trim();(!t||!r)&&f.default.stderr.write(`urun compat: catalog oracle off (URUN_CATALOG_URL/URUN_CATALOG_ANON_KEY unset) \u2014 an undeployed uRun CATALOG model name falls through to the default app instead of a loud 404; deployed apps still resolve exactly, and /stats.models records every mapping
|
|
24
|
+
`);let o=e.auth,s=new J({defaultApp:e.appId,fnName:e.fnName,openSession:a=>Be(e,a),closeSession:async a=>{await a.session.end()},listApps:o.lane==="api-key"?()=>he({apiUrl:e.apiUrl,apiKey:o.apiKey}):null,listCatalog:t&&r?()=>ye({catalogUrl:t,anonKey:r}):null});return s.seed(e.appId,Be(e,e.appId)),s}function Ye(e){return{createResponse:async n=>{let{entry:t}=await e.sessionFor(n.model);return t.responses.responses.create(n)},listModels:()=>e.modelList()}}async function Et(e,n,t){let r=(0,pe.spawn)(e,n,{stdio:"inherit",env:t});return new Promise(o=>{r.once("exit",s=>o(s??1)),r.once("error",s=>{f.default.stderr.write(`failed to launch ${e}: ${String(s)}
|
|
25
|
+
`),o(127)})})}function Pt(){let e=(0,Ke.fileURLToPath)(new URL("../pi-extension/index.js",v));if(!(0,I.existsSync)(e))throw new Error(`pi extension entry not found at ${e} \u2014 the pi lane loads @urun-sh/openai/pi-extension from the installed package (run \`urun compat pi\` via the npm-installed CLI, e.g. \`npx @urun-sh/openai\`), or pass it to pi yourself: \`pi -e <path-to-@urun-sh/openai>/dist/pi-extension.js\``);return e}function We(e){for(let[n,t]of Object.entries(oe(e)))f.default.stdout.write(`export ${n}=${t}
|
|
26
|
+
`)}async function Je(e,n){let t=C(f.default.env),r=n?ie(t):void 0,o=Ge(t),s=Re({clients:Ye(o),identity:r});try{await new Promise((i,u)=>{s.once("error",u),s.listen(e,"127.0.0.1",i)})}catch(i){throw await o.closeAll().catch(()=>{}),i.code==="EADDRINUSE"?new Error(je(e,await ae(e))):i}let a=s.address().port;return n&&(f.default.stdout.write(`urun openai proxy listening on http://127.0.0.1:${a}/v1
|
|
27
|
+
`),We(a)),{port:a,close:async()=>{let i=new Promise(u=>s.close(()=>u()));s.closeAllConnections(),await i,await o.closeAll()}}}async function Ut(){let e=f.default.argv.slice(2),n=Ne(e),t=e.shift()??"proxy";if(t==="env")return We(Le(n??se(e))),0;if(t==="proxy"){let o=n??se(e),s=await Je(o,!0);return await new Promise(a=>{f.default.once("SIGINT",a),f.default.once("SIGTERM",a)}),await s.close(),0}let r=t==="run"?e.shift():t;{if(!r)throw new Error(`launch needs an agent (${Object.keys(W).join("|")}) or \`-- <cmd>\``);if(r==="pi"){let p=C(f.default.env);return Et("pi",["-e",Pt(),...Oe(p.appId,e)],f.default.env)}let o,s,a="openai";if(r==="--"){if(o=e.shift()??"",s=e,!o)throw new Error("`-- <cmd>` needs a command to launch")}else{let p=W[r];if(!p)throw new Error(`unknown command/agent ${r}; commands: proxy | env; agents: ${Object.keys(W).join(" | ")} \u2014 or \`-- <cmd>\``);o=p.bin,s=[...p.args??[],...e],a=p.env}let i=await Ce(n,ie(C(f.default.env))),u=null,d=i.port;i.mode==="reuse"&&f.default.stdout.write(`reusing the running urun openai proxy on http://127.0.0.1:${d} (app ${i.identity.app}, fn ${i.identity.fn}, v${i.identity.proxy_version})
|
|
28
|
+
`);let l=null,m=null;if(i.mode!=="reuse"){u=await Je(i.port,!1),d=u.port;let p=Te(d);(0,I.mkdirSync)(qe.default.dirname(p),{recursive:!0}),m=(0,I.createWriteStream)(p,{flags:"a"}),m.on("error",c=>{l?.(),l=null,f.default.stderr.write(`urun compat: proxy log ${p} failed: ${String(c)} \u2014 proxy output continues on the terminal
|
|
29
|
+
`)}),f.default.stdout.write(`urun openai proxy on http://127.0.0.1:${d}/v1 \u2014 proxy logs: ${p}
|
|
30
|
+
`),l=Me(m)}r==="codex"&&(s=Ie(d,C(f.default.env).appId,s));let w=(0,pe.spawn)(o,s,{stdio:"inherit",env:Ue(f.default.env,d,a)}),x=await new Promise(p=>{w.once("exit",c=>p(c??1)),w.once("error",c=>{l?.(),l=null,f.default.stderr.write(`failed to launch ${o}: ${String(c)}
|
|
31
|
+
`),p(127)})});try{u&&await u.close()}finally{l?.(),l=null,m?.end()}return x}}Ut().then(e=>f.default.exit(e),e=>{f.default.stderr.write(`${String(e instanceof Error?e.message:e)}
|
|
32
|
+
`),f.default.exit(1)});0&&(module.exports={buildClients,buildRouter});
|
package/dist/proxy/cli.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
3
|
-
`)}
|
|
4
|
-
`)
|
|
5
|
-
`),
|
|
6
|
-
`),r.
|
|
2
|
+
import{a as E}from"../chunk-OLE2YJO3.js";import{a as I}from"../chunk-KJ25VEMG.js";import{c as x}from"../chunk-FSS2A7IU.js";import{a as A,c as S,d as N}from"../chunk-PS4BLX7J.js";import{spawn as F}from"child_process";import{createWriteStream as ie,existsSync as ae,mkdirSync as pe}from"fs";import{fileURLToPath as ue}from"url";import ce from"path";import p from"process";import{App as J,createClientToken as de}from"@urun-sh/core";import Z from"os";import ee from"path";import{format as te}from"util";var O="0.2.51";var $=4141,ne=O,h={claude:{bin:"claude",env:"anthropic"},codex:{bin:"codex",env:"openai"},pi:{bin:"pi",env:"openai"},aider:{bin:"aider",env:"openai"},opencode:{bin:"opencode",env:"openai"},crush:{bin:"crush",env:"openai"}};function v(e,t="openai"){if(t==="anthropic")return{ANTHROPIC_BASE_URL:`http://127.0.0.1:${e}`,ANTHROPIC_API_KEY:"urun-local"};let n=`http://127.0.0.1:${e}/v1`;return{OPENAI_BASE_URL:n,OPENAI_API_BASE:n,OPENAI_API_KEY:"urun-local"}}function k(e,t,n){let r={...e,...v(t,n)};return delete r.URUN_API_KEY,delete r.URUN_JWT,r}function L(e,t,n){let r=["-c","model_providers.urun.name=urun","-c",`model_providers.urun.base_url="http://127.0.0.1:${e}/v1"`,"-c",'model_providers.urun.wire_api="responses"',"-c",'model_provider="urun"'],s=n.indexOf("--"),a=(s===-1?n:n.slice(0,s)).some(o=>o==="-m"||o==="--model"||o.startsWith("--model="));return[...r,...a?[]:["-m",t],...n]}function T(e,t){return t.some(r=>r==="--model"||r==="--provider"||r.startsWith("--model=")||r.startsWith("--provider="))?t:["--provider","urun","--model",e,...t]}function C(e){let t=Number(e);if(!Number.isInteger(t)||t<0)throw new Error(`--port needs a non-negative integer (0 = OS-assigned ephemeral), got ${e}`);return t}function j(e){if(e[0]!=="--port")return null;let t=C(e[1]);return e.splice(0,2),t}function _(e){let t=e.indexOf("--port");if(t===-1)return $;let n=C(e[t+1]);return e.splice(t,2),n}function G(e){if(e===0)throw new Error("`env` cannot use --port 0: it starts no proxy, so the OS never assigns the ephemeral port \u2014 pass the actual port of the running proxy");return e}function w(e,t){let n=(e[t]??"").trim();if(!n)throw new Error(`${t} is not set. The proxy needs URUN_BASE_URL, URUN_ORG_ID, URUN_APP and either URUN_API_KEY (the org key; the proxy vends short-lived function-scoped client tokens from it via the SDK) or URUN_JWT (an explicit pre-vended token override) \u2014 plus optional URUN_FUNCTION, default "serve" \u2014 to open the backhaul session.`);return n}function f(e){let t=re(w(e,"URUN_BASE_URL")),n=w(e,"URUN_ORG_ID"),r=w(e,"URUN_APP"),s=(e.URUN_FUNCTION??"serve").trim()||"serve",i=(e.URUN_API_URL??"").trim(),a=i?g(i,"URUN_API_URL"):A,o=(e.URUN_JWT??"").trim();if(o)return{baseUrl:t,orgId:n,appId:r,fnName:s,apiUrl:a,auth:{lane:"jwt",jwt:o}};let u=(e.URUN_API_KEY??"").trim();if(u){let d=(e.URUN_GATEWAY_URL??"").trim(),l=d?g(d):void 0;return{baseUrl:t,orgId:n,appId:r,fnName:s,apiUrl:a,auth:{lane:"api-key",apiKey:u,gatewayUrl:l}}}throw new Error("neither URUN_API_KEY nor URUN_JWT is set. Set URUN_API_KEY (the org API key \u2014 the proxy vends short-lived, function-scoped client tokens from it via the SDK, so the key never reaches the agent process) or URUN_JWT (an explicit pre-vended token; when set it always wins). URUN_BASE_URL, URUN_ORG_ID and URUN_APP are also required.")}function re(e,t="URUN_BASE_URL"){if(/\/v1\/*$/.test(new URL(e).pathname))throw new Error(`${t} must be the session-gateway base (e.g. https://api.urun.sh), got ${e} \u2014 a trailing /v1 is the org control-plane API form (that belongs in URUN_API_URL); session allocation 404s against it. Drop the /v1.`);return e}function g(e,t="URUN_GATEWAY_URL"){let n=new URL(e),r=n.hostname==="localhost"||n.hostname==="127.0.0.1"||n.hostname==="[::1]";if(n.protocol!=="https:"&&!r)throw new Error(`${t} must be https (got ${e}) \u2014 the org API key rides the request's Authorization header`);return e}function R(e){return{app:e.appId,org:e.orgId,fn:e.fnName,base_url:e.baseUrl,proxy_version:ne}}function oe(e,t){return e.app===t.app&&e.org===t.org&&e.fn===t.fn&&e.base_url===t.base_url&&e.proxy_version===t.proxy_version}var se=250;async function P(e,t=se){try{let n=await fetch(`http://127.0.0.1:${e}/stats`,{signal:AbortSignal.timeout(t)});if(!n.ok)return null;let r=(await n.json()).identity;if(r==null||typeof r!="object")return null;let{app:s,org:i,fn:a,base_url:o,proxy_version:u}=r;return typeof s!="string"||typeof i!="string"||typeof a!="string"||typeof o!="string"||typeof u!="string"?null:{app:s,org:i,fn:a,base_url:o,proxy_version:u}}catch{return null}}async function K(e,t,n=$){if(e!=null)return{mode:"spawn",port:e};let r=await P(n);return r!==null&&oe(r,t)?{mode:"reuse",port:n,identity:r}:{mode:"spawn",port:0}}function D(e,t=Z.homedir()){return ee.join(t,".urun","logs",`compat-proxy-${e}.log`)}var U=["log","info","warn","error","debug","trace"];function W(e,t=process,n=console){let r=t.stdout.write,s=t.stderr.write,i=(o,...u)=>e.write(o,...u);t.stdout.write=i,t.stderr.write=i;let a=Object.fromEntries(U.map(o=>[o,n[o]]));for(let o of U)n[o]=(...u)=>{e.write(`${te(...u)}
|
|
3
|
+
`)};return()=>{t.stdout.write=r,t.stderr.write=s;for(let o of U)n[o]=a[o]}}function M(e,t){let n="pass --port 0 for an OS-assigned ephemeral port";return t?`port ${e} is held by another urun-openai proxy serving app ${t.app} (org ${t.org}, fn ${t.fn}, version ${t.proxy_version}) \u2014 reuse it by pointing your agent at http://127.0.0.1:${e}, or ${n}`:`port ${e} is already in use and does not answer the urun-openai /stats identity probe \u2014 ${n}`}function Y(e,t,n){let r=e[n];if(typeof r!="function")throw new Error(`app "${t}" has no function "${n}" (set URUN_FUNCTION)`);let s=r();if(typeof s.end!="function")throw new Error(`app "${t}" function "${n}" returned a session without end()`);return s}function B(e,t){let{baseUrl:n,orgId:r,fnName:s,auth:i}=e;if(i.lane==="jwt"){let l=J(t,{baseUrl:n,orgId:r,jwt:i.jwt}),m=Y(l,t,s);return{session:m,responses:new x(m)}}let{apiKey:a,gatewayUrl:o}=i,u=J(t,{baseUrl:n,orgId:r,getAccessToken:async()=>(await de(a,{baseUrl:o,expiresIn:300,allowedFunctions:[`${t}/${s}`]})).token}),d=Y(u,t,s);return{session:d,responses:new x(d)}}function le(e){let t=(p.env.URUN_CATALOG_URL??"").trim(),n=t?g(t,"URUN_CATALOG_URL"):"",r=(p.env.URUN_CATALOG_ANON_KEY??"").trim();(!n||!r)&&p.stderr.write(`urun compat: catalog oracle off (URUN_CATALOG_URL/URUN_CATALOG_ANON_KEY unset) \u2014 an undeployed uRun CATALOG model name falls through to the default app instead of a loud 404; deployed apps still resolve exactly, and /stats.models records every mapping
|
|
4
|
+
`);let s=e.auth,i=new N({defaultApp:e.appId,fnName:e.fnName,openSession:a=>B(e,a),closeSession:async a=>{await a.session.end()},listApps:s.lane==="api-key"?()=>S({apiUrl:e.apiUrl,apiKey:s.apiKey}):null,listCatalog:n&&r?()=>E({catalogUrl:n,anonKey:r}):null});return i.seed(e.appId,B(e,e.appId)),i}function me(e){return{createResponse:async t=>{let{entry:n}=await e.sessionFor(t.model);return n.responses.responses.create(t)},listModels:()=>e.modelList()}}async function ye(e,t,n){let r=F(e,t,{stdio:"inherit",env:n});return new Promise(s=>{r.once("exit",i=>s(i??1)),r.once("error",i=>{p.stderr.write(`failed to launch ${e}: ${String(i)}
|
|
5
|
+
`),s(127)})})}function fe(){let e=ue(new URL("../pi-extension/index.js",import.meta.url));if(!ae(e))throw new Error(`pi extension entry not found at ${e} \u2014 the pi lane loads @urun-sh/openai/pi-extension from the installed package (run \`urun compat pi\` via the npm-installed CLI, e.g. \`npx @urun-sh/openai\`), or pass it to pi yourself: \`pi -e <path-to-@urun-sh/openai>/dist/pi-extension.js\``);return e}function H(e){for(let[t,n]of Object.entries(v(e)))p.stdout.write(`export ${t}=${n}
|
|
6
|
+
`)}async function q(e,t){let n=f(p.env),r=t?R(n):void 0,s=le(n),i=I({clients:me(s),identity:r});try{await new Promise((o,u)=>{i.once("error",u),i.listen(e,"127.0.0.1",o)})}catch(o){throw await s.closeAll().catch(()=>{}),o.code==="EADDRINUSE"?new Error(M(e,await P(e))):o}let a=i.address().port;return t&&(p.stdout.write(`urun openai proxy listening on http://127.0.0.1:${a}/v1
|
|
7
|
+
`),H(a)),{port:a,close:async()=>{let o=new Promise(u=>i.close(()=>u()));i.closeAllConnections(),await o,await s.closeAll()}}}async function ge(){let e=p.argv.slice(2),t=j(e),n=e.shift()??"proxy";if(n==="env")return H(G(t??_(e))),0;if(n==="proxy"){let s=t??_(e),i=await q(s,!0);return await new Promise(a=>{p.once("SIGINT",a),p.once("SIGTERM",a)}),await i.close(),0}let r=n==="run"?e.shift():n;{if(!r)throw new Error(`launch needs an agent (${Object.keys(h).join("|")}) or \`-- <cmd>\``);if(r==="pi"){let c=f(p.env);return ye("pi",["-e",fe(),...T(c.appId,e)],p.env)}let s,i,a="openai";if(r==="--"){if(s=e.shift()??"",i=e,!s)throw new Error("`-- <cmd>` needs a command to launch")}else{let c=h[r];if(!c)throw new Error(`unknown command/agent ${r}; commands: proxy | env; agents: ${Object.keys(h).join(" | ")} \u2014 or \`-- <cmd>\``);s=c.bin,i=[...c.args??[],...e],a=c.env}let o=await K(t,R(f(p.env))),u=null,d=o.port;o.mode==="reuse"&&p.stdout.write(`reusing the running urun openai proxy on http://127.0.0.1:${d} (app ${o.identity.app}, fn ${o.identity.fn}, v${o.identity.proxy_version})
|
|
8
|
+
`);let l=null,m=null;if(o.mode!=="reuse"){u=await q(o.port,!1),d=u.port;let c=D(d);pe(ce.dirname(c),{recursive:!0}),m=ie(c,{flags:"a"}),m.on("error",y=>{l?.(),l=null,p.stderr.write(`urun compat: proxy log ${c} failed: ${String(y)} \u2014 proxy output continues on the terminal
|
|
9
|
+
`)}),p.stdout.write(`urun openai proxy on http://127.0.0.1:${d}/v1 \u2014 proxy logs: ${c}
|
|
10
|
+
`),l=W(m)}r==="codex"&&(i=L(d,f(p.env).appId,i));let b=F(s,i,{stdio:"inherit",env:k(p.env,d,a)}),V=await new Promise(c=>{b.once("exit",y=>c(y??1)),b.once("error",y=>{l?.(),l=null,p.stderr.write(`failed to launch ${s}: ${String(y)}
|
|
11
|
+
`),c(127)})});try{u&&await u.close()}finally{l?.(),l=null,m?.end()}return V}}ge().then(e=>p.exit(e),e=>{p.stderr.write(`${String(e instanceof Error?e.message:e)}
|
|
12
|
+
`),p.exit(1)});export{me as buildClients,le as buildRouter};
|