diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 4c511fb1d..eb36b6d6a 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -24,7 +24,7 @@ jobs: - name: Checkout uses: actions/checkout@v5 - name: Annotate locations with typos - uses: codespell-project/codespell-problem-matcher@9ba2c57125d4908eade4308f32c4ff814c184633 # v1 + uses: codespell-project/codespell-problem-matcher@b80729f885d32f78a716c2f107b4db1025001c42 # v1 - name: Codespell uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2.1 with: diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index aa9f45c77..e853a97ca 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -176,8 +176,7 @@ jobs: - name: Install sccache uses: taiki-e/install-action@6f9c7cc51aa54b13cbcbd12f8bbf69d8ba405b4b # v2 with: - tool: sccache - version: 0.7.5 + tool: sccache@0.7.5 - name: Configure sccache backend shell: bash diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index fbc23aef7..fa3fbe85a 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2210,15 +2210,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "endi" version = "1.1.0" @@ -2974,11 +2965,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.0", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -4953,11 +4942,9 @@ checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", - "encoding_rs", "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", "http-body-util", @@ -4967,7 +4954,6 @@ dependencies = [ "hyper-util", "js-sys", "log", - "mime", "native-tls", "percent-encoding", "pin-project-lite", @@ -6078,27 +6064,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.23.0" @@ -7240,17 +7205,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - [[package]] name = "windows-result" version = "0.3.4" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 0093350ad..295196be8 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -161,7 +161,7 @@ rand = "0.9" ratatui = "0.29.0" ratatui-macros = "0.6.0" regex-lite = "0.1.7" -reqwest = "0.12" +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } rmcp = { version = "0.8.4", default-features = false } schemars = "0.8.22" seccompiler = "0.5.0" diff --git a/codex-rs/app-server/tests/suite/user_agent.rs b/codex-rs/app-server/tests/suite/user_agent.rs index 52ba6e56a..9ca5332b1 100644 --- a/codex-rs/app-server/tests/suite/user_agent.rs +++ b/codex-rs/app-server/tests/suite/user_agent.rs @@ -25,8 +25,9 @@ async fn get_user_agent_returns_current_codex_user_agent() -> Result<()> { .await??; let os_info = os_info::get(); + let originator = codex_core::default_client::originator().value.as_str(); let user_agent = format!( - "codex_cli_rs/0.0.0 ({} {}; {}) {} (codex-app-server-tests; 0.1.0)", + "{originator}/0.0.0 ({} {}; {}) {} (codex-app-server-tests; 0.1.0)", os_info.os_type(), os_info.version(), os_info.architecture().unwrap_or("unknown"), diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 17a7a1660..fa5577422 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -50,7 +50,7 @@ mcp-types = { workspace = true } os_info = { workspace = true } rand = { workspace = true } regex-lite = { workspace = true } -reqwest = { workspace = true, features = ["json", "stream"] } +reqwest = { workspace = true, features = ["json", "stream", "rustls-tls"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha1 = { workspace = true } diff --git a/codex-rs/core/src/default_client.rs b/codex-rs/core/src/default_client.rs index b1cf11ed0..2b9497476 100644 --- a/codex-rs/core/src/default_client.rs +++ b/codex-rs/core/src/default_client.rs @@ -267,6 +267,7 @@ pub fn create_client() -> CodexHttpClient { let build_client = || { let mut builder = reqwest::Client::builder() .use_rustls_tls() + // Set UA via dedicated helper to avoid header validation pitfalls .user_agent(ua.clone()) .default_headers(default_headers.clone()); if is_sandboxed() { @@ -302,7 +303,8 @@ mod tests { #[test] fn test_get_codex_user_agent() { let user_agent = get_codex_user_agent(); - assert!(user_agent.starts_with("codex_cli_rs/")); + let prefix = format!("{}/", originator().value.as_str()); + assert!(user_agent.starts_with(&prefix)); } #[tokio::test] @@ -343,7 +345,10 @@ mod tests { let originator_header = headers .get("originator") .expect("originator header missing"); - assert_eq!(originator_header.to_str().unwrap(), "codex_cli_rs"); + assert_eq!( + originator_header.to_str().unwrap(), + originator().value.as_str(), + ); // User-Agent matches the computed Codex UA for that originator let expected_ua = get_codex_user_agent(); @@ -379,11 +384,14 @@ mod tests { #[cfg(target_os = "macos")] fn test_macos() { use regex_lite::Regex; + use regex_lite::escape; let user_agent = get_codex_user_agent(); - let re = Regex::new( - r"^codex_cli_rs/\d+\.\d+\.\d+ \(Mac OS \d+\.\d+\.\d+; (x86_64|arm64)\) (\S+)$", - ) - .unwrap(); + let originator = escape(originator().value.as_str()); + let pattern = format!( + r"^{}/\d+\.\d+\.\d+ \(Mac OS \d+\.\d+\.\d+; (x86_64|arm64)\) (\S+)$", + originator, + ); + let re = Regex::new(&pattern).unwrap(); assert!(re.is_match(&user_agent)); } } diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index c2c5048de..9e259ee07 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -14,6 +14,7 @@ use codex_core::ResponseItem; use codex_core::WireApi; use codex_core::auth::AuthCredentialsStoreMode; use codex_core::built_in_model_providers; +use codex_core::default_client::originator; use codex_core::error::CodexErr; use codex_core::model_family::find_family_for_model; use codex_core::protocol::EventMsg; @@ -368,7 +369,10 @@ async fn includes_conversation_id_and_model_headers_in_request() { request_conversation_id.to_str().unwrap(), conversation_id.to_string() ); - assert_eq!(request_originator.to_str().unwrap(), "codex_cli_rs"); + assert_eq!( + request_originator.to_str().unwrap(), + originator().value.as_str(), + ); assert_eq!( request_authorization.to_str().unwrap(), "Bearer Test API Key" @@ -485,7 +489,10 @@ async fn chatgpt_auth_sends_correct_request() { request_conversation_id.to_str().unwrap(), conversation_id.to_string() ); - assert_eq!(request_originator.to_str().unwrap(), "codex_cli_rs"); + assert_eq!( + request_originator.to_str().unwrap(), + originator().value.as_str(), + ); assert_eq!( request_authorization.to_str().unwrap(), "Bearer Access Token" diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index a6bc966d7..31b3608f5 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -143,8 +143,9 @@ impl McpProcess { let initialized = self.read_jsonrpc_message().await?; let os_info = os_info::get(); + let originator = codex_core::default_client::originator().value.as_str(); let user_agent = format!( - "codex_cli_rs/0.0.0 ({} {}; {}) {} (elicitation test; 0.0.0)", + "{originator}/0.0.0 ({} {}; {}) {} (elicitation test; 0.0.0)", os_info.os_type(), os_info.version(), os_info.architecture().unwrap_or("unknown"), diff --git a/sdk/native/examples/agents/agents-guardrails.ts b/sdk/native/examples/agents/agents-guardrails.ts index 6406516aa..cdf1c082a 100644 --- a/sdk/native/examples/agents/agents-guardrails.ts +++ b/sdk/native/examples/agents/agents-guardrails.ts @@ -30,6 +30,43 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { promises as fs } from 'node:fs'; import { z } from 'zod'; +import { Agent, run } from '@openai/agents'; +import { CodexProvider } from '../../src/index'; + +type GuardrailInput = string | unknown[]; + +function normalizeInput(input: GuardrailInput): string { + if (typeof input === 'string') { + return input; + } + + try { + return input + .map(item => { + if (typeof item === 'string') { + return item; + } + return JSON.stringify(item); + }) + .join('\n'); + } catch { + return JSON.stringify(input); + } +} + +function guardrailSuccess(outputInfo?: Record) { + return { + tripwireTriggered: false, + outputInfo, + }; +} + +function guardrailFailure(reason: string) { + return { + tripwireTriggered: true, + outputInfo: { reason }, + }; +} import * as Agents from '@openai/agents'; import { setDefaultModelProvider } from '@openai/agents-core'; import { CodexProvider } from '../../src/index'; @@ -83,26 +120,24 @@ async function main() { }); // Create a guardrail that validates input structure - const inputValidationGuardrail = guardrail({ + const inputValidationGuardrail = { name: 'input-validation', - validate: async (input: string) => { + execute: async ({ input }: { input: GuardrailInput }) => { + const normalized = normalizeInput(input); try { - // Try to parse as JSON first - const parsed = JSON.parse(input); + const parsed = JSON.parse(normalized); validInputSchema.parse(parsed); - return { valid: true }; + return guardrailSuccess({ mode: 'json' }); } catch (error) { - // If not JSON, check if it's a plain string that meets requirements - if (typeof input === 'string' && input.length >= 10 && input.length <= 1000) { - return { valid: true }; + if (normalized.length >= 10 && normalized.length <= 1000) { + return guardrailSuccess({ mode: 'text', length: normalized.length }); } - return { - valid: false, - reason: 'Input must be between 10 and 1000 characters, or a valid JSON object with type, content, and optional priority fields', - }; + return guardrailFailure( + 'Input must be between 10 and 1000 characters, or a valid JSON object with type, content, and optional priority fields' + ); } }, - }); + }; const agentWithValidation = new Agent({ name: 'ValidatedAgent', @@ -143,22 +178,19 @@ async function main() { // List of blocked terms (in a real app, this would be more sophisticated) const blockedTerms = ['spam', 'phishing', 'malware', 'hack']; - const contentFilterGuardrail = guardrail({ + const contentFilterGuardrail = { name: 'content-filter', - validate: async (input: string) => { - const lowerInput = input.toLowerCase(); + execute: async ({ input }: { input: GuardrailInput }) => { + const lowerInput = normalizeInput(input).toLowerCase(); const foundTerms = blockedTerms.filter(term => lowerInput.includes(term)); if (foundTerms.length > 0) { - return { - valid: false, - reason: `Input contains blocked terms: ${foundTerms.join(', ')}`, - }; + return guardrailFailure(`Input contains blocked terms: ${foundTerms.join(', ')}`); } - return { valid: true }; + return guardrailSuccess(); }, - }); + }; const filteredAgent = new Agent({ name: 'FilteredAgent', @@ -202,20 +234,20 @@ async function main() { /drop\s+table/i, ]; - const securityGuardrail = guardrail({ + const securityGuardrail = { name: 'security-check', - validate: async (input: string) => { + execute: async ({ input }: { input: GuardrailInput }) => { + const normalized = normalizeInput(input); for (const pattern of dangerousPatterns) { - if (pattern.test(input)) { - return { - valid: false, - reason: 'Input contains potentially dangerous commands that could cause data loss', - }; + if (pattern.test(normalized)) { + return guardrailFailure( + 'Input contains potentially dangerous commands that could cause data loss' + ); } } - return { valid: true }; + return guardrailSuccess(); }, - }); + }; const secureAgent = new Agent({ name: 'SecureAgent', diff --git a/sdk/native/examples/agents/agents-handoffs.ts b/sdk/native/examples/agents/agents-handoffs.ts index e904341e8..c50441723 100644 --- a/sdk/native/examples/agents/agents-handoffs.ts +++ b/sdk/native/examples/agents/agents-handoffs.ts @@ -29,6 +29,7 @@ import path from 'node:path'; import { promises as fs } from 'node:fs'; import { Agent, run, handoff } from '@openai/agents'; import { CodexProvider } from '../../src/index'; +import { runExampleStep } from '../utils'; async function main() { console.log('๐Ÿค Agent Handoffs with CodexProvider\n'); @@ -80,14 +81,20 @@ async function main() { console.log('\nQuery: "Review this function and ensure it has tests: function add(a, b) { return a + b; }"\n'); - const reviewResult = await run( - codeReviewer, - 'Review this function and ensure it has tests: function add(a, b) { return a + b; }' + const reviewResult = await runExampleStep('Code review handoff', () => + run( + codeReviewer, + 'Review this function and ensure it has tests: function add(a, b) { return a + b; }' + ) ); - console.log('\n[Review Result]'); - console.log(reviewResult.finalOutput); - console.log('\nโœ“ Example 1 completed'); + if (reviewResult) { + console.log('\n[Review Result]'); + console.log(reviewResult.finalOutput); + console.log('\nโœ“ Example 1 completed'); + } else { + console.log('Skipping Example 1 output due to a connection issue.'); + } // ============================================================================ // Example 2: Multi-Agent Chain - Architect โ†’ Developer โ†’ QA @@ -136,14 +143,17 @@ async function main() { console.log('\nQuery: "Design a user authentication system"\n'); - const architectureResult = await run( - architect, - 'Design a user authentication system' + const architectureResult = await runExampleStep('Architecture handoff', () => + run(architect, 'Design a user authentication system') ); - console.log('\n[Architecture Result]'); - console.log(architectureResult.finalOutput); - console.log('\nโœ“ Example 2 completed'); + if (architectureResult) { + console.log('\n[Architecture Result]'); + console.log(architectureResult.finalOutput); + console.log('\nโœ“ Example 2 completed'); + } else { + console.log('Skipping Example 2 output due to a connection issue.'); + } // ============================================================================ // Example 3: Conditional Handoff - Router Agent @@ -201,14 +211,17 @@ async function main() { console.log('\nQuery: "I found a bug where the login button doesn\'t work"\n'); - const routerResult = await run( - router, - "I found a bug where the login button doesn't work" + const routerResult = await runExampleStep('Router handoff', () => + run(router, "I found a bug where the login button doesn't work") ); - console.log('\n[Router Result]'); - console.log(routerResult.finalOutput); - console.log('\nโœ“ Example 3 completed'); + if (routerResult) { + console.log('\n[Router Result]'); + console.log(routerResult.finalOutput); + console.log('\nโœ“ Example 3 completed'); + } else { + console.log('Skipping Example 3 output due to a connection issue.'); + } // ============================================================================ // Summary diff --git a/sdk/native/examples/agents/agents-multi-agent-workflow.ts b/sdk/native/examples/agents/agents-multi-agent-workflow.ts index 313cc024f..a19bfbf7d 100644 --- a/sdk/native/examples/agents/agents-multi-agent-workflow.ts +++ b/sdk/native/examples/agents/agents-multi-agent-workflow.ts @@ -31,6 +31,7 @@ import path from 'node:path'; import { promises as fs } from 'node:fs'; import { Agent, run, handoff } from '@openai/agents'; import { CodexProvider } from '../../src/index'; +import { runExampleStep, ensureResult } from '../utils'; async function main() { console.log('๐Ÿ”„ Complex Multi-Agent Workflow with CodexProvider\n'); @@ -150,49 +151,74 @@ async function main() { // Step 1: Product Manager defines requirements console.log('๐Ÿ“‹ Step 1: Product Manager defining requirements...'); console.log('โ”€'.repeat(60)); - const pmResult = await run(productManager, task); - console.log('\n[Product Manager Output]'); - console.log(pmResult.finalOutput.substring(0, 300) + '...\n'); - - // Step 2: Architect designs the system - console.log('\n๐Ÿ—๏ธ Step 2: Architect designing system...'); - console.log('โ”€'.repeat(60)); - const archResult = await run( - architect, - `Based on these requirements, design the architecture:\n\n${pmResult.finalOutput}` - ); - console.log('\n[Architect Output]'); - console.log(archResult.finalOutput.substring(0, 300) + '...\n'); - - // Step 3: Developer implements - console.log('\n๐Ÿ’ป Step 3: Developer implementing...'); - console.log('โ”€'.repeat(60)); - const devResult = await run( - developer, - `Based on this architecture, implement the code:\n\n${archResult.finalOutput}` + const pmResult = await runExampleStep('Product Manager requirements', () => + run(productManager, task) ); - console.log('\n[Developer Output]'); - console.log(devResult.finalOutput.substring(0, 300) + '...\n'); - // Step 4: Tester tests - console.log('\n๐Ÿงช Step 4: Tester testing...'); - console.log('โ”€'.repeat(60)); - const testResult = await run( - tester, - `Review and test this implementation:\n\n${devResult.finalOutput}` - ); - console.log('\n[Tester Output]'); - console.log(testResult.finalOutput.substring(0, 300) + '...\n'); - - // Step 5: Documenter writes docs - console.log('\n๐Ÿ“š Step 5: Documenter writing documentation...'); - console.log('โ”€'.repeat(60)); - const docResult = await run( - documenter, - `Write documentation for this feature:\n\nRequirements: ${pmResult.finalOutput.substring(0, 200)}\n\nImplementation: ${devResult.finalOutput.substring(0, 200)}` - ); - console.log('\n[Documenter Output]'); - console.log(docResult.finalOutput.substring(0, 300) + '...\n'); + if (ensureResult(pmResult, 'the remaining workflow steps')) { + console.log('\n[Product Manager Output]'); + console.log(pmResult.finalOutput.substring(0, 300) + '...\n'); + + // Step 2: Architect designs the system + console.log('\n๐Ÿ—๏ธ Step 2: Architect designing system...'); + console.log('โ”€'.repeat(60)); + const archResult = await runExampleStep('Architect design', () => + run( + architect, + `Based on these requirements, design the architecture:\n\n${pmResult.finalOutput}` + ) + ); + + if (ensureResult(archResult, 'the remaining workflow steps')) { + console.log('\n[Architect Output]'); + console.log(archResult.finalOutput.substring(0, 300) + '...\n'); + + // Step 3: Developer implements + console.log('\n๐Ÿ’ป Step 3: Developer implementing...'); + console.log('โ”€'.repeat(60)); + const devResult = await runExampleStep('Developer implementation', () => + run( + developer, + `Based on this architecture, implement the code:\n\n${archResult.finalOutput}` + ) + ); + + if (ensureResult(devResult, 'the remaining workflow steps')) { + console.log('\n[Developer Output]'); + console.log(devResult.finalOutput.substring(0, 300) + '...\n'); + + // Step 4: Tester tests + console.log('\n๐Ÿงช Step 4: Tester testing...'); + console.log('โ”€'.repeat(60)); + const testResult = await runExampleStep('Tester review', () => + run( + tester, + `Review and test this implementation:\n\n${devResult.finalOutput}` + ) + ); + + if (ensureResult(testResult, 'the remaining workflow steps')) { + console.log('\n[Tester Output]'); + console.log(testResult.finalOutput.substring(0, 300) + '...\n'); + + // Step 5: Documenter writes docs + console.log('\n๐Ÿ“š Step 5: Documenter writing documentation...'); + console.log('โ”€'.repeat(60)); + const docResult = await runExampleStep('Documenter write-up', () => + run( + documenter, + `Write documentation for this feature:\n\nRequirements: ${pmResult.finalOutput.substring(0, 200)}\n\nImplementation: ${devResult.finalOutput.substring(0, 200)}` + ) + ); + + if (ensureResult(docResult, 'the remaining workflow steps')) { + console.log('\n[Documenter Output]'); + console.log(docResult.finalOutput.substring(0, 300) + '...\n'); + } + } + } + } + } // ============================================================================ // Example: Parallel Agent Workflow @@ -209,35 +235,44 @@ async function main() { console.log('Running agents in parallel...\n'); const [apiDesign, dbDesign, securityDesign] = await Promise.all([ - run( - architect, - `${parallelTask} - Focus on API endpoints and request/response formats` + runExampleStep('API design exploration', () => + run( + architect, + `${parallelTask} - Focus on API endpoints and request/response formats` + ) ), - run( - architect, - `${parallelTask} - Focus on database schema and data modeling` + runExampleStep('Database design exploration', () => + run( + architect, + `${parallelTask} - Focus on database schema and data modeling` + ) ), - run( - architect, - `${parallelTask} - Focus on security and authentication` + runExampleStep('Security design exploration', () => + run( + architect, + `${parallelTask} - Focus on security and authentication` + ) ), ]); - console.log('โœ“ API Design completed'); - console.log('โœ“ Database Design completed'); - console.log('โœ“ Security Design completed'); - - console.log('\n[Combined Results]'); - console.log('\nAPI Design:'); - console.log(apiDesign.finalOutput.substring(0, 200) + '...'); - console.log('\nDatabase Design:'); - console.log(dbDesign.finalOutput.substring(0, 200) + '...'); - console.log('\nSecurity Design:'); - console.log(securityDesign.finalOutput.substring(0, 200) + '...'); + if ( + ensureResult(apiDesign, 'parallel workflow results') && + ensureResult(dbDesign, 'parallel workflow results') && + ensureResult(securityDesign, 'parallel workflow results') + ) { + console.log('โœ“ API Design completed'); + console.log('โœ“ Database Design completed'); + console.log('โœ“ Security Design completed'); + + console.log('\n[Combined Results]'); + console.log('\nAPI Design:'); + console.log(apiDesign.finalOutput.substring(0, 200) + '...'); + console.log('\nDatabase Design:'); + console.log(dbDesign.finalOutput.substring(0, 200) + '...'); + console.log('\nSecurity Design:'); + console.log(securityDesign.finalOutput.substring(0, 200) + '...'); + } - // ============================================================================ - // Example: Iterative Refinement Workflow - // ============================================================================ console.log('\n\nExample: Iterative Refinement Workflow'); console.log('โ”€'.repeat(60)); console.log('\nAgents iteratively refining a solution\n'); @@ -248,24 +283,37 @@ async function main() { // Initial implementation console.log('๐Ÿ”„ Iteration 1: Initial implementation'); - let currentCode = await run(developer, iterativeTask); - console.log('Code:', currentCode.finalOutput.substring(0, 200) + '...\n'); - - // Test and get feedback - console.log('๐Ÿ”„ Iteration 2: Testing and feedback'); - const testFeedback = await run( - tester, - `Test this code and provide feedback:\n\n${currentCode.finalOutput}` + let currentCode = await runExampleStep('Initial implementation', () => + run(developer, iterativeTask) ); - console.log('Feedback:', testFeedback.finalOutput.substring(0, 200) + '...\n'); - // Refine based on feedback - console.log('๐Ÿ”„ Iteration 3: Refining based on feedback'); - const refinedCode = await run( - developer, - `Improve this code based on feedback:\n\nOriginal: ${currentCode.finalOutput}\n\nFeedback: ${testFeedback.finalOutput}` - ); - console.log('Refined Code:', refinedCode.finalOutput.substring(0, 200) + '...\n'); + if (ensureResult(currentCode, 'iterative workflow steps')) { + console.log('Code:', currentCode.finalOutput.substring(0, 200) + '...\n'); + + // Test and get feedback + console.log('๐Ÿ”„ Iteration 2: Testing and feedback'); + const testFeedback = await runExampleStep('Testing feedback', () => + run( + tester, + `Test this code and provide feedback:\n\n${currentCode.finalOutput}` + ) + ); + if (ensureResult(testFeedback, 'iterative workflow steps')) { + console.log('Feedback:', testFeedback.finalOutput.substring(0, 200) + '...\n'); + + // Refine based on feedback + console.log('๐Ÿ”„ Iteration 3: Refining based on feedback'); + const refinedCode = await runExampleStep('Refined implementation', () => + run( + developer, + `Improve this code based on feedback:\n\nOriginal: ${currentCode.finalOutput}\n\nFeedback: ${testFeedback.finalOutput}` + ) + ); + if (ensureResult(refinedCode, 'iterative workflow steps')) { + console.log('Refined Code:', refinedCode.finalOutput.substring(0, 200) + '...\n'); + } + } + } console.log('โœ“ Iterative refinement completed'); diff --git a/sdk/native/examples/agents/agents-streaming.ts b/sdk/native/examples/agents/agents-streaming.ts index 2576b7c7b..caaf43b70 100644 --- a/sdk/native/examples/agents/agents-streaming.ts +++ b/sdk/native/examples/agents/agents-streaming.ts @@ -31,6 +31,7 @@ import path from 'node:path'; import { promises as fs } from 'node:fs'; import { Agent, run } from '@openai/agents'; import { CodexProvider } from '../../src/index'; +import { runExampleStep } from '../utils'; async function main() { console.log('๐ŸŒŠ Streaming Responses with CodexProvider\n'); @@ -64,53 +65,66 @@ async function main() { console.log('\nQuery: "Explain how streaming works in AI agents"\n'); console.log('[Streaming Response]\n'); + const stream1 = await runExampleStep('Basic streaming run', () => + run(streamingAgent, 'Explain how streaming works in AI agents') + ); const stream1 = await run(streamingAgent, 'Explain how streaming works in AI agents', { stream: true, }); - let accumulatedText = ''; - let tokenCount = 0; + if (stream1) { + let accumulatedText = ''; + let tokenCount = 0; + for await (const event of stream1.events) { + switch (event.type) { + case 'response_started': + console.log('๐Ÿ“ก Response started...\n'); + break; for await (const event of stream1) { switch (event.type) { case 'response_started': console.log('๐Ÿ“ก Response started...\n'); break; - case 'output_text_delta': - // Print deltas as they arrive - process.stdout.write(event.delta); - accumulatedText += event.delta; - tokenCount += event.delta.split(/\s+/).length; - break; + case 'output_text_delta': + // Print deltas as they arrive + process.stdout.write(event.delta); + accumulatedText += event.delta; + tokenCount += event.delta.split(/\s+/).length; + break; - case 'output_text_done': - console.log('\n\nโœ“ Text output completed'); - break; + case 'output_text_done': + console.log('\n\nโœ“ Text output completed'); + break; - case 'reasoning_delta': - // Reasoning deltas (if supported) - process.stdout.write(`[Reasoning] ${event.delta}`); - break; + case 'reasoning_delta': + // Reasoning deltas (if supported) + process.stdout.write(`[Reasoning] ${event.delta}`); + break; - case 'reasoning_done': - console.log('\nโœ“ Reasoning completed'); - break; + case 'reasoning_done': + console.log('\nโœ“ Reasoning completed'); + break; - case 'response_done': - console.log('\n๐Ÿ“Š Final Statistics:'); - console.log(` Total tokens: ${event.response.usage.totalTokens}`); - console.log(` Input tokens: ${event.response.usage.inputTokens}`); - console.log(` Output tokens: ${event.response.usage.outputTokens}`); - break; + case 'response_done': + console.log('\n๐Ÿ“Š Final Statistics:'); + console.log(` Total tokens: ${event.response.usage.totalTokens}`); + console.log(` Input tokens: ${event.response.usage.inputTokens}`); + console.log(` Output tokens: ${event.response.usage.outputTokens}`); + break; - case 'error': - console.error('\nโœ— Error:', event.error.message); - break; + case 'error': + console.error('\nโœ— Error:', event.error.message); + break; + } } + + console.log('\nโœ“ Example 1 completed'); + } else { + console.log('Skipping Example 1 due to a connection issue.'); } - console.log('\nโœ“ Example 1 completed'); // ============================================================================ // Example 2: Streaming with Progress Indicator @@ -127,45 +141,58 @@ async function main() { console.log('\nQuery: "Write a function to calculate fibonacci numbers"\n'); console.log('[Streaming with Progress]\n'); + const stream2 = await runExampleStep('Progress streaming run', () => + run(progressAgent, 'Write a function to calculate fibonacci numbers') + ); const stream2 = await run(progressAgent, 'Write a function to calculate fibonacci numbers', { stream: true, }); - let chunkCount = 0; - let lastUpdate = Date.now(); + if (stream2) { + let chunkCount = 0; + let lastUpdate = Date.now(); + for await (const event of stream2.events) { + switch (event.type) { + case 'response_started': + console.log('โณ Starting response generation...\n'); + break; for await (const event of stream2) { switch (event.type) { case 'response_started': console.log('โณ Starting response generation...\n'); break; - case 'output_text_delta': - chunkCount++; - const now = Date.now(); - // Update progress indicator every 500ms - if (now - lastUpdate > 500) { - process.stdout.write(`\r๐Ÿ“ Received ${chunkCount} chunks... `); - lastUpdate = now; - } - // Still print the actual content - process.stdout.write(event.delta); - break; + case 'output_text_delta': + chunkCount++; + const now = Date.now(); + // Update progress indicator every 500ms + if (now - lastUpdate > 500) { + process.stdout.write(`\r๐Ÿ“ Received ${chunkCount} chunks... `); + lastUpdate = now; + } + // Still print the actual content + process.stdout.write(event.delta); + break; - case 'output_text_done': - console.log(`\n\nโœ“ Completed (${chunkCount} chunks)`); - break; + case 'output_text_done': + console.log(`\n\nโœ“ Completed (${chunkCount} chunks)`); + break; - case 'response_done': - console.log('\n๐Ÿ“Š Usage:'); - console.log(` Input: ${event.response.usage.inputTokens} tokens`); - console.log(` Output: ${event.response.usage.outputTokens} tokens`); - console.log(` Total: ${event.response.usage.totalTokens} tokens`); - break; + case 'response_done': + console.log('\n๐Ÿ“Š Usage:'); + console.log(` Input: ${event.response.usage.inputTokens} tokens`); + console.log(` Output: ${event.response.usage.outputTokens} tokens`); + console.log(` Total: ${event.response.usage.totalTokens} tokens`); + break; + } } + + console.log('\nโœ“ Example 2 completed'); + } else { + console.log('Skipping Example 2 due to a connection issue.'); } - console.log('\nโœ“ Example 2 completed'); // ============================================================================ // Example 3: Streaming with Custom Processing @@ -182,15 +209,32 @@ async function main() { console.log('\nQuery: "List the benefits of using streaming in AI applications"\n'); console.log('[Custom Processing]\n'); + const stream3 = await runExampleStep('Custom processing streaming run', () => + run( + customAgent, + 'List the benefits of using streaming in AI applications' + ) const stream3 = await run( customAgent, 'List the benefits of using streaming in AI applications', { stream: true } ); - const lines: string[] = []; - let currentLine = ''; + if (stream3) { + const lines: string[] = []; + let currentLine = ''; + for await (const event of stream3.events) { + switch (event.type) { + case 'output_text_delta': + currentLine += event.delta; + // Process line by line + if (event.delta.includes('\n')) { + const parts = currentLine.split('\n'); + // Add all but the last part (which might be incomplete) + for (let i = 0; i < parts.length - 1; i++) { + lines.push(parts[i]); + console.log(`โ€ข ${parts[i]}`); for await (const event of stream3) { switch (event.type) { case 'output_text_delta': @@ -204,28 +248,32 @@ async function main() { lines.push(parts[i]!); console.log(` ${lines.length}. ${parts[i]!.trim()}`); } + currentLine = parts[parts.length - 1]; } - // Keep the last part as current line - currentLine = parts[parts.length - 1] || ''; - } - break; + break; - case 'output_text_done': - // Process remaining line - if (currentLine.trim()) { - lines.push(currentLine); - console.log(` ${lines.length}. ${currentLine.trim()}`); - } - console.log(`\nโœ“ Processed ${lines.length} items`); - break; + case 'output_text_done': + if (currentLine.trim()) { + lines.push(currentLine.trim()); + console.log(`โ€ข ${currentLine.trim()}`); + } + console.log('\nโœ“ Completed custom processing'); + break; - case 'response_done': - console.log('\n๐Ÿ“Š Response completed'); - break; + case 'response_done': + console.log('\n๐Ÿ“„ Structured Summary:'); + lines.forEach((line, index) => { + console.log(` ${index + 1}. ${line}`); + }); + break; + } } + + console.log('\nโœ“ Example 3 completed'); + } else { + console.log('Skipping Example 3 due to a connection issue.'); } - console.log('\nโœ“ Example 3 completed'); // ============================================================================ // Example 4: Error Handling in Streaming @@ -242,14 +290,23 @@ async function main() { console.log('\nQuery: "Explain error handling in streaming"\n'); console.log('[Error Handling]\n'); + const stream4 = await runExampleStep('Error handling streaming run', () => + run(errorHandlingAgent, 'Explain error handling in streaming') const stream4 = await run( errorHandlingAgent, 'Explain error handling in streaming', { stream: true } ); - let hasError = false; + if (stream4) { + let hasError = false; + try { + for await (const event of stream4.events) { + switch (event.type) { + case 'output_text_delta': + process.stdout.write(event.delta); + break; try { for await (const event of stream4) { switch (event.type) { @@ -257,23 +314,29 @@ async function main() { process.stdout.write(event.delta); break; - case 'error': - hasError = true; - console.error('\n\nโœ— Streaming error:', event.error.message); - break; + case 'error': + hasError = true; + console.error('\n\nโœ— Streaming error:', event.error.message); + break; - case 'response_done': - if (!hasError) { - console.log('\n\nโœ“ Stream completed successfully'); - } - break; + case 'response_done': + if (!hasError) { + console.log('\n\nโœ“ Stream completed successfully'); + } + break; + } } + } catch (error) { + console.error( + '\n\nโœ— Fatal error during streaming:', + error instanceof Error ? error.message : String(error), + ); } - } catch (error) { - console.error('\n\nโœ— Fatal error during streaming:', error instanceof Error ? error.message : String(error)); - } - console.log('\nโœ“ Example 4 completed'); + console.log('\nโœ“ Example 4 completed'); + } else { + console.log('Skipping Example 4 due to a connection issue.'); + } // ============================================================================ // Summary @@ -283,6 +346,7 @@ async function main() { console.log('='.repeat(60)); console.log('\nKey takeaways:'); console.log(' โ€ข Streaming provides real-time updates as responses are generated'); + console.log(' โ€ข Use run() to obtain StreamedRunResult for streaming flows'); console.log(' โ€ข Call run(agent, input, { stream: true }) to enable streaming'); console.log(' โ€ข Handle different event types: deltas, done, errors'); console.log(' โ€ข CodexProvider supports full streaming capabilities'); diff --git a/sdk/native/examples/agents/agents-structured-output.ts b/sdk/native/examples/agents/agents-structured-output.ts index 07f038eb8..99727cd05 100644 --- a/sdk/native/examples/agents/agents-structured-output.ts +++ b/sdk/native/examples/agents/agents-structured-output.ts @@ -31,6 +31,7 @@ import { promises as fs } from 'node:fs'; import { z } from 'zod'; import { Agent, run } from '@openai/agents'; import { CodexProvider } from '../../src/index'; +import { runExampleStep, ensureResult } from '../utils'; async function main() { console.log('๐Ÿ“‹ Structured Output with CodexProvider\n'); @@ -74,18 +75,22 @@ async function main() { console.log('\nQuery: "Analyze the current repository status"\n'); - const result1 = await run(agentWithSchema, 'Analyze the current repository status', { - outputSchema: simpleSchema, - }); + const result1 = await runExampleStep('Simple schema run', () => + run(agentWithSchema, 'Analyze the current repository status', { + outputSchema: simpleSchema, + }) + ); - console.log('\n[Structured Response]'); - try { - const parsed = JSON.parse(result1.finalOutput); - console.log(JSON.stringify(parsed, null, 2)); - console.log('\nโœ“ Response matches schema'); - } catch (error) { - console.log('Response:', result1.finalOutput); - console.log('โš  Could not parse as JSON'); + if (ensureResult(result1, 'Example 1 structured output')) { + console.log('\n[Structured Response]'); + try { + const parsed = JSON.parse(result1.finalOutput); + console.log(JSON.stringify(parsed, null, 2)); + console.log('\nโœ“ Response matches schema'); + } catch (error) { + console.log('Response:', result1.finalOutput); + console.log('โš  Could not parse as JSON'); + } } // ============================================================================ @@ -140,22 +145,26 @@ async function main() { console.log('\nQuery: "Create a task plan for implementing user authentication"\n'); - const result2 = await run( - taskAgent, - 'Create a task plan for implementing user authentication', - { - outputSchema: complexSchema, - } + const result2 = await runExampleStep('Complex schema run', () => + run( + taskAgent, + 'Create a task plan for implementing user authentication', + { + outputSchema: complexSchema, + } + ) ); - console.log('\n[Structured Response]'); - try { - const parsed = JSON.parse(result2.finalOutput); - console.log(JSON.stringify(parsed, null, 2)); - console.log('\nโœ“ Response matches complex schema'); - } catch (error) { - console.log('Response:', result2.finalOutput); - console.log('โš  Could not parse as JSON'); + if (ensureResult(result2, 'Example 2 structured output')) { + console.log('\n[Structured Response]'); + try { + const parsed = JSON.parse(result2.finalOutput); + console.log(JSON.stringify(parsed, null, 2)); + console.log('\nโœ“ Response matches complex schema'); + } catch (error) { + console.log('Response:', result2.finalOutput); + console.log('โš  Could not parse as JSON'); + } } // ============================================================================ @@ -214,28 +223,32 @@ async function main() { console.log('\nQuery: "Create a feature request for adding dark mode"\n'); - const result3 = await run( - zodAgent, - 'Create a feature request for adding dark mode', - { - outputSchema: jsonSchemaFromZod, - } + const result3 = await runExampleStep('Zod schema run', () => + run( + zodAgent, + 'Create a feature request for adding dark mode', + { + outputSchema: jsonSchemaFromZod, + } + ) ); - console.log('\n[Structured Response]'); - try { - const parsed = JSON.parse(result3.finalOutput); - // Validate with Zod schema - const validated = zodSchema.parse(parsed); - console.log(JSON.stringify(validated, null, 2)); - console.log('\nโœ“ Response matches Zod schema'); - } catch (error) { - console.log('Response:', result3.finalOutput); - if (error instanceof z.ZodError) { - console.log('\nโš  Zod validation errors:'); - console.log(error.errors); - } else { - console.log('โš  Could not parse or validate'); + if (ensureResult(result3, 'Example 3 structured output')) { + console.log('\n[Structured Response]'); + try { + const parsed = JSON.parse(result3.finalOutput); + // Validate with Zod schema + const validated = zodSchema.parse(parsed); + console.log(JSON.stringify(validated, null, 2)); + console.log('\nโœ“ Response matches Zod schema'); + } catch (error) { + console.log('Response:', result3.finalOutput); + if (error instanceof z.ZodError) { + console.log('\nโš  Zod validation errors:'); + console.log(error.errors); + } else { + console.log('โš  Could not parse or validate'); + } } } @@ -266,22 +279,26 @@ async function main() { console.log('\nQuery: "List 3 common programming best practices"\n'); - const result4 = await run(listAgent, 'List 3 common programming best practices', { - outputSchema: arraySchema, - }); + const result4 = await runExampleStep('Array schema run', () => + run(listAgent, 'List 3 common programming best practices', { + outputSchema: arraySchema, + }) + ); - console.log('\n[Structured Response]'); - try { - const parsed = JSON.parse(result4.finalOutput); - if (Array.isArray(parsed)) { - console.log(JSON.stringify(parsed, null, 2)); - console.log(`\nโœ“ Response is an array with ${parsed.length} items`); - } else { - console.log('โš  Response is not an array'); + if (ensureResult(result4, 'Example 4 structured output')) { + console.log('\n[Structured Response]'); + try { + const parsed = JSON.parse(result4.finalOutput); + if (Array.isArray(parsed)) { + console.log(JSON.stringify(parsed, null, 2)); + console.log(`\nโœ“ Response is an array with ${parsed.length} items`); + } else { + console.log('โš  Response is not an array'); + } + } catch (error) { + console.log('Response:', result4.finalOutput); + console.log('โš  Could not parse as JSON'); } - } catch (error) { - console.log('Response:', result4.finalOutput); - console.log('โš  Could not parse as JSON'); } // ============================================================================ diff --git a/sdk/native/examples/utils.ts b/sdk/native/examples/utils.ts new file mode 100644 index 000000000..f35c096aa --- /dev/null +++ b/sdk/native/examples/utils.ts @@ -0,0 +1,26 @@ +export function describeError(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + return String(error); +} + +export async function runExampleStep( + label: string, + step: () => Promise, +): Promise { + try { + return await step(); + } catch (error) { + console.error(`โœ— ${label} failed: ${describeError(error)}`); + return null; + } +} + +export function ensureResult(result: T | null, context: string): result is T { + if (!result) { + console.log(`Skipping ${context} due to a connection issue.`); + return false; + } + return true; +} diff --git a/sdk/native/src/agents/CodexProvider.ts b/sdk/native/src/agents/CodexProvider.ts index 3fe708ed4..eae11224a 100644 --- a/sdk/native/src/agents/CodexProvider.ts +++ b/sdk/native/src/agents/CodexProvider.ts @@ -573,7 +573,22 @@ class CodexModel implements Model { // Convert AgentInputItem[] to UserInput[] for (const item of request.input) { // Handle different item types - if (item.type === "function_call_result") { + if ((item as any).type === "input_file") { + throw new Error( + `CodexProvider does not yet support input_file type. ` + + `File handling needs to be implemented based on file type and format.` + ); + } else if ((item as any).type === "input_audio") { + throw new Error( + `CodexProvider does not yet support input_audio type. ` + + `Audio handling needs to be implemented.` + ); + } else if ((item as any).type === "input_image") { + const imagePath = await this.handleImageInput(item as any); + if (imagePath) { + parts.push({ type: "local_image", path: imagePath }); + } + } else if (item.type === "function_call_result") { // Tool results - for now, convert to text describing the result const result = item as any; parts.push({ @@ -832,14 +847,10 @@ class CodexModel implements Model { this.streamedTurnItems.push(event.item); if (event.item.type === "agent_message") { - // Use "model" type for custom output_text_done events events.push({ - type: "model", - event: { - type: "output_text_done", - text: event.item.text, - }, - } as StreamEvent); + type: "output_text_done", + text: event.item.text, + } as any); textAccumulator.delete("agent_message"); this.lastStreamedMessage = event.item.text; } else if (event.item.type === "reasoning") { diff --git a/sdk/native/tests/agents-integration.test.mjs b/sdk/native/tests/agents-integration.test.mjs index a100a2913..f6e9438e0 100644 --- a/sdk/native/tests/agents-integration.test.mjs +++ b/sdk/native/tests/agents-integration.test.mjs @@ -9,6 +9,9 @@ */ import { describe, expect, it, beforeAll } from "@jest/globals"; +const RUN_REAL_BACKEND = process.env.CODEX_NATIVE_RUN_REAL_BACKEND === "1"; +const realBackendTest = RUN_REAL_BACKEND ? it : it.skip; + import { fileURLToPath } from "node:url"; function resolveNativeBindingPath() { @@ -218,7 +221,7 @@ describe("CodexProvider - OpenAI Agents Integration", () => { const provider = new CodexProvider({ skipGitRepoCheck: true, - }); + }); const agent = new Agent({ name: "TestAgent",