pax_global_header00006660000000000000000000000064152726040320014513gustar00rootroot0000000000000052 comment=789866de11db2aa096f329b081f3d5d48ca5876c elixpo-crumb.elixpo-789866d/000077500000000000000000000000001527260403200157215ustar00rootroot00000000000000elixpo-crumb.elixpo-789866d/.claude-plugin/000077500000000000000000000000001527260403200205305ustar00rootroot00000000000000elixpo-crumb.elixpo-789866d/.claude-plugin/marketplace.json000066400000000000000000000004441527260403200237150ustar00rootroot00000000000000{ "name": "elixpo", "owner": { "name": "Elixpo", "url": "https://github.com/elixpo" }, "plugins": [ { "name": "crumb", "source": "./integrations/claude-code", "description": "Delegate whole tasks to Crumb's agent and use its tools over MCP." } ] } elixpo-crumb.elixpo-789866d/.elixpo/000077500000000000000000000000001527260403200172775ustar00rootroot00000000000000elixpo-crumb.elixpo-789866d/.elixpo/project.yml000066400000000000000000000037161527260403200214770ustar00rootroot00000000000000schema_version: '1.0' project: id: crumb name: crumb.elixpo title: Crumb description: Self-hosted autonomous Rust agent harness and natural-language terminal powered by Pollinations. family: project artifact_type: cli profile: software technologies: - Rust - Next.js - TypeScript - Pollinations chapter_status: null lifecycle: active relationship: core custody: elixpo-owned stack: rust-agent-harness-and-web-surface repository: https://github.com/elixpo/crumb.elixpo deployment: https://crumb.elixpo.com discussions: https://github.com/orgs/elixpo/discussions status_owner: elixpo status_verified_at: '2026-10-18' publication: public: true featured: true canonical_slug: crumb canonical_url: https://elixpo.com/projects/crumb provenance: classification_decision: https://github.com/elixpo/elixpo/blob/main/docs/governance/repository-classification-decisions.md license_evidence: https://github.com/elixpo/elixpo/blob/main/content/repository-licenses.json status_checked_at: '2026-10-18' license: current_file: LICENSE notice_file: LICENSES/NOTICE migration_status: current target_code_license: MIT trademark_policy: https://elixpo.com/terms ci: lint_cmd: cargo fmt --all --check test_cmd: cargo test --workspace --all-targets build_cmd: cargo build --workspace --all-targets maintainers: - github: Circuit-Overtime skills: - Rust - Agent systems - Terminal engineering - Next.js lead: true - github: ez-vivek skills: - Rust - Agent systems lead: false governance: model: maintainer-led document: GOVERNANCE.md support: document: SUPPORT.md security: document: SECURITY.md conformance: standard: '1.0' status: conformant-with-exceptions assessed_at: '2026-10-18' exceptions: - id: specialized-agent-workflows reason: Autonomous harness and deployment workflows require a dedicated security audit outside the ordinary workflow baseline. owner: elixpo review_at: '2027-01-18' elixpo-crumb.elixpo-789866d/.env000066400000000000000000000103201527260403200165060ustar00rootroot00000000000000NEXT_PUBLIC_REDIRECT_URI_LOCAL=ENC[AES256_GCM,data:bfEP1/UGO6M72326/sqGZCaHk3cXnyDHqxcxYJUQvdC7AS8=,iv:La1n0quB92mwhdgNDwE5KldFy+yS6OysyarX8LqJAEg=,tag:L8FsS3uo1iW8bPXE6l9uYQ==,type:str] NEXT_PUBLIC_REDIRECT_URI_PROD=ENC[AES256_GCM,data:Pse29ay8hMSV5vyvooDfySLXLDlGdqMYpOSHiakcygNgqgVR8w8=,iv:yS9yJdGxMzsGhf9/0lQmWimpXuQ0CeuH+GSWdwn1A78=,tag:nijBOUDU1DMKIucJHn8Dtw==,type:str] NEXT_PUBLIC_ELIXPO_CLIENT_ID=ENC[AES256_GCM,data:G9E8tMsvSOqUSsNUYnKHz+jiz4wFtJEFbQ9BxmYONob799qZxQedrN8YIUVo5iMge/ibcp6/KyFu6EVe+LwdhNlBhVY=,iv:ttEXX1Ot5mO+dWXO+gIixSF2eAhP156qT7E4ZwijuOo=,tag:oEVCWRsaJtUZoh2A/4E2aw==,type:str] ELIXPO_CLIENT_SECRET=ENC[AES256_GCM,data:ZQobpfI7Q5rQEMfn5xnLNUpnCD2rFmxrf9fOCfmPH11Z9ZY+7qYI4gqo5cWw/kNhhIqIfMGK8/az6R7PtnYCqlP/Fznik7piOy1bKaPhNeUW+NUoHLSuETjAeH+7Pn81WVPpXFfmQhr+HRTvR6557NushVG7NGj7mmFDuCJS0QMsidmaP6nl,iv:HSQ8q028XIyk/9NPGGnXrK9yis2GU5yeibGAyaUZjuE=,tag:z6a+VX9pyjD6BVepsxa6pg==,type:str] ELIXPO_ACCOUNTS_WEBHOOK_SECRET=ENC[AES256_GCM,data:Vh/nSjS09nBjhon3YxtszNFtFGqxxEvajwZMQTjjbjWXfSpSZYBYjWvoU9cOjTIoS94Lr4FyWn/1/PEwqOflveCS540=,iv:K6gSFGDl8y7lGvtjYAWZRTHKUhZmxzuLQM9H6gAFAFQ=,tag:C9OxltnnZILAwAIPwil9uw==,type:str] ELIXPO_ACCOUNTS_DELETION_WEBHOOK_URL=ENC[AES256_GCM,data:1GTOurmtiGRMh1SHETnWPROdzxZt8Gm6jyt2ItVUk8Kdf0MPJekm1M4AvdvbntHWHg==,iv:s5BjZmy/fTqWkISxkwDxBGbwdI+rmbqKyJoQyIkY8Tk=,tag:nmpFfm+e4PhngfC/A7pPoQ==,type:str] POLLINATIONS_INTEGRATION_CALLBACK=ENC[AES256_GCM,data:jTjLfqXy6nCZRf+XzkcpzSiyz/mFoIkTN3YPt3xXmwIV1vLSeVDlwlq6c3HjG0+J4ve10GouamjtTIE9,iv:crnjOAxYj5hyXHg0zwQG4m66hafikdasSJXkMIdfeGk=,tag:ABvhJLTEIbdeohg5crV2bQ==,type:str] POLLINATIONS_APP_KEY=ENC[AES256_GCM,data:8MosNTLzRv4RQsW8rMezeSdUPQ==,iv:UDgKC6ug3hAG9JesfI/n2oteZTQrs4gvNEbWNUu9iac=,tag:yuMZs5KTRnOILz1QiioOzA==,type:str] NEXT_PUBLIC_ELIXPO_CLIENT_ID_CLI=ENC[AES256_GCM,data:b/w6LBcCBTF305qzUf/rOccSAAe9+NfN1QnXndZL0R9t9LgPwrrqQMWufSM7WRUPfHw5X9iIQQXt7hvZWPb6oiZyjpo=,iv:RXmqnxZZpIkLTHws0Xt0AEnwamJ+KhV+/hbtIhNcDC4=,tag:xZl0DL/lSCGNOssbCTvNUw==,type:str] CLOUDFLARE_API_TOKEN=ENC[AES256_GCM,data:Zv6M3n8oNU6VyD8q6YxTLpUIiRlK9jY387bQUWoro/LdrdP2JDzdAe9hugOPgf3wWfBqCG0=,iv:teyRtMUKWmMAfoDdE9QgOopecsO9IeY7x12dhUFy0iA=,tag:/AEwqLXgkiZHYBB4kcH/Eg==,type:str] CLOUDFLARE_ACCOUNT_ID=ENC[AES256_GCM,data:dIB3UZwY2cQ/G1KFnvLgi2GtPEE6i7swRriuTeVwD3U=,iv:P92qQV7hpKDKcTS0GHNhZ/b9DxOYT3zkhIzTM+XSCW4=,tag:EvqtoAkMta6GX67Hubu9VQ==,type:str] CONNECTOR_ENCRYPTION_KEY=ENC[AES256_GCM,data:lpXkiIR5hES5xxQSgGC5/AeLHGKXtMdaj3Y0UUUmUle+MDewsids8PE1Oil95XeICr4xEyCwspE+6myPFpKZGg==,iv:butSMzhVRz7iW5n3Bqmhx1kDPgRum2BZLiYCmlHRKZQ=,tag:p4lVUirqx6nTQczuGRuWnQ==,type:str] POLLINATIONS_KEY=ENC[AES256_GCM,data:x4BtGxIsGKlrXB35fdmYv8nC9wAxxsfOcCnYn0uqJyInw34=,iv:9UnV5FQyhvghmldywr2gpMKBJuV1cg3MB50G8cvp5XM=,tag:i+Qk+o87rnegLdfVAnMQVA==,type:str] GPG_SIGN_PASSWORD=ENC[AES256_GCM,data:KzjR40rKoiZGbLs=,iv:PHlJA+6e6K5QOK0oiXzqEF8RqN0NL/zZ1niA0fr05eM=,tag:Sr2Hk9n7LcZxVtk4UrjPuQ==,type:str] GPG_SIGN_EMAIL=ENC[AES256_GCM,data:w6EdztCy3mJBmtcdgSYrrcKLH7eN1s+6zA==,iv:NAVpS+HHt5BizUmxjPtbnjj4kpuhH/fvOUWkzSmBRuA=,tag:gO6PC3RLMc6dktMfJ71owg==,type:str] GPG_FINGERPRINT=ENC[AES256_GCM,data:ei2rQzGLPBpsPOyDQBxzqXua2QHKNVzsQLYHk1MnUk6LdWyl/6OtwQ==,iv:KF8/3zAP95AcWsClCmQsSRulwugqF1nMwl7DJsK7Z9E=,tag:Ckb7aFhiwcHTIawYK2rzvA==,type:str] CRUMB_EVAL_POLLINATIONS_KEY=ENC[AES256_GCM,data:S+8+IVPwd663IS3ty42+wF0T6/VnAOoAT0GKIZWJR0JSeZkt,iv:kDyu6veIHrQOHUjAIxYAEswSQu/wVBexae/HFVMqu7I=,tag:AH8ixFAj7bP/nKO2ZdOT1A==,type:str] sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBydm1YNUlNRFM5Mi9KZndU\nMmcxdlAwSFZYek9lUFZGaDBHWlhaSGVNZkFFCjUxenlOQ3pJR0MwSXYwdG12VzN4\nbnZYbytNMmlibnNMbjI4d1FRZ0pxUTgKLS0tIGYrci9CdTN5c0hYV3pHb24xT1Zy\nZklzbEFFbWtvcEoxcWFjMFBMbWR1SlkKYrYEwykEdW09fxMmcmRCpRJXrMyUshey\n/vgbYEaGDMIj74UIZZRfzV6MqjfM9CqQiYiqGNVOgrx19T10/mGHUg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1zptpgxhs9chnkudu69qut2hn706pmyngasvaz4ppf452un2x7p2ss6j96f sops_lastmodified=2026-10-26T08:18:12Z sops_mac=ENC[AES256_GCM,data:r7JOKm4E/cWUWpbvd9+pgXuxQWibaDIib4ziPfd9zKL+8capu+ul8DY4vpm9UBksFS0MeTWOsq+tVavlSrsoHVTQjY050sHk6D9gXbRy2YTDws41kr15OsBc/a5dGlLgDkn0CZoDx+f3gd63FcTm/kzMwhgMbFrG0rOPIaAqspc=,iv:VgSSXsxtJBwSBjOLEwUErKypGXnCW61B5LADVsetva0=,tag:eBFvqqS9KI3APlR9Y7jwyA==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.12.2 elixpo-crumb.elixpo-789866d/.gitattributes000066400000000000000000000002141527260403200206110ustar00rootroot00000000000000* text=auto *.json text eol=lf *.md text eol=lf *.rs text eol=lf *.toml text eol=lf *.txt text eol=lf *.yaml text eol=lf *.yml text eol=lf elixpo-crumb.elixpo-789866d/.github/000077500000000000000000000000001527260403200172615ustar00rootroot00000000000000elixpo-crumb.elixpo-789866d/.github/CODEOWNERS000066400000000000000000000001651527260403200206560ustar00rootroot00000000000000# Default reviewers. GitHub requests a review from these owners on every pull request. * @Circuit-Overtime @ez-vivek elixpo-crumb.elixpo-789866d/.github/ISSUE_TEMPLATE/000077500000000000000000000000001527260403200214445ustar00rootroot00000000000000elixpo-crumb.elixpo-789866d/.github/ISSUE_TEMPLATE/abuse-report.yml000066400000000000000000000037161527260403200246060ustar00rootroot00000000000000name: Abuse report description: Report a Crumb package, connector, skill, or distribution link used for harmful activity. title: "[Abuse report]: " body: - type: markdown attributes: value: | Thanks for helping keep Crumb safe. Reports are reviewed by the maintainers. GitHub issues are public. Do not include passwords, API keys, private messages, personal information, or other sensitive material. Treat the reported destination as unsafe and do not open it unless necessary. - type: input id: affected-resource attributes: label: Affected Crumb resource description: Paste the complete crumb.elixpo.com URL, marketplace package ID, connector name, or repository path. placeholder: https://crumb.elixpo.com/marketplace/catalog.json validations: required: true - type: dropdown id: reason attributes: label: Reason for the report options: - Phishing or credential theft - Malware or unwanted software - Spam or deceptive promotion - Impersonation - Illegal or harmful content - Other abuse validations: required: true - type: textarea id: details attributes: label: What happened? description: Explain what makes the resource harmful and include non-sensitive evidence that helps reviewers verify it. placeholder: Describe the behavior, where you found the resource, and any safe evidence available. validations: required: true - type: textarea id: evidence attributes: label: Additional evidence description: Optional screenshots, public references, or detection results. Remove personal information before attaching anything. - type: checkboxes id: confirmation attributes: label: Confirmation options: - label: I understand this report is public, contains no sensitive information, and is accurate to the best of my knowledge. required: true elixpo-crumb.elixpo-789866d/.github/ISSUE_TEMPLATE/bug.yml000066400000000000000000000015211527260403200227430ustar00rootroot00000000000000name: Bug report description: Report a reproducible problem in crumb. title: "bug: " labels: [bug] body: - type: textarea id: problem attributes: label: What happened? description: Include the command, expected behavior, and actual behavior. validations: required: true - type: dropdown id: platform attributes: label: Platform options: [Linux, macOS, Windows] validations: required: true - type: input id: version attributes: label: crumb version placeholder: crumb --version - type: textarea id: reproduction attributes: label: Reproduction steps validations: required: true - type: textarea id: context attributes: label: Additional context description: Remove credentials and other secrets before posting logs. elixpo-crumb.elixpo-789866d/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000331527260403200234300ustar00rootroot00000000000000blank_issues_enabled: true elixpo-crumb.elixpo-789866d/.github/ISSUE_TEMPLATE/feature.yml000066400000000000000000000007631527260403200236300ustar00rootroot00000000000000name: Feature request description: Propose a focused improvement to crumb. title: "feat: " labels: [enhancement] body: - type: textarea id: problem attributes: label: Problem description: What user problem should this solve? validations: required: true - type: textarea id: proposal attributes: label: Proposed behavior validations: required: true - type: textarea id: alternatives attributes: label: Alternatives considered elixpo-crumb.elixpo-789866d/.github/ISSUE_TEMPLATE/plugin.yml000066400000000000000000000046421527260403200234730ustar00rootroot00000000000000name: Plugin request description: Propose a typed capability or integration for Crumb. title: "plugin: " labels: [enhancement] body: - type: markdown attributes: value: | Plugins provide capabilities. External accounts should use scoped connectors, and every operation must retain Crumb's approval boundary. - type: textarea id: capability attributes: label: Capability description: What new operation should the plugin expose? validations: required: true - type: textarea id: use_cases attributes: label: User scenarios description: Give concrete requests and the expected result. validations: required: true - type: textarea id: interface attributes: label: Proposed interface description: Describe inputs, bounded outputs, errors, and whether MCP or a native adapter is appropriate. validations: required: true - type: checkboxes id: risks attributes: label: Required risk classes options: - label: Read-only local access - label: Local file modification - label: Command execution - label: Network access - label: Credential-sensitive access - label: Destructive operation - type: textarea id: authorization attributes: label: Authorization and scopes description: For external services, list the minimum OAuth scopes and revocation behavior. - type: dropdown id: platforms attributes: label: Platforms multiple: true options: - Linux / Bash - macOS / Zsh - Windows / PowerShell - Platform independent validations: required: true - type: textarea id: failure attributes: label: Failure and cancellation description: How should timeouts, partial work, cancellation, and unavailable dependencies behave? validations: required: true - type: textarea id: acceptance attributes: label: Acceptance criteria description: List observable behavior and tests required before the plugin is usable. validations: required: true - type: checkboxes id: safety attributes: label: Safety check options: - label: I have not included credentials, private logs, or sensitive data. required: true - label: The plugin keeps permissions and approvals outside the model. required: true elixpo-crumb.elixpo-789866d/.github/ISSUE_TEMPLATE/skill.yml000066400000000000000000000042231527260403200233060ustar00rootroot00000000000000name: Skill request description: Propose a focused, reviewable skill for Crumb. title: "skill: " labels: [enhancement] body: - type: markdown attributes: value: | A skill describes how Crumb should approach a task. It does not bypass permissions or install a connector. - type: textarea id: outcome attributes: label: User outcome description: What should a user be able to accomplish? placeholder: "A Rust maintainer can isolate and fix a failing workspace test without rewriting unrelated code." validations: required: true - type: textarea id: prompts attributes: label: Example requests description: Provide two or three natural-language requests that should activate this skill. placeholder: | - Find why this test fails and prepare the smallest fix. - Explain this borrow-checker error in the context of the workspace. validations: required: true - type: textarea id: context attributes: label: Required context description: Which files, folders, commands, or connector resources does the skill need? validations: required: true - type: textarea id: tools attributes: label: Tools and permissions description: List required read, write, execution, network, or credential-sensitive capabilities. validations: required: true - type: dropdown id: platforms attributes: label: Platforms multiple: true options: - Linux / Bash - macOS / Zsh - Windows / PowerShell - Platform independent validations: required: true - type: textarea id: acceptance attributes: label: Acceptance examples description: How can maintainers verify that the skill behaves safely and usefully? validations: required: true - type: checkboxes id: safety attributes: label: Safety check options: - label: I have not included credentials, private logs, or sensitive data. required: true - label: The proposed skill does not require models to grant their own permissions. required: true elixpo-crumb.elixpo-789866d/.github/ccr-adapters/000077500000000000000000000000001527260403200216315ustar00rootroot00000000000000elixpo-crumb.elixpo-789866d/.github/ccr-adapters/openai-normalize.js000066400000000000000000000175511527260403200254510ustar00rootroot00000000000000#!/usr/bin/env node /** * openai-normalize.js — CCR transformer for any non-Anthropic provider. * * Originally written for Vertex Gemini's `thought_signature` requirement, the * same flatten-and-strip pass is needed for every non-Anthropic model served * through an OpenAI-style proxy (Pollinations: kimi, qwen-coder, perplexity-*, * glm, gemini, gemini-search, and friends). Each validates OpenAI schema * strictly and rejects: * - `content: [{type: 'text', text: '...'}, ...]` structured arrays, * - `cache_control: {type: 'ephemeral'}` markers on text blocks, * - Anthropic-style `tool_use` / `tool_result` blocks in history. * * The fix: before the request hits the proxy, collapse every message's * content into a plain string and drop cache_control by virtue of the join. * * Rules: * 1. Tool exchanges (tool_use → tool_result) become user-role narrative * notes like "Earlier I ran X(args). It returned: Y". * 2. Assistant messages that contained ONLY tool_use (no real text) are * dropped entirely — no placeholders to copy. * 3. Assistant messages with real text keep that text only; any tool_use * blocks inside are discarded (their context moves to the next user * note). * 4. System and user messages with array content are joined to a single * string; cache_control on individual text blocks is dropped in the join. * 5. Consecutive user messages are merged so the proxy sees clean alternation. * * The current turn's response is untouched; real tool calls still flow. * * The filter: SKIP flattening only for models that accept Anthropic-native * structured content (real Claude). Everything else gets the flatten pass. * * CCR contract: `registerTransformerFromConfig` calls `new Ctor(options)`. * Register at top-level and reference by name: * * { * "transformers": [{"path": "/abs/path/openai-normalize.js"}], * "Providers": [{ * "transformer": { "use": ["openai-normalize", "openai", ...] } * }] * } */ // Only SKIP flattening for models we know accept Anthropic-native structured // content. Everything else needs the flatten pass. const ANTHROPIC_NATIVE_RE = /^claude(-|$)/i; function asString(x) { if (x == null) return ""; if (typeof x === "string") return x; try { return JSON.stringify(x); } catch { return String(x); } } function buildToolUseIndex(messages) { const index = new Map(); for (const m of messages) { if (m?.role !== "assistant") continue; if (Array.isArray(m.content)) { for (const b of m.content) { if (b?.type === "tool_use" && b.id) { index.set(b.id, { name: b.name, input: asString(b.input) }); } } } else if (Array.isArray(m.tool_calls)) { for (const tc of m.tool_calls) { if (tc?.id) { const fn = tc.function || {}; index.set(tc.id, { name: fn.name, input: fn.arguments || "{}", }); } } } } return index; } function toolResultNarrative(toolUseIndex, toolUseId, resultContent) { const tu = toolUseIndex.get(toolUseId); const result = asString(resultContent); return tu ? `Earlier I ran ${tu.name} with ${tu.input}. It returned: ${result}` : `Earlier tool output: ${result}`; } function narrate(index, toolId, result) { return toolResultNarrative(index, toolId, result); } function isAnthropicModel(model) { return typeof model === "string" && ANTHROPIC_NATIVE_RE.test(model); } function flattenTextBlocks(content) { // Join `[{type:'text',text:'...'}, ...]` into a single string, dropping // any cache_control markers and non-text blocks (images, documents, etc.). if (typeof content === "string") return content; if (!Array.isArray(content)) return asString(content); const parts = []; for (const b of content) { if (b && b.type === "text" && typeof b.text === "string") { parts.push(b.text); } } return parts.join("\n"); } function flattenMessages(messages) { const toolUseIndex = buildToolUseIndex(messages); const out = []; for (const m of messages) { if (!m || typeof m !== "object") continue; const role = m.role; // System messages: flatten content array, drop cache_control. if (role === "system") { const content = flattenTextBlocks(m.content); if (content) out.push({ role: "system", content }); continue; } if (role === "assistant" && Array.isArray(m.content)) { const texts = []; for (const b of m.content) { if (b?.type === "text" && b.text) texts.push(b.text); } if (texts.length > 0) { const content = texts.join("\n"); out.push({ role: "assistant", content }); } continue; } const toolCalls = m.tool_calls; const isToolCallArray = Array.isArray(toolCalls); const hasToolCalls = isToolCallArray && toolCalls.length > 0; if (role === "assistant" && hasToolCalls) { const c = asString(m.content); if (c) out.push({ role: "assistant", content: c }); continue; } if (role === "assistant") { const c = asString(m.content); if (c) out.push({ role: "assistant", content: c }); continue; } if (role === "user" && Array.isArray(m.content)) { const parts = []; for (const b of m.content) { if (b?.type === "text" && b.text) parts.push(b.text); else if (b?.type === "tool_result") { const toolId = b.tool_use_id; const result = b.content; const index = toolUseIndex; const narrative = narrate(index, toolId, result); parts.push(narrative); } } if (parts.length > 0) { const content = parts.join("\n"); out.push({ role: "user", content }); } continue; } if (role === "tool") { const toolId = m.tool_call_id; const result = m.content; out.push({ role: "user", content: toolResultNarrative(toolUseIndex, toolId, result), }); continue; } out.push(m); } const merged = []; for (const m of out) { const last = merged[merged.length - 1]; if (last && last.role === "user" && m.role === "user") { last.content = `${last.content}\n${m.content}`; } else { merged.push(m); } } for (const m of merged) { if (m && typeof m === "object") { m.tool_calls = undefined; m.tool_call_id = undefined; } } return merged; } class OpenAINormalizeAdapter { constructor(options) { this.name = "openai-normalize"; this.options = options || {}; } async transformRequestIn(request) { return this._flatten(request); } async transformRequestOut(request) { return this._flatten(request); } _flatten(request) { const body = request?.body ? request.body : request; if (!body || typeof body !== "object") return request; if (!Array.isArray(body.messages)) return request; // Only pass through unchanged for real Claude models; everything else // (kimi, qwen, perplexity, glm, gemini, ...) gets flattened. if (isAnthropicModel(body.model)) { return request; } body.messages = flattenMessages(body.messages); return request; } } module.exports = OpenAINormalizeAdapter; elixpo-crumb.elixpo-789866d/.github/ccr-adapters/tool-schema-patcher.js000066400000000000000000000051001527260403200260220ustar00rootroot00000000000000#!/usr/bin/env node /** * tool-schema-patcher.js — CCR transformer that backfills missing * `parameters` on OpenAI-format tool definitions before they hit the provider. * * Pollinations' qwen-coder backend strictly validates OpenAI function schemas * and 400s any request where `tools[i].function.parameters` is absent: * "Failed to deserialize: tools[0].function: missing field `parameters`" * * WebSearch and some MCP tools ship without a parameters schema (valid per * OpenAI spec — parameters is optional). Gemini tolerates this; qwen-coder * does not. Rather than route background subagent calls away from qwen-coder * (losing the cost split), this transformer injects a minimum valid schema: * { "type": "object", "properties": {} } * * Runs AFTER the `openai` transformer in the chain so we're always working * with OpenAI-format `tools[].function.parameters` rather than Anthropic's * `input_schema`. Registered at top-level `transformers` and referenced by * name in `Providers[].transformer.use`. */ class ToolSchemaPatcher { constructor(options) { this.name = "tool-schema-patcher"; this.options = options || {}; } async transformRequestIn(request) { return this._patch(request); } async transformRequestOut(request) { return this._patch(request); } _patch(request) { const body = request?.body ? request.body : request; if (!body || typeof body !== "object") return request; if (!Array.isArray(body.tools)) return request; for (const tool of body.tools) { if (!tool || typeof tool !== "object") continue; if (tool.type !== "function") continue; const fn = tool.function; if (!fn || typeof fn !== "object") continue; if ( fn.parameters == null || typeof fn.parameters !== "object" || Array.isArray(fn.parameters) ) { fn.parameters = { type: "object", properties: {} }; continue; } // Tool shipped a parameters object but it's missing required fields // — qwen-coder also rejects `{}` with no `type`. if (!fn.parameters.type) fn.parameters.type = "object"; const isObjectSchema = fn.parameters.type === "object"; const hasNoProperties = fn.parameters.properties == null; if (isObjectSchema && hasNoProperties) { fn.parameters.properties = {}; } } return request; } } module.exports = ToolSchemaPatcher; elixpo-crumb.elixpo-789866d/.github/ci_config.py000066400000000000000000000120201527260403200215460ustar00rootroot00000000000000""" Elixpo CI Configuration — crumb.elixpo Single source of truth for all CI workflows and scripts. """ # ── LLM ───────────────────────────────────────────── LLM_API_URL = "https://gen.pollinations.ai/v1/chat/completions" LLM_MODEL_AGENT = "deepseek" LLM_MODEL_CODE = "nova-fast" LLM_MODEL_CHAT = "nova-fast" LLM_MODEL_THINKING = "deepseek" LLM_MODEL_SEARCH = "perplexity-fast" LLM_MAX_TOKENS_AGENT = 3500 LLM_MAX_TOKENS_CODE = 6000 LLM_MAX_TOKENS_THINKING = 5000 LLM_MAX_TOKENS_SEARCH = 2500 # Back-compat alias — scripts that haven't been migrated still import LLM_MODEL. LLM_MODEL = LLM_MODEL_CHAT # ── Repository ────────────────────────────────────── REPO = "elixpo/crumb.elixpo" PROJECT_NAME = "crumb.elixpo" PROJECT_DESCRIPTION = "Native-first natural language terminal and agent runtime" # ── GitHub Projects V2 ────────────────────────────── # Shared org-wide projects (linked to all elixpo repos). # One project per category — Repository column shows which repo each issue came from. # Each project has its own Priority field with Urgent/High/Medium/Low. PROJECT_OWNER = "elixpo" PROJECTS = { "Feature": { "id": "PVT_kwDOCZpXlc4BU5G2", "number": 2, "url": "https://github.com/orgs/elixpo/projects/2", "priority_field_id": "PVTSSF_lADOCZpXlc4BU5G2zhI73CA", "priority_options": { "Urgent": "b23fe0c8", "High": "d7eb7128", "Medium": "5f4dffcc", "Low": "69fbb73e", }, }, "Bugs": { "id": "PVT_kwDOCZpXlc4BU5G4", "number": 3, "url": "https://github.com/orgs/elixpo/projects/3", "priority_field_id": "PVTSSF_lADOCZpXlc4BU5G4zhI74wo", "priority_options": { "Urgent": "941c406a", "High": "bf1e8234", "Medium": "26edf47b", "Low": "a8b967d7", }, }, "Support": { "id": "PVT_kwDOCZpXlc4BU5G6", "number": 4, "url": "https://github.com/orgs/elixpo/projects/4", "priority_field_id": "PVTSSF_lADOCZpXlc4BU5G6zhI76r8", "priority_options": { "Urgent": "31ddaa82", "High": "6bedb306", "Medium": "843e5a46", "Low": "f57a96cd", }, }, "Dev": { "id": "PVT_kwDOCZpXlc4BU5G7", "number": 5, "url": "https://github.com/orgs/elixpo/projects/5", "priority_field_id": "PVTSSF_lADOCZpXlc4BU5G7zhI78bE", "priority_options": { "Urgent": "0442c856", "High": "fe20675f", "Medium": "508c18fa", "Low": "987b0728", }, }, } # Valid categories and priorities (for LLM prompts + validation) CATEGORIES = list(PROJECTS.keys()) PRIORITIES = ["Urgent", "High", "Medium", "Low"] # ── GitHub Issue Types (sidebar "Type") ───────────── # Org-wide issue types — fetched once with the GraphQL API. # We map our Project categories → these native GitHub issue types. ISSUE_TYPES = { "Task": "IT_kwDOCZpXlc4BRtJ8", "Bug": "IT_kwDOCZpXlc4BRtJ-", "Feature": "IT_kwDOCZpXlc4BRtJ_", } # Category → Issue Type mapping CATEGORY_TO_TYPE = { "Feature": "Feature", "Bugs": "Bug", "Support": "Task", "Dev": "Task", } # ── Organization ──────────────────────────────────── ORG_MEMBERS = ["Circuit-Overtime", "anwe-ch", "elixpoo", "ez-vivek"] # Maintainer skill map — used for LLM-driven PR reviewer assignment MAINTAINERS = { "Circuit-Overtime": { "skills": ["agent", "agentic", "breaking", "urgent", "backend", "python", "ci", "devops", "llm", "core"], "role": "Lead — handles agentic work, breaking fixes, and urgent issues", }, "ez-vivek": { "skills": ["frontend", "ui", "ux", "nextjs", "react", "css", "components", "styling", "design"], "role": "Frontend maintainer", }, "anwe-ch": { "skills": ["docs", "documentation", "readme", "guide", "tutorial", "api-docs", "examples"], "role": "Documentation maintainer", }, } # ── Gist ──────────────────────────────────────────── GIST_ACCOUNT = "elixpoo" GIST_ID = "" # filled after first merge creates it # ── Agent ─────────────────────────────────────────── AGENT_TRIGGER = "@elixpoo" AGENT_LABEL = "ELIXPO" # ── README update heuristics ──────────────────────── # Path prefixes that count as "core" for this repo — changes touching these # are more likely to warrant a README update. Override per-repo. CORE_PATHS = ( "crates/", "crumb.elixpo/", "marketplace/", "Cargo.toml", "deploy.sh", ) elixpo-crumb.elixpo-789866d/.github/dependabot.yml000066400000000000000000000012241527260403200221100ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: cargo directory: / schedule: interval: weekly groups: rust-dependencies: patterns: ['*'] # Debian builds Crumb against Debian's own Rust crates, which ship # base64 0.22, sha2 0.10 and rusqlite 0.40; these breaking 0.x releases would break that # build and the code that uses them. ignore: - dependency-name: base64 versions: ['>= 0.23'] - dependency-name: sha2 versions: ['>= 0.11'] - dependency-name: rusqlite versions: ['>= 0.41'] - package-ecosystem: github-actions directory: / schedule: interval: monthly elixpo-crumb.elixpo-789866d/.github/docs/000077500000000000000000000000001527260403200202115ustar00rootroot00000000000000elixpo-crumb.elixpo-789866d/.github/docs/agentic-planner.md000066400000000000000000000213441527260403200236060ustar00rootroot00000000000000# Elixpo repository agent ## Workflows - `elixpo-agent.yml` owns every scoped `@elixpoo` invocation from issues and pull requests. - `elixpo-triage.yml` classifies new issues and pull requests, applies category, priority, and task-type metadata, and files the item on the matching Project V2 board. - `artifact-update.yml` builds the repository snapshot consumed at the start of each run. - `on-merge.yml` maintains the gist changelog consumed alongside that snapshot. The retired issue and PR agent workflows must not be restored: separate listeners cause duplicate runs for issue comments attached to pull requests. ## Organization secrets Configure these as organization-level Actions secrets and grant them to every repository that installs the agent workflows: | Secret | Purpose | Required access | | --- | --- | --- | | `ELIXPO_POLLINATIONS_API_KEY` | Every model request: agent, triage, PR metadata, and changelog summaries | Pollinations text API; this is the only model credential | | `ELIXPOO_GITHUB_AGENTIC_TOKEN` | Repository reads/writes, issue and PR metadata, branches, failed-run retries, repository variables, and Project V2 fields | See the token profiles below | | `ELIXPOO_GITHUB_NOTIFICATIONS_TOKEN` | Optional discovery of mentions outside repositories with installed webhooks | Classic PAT with `notifications`; fine-grained PATs are unsupported by this endpoint | | `AGENT_GITHUB_SOLVER_TOKEN` | Solve/Submit fork creation, fork branch pushes, and pull-request creation | Classic PAT from the fork owner with `public_repo` for public targets | | `ELIXPOO_GIST_AGENTIC_TOKEN` | Merge changelog and Steward follow-up memory | Gist read/write | `GITHUB_TOKEN` is created automatically for each workflow run. It is not an organization secret and must not be copied into organization settings. CCR creates all model routes with `ELIXPO_POLLINATIONS_API_KEY`. Do not create per-model or per-provider keys. Repository-specific deployment, package publishing, payment, and moderation secrets are unrelated to the agent and remain scoped only to repos whose workflows use them. ### Token profiles Recommended `ELIXPOO_GITHUB_AGENTIC_TOKEN` fine-grained PAT: - Token owner: the human `elixpoo` account. State-writing workflows use this credential for pushes so GitHub records `elixpoo`, not `github-actions[bot]`, as the authenticated pusher. - Resource owner: `elixpo`; repository access: every repository using the agent. - Repository permissions: Actions read/write, Contents read/write, Issues read/write, Pull requests read/write, Variables read/write, Workflows read/write, and Metadata read. - Organization permissions: Projects read/write. Classic PAT fallback for `ELIXPOO_GITHUB_AGENTIC_TOKEN`: `repo`, `workflow`, and `project`. Add `read:org` only if the organization restricts project access in a way that requires membership lookup. This is broader than the fine-grained profile. The commit identity is `elixpoo `. Keep that email verified on the `elixpoo` GitHub account or replace it everywhere with the account's GitHub-provided private noreply address; an unverified email will not link the commit to the profile. `ELIXPOO_GIST_AGENTIC_TOKEN` needs either the fine-grained Gists user permission set to read/write or the classic PAT scope `gist`. It does not need `repo`, `workflow`, or organization administration scopes. An optional `ELIXPOO_GITHUB_NOTIFICATIONS_TOKEN` must be a classic PAT with the `notifications` scope; GitHub rejects fine-grained tokens for that endpoint. Set the organization variable `ELIXPOO_FOLLOWUP_GIST_ID` to one private Gist owned by `elixpoo`. Steward stores `elixpoo-followups.json` beside any other Gist files; it never overwrites the merge changelog. Optionally set `ELIXPO_FOLLOWUP_TTL_DAYS` from 60 through 360 (default 360). Set `ELIXPO_GITHUB_CONTROL_REPO` to the `owner/repository` containing the squad workflows when Steward runs anywhere other than that control repository; Actions otherwise uses `GITHUB_REPOSITORY`. `ELIXPO_AGENT_MAX_TURNS` is retired: the repository responder no longer runs a coding tool loop. `AGENT_GITHUB_SOLVER_TOKEN` is deliberately separate from the general agentic token. Mint it from the account that owns the forks. Use classic scope `public_repo` for public targets; add private-repository access only if private targets are explicitly enabled. Solve and Submit never fall back to another GitHub credential. `ELIXPO_POLLINATIONS_API_KEY` is not a GitHub token and receives no GitHub permissions. Give it only Pollinations text-generation access. Use expirations and rotation reminders on both PATs. Organization secret visibility should be limited to selected agent-enabled repositories until the workflow is rolled out everywhere. ### Portable repository baseline For another Elixpo repository, use the canonical bundle in `config/org_standard.yaml`. `python -m agents.standard_sync` reports drift; `--apply` opens one reviewable update PR per repository. Do not manually copy individual workflow files because that recreates version drift. Required organization secrets: - `ELIXPO_POLLINATIONS_API_KEY` - `ELIXPOO_GITHUB_AGENTIC_TOKEN` - `AGENT_GITHUB_SOLVER_TOKEN` - `ELIXPOO_GIST_AGENTIC_TOKEN` No `GH_SECRET` is required. GitHub supplies `GITHUB_TOKEN` automatically, while cross-repository and Project V2 operations use `ELIXPOO_GITHUB_AGENTIC_TOKEN`. No SOPS/age key is required by the agent stack. If a repository separately decrypts deployment configuration, keep that key in a deployment environment secret and expose it only to the deployment job as `SOPS_AGE_KEY`. Never pass an age private key to the repository agent, triage, acknowledgement, retry, or changelog jobs. `CI_GIST_ID` is a repository Actions variable, not a secret. It may start unset; `on-merge.yml` creates a changelog gist on the first merge and persists the resulting ID for later runs. ### Other secrets referenced only by this repository These are not part of the organization agent bundle: - `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`: production deployment. - `ELIXPO_PAY_API_KEY`: payout catalog and deployment configuration. - `MODERATION_SECRET`: moderation workflow authentication. - `NPM_LIXEDITOR_PUBLISH_TOKEN`: npm publishing. - `VSCODE_LIXSKETCH_EXT_PUBLISH_TOKEN`: VS Code Marketplace publishing. ## Cost-aware routes | Route | Model | Use | | --- | --- | --- | | repository_agent | `nova-fast` | bounded issue replies, PR review, and OreoFlow routing | | code | `qwen-coder` | Solve and Steward Fix coding only | | webSearch | `perplexity-fast` | time-sensitive external lookup only | Token ceilings are centralized in `.github/ci_config.py`. The prompt directs the agent to read the prepared context once, use targeted repository reads, and avoid search unless local context is insufficient. RTK compresses supported shell output before it reaches the model. The repository responder receives bounded issue context or at most 12,000 characters of PR diff. It makes one `repository_agent` call and one safety call, with a 16,000-token soft budget and 20,000-token ceiling. It has no file, shell, branch, or metadata tools. Implementation requests enter OreoFlow Vet; Solve is the only CCR coding harness and remains supervised by Doctor and Janitor. Steward polls the elixpoo account's participating mention notifications every ten minutes. This catches public issue and PR mentions outside repositories that host the portable workflow. A new thread receives a Gist intake record and a safety-gated response; repository-changing work still enters the normal grounded repository workflow. A structured Steward decision dispatches only an explicit issue implementation request. The serialized intake workflow checks the blocklist, daily cap, active repository work, and Pick/Vet slot, then sends the issue through Vet before Solve can fork or edit anything. Submitted PRs are registered from the Solve and Submit state receipts, then removed from active memory immediately on merge or close, or on TTL expiry. A bounded completion tracker retains the outcome. GitHub Discussion mentions are handled both by direct `discussion` and `discussion_comment` events and by the existing ten-minute target-repository poll, which covers webhook gaps and nested replies. CCR configuration applies only to the bounded Solve coding harness. ## Scope and safety - Only configured organization members can invoke the workflow. - An issue invocation may answer, edit metadata, inspect a linked PR, update its writable branch, or open one linked PR. - A pull-request invocation may answer, edit metadata, review, or update the existing same-repository head branch. - Fork PRs are read-only. - The agent cannot push `main`, force-push, merge, expose secrets, or act in another repository. - Per-item concurrency prevents simultaneous runs from racing on one issue or pull request. elixpo-crumb.elixpo-789866d/.github/docs/pollinations_ai.md000066400000000000000000001310341527260403200237210ustar00rootroot00000000000000> Generate text, images, video, audio, realtime voice, and embeddings with a single API. OpenAI-compatible — use any OpenAI SDK by changing the base URL. **Base URL:** `https://gen.pollinations.ai` **Get your API key:** [enter.pollinations.ai](https://enter.pollinations.ai/keys) **Integration guides:** [BYOP, CLI, MCP Server](/docs/guides) ## Quick Start ### Text (Python, OpenAI SDK) ```python from openai import OpenAI client = OpenAI(base_url="https://gen.pollinations.ai/v1", api_key="YOUR_API_KEY") response = client.chat.completions.create(model="openai", messages=[{"role": "user", "content": "Hello!"}]) print(response.choices[0].message.content) ``` ### Image (URL — no code needed) ```plaintext https://gen.pollinations.ai/image/a%20cat%20in%20space?model=flux ``` ### Audio (cURL) ```bash curl "https://gen.pollinations.ai/audio/Hello%20world?voice=nova" \ -H "Authorization: Bearer YOUR_API_KEY" -o speech.mp3 ``` ### 3D (cURL) ```bash curl "https://gen.pollinations.ai/3d/no_prompt_for_trellis_needed?image=https://inferenceport.ai/img/trellis.jpg&model=trellis-2-low" \ -H "Authorization: Bearer YOUR_API_KEY" -o model.glb ``` ### Embeddings (OpenAI-compatible) ```bash curl https://gen.pollinations.ai/v1/embeddings \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"openai-3-small","input":"Hello world","dimensions":512}' ``` See `GET /v1/models` for every text, image, audio, video, and embedding model available. ## Authentication All generation requests require an API key from [enter.pollinations.ai](https://enter.pollinations.ai/keys). Model listing endpoints work without authentication. | Type | Prefix | Use case | Rate limits | Description | |------|--------|----------|-------------|-------------| | Secret | `sk_` | Server-side apps | None | Personal developer key. Never expose in client-side code. | | App Key (BYOP) | `pk_` | Client-side & Frontend apps | None | Publishable key used in the **BYOP (Bring Your Own Pollen)** flow to authorize users' balances. | > **Note:** Publishable Keys (`pk_`) for direct client-side requests have been replaced by the **BYOP (Bring Your Own Pollen)** auth flow. Frontend applications should use the OAuth authorization-code flow with PKCE to obtain a temporary user-authorized secret key (`sk_`). The legacy fragment redirect and device flow remain supported. Two ways to authenticate generation requests: - Header: `Authorization: Bearer YOUR_API_KEY` - Query param: `?key=YOUR_API_KEY` For detailed integration guides on user-pays authorization, including OAuth discovery and token exchange, refer to the [Bring Your Own Pollen (BYOP) guide](https://github.com/pollinations/pollinations/blob/main/BRING_YOUR_OWN_POLLEN.md). ## Text Generation Generate text responses using AI models. Fully compatible with the OpenAI Chat Completions API — use any OpenAI SDK by changing the base URL. | Endpoint | Best for | |----------|----------| | `POST /v1/chat/completions` | Full OpenAI compatibility — streaming, tools, vision, structured outputs | | `GET /text/{prompt}` | Quick prototyping — simple GET, returns plain text | **Available models:** openai, openai-fast, gpt-oss, gpt-5.4, gpt-5.4-mini, openai-large, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, mercury, command-a-plus, qwen-coder, mistral-small-3.2, mistral, openai-audio, openai-audio-large, gemini-3-flash, gemini, gemini-flash-lite-3.5, gemini-fast, deepseek, gemma, gemma-4-31b, deepseek-pro, grok, grok-4-20-reasoning, grok-large, grok-4.5, gemini-search, gemini-search-fast, gemini-search-large, midijourney, midijourney-large, claude-fast, claude, claude-sonnet-5, claude-opus-4.6, claude-opus-4.7, claude-large, claude-fable-5, perplexity-fast, perplexity-high, perplexity, perplexity-reasoning, kimi, kimi-code, kimi-k3, laguna, longcat, nemotron, mimo-v2.5, mimo-v2.5-pro, gemini-large, nova-fast, nova, glm, llama, llama-maverick, llama-scout, minimax-m2.7, minimax, muse-spark-1.1, mistral-large, qwen-coder-large, qwen-large, qwen3.7-max, qwen3.7-flash, qwen-vision, qwen-vision-pro, step-flash, step-3.5-flash, qwen-safety ### Prompt caching On Gemini, Claude, and Nova models, a large static prompt prefix can be cached so repeat requests bill it at a fraction of the input rate. Mark the end of the static prefix with `cache_control` on a content block (not on the message); everything before the marker must be byte-identical across requests, everything dynamic goes after. The first request creates the cache (`usage` reports `cache_creation_input_tokens`); repeat requests within the TTL report `prompt_tokens_details.cached_tokens` at the discounted rate. ```json { "model": "gemini-fast", "messages": [ { "role": "system", "content": [ { "type": "text", "text": "", "cache_control": { "type": "ephemeral" } } ] }, { "role": "user", "content": "" } ] } ``` **Gemini** — the prefix must be at least ~2,048 tokens (~4,096 on Gemini 3 models). Requests with tools are not cached — including built-in tools, so `gemini`, `gemini-3-flash`, `gemini-large`, and the search variants only cache when tools are disabled (`"tools": []`) or a JSON `response_format` is set; `gemini-fast` and `gemini-flash-lite-3.5` cache by default. Cache creates bill at the standard input rate plus a storage fee for the 1-hour TTL ($1 per 1M cached tokens on Flash models, $4.50 on Pro); hits bill at ~10% of input. The storage fee means caching pays off only when the prefix is reused often — roughly a dozen reuses per hour on the cheapest models. **Claude** — all Claude models cache. The prefix must be at least 4,096 tokens (1,024 on `claude` and `claude-fable-5`); tools are fine. Cache creates bill at 1.25× the input rate (no storage fee); hits bill at 10% of input. The cache lives ~5 minutes, refreshed on each hit. **Nova** — `nova` and `nova-fast` cache. The prefix must be at least ~1,000 tokens (up to 20K tokens cacheable). Cache creates are free; hits bill at 25% of input. ~5-minute TTL. ## Image Generation Generate images from text prompts via a simple GET request. Returns JPEG, PNG, or SVG depending on the selected model. ``` https://gen.pollinations.ai/image/a%20cat%20in%20space?model=flux ``` **Available models:** dreamshaper, kontext, nanobanana, nanobanana-2, nanobanana-2-lite, nanobanana-pro, seedream5, seedream5-pro, seedream, seedream-pro, ideogram-v4-turbo, ideogram-v4-balanced, ideogram-v4-quality, gptimage, gptimage-large, gpt-image-2, flux, zimage, wan-image, wan-image-pro, qwen-image, grok-imagine, grok-imagine-pro, recraft-v4.1-vector, klein, p-image, p-image-edit, nova-canvas ### Community image models Community image models use an owner/model id and support generation through `/image/{prompt}` and `/v1/images/generations`. The registration test adds image input and `/v1/images/edits` metadata when the registrant's edit endpoint succeeds. OpenAI-compatible responses use `b64_json`; URL responses are not supported for community models. See `/image/models` for the live model list and supported endpoints. ## Video Generation Generate videos from text prompts or reference images. Returns MP4. ``` https://gen.pollinations.ai/video/sunset%20timelapse?model=veo&duration=4 ``` **Available models:** veo, veo-1080p, seedance-pro, seedance-2.0, wan, wan-fast, wan-pro, wan-pro-1080p, grok-video-pro, happyhorse-1.1, p-video-720p, p-video-1080p, nova-reel ## Realtime Voice OpenAI-compatible Realtime WebSocket proxy for voice and multimodal sessions. | Endpoint | Description | |----------|-------------| | `GET /v1/realtime` | WebSocket Realtime session (`model=gpt-realtime-2.1`) | Requires an API key with positive balance. Server clients can use `Authorization: Bearer `; browser WebSocket clients can use `?key=pk_...`. The WebSocket proxy aggregates observed `response.done` usage and settles one billing event when the session closes. Input transcription sessions are not supported yet. Events sent and received over the socket use the OpenAI Realtime protocol unchanged. See OpenAI's [Realtime WebSocket events guide](https://developers.openai.com/api/docs/guides/realtime-websocket#sending-and-receiving-events). ```js import WebSocket from "ws"; // Server: Bearer auth. Browser: append `&key=pk_...` instead (headers aren't settable). const ws = new WebSocket( "wss://gen.pollinations.ai/v1/realtime?model=gpt-realtime-2.1", { headers: { Authorization: `Bearer ${process.env.POLLINATIONS_API_KEY}` } }, ); ws.on("open", () => ws.send(JSON.stringify({ type: "session.update", session: { type: "realtime", instructions: "Be concise." }, }))); ws.on("message", (m) => console.log(JSON.parse(m.toString()))); ``` **Browser audio:** play the model's audio through an `