/** OpenAI-compatible chat/completions HTTP + SSE (preamble for /bin/agent). */ /** * @param {Record} ctx * @returns {typeof fetch | null} */ function bareAgentResolveFetch(ctx) { if (typeof ctx.httpFetch === 'function') return /** @type {typeof fetch} */ (ctx.httpFetch.bind(ctx)) const bare = ctx.bare && typeof ctx.bare === 'object' ? ctx.bare : null let f = bare && typeof bare.fetch === 'function' ? bare.fetch : bare && bare.default && typeof bare.default === 'object' && typeof bare.default.fetch === 'function' ? bare.default.fetch : null if (typeof f === 'function') return /** @type {typeof fetch} */ (f.bind(bare)) if (typeof globalThis.fetch === 'function') return globalThis.fetch.bind(globalThis) return null } /** * @param {string} base */ function bareAgentNormalizeBaseUrl(base) { let s = String(base || '').trim() while (s.endsWith('/')) s = s.slice(0, -1) return s } /** * @param {Record} obj * @param {string} key */ function bareAgentDeepGet(obj, key) { const parts = key.split('.') let cur = obj for (const p of parts) { if (cur == null || typeof cur !== 'object') return undefined cur = /** @type {Record} */ (cur)[p] } return cur } /** * Stream chat/completions; invoke onEvent for each parsed chunk. * @param {{ * fetchFn: typeof fetch, * url: string, * headers: Record, * body: Record, * signal?: AbortSignal | null, * onEvent: (ev: Record) => void * }} opts */ async function bareAgentStreamChatCompletions(opts) { const { fetchFn, url, headers, body, signal, onEvent } = opts const res = await fetchFn(url, { method: 'POST', headers, body: JSON.stringify(body), signal: signal || undefined }) if (!res.ok) { let errText = '' try { errText = await res.text() } catch { /* ignore */ } throw new Error('HTTP ' + res.status + ' ' + errText.slice(0, 800)) } const stream = res.body if (!stream || typeof stream.getReader !== 'function') { throw new Error('agent: response body is not a readable stream') } const reader = stream.getReader() const dec = new TextDecoder() let buf = '' let emittedShape = false try { for (;;) { const { done, value } = await reader.read() if (done) break buf += dec.decode(value, { stream: true }) const sp = bareAgentSplitSseLines(buf) buf = sp.rest for (const line of sp.lines) { if (!line.trim()) continue if (line.startsWith(':')) continue if (!line.startsWith('data:')) continue const payload = line.slice(5).replace(/^\s/, '') const parsed = bareAgentParseSseDataPayload(payload) if (parsed.kind === 'done') { onEvent({ type: 'sse_done' }) continue } if (parsed.kind !== 'json' || !parsed.value || typeof parsed.value !== 'object') continue const j = /** @type {Record} */ (parsed.value) if (!emittedShape) { emittedShape = true onEvent({ type: 'response_shape_keys', keys: Object.keys(j).slice(0, 24) }) } if (typeof j.type === 'string') { if (j.type === 'response.reasoning_summary_text.delta' && typeof j.delta === 'string') { onEvent({ type: 'delta_reasoning', reasoning: j.delta }) } if (j.type === 'response.output_text.delta' && typeof j.delta === 'string') { onEvent({ type: 'delta_content', content: j.delta }) } if ( j.type === 'response.function_call_arguments.delta' && typeof j.delta === 'string' ) { onEvent({ type: 'delta_tool_calls', tool_calls: [{ index: 0, function: { arguments: j.delta } }] }) } } const choices = bareAgentDeepGet(j, 'choices') const ch0 = Array.isArray(choices) && choices[0] && typeof choices[0] === 'object' ? /** @type {Record} */ (choices[0]) : null const delta = ch0 && typeof ch0.delta === 'object' ? /** @type {Record} */ (ch0.delta) : null const finishReason = typeof ch0?.finish_reason === 'string' ? ch0.finish_reason : '' const usage = typeof j.usage === 'object' && j.usage ? j.usage : undefined if (usage) { onEvent({ type: 'usage', usage }) } if (delta) { const c = delta.content if (typeof c === 'string' && c.length) { onEvent({ type: 'delta_content', content: c }) } const toolCalls = delta.tool_calls if (toolCalls !== undefined) onEvent({ type: 'delta_tool_calls', tool_calls: toolCalls }) const rc = delta.reasoning_content if (typeof rc === 'string' && rc.length) { onEvent({ type: 'delta_reasoning', reasoning: rc }) } const r = delta.reasoning if (typeof r === 'string' && r.length) { onEvent({ type: 'delta_reasoning', reasoning: r }) } else if (Array.isArray(r)) { for (const chunk of r) { if (!chunk || typeof chunk !== 'object') continue const ro = /** @type {Record} */ (chunk) const tx = typeof ro.text === 'string' ? ro.text : typeof ro.content === 'string' ? ro.content : '' if (tx) { onEvent({ type: 'delta_reasoning', reasoning: tx }) } } } } if (finishReason) onEvent({ type: 'finish_reason', finish_reason: finishReason }) } } } finally { try { reader.releaseLock() } catch { /* ignore */ } } } /** * Non-streaming completion (same endpoint, stream:false). */ async function bareAgentCompleteOnce(opts) { const { fetchFn, url, headers, body, signal } = opts const res = await fetchFn(url, { method: 'POST', headers, body: JSON.stringify({ ...body, stream: false }), signal: signal || undefined }) if (!res.ok) { let errText = '' try { errText = await res.text() } catch { /* ignore */ } throw new Error('HTTP ' + res.status + ' ' + errText.slice(0, 800)) } const j = /** @type {Record} */ (await res.json()) return j }