SpyBara
Go Premium

Documentation 2026-09-03 23:00 UTC to 2026-09-04 23:59 UTC

26 files changed +5,503 −607. View all changes and history on the product overview
2026
Tue 29 22:57 Mon 28 22:57 Sat 26 23:59 Fri 25 23:58 Thu 24 23:58 Wed 23 23:58 Tue 22 23:57 Mon 21 23:00 Sat 19 23:00 Fri 18 22:59 Thu 17 10:04 Wed 16 20:58 Tue 15 22:59 Mon 14 22:58 Sun 13 15:02 Fri 11 20:00 Thu 10 18:01 Wed 9 23:59 Sat 5 17:01 Fri 4 23:59 Thu 3 23:00 Wed 2 22:59
Details

32 32 

33## Make Responses API requests33## Make Responses API requests

34 34 

35To send OpenAI SDK requests through Amazon Bedrock, use the Bedrock-aware SDK35To send OpenAI SDK requests through Amazon Bedrock, configure your client for

36client and select the AWS Region and model ID for your deployment:36the AWS Region and model ID for your deployment:

37 37 

38- Instantiate `BedrockOpenAI` instead of the default `OpenAI` client. The client38- Client libraries with a Bedrock provider derive a regional Mantle base URL

39 derives the regional Mantle base URL from the AWS Region.39 from the AWS Region. The JavaScript, Python, Go, and Java providers use

40- This guide's examples use `us-east-2`, which resolves to40 `https://bedrock-mantle.us-east-2.api.aws/openai/v1` for this guide's

41 `https://bedrock-mantle.us-east-2.api.aws/openai/v1`.41 `us-east-2` examples. The Ruby examples configure this `/openai/v1`

42 endpoint directly because the provider's default `/v1` route doesn't

43 support this model. The .NET SDK also configures the endpoint directly

44 because it doesn't include a Bedrock provider.

42- Use a Bedrock model ID with the `openai.` prefix, such as45- Use a Bedrock model ID with the `openai.` prefix, such as

43 `openai.gpt-5.6-sol`.46 `openai.gpt-5.6-sol`.

44 47 


48The following example uses a Bedrock API key stored as51The following example uses a Bedrock API key stored as

49`AWS_BEARER_TOKEN_BEDROCK`. See52`AWS_BEARER_TOKEN_BEDROCK`. See

50[Amazon Bedrock API keys](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html)53[Amazon Bedrock API keys](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html)

51for information about generating and using a Bedrock API key. The SDK reads the54for information about generating and using a Bedrock API key. Each example

52token from your environment.55passes the token from your environment to its language's Bedrock provider, or

56for .NET, to the regional OpenAI-compatible endpoint. The .NET SDK doesn't

57currently include a Bedrock provider.

58 

59Install the optional Java Bedrock provider before using either Java example:

60 

61```xml

62<dependency>

63 <groupId>com.openai</groupId>

64 <artifactId>openai-java-bedrock</artifactId>

65 <version>4.57.0</version>

66</dependency>

67```

53 68 

54Send a Responses API request through Amazon Bedrock69Send a Responses API request through Amazon Bedrock

55 70 

56```javascript71```javascript

57import { BedrockOpenAI } from "openai";72import OpenAI from "openai";

58 73import { bedrock } from "openai/providers/bedrock";

59const client = new BedrockOpenAI({74 

60 awsRegion: "us-east-2",75const client = new OpenAI({

76 provider: bedrock({

77 region: "us-east-2",

78 apiKey: process.env.AWS_BEARER_TOKEN_BEDROCK,

79 }),

61});80});

62 81 

63const response = await client.responses.create({82const response = await client.responses.create({


69```88```

70 89 

71```python90```python

72from openai import BedrockOpenAI91import os

73 92 

74client = BedrockOpenAI(aws_region="us-east-2")93from openai import OpenAI

94from openai.providers import bedrock

95 

96client = OpenAI(

97 provider=bedrock(

98 region="us-east-2",

99 api_key=os.environ["AWS_BEARER_TOKEN_BEDROCK"],

100 )

101)

75 102 

76response = client.responses.create(103response = client.responses.create(

77 model="openai.gpt-5.6-sol",104 model="openai.gpt-5.6-sol",


81print(response.output_text)108print(response.output_text)

82```109```

83 110 

111```go

112package main

113 

114import (

115 "context"

116 "fmt"

117 "os"

118 

119 "github.com/openai/openai-go/v3"

120 "github.com/openai/openai-go/v3/bedrock"

121 "github.com/openai/openai-go/v3/responses"

122)

123 

124func main() {

125 client, err := bedrock.NewClient(context.Background(), bedrock.Config{

126 AWSRegion: "us-east-2",

127 APIKey: os.Getenv("AWS_BEARER_TOKEN_BEDROCK"),

128 })

129 if err != nil {

130 panic(err)

131 }

132 

133 response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{

134 Model: "openai.gpt-5.6-sol",

135 Input: responses.ResponseNewParamsInputUnion{

136 OfString: openai.String("Write a haiku about cloud infrastructure."),

137 },

138 })

139 if err != nil {

140 panic(err)

141 }

142 fmt.Println(response.OutputText())

143}

144```

145 

146```java

147import com.openai.client.OpenAIClient;

148import com.openai.client.okhttp.BedrockOpenAIOkHttpClient;

149import com.openai.models.responses.ResponseCreateParams;

150 

151public final class AmazonBedrockCreateResponseExample {

152 private AmazonBedrockCreateResponseExample() {}

153 

154 public static void main(String[] args) {

155 OpenAIClient client =

156 BedrockOpenAIOkHttpClient.builder()

157 .awsRegion("us-east-2")

158 .apiKey(System.getenv("AWS_BEARER_TOKEN_BEDROCK"))

159 .build();

160 

161 ResponseCreateParams params =

162 ResponseCreateParams.builder()

163 .model("openai.gpt-5.6-sol")

164 .input("Write a haiku about cloud infrastructure.")

165 .build();

166 

167 client.responses().create(params).output().stream()

168 .flatMap(item -> item.message().stream())

169 .flatMap(message -> message.content().stream())

170 .flatMap(content -> content.outputText().stream())

171 .forEach(text -> System.out.println(text.text()));

172 }

173}

174```

175 

176```csharp

177using System.ClientModel;

178using OpenAI.Responses;

179#pragma warning disable OPENAI001

180 

181string key = Environment.GetEnvironmentVariable("AWS_BEARER_TOKEN_BEDROCK")!;

182ResponsesClient client = new(

183 new ApiKeyCredential(key),

184 new ResponsesClientOptions

185 {

186 Endpoint = new Uri("https://bedrock-mantle.us-east-2.api.aws/openai/v1"),

187 }

188);

189 

190CreateResponseOptions options = new()

191{

192 Model = "openai.gpt-5.6-sol",

193};

194options.InputItems.Add(

195 ResponseItem.CreateUserMessageItem("Write a haiku about cloud infrastructure.")

196);

197 

198ResponseResult response = await client.CreateResponseAsync(options);

199 

200Console.WriteLine(response.GetOutputText());

201```

202 

203```ruby

204require "openai"

205 

206client = OpenAI::Client.new(

207 provider: OpenAI::Providers.bedrock(

208 region: "us-east-2",

209 base_url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",

210 api_key: ENV.fetch("AWS_BEARER_TOKEN_BEDROCK")

211 )

212)

213 

214response = client.responses.create(

215 model: "openai.gpt-5.6-sol",

216 input: "Write a haiku about cloud infrastructure."

217)

218 

219puts(response.output_text)

220```

221 

84```bash222```bash

85curl "https://bedrock-mantle.us-east-2.api.aws/openai/v1/responses" \223curl "https://bedrock-mantle.us-east-2.api.aws/openai/v1/responses" \

86 -H "Content-Type: application/json" \224 -H "Content-Type: application/json" \


92```230```

93 231 

94 232 

95For long-running applications, pass a token provider instead of a static API233For long-running applications, prefer the standard AWS credential chain instead

96key. The SDK calls the provider before each request. The AWS token-generator234of a static bearer token. The JavaScript, Python, Go, Java, and Ruby SDK

97packages return a cached short-term key when the current key is valid and235providers resolve fresh AWS credentials and sign each request attempt with

98generate a new key when needed. They use the AWS credential chain, which can236SigV4. The chain can include credentials configured with `aws login`, shared

99include credentials configured with `aws login`.237profiles, workload roles, and instance or container credentials.

100 238 

101Install the token-generator package for your SDK:239Install optional dependencies for AWS credential-chain examples before using

240this path:

102 241 

103```shell242```shell

104npm install @aws/bedrock-token-generator243npm install @aws-sdk/credential-provider-node @smithy/hash-node @smithy/signature-v4

105pip install aws-bedrock-token-generator244pip install 'openai[bedrock]'

245go get github.com/openai/openai-go/v3/bedrock

246bundle add aws-sdk-core

106```247```

107 248 

108Send a request with refreshable Bedrock credentials249The .NET SDK doesn't currently expose an equivalent Bedrock provider or AWS

250SigV4 authentication policy. Use a Bedrock API key with .NET, or send a signed

251HTTP request through an AWS-supported client when your application requires the

252AWS credential chain.

109 253 

110```javascript254Send a request with AWS-managed Bedrock credentials

111import { getTokenProvider } from "@aws/bedrock-token-generator";

112import { BedrockOpenAI } from "openai";

113 255 

114const client = new BedrockOpenAI({256```javascript

115 awsRegion: "us-east-2",257import OpenAI from "openai";

116 bedrockTokenProvider: getTokenProvider(),258import { defaultProvider } from "@aws-sdk/credential-provider-node";

259import { bedrock } from "openai/providers/bedrock/aws";

260 

261const client = new OpenAI({

262 provider: bedrock({

263 region: "us-east-2",

264 endpoint: "mantle",

265 credentialProvider: defaultProvider(),

266 }),

117});267});

118 268 

119const response = await client.responses.create({269const response = await client.responses.create({


125```275```

126 276 

127```python277```python

128from aws_bedrock_token_generator import provide_token278from openai import OpenAI

129from openai import BedrockOpenAI279from openai.providers import bedrock

130 280 

131client = BedrockOpenAI(281client = OpenAI(

132 aws_region="us-east-2",282 provider=bedrock(

133 bedrock_token_provider=provide_token,283 region="us-east-2",

284 api_key=None,

285 )

134)286)

135 287 

136response = client.responses.create(288response = client.responses.create(


141print(response.output_text)293print(response.output_text)

142```294```

143 295 

296```go

297package main

298 

299import (

300 "context"

301 "fmt"

302 

303 "github.com/aws/aws-sdk-go-v2/config"

304 "github.com/openai/openai-go/v3"

305 "github.com/openai/openai-go/v3/bedrock"

306 "github.com/openai/openai-go/v3/responses"

307)

308 

309func main() {

310 awsConfig, err := config.LoadDefaultConfig(context.Background())

311 if err != nil {

312 panic(err)

313 }

314 

315 client, err := bedrock.NewClient(context.Background(), bedrock.Config{

316 AWSRegion: "us-east-2",

317 AWSCredentialsProvider: awsConfig.Credentials,

318 })

319 if err != nil {

320 panic(err)

321 }

322 

323 response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{

324 Model: "openai.gpt-5.6-sol",

325 Input: responses.ResponseNewParamsInputUnion{

326 OfString: openai.String("Write a haiku about cloud infrastructure."),

327 },

328 })

329 if err != nil {

330 panic(err)

331 }

332 fmt.Println(response.OutputText())

333}

334```

335 

336```java

337import com.openai.client.OpenAIClient;

338import com.openai.client.okhttp.BedrockOpenAIOkHttpClient;

339import com.openai.models.responses.ResponseCreateParams;

340import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;

341 

342public final class AmazonBedrockCreateResponseWithAwsCredentialsExample {

343 private AmazonBedrockCreateResponseWithAwsCredentialsExample() {}

344 

345 public static void main(String[] args) {

346 OpenAIClient client =

347 BedrockOpenAIOkHttpClient.builder()

348 .awsRegion("us-east-2")

349 .awsCredentialsProvider(DefaultCredentialsProvider.create())

350 .build();

351 

352 ResponseCreateParams params =

353 ResponseCreateParams.builder()

354 .model("openai.gpt-5.6-sol")

355 .input("Write a haiku about cloud infrastructure.")

356 .build();

357 

358 client.responses().create(params).output().stream()

359 .flatMap(item -> item.message().stream())

360 .flatMap(message -> message.content().stream())

361 .flatMap(content -> content.outputText().stream())

362 .forEach(text -> System.out.println(text.text()));

363 }

364}

365```

366 

367```ruby

368require "openai"

369 

370client = OpenAI::Client.new(

371 provider: OpenAI::Providers.bedrock(

372 region: "us-east-2",

373 base_url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",

374 api_key: nil

375 )

376)

377 

378response = client.responses.create(

379 model: "openai.gpt-5.6-sol",

380 input: "Write a haiku about cloud infrastructure."

381)

382 

383puts(response.output_text)

384```

385 

144 386 

145## Availability and operations387## Availability and operations

146 388 

Details

27Run a weather lookup in the background27Run a weather lookup in the background

28 28 

29```javascript29```javascript

30const model = process.env.OPENAI_MODEL ?? "gpt-6-astra";30import OpenAI from "openai";

31 31 

32const client = new OpenAI();

33const model = "gpt-6-astra";

34 

35/** @type {OpenAI.Responses.FunctionTool[]} */

32const tools = [36const tools = [

33 {37 {

34 type: "function",38 type: "function",


44 },48 },

45 },49 },

46];50];

47const apiKey = process.env.OPENAI_API_KEY;

48if (!apiKey) {

49 throw new Error("Set OPENAI_API_KEY before running this example.");

50}

51const baseURL = (

52 process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"

53).replace(/\/$/, "");

54 

55async function postResponse(body) {

56 const response = await fetch(`${baseURL}/responses`, {

57 method: "POST",

58 headers: {

59 Authorization: `Bearer ${apiKey}`,

60 "Content-Type": "application/json",

61 },

62 body: JSON.stringify(body),

63 });

64 if (!response.ok) {

65 throw new Error(

66 `Responses API ${response.status}: ${await response.text()}`

67 );

68 }

69 return response.json();

70}

71 

72async function getWeather(city) {51async function getWeather(city) {

73 const snapshots = {52 const snapshots = {

74 Paris: {53 Paris: {


87 "Start the weather lookup and answer the independent packing question " +66 "Start the weather lookup and answer the independent packing question " +

88 "without waiting. Use the demo weather result when it arrives; never invent it.";67 "without waiting. Use the demo weather result when it arrives; never invent it.";

89 68 

90let response = await postResponse({69let response = await client.responses.create({

91 model,70 model,

92 tools,71 tools,

93 instructions,72 instructions,


96 "list three essentials for any city trip.",75 "list three essentials for any city trip.",

97});76});

98 77 

99const call = response.output.find(78const call = response.output.find((item) => item.type === "function_call");

100 (item) => item.type === "function_call" && item.name === "get_weather"79if (!call || call.name !== "get_weather") {

101);

102if (!call) {

103 throw new Error("The response did not include a weather call.");80 throw new Error("The response did not include a weather call.");

104}81}

105const { city } = JSON.parse(call.arguments);82const { city } = JSON.parse(call.arguments);


115// Independent work or conversation turns can happen here.92// Independent work or conversation turns can happen here.

116// Update latestResponseId after each continuation.93// Update latestResponseId after each continuation.

117const result = await job;94const result = await job;

118response = await postResponse({95response = await client.responses.create({

119 model,96 model,

120 tools,97 tools,

121 instructions,98 instructions,


134 111 

135```python112```python

136import json113import json

137import os

138from concurrent.futures import ThreadPoolExecutor114from concurrent.futures import ThreadPoolExecutor

139from urllib.request import Request, urlopen

140 115 

141 116from openai import OpenAI

142def post_response(body):117from openai.types.responses import FunctionToolParam

143 base_url = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")

144 request = Request(

145 f"{base_url.rstrip('/')}/responses",

146 data=json.dumps(body).encode(),

147 headers={

148 "Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",

149 "Content-Type": "application/json",

150 },

151 method="POST",

152 )

153 with urlopen(request, timeout=120) as response:

154 return json.load(response)

155 118 

156 119 

157def get_weather(city):120def get_weather(city):


171 134 

172 135 

173def main():136def main():

174 model = os.environ.get("OPENAI_MODEL", "gpt-6-astra")137 client = OpenAI()

175 tools = [138 model = "gpt-6-astra"

139 tools: list[FunctionToolParam] = [

176 {140 {

177 "type": "function",141 "type": "function",

178 "name": "get_weather",142 "name": "get_weather",


193 "question without waiting. Use the actual tool result when it "157 "question without waiting. Use the actual tool result when it "

194 "arrives; never invent it. Identify the weather as demo data."158 "arrives; never invent it. Identify the weather as demo data."

195 )159 )

196 response = post_response(160 response = client.responses.create(

197 {161 model=model,

198 "model": model,162 tools=tools,

199 "tools": tools,163 instructions=instructions,

200 "instructions": instructions,164 input=(

201 "input": (

202 "Check the demo weather in Paris. Meanwhile, "165 "Check the demo weather in Paris. Meanwhile, "

203 "list three essentials for any city trip."166 "list three essentials for any city trip."

204 ),167 ),

205 }

206 )168 )

207 169 

208 call = next(item for item in response["output"] if item["type"] == "function_call")170 call = next(item for item in response.output if item.type == "function_call")

209 arguments = json.loads(call["arguments"])171 arguments = json.loads(call.arguments)

210 if call["name"] != "get_weather" or arguments != {"city": "Paris"}:172 if call.name != "get_weather" or arguments != {"city": "Paris"}:

211 raise ValueError("Expected a weather lookup for Paris")173 raise ValueError("Expected a weather lookup for Paris")

212 174 

213 latest_response_id = response["id"]175 latest_response_id = response.id

214 if call.get("async", False):176 if call.async_:

215 job = worker.submit(get_weather, **arguments)177 job = worker.submit(get_weather, **arguments)

216 print(response["output"])178 print(response.output_text)

217 # Independent work or conversation turns can happen here.179 # Independent work or conversation turns can happen here.

218 # Update latest_response_id after each continuation.180 # Update latest_response_id after each continuation.

219 result = job.result()181 result = job.result()

220 else:182 else:

221 result = get_weather(**arguments)183 result = get_weather(**arguments)

222 184 

223 response = post_response(185 response = client.responses.create(

224 {186 model=model,

225 "model": model,187 tools=tools,

226 "tools": tools,188 instructions=instructions,

227 "instructions": instructions,189 previous_response_id=latest_response_id,

228 "previous_response_id": latest_response_id,190 input=[

229 "input": [

230 {191 {

231 "type": "function_call_output",192 "type": "function_call_output",

232 "call_id": call["call_id"],193 "call_id": call.call_id,

233 "output": json.dumps(result),194 "output": json.dumps(result),

234 },195 },

235 ],196 ],

236 }

237 )197 )

238 print(response["output"])198 print(response.output_text)

239 199 

240 200 

241if __name__ == "__main__":201if __name__ == "__main__":


245 worker.shutdown(wait=True)205 worker.shutdown(wait=True)

246```206```

247 207 

208```go

209package main

210 

211import (

212 "context"

213 "encoding/json"

214 "fmt"

215 

216 "github.com/openai/openai-go/v3"

217 "github.com/openai/openai-go/v3/responses"

218)

219 

220type weatherArguments struct {

221 City string `json:"city"`

222}

223 

224type weatherSnapshot struct {

225 City string `json:"city"`

226 TemperatureC int `json:"temperature_c"`

227 Condition string `json:"condition"`

228 Source string `json:"source"`

229}

230 

231func getWeather(city string) weatherSnapshot {

232 // Demo data. Replace this function with your weather service.

233 if city != "Paris" {

234 panic("No demo weather snapshot for " + city)

235 }

236 return weatherSnapshot{

237 City: city, TemperatureC: 22, Condition: "Clear", Source: "demo weather snapshot",

238 }

239}

240 

241func main() {

242 client := openai.NewClient()

243 ctx := context.Background()

244 tool := responses.ToolParamOfFunction("get_weather", map[string]any{

245 "type": "object",

246 "properties": map[string]any{"city": map[string]string{"type": "string"}},

247 "required": []string{"city"},

248 "additionalProperties": false,

249 }, true)

250 tool.OfFunction.Description = openai.String("Read the demo weather snapshot for a city.")

251 tool.OfFunction.Async = openai.Bool(true)

252 tools := []responses.ToolUnionParam{tool}

253 instructions := "Start the weather lookup and answer the independent packing question " +

254 "without waiting. Use the actual tool result when it arrives; never invent it. " +

255 "Identify the weather as demo data."

256 response, err := client.Responses.New(ctx, responses.ResponseNewParams{

257 Model: "gpt-6-astra",

258 Tools: tools,

259 Instructions: openai.String(instructions),

260 Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Check the demo weather in Paris. Meanwhile, list three essentials for any city trip.")},

261 })

262 if err != nil {

263 panic(err)

264 }

265 var call responses.ResponseFunctionToolCall

266 for _, item := range response.Output {

267 if item.Type == "function_call" && item.AsFunctionCall().Name == "get_weather" {

268 call = item.AsFunctionCall()

269 break

270 }

271 }

272 if call.CallID == "" {

273 panic("The response did not include a weather call.")

274 }

275 var arguments weatherArguments

276 if err := json.Unmarshal([]byte(call.Arguments), &arguments); err != nil {

277 panic(err)

278 }

279 latestResponseID := response.ID

280 var result weatherSnapshot

281 if call.Async {

282 job := make(chan weatherSnapshot, 1)

283 go func() { job <- getWeather(arguments.City) }()

284 fmt.Println(response.OutputText())

285 // Independent work or conversation turns can happen here.

286 // Update latestResponseID after each continuation.

287 result = <-job

288 } else {

289 result = getWeather(arguments.City)

290 }

291 output, err := json.Marshal(result)

292 if err != nil {

293 panic(err)

294 }

295 functionOutput := responses.ResponseInputItemParamOfFunctionCallOutput(string(output))

296 functionOutput.OfFunctionCallOutput.CallID = openai.String(call.CallID)

297 response, err = client.Responses.New(ctx, responses.ResponseNewParams{

298 Model: "gpt-6-astra",

299 Tools: tools,

300 Instructions: openai.String(instructions),

301 PreviousResponseID: openai.String(latestResponseID),

302 Input: responses.ResponseNewParamsInputUnion{OfInputItemList: responses.ResponseInputParam{functionOutput}},

303 })

304 if err != nil {

305 panic(err)

306 }

307 fmt.Println(response.OutputText())

308}

309```

310 

311```java

312import com.fasterxml.jackson.annotation.JsonProperty;

313import com.fasterxml.jackson.databind.ObjectMapper;

314import com.openai.core.JsonValue;

315import com.openai.models.responses.FunctionTool;

316import com.openai.models.responses.Response;

317import com.openai.models.responses.ResponseCreateParams;

318import com.openai.models.responses.ResponseFunctionToolCall;

319import com.openai.models.responses.ResponseInputItem;

320import java.util.List;

321import java.util.Map;

322import java.util.concurrent.CompletableFuture;

323 

324record WeatherArguments(String city) {}

325 

326record WeatherSnapshot(

327 String city,

328 @JsonProperty("temperature_c") int temperatureC,

329 String condition,

330 String source) {}

331 

332static WeatherSnapshot getWeather(String city) {

333 // Demo data. Replace this function with your weather service.

334 if (!city.equals("Paris")) {

335 throw new IllegalArgumentException("No demo weather snapshot for " + city);

336 }

337 return new WeatherSnapshot(city, 22, "Clear", "demo weather snapshot");

338}

339 

340FunctionTool tool =

341 FunctionTool.builder()

342 .name("get_weather")

343 .description("Read the demo weather snapshot for a city.")

344 .async(true)

345 .strict(true)

346 .parameters(

347 FunctionTool.Parameters.builder()

348 .putAdditionalProperty("type", JsonValue.from("object"))

349 .putAdditionalProperty(

350 "properties", JsonValue.from(Map.of("city", Map.of("type", "string"))))

351 .putAdditionalProperty("required", JsonValue.from(List.of("city")))

352 .putAdditionalProperty("additionalProperties", JsonValue.from(false))

353 .build())

354 .build();

355String instructions =

356 "Start the weather lookup and answer the independent packing question without waiting. Use the actual tool result when it arrives; never invent it. Identify the weather as demo data.";

357Response response =

358 client

359 .responses()

360 .create(

361 ResponseCreateParams.builder()

362 .model("gpt-6-astra")

363 .addTool(tool)

364 .instructions(instructions)

365 .input(

366 "Check the demo weather in Paris. Meanwhile, list three essentials for any city trip.")

367 .build());

368ResponseFunctionToolCall call =

369 response.output().stream()

370 .flatMap(item -> item.functionCall().stream())

371 .filter(item -> item.name().equals("get_weather"))

372 .findFirst()

373 .orElseThrow(

374 () -> new IllegalStateException("The response did not include a weather call."));

375WeatherArguments arguments = call.arguments(WeatherArguments.class);

376String latestResponseId = response.id();

377WeatherSnapshot result;

378if (call.async().orElse(false)) {

379 CompletableFuture<WeatherSnapshot> job =

380 CompletableFuture.supplyAsync(() -> getWeather(arguments.city()));

381 System.out.println(response.output());

382 // Independent work or conversation turns can happen here.

383 // Update latestResponseId after each continuation.

384 result = job.join();

385} else {

386 result = getWeather(arguments.city());

387}

388response =

389 client

390 .responses()

391 .create(

392 ResponseCreateParams.builder()

393 .model("gpt-6-astra")

394 .addTool(tool)

395 .instructions(instructions)

396 .previousResponseId(latestResponseId)

397 .inputOfResponse(

398 List.of(

399 ResponseInputItem.ofFunctionCallOutput(

400 ResponseInputItem.FunctionCallOutput.builder()

401 .callId(call.callId())

402 .output(new ObjectMapper().writeValueAsString(result))

403 .build())))

404 .build());

405response.output().stream()

406 .flatMap(item -> item.message().stream())

407 .flatMap(message -> message.content().stream())

408 .flatMap(content -> content.outputText().stream())

409 .forEach(text -> System.out.println(text.text()));

410```

411 

412```ruby

413require "json"

414require "openai"

415 

416def get_weather(city)

417 # Demo data. Replace this function with your weather service.

418 raise "No demo weather snapshot for #{city}" unless city == "Paris"

419 

420 {city: city, temperature_c: 22, condition: "Clear", source: "demo weather snapshot"}

421end

422 

423client = OpenAI::Client.new

424tools = [OpenAI::Models::Responses::FunctionTool.new(

425 name: "get_weather",

426 description: "Read the demo weather snapshot for a city.",

427 async: true,

428 strict: true,

429 parameters: {

430 type: "object",

431 properties: {city: {type: "string"}},

432 required: ["city"],

433 additionalProperties: false

434 }

435)]

436instructions = "Start the weather lookup and answer the independent packing question " \

437 "without waiting. Use the actual tool result when it arrives; never invent it. " \

438 "Identify the weather as demo data."

439response = client.responses.create(

440 model: "gpt-6-astra",

441 tools: tools,

442 instructions: instructions,

443 input: "Check the demo weather in Paris. Meanwhile, list three essentials for any city trip."

444)

445call = response.output.find do |item|

446 item.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall) && item.name == "get_weather"

447end

448unless call.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall)

449 raise "The response did not include a weather call."

450end

451city = JSON.parse(call.arguments).fetch("city")

452latest_response_id = response.id

453result = if call.async

454 job = Thread.new { get_weather(city) }

455 puts(response.output_text)

456 # Independent work or conversation turns can happen here.

457 # Update latest_response_id after each continuation.

458 job.value

459else

460 get_weather(city)

461end

462response = client.responses.create(

463 model: "gpt-6-astra",

464 tools: tools,

465 instructions: instructions,

466 previous_response_id: latest_response_id,

467 input: [OpenAI::Models::Responses::ResponseInputItem::FunctionCallOutput.new(

468 call_id: call.call_id,

469 output: JSON.generate(result)

470 )]

471)

472puts(response.output_text)

473```

474 

248 475 

249The response can contain both the async call and an answer. If other conversation turns happen before the job finishes, update `latest_response_id` to continue from the latest response while keeping the original tool `call_id`.476The response can contain both the async call and an answer. If other conversation turns happen before the job finishes, update `latest_response_id` to continue from the latest response while keeping the original tool `call_id`.

250 477 

Details

413 return clean_text413 return clean_text

414```414```

415 415 

416```ruby

417CITATION_START = "\u{E200}"

418CITATION_DELIMITER = "\u{E202}"

419CITATION_STOP = "\u{E201}"

420 

421SOURCE_ID_RE = /\A[A-Za-z0-9_-]+\z/

422LINE_LOCATOR_RE = /\AL\d+(?:-L\d+)?\z/

423 

424def extract_citations(text, families: ["cite"])

425 return [] if families.empty?

426 

427 family_pattern = families.map { |family| Regexp.escape(family) }.join("|")

428 token_re = Regexp.new(

429 "#{Regexp.escape(CITATION_START)}" \

430 "(?<family>#{family_pattern})" \

431 "#{Regexp.escape(CITATION_DELIMITER)}" \

432 "(?<body>.*?)" \

433 "#{Regexp.escape(CITATION_STOP)}",

434 Regexp::MULTILINE

435 )

436 

437 text.enum_for(:scan, token_re).map do

438 match = Regexp.last_match

439 next unless match

440 

441 body = match[:body]

442 next unless body

443 

444 parts = body.split(CITATION_DELIMITER).map(&:strip).reject(&:empty?)

445 locator = parts.pop if parts.last&.match?(LINE_LOCATOR_RE)

446 next if parts.empty? || parts.any? { |part| !part.match?(SOURCE_ID_RE) }

447 

448 {

449 raw: match[0],

450 family: match[:family],

451 source_ids: parts,

452 locator: locator,

453 start: match.begin(0),

454 end: match.end(0)

455 }

456 end.compact

457end

458 

459def strip_citations(text, citations)

460 citations.sort_by { |citation| -citation.fetch(:start) }.each_with_object(text.dup) do |citation, clean|

461 clean[citation.fetch(:start)...citation.fetch(:end)] = ""

462 end

463end

464```

465 

416 466 

417 467 

418 468 

Details

1696Note: WebSocket mode works with ZDR because your data is not stored to disk,1696Note: WebSocket mode works with ZDR because your data is not stored to disk,

1697only stored in memory.1697only stored in memory.

1698 1698 

1699The default Python sample uses `websocket-client` (`pip install1699The Python sample uses `pip install "openai[realtime]>=3.8.0"`.

1700websocket-client`). The JavaScript sample uses `ws` (`npm install ws`).1700The JavaScript sample uses `npm install openai@^7.10.0 ws`.

1701 1701 

1702Start a Responses API WebSocket session1702Start a Responses API WebSocket session

1703 1703 

1704```javascript1704```javascript

1705import OpenAI from "openai";1705import OpenAI from "openai";

1706import WebSocket from "ws";1706import { ResponsesWS } from "openai/resources/responses/ws";

1707 1707 

1708const openai = new OpenAI();1708const openai = new OpenAI();

1709 1709 

1710const ws = new WebSocket("wss://api.openai.com/v1/responses", {1710const ws = new ResponsesWS(openai);

1711 headers: {1711 

1712 Authorization: "Bearer " + openai.apiKey,1712ws.on("event", (event) => {

1713 },1713 console.log(event.type);

1714 if (

1715 event.type === "response.completed" ||

1716 event.type === "response.failed" ||

1717 event.type === "response.incomplete"

1718 ) {

1719 ws.close();

1720 }

1721});

1722ws.on("error", (error) => {

1723 console.error(error);

1724 ws.close();

1714});1725});

1715 1726 

1716ws.on("open", () => {1727ws.send({

1717 ws.send(

1718 JSON.stringify({

1719 type: "response.create",1728 type: "response.create",

1720 model: "gpt-6-astra",1729 model: "gpt-6-astra",

1721 store: false,1730 store: false,


1734 },1743 },

1735 ],1744 ],

1736 tools: [testLogTool, codeSearchTool],1745 tools: [testLogTool, codeSearchTool],

1737 })

1738 );

1739});

1740 

1741ws.on("message", (data) => {

1742 const firstEvent = JSON.parse(data.toString());

1743 console.log(firstEvent.type);

1744});1746});

1745```1747```

1746 1748 

1747```python1749```python

1748from openai import OpenAI1750from openai import OpenAI

1749from websocket import create_connection

1750import json

1751 1751 

1752client = OpenAI()1752client = OpenAI()

1753 1753 

1754ws = create_connection(1754with client.responses.connect() as connection:

1755 "wss://api.openai.com/v1/responses",1755 # Use the same typed parameters as client.responses.create(...).

1756 header=[f"Authorization: Bearer {client.api_key}"],1756 connection.response.create(

1757)1757 model="gpt-6-astra",

1758 1758 store=False,

1759# Same request body you would send to client.responses.create(...).1759 input=[

1760ws.send(

1761 json.dumps(

1762 {

1763 "type": "response.create",

1764 "model": "gpt-6-astra",

1765 "store": False,

1766 "input": [

1767 {1760 {

1768 "type": "message",1761 "type": "message",

1769 "role": "user",1762 "role": "user",


1779 ],1772 ],

1780 }1773 }

1781 ],1774 ],

1782 "tools": [test_log_tool, code_search_tool],1775 tools=[test_log_tool, code_search_tool],

1783 }

1784 )1776 )

1785)1777 first_event = connection.recv()

1786 1778 print(first_event.type)

1787first_event = json.loads(ws.recv())

1788print(first_event["type"])

1789```1779```

1790 1780 

1791 1781 

Details

9 9 

10> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.10> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

11 11 

12GPT‑6 Astra is rolling out today for enterprises in our [Trusted Access Program⁠](https://openai.com/form/enterprise-trusted-access-for-cyber/), with access through API and our Plus, Pro, Business and Enterprise plans coming in the coming days.

13 

14## Introduction12## Introduction

15 13 

16GPT-6 Astra is our most intelligent model yet, with state-of-the-art performance in computer use, browsing, software engineering, science, and professional work. It excels at carrying out multistep workflows across code, browsers, and professional software. In [several evaluations](https://openai.com/index/gpt-6-astra/), Astra achieves stronger results while using substantially fewer output tokens—delivering a lower estimated API cost per task than earlier models despite its higher per-token pricing.14GPT-6 Astra is our most intelligent model yet, with state-of-the-art performance in computer use, browsing, software engineering, science, and professional work. It excels at carrying out multistep workflows across code, browsers, and professional software. In [several evaluations](https://openai.com/index/gpt-6-astra/), Astra achieves stronger results while using substantially fewer output tokens—delivering a lower estimated API cost per task than earlier models despite its higher per-token pricing.

Details

9 9 

10> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.10> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

11 11 

12GPT‑6 Astra is rolling out today for enterprises in our [Trusted Access Program⁠](https://openai.com/form/enterprise-trusted-access-for-cyber/), with access through API and our Plus, Pro, Business and Enterprise plans coming in the coming days.

13 

14## Introduction12## Introduction

15 13 

16GPT-6 Astra is our most intelligent model yet, with state-of-the-art performance in computer use, browsing, software engineering, science, and professional work. It excels at carrying out multistep workflows across code, browsers, and professional software. In [several evaluations](https://openai.com/index/gpt-6-astra/), Astra achieves stronger results while using substantially fewer output tokens—delivering a lower estimated API cost per task than earlier models despite its higher per-token pricing.14GPT-6 Astra is our most intelligent model yet, with state-of-the-art performance in computer use, browsing, software engineering, science, and professional work. It excels at carrying out multistep workflows across code, browsers, and professional software. In [several evaluations](https://openai.com/index/gpt-6-astra/), Astra achieves stronger results while using substantially fewer output tokens—delivering a lower estimated API cost per task than earlier models despite its higher per-token pricing.

Details

377String promptCacheKey = promptVersion + ":" + tenantId + ":shard-" + shard;377String promptCacheKey = promptVersion + ":" + tenantId + ":shard-" + shard;

378```378```

379 379 

380```ruby

381require "digest"

382 

383tenant_id = "acme"

384session_id = "session-42"

385prompt_version = "support-v3"

386# Tune for peak traffic per tenant and reusable prompt group; monitor cache hits.

387shard_count = 16

388 

389digest = Digest::SHA256.hexdigest("#{tenant_id}:#{session_id}")

390shard = digest.slice(0, 8).to_s.to_i(16) % shard_count

391prompt_cache_key = "#{prompt_version}:#{tenant_id}:shard-#{shard}"

392```

393 

380 394 

381 395 

382 396 


489 return input_cost503 return input_cost

490```504```

491 505 

506```ruby

507def calculate_input_cost(

508 usage,

509 input_price_per_million,

510 cache_input_multiplier = 0.1,

511 cache_write_multiplier = 1.25

512)

513 input_tokens = usage.input_tokens

514 details = usage.input_tokens_details

515 cached_tokens = details.cached_tokens

516 cache_write_tokens = details.cache_write_tokens

517 ordinary_input_tokens = input_tokens - cached_tokens - cache_write_tokens

518 

519 weighted_input_tokens =

520 ordinary_input_tokens +

521 (cached_tokens * cache_input_multiplier) +

522 (cache_write_tokens * cache_write_multiplier)

523 (weighted_input_tokens * input_price_per_million) / 1_000_000

524end

525```

526 

492 527 

493 528 

494 529 

Details

960 960 

961For example, if the conversation starts with request-level effort `low`, this update selects `high` for the next response and subsequent responses until another update overrides it.961For example, if the conversation starts with request-level effort `low`, this update selects `high` for the next response and subsequent responses until another update overrides it.

962 962 

963Increase reasoning effort for a follow-up

964 

965```javascript

966import OpenAI from "openai";

967 

968const client = new OpenAI();

969const model = "gpt-6-astra";

970 

971const first = await client.responses.create({

972 model,

973 reasoning: { effort: "low" },

974 input: "Draft a database migration plan.",

975});

976 

977const next = await client.responses.create({

978 model,

979 reasoning: { effort: "low" },

980 previous_response_id: first.id,

981 input: [

982 { type: "configuration_update", reasoning: { effort: "high" } },

983 {

984 role: "user",

985 content: "Analyze the failure modes and propose rollback steps.",

986 },

987 ],

988});

989console.log(next.output_text);

990```

991 

992```python

993from openai import OpenAI

994 

995client = OpenAI()

996model = "gpt-6-astra"

997 

998response = client.responses.create(

999 model=model,

1000 reasoning={"effort": "low"},

1001 input="Draft a database migration plan.",

1002 store=True,

1003)

1004print(response.output_text)

1005 

1006response = client.responses.create(

1007 model=model,

1008 previous_response_id=response.id,

1009 reasoning={"effort": "low"},

1010 input=[

1011 {

1012 "type": "configuration_update",

1013 "reasoning": {"effort": "high"},

1014 },

1015 {

1016 "role": "user",

1017 "content": "Analyze the failure modes and propose rollback steps.",

1018 },

1019 ],

1020 store=True,

1021)

1022print(response.output_text)

1023```

1024 

1025```go

1026package main

1027 

1028import (

1029 "context"

1030 "fmt"

1031 

1032 "github.com/openai/openai-go/v3"

1033 "github.com/openai/openai-go/v3/responses"

1034 "github.com/openai/openai-go/v3/shared"

1035)

1036 

1037func main() {

1038 client := openai.NewClient()

1039 ctx := context.Background()

1040 first, err := client.Responses.New(ctx, responses.ResponseNewParams{

1041 Store: openai.Bool(true),

1042 Model: "gpt-6-astra",

1043 Reasoning: shared.ReasoningParam{Effort: shared.ReasoningEffortLow},

1044 Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Draft a database migration plan.")},

1045 })

1046 if err != nil {

1047 panic(err)

1048 }

1049 fmt.Println(first.OutputText())

1050 response, err := client.Responses.New(ctx, responses.ResponseNewParams{

1051 Model: "gpt-6-astra",

1052 PreviousResponseID: openai.String(first.ID),

1053 // Keep the original request-level setting; the item updates the conversation.

1054 Reasoning: shared.ReasoningParam{Effort: shared.ReasoningEffortLow},

1055 Input: responses.ResponseNewParamsInputUnion{OfInputItemList: responses.ResponseInputParam{

1056 {OfConfigurationUpdate: &responses.ResponseConfigurationUpdateItemParam{

1057 Reasoning: responses.ResponseConfigurationUpdateItemParamReasoning{Effort: shared.ReasoningEffortHigh},

1058 }},

1059 responses.ResponseInputItemParamOfMessage("Analyze the failure modes and propose rollback steps.", responses.EasyInputMessageRoleUser),

1060 }},

1061 })

1062 if err != nil {

1063 panic(err)

1064 }

1065 fmt.Println(response.OutputText())

1066}

1067```

1068 

1069```java

1070import com.openai.models.Reasoning;

1071import com.openai.models.ReasoningEffort;

1072import com.openai.models.responses.EasyInputMessage;

1073import com.openai.models.responses.Response;

1074import com.openai.models.responses.ResponseConfigurationUpdateItemParam;

1075import com.openai.models.responses.ResponseCreateParams;

1076import com.openai.models.responses.ResponseInputItem;

1077import java.util.List;

1078 

1079Response first =

1080 client

1081 .responses()

1082 .create(

1083 ResponseCreateParams.builder()

1084 .model("gpt-6-astra")

1085 .store(true)

1086 .reasoning(Reasoning.builder().effort(ReasoningEffort.LOW).build())

1087 .input("Draft a database migration plan.")

1088 .build());

1089Response response =

1090 client

1091 .responses()

1092 .create(

1093 ResponseCreateParams.builder()

1094 .model("gpt-6-astra")

1095 .previousResponseId(first.id())

1096 // Keep the original request-level setting; the item updates the conversation.

1097 .reasoning(Reasoning.builder().effort(ReasoningEffort.LOW).build())

1098 .inputOfResponse(

1099 List.of(

1100 ResponseInputItem.ofConfigurationUpdate(

1101 ResponseConfigurationUpdateItemParam.builder()

1102 .reasoning(

1103 ResponseConfigurationUpdateItemParam.Reasoning.builder()

1104 .effort(ReasoningEffort.HIGH)

1105 .build())

1106 .build()),

1107 ResponseInputItem.ofEasyInputMessage(

1108 EasyInputMessage.builder()

1109 .role(EasyInputMessage.Role.USER)

1110 .content(

1111 "Analyze the failure modes and propose rollback steps.")

1112 .build())))

1113 .build());

1114response.output().stream()

1115 .flatMap(item -> item.message().stream())

1116 .flatMap(message -> message.content().stream())

1117 .flatMap(content -> content.outputText().stream())

1118 .forEach(text -> System.out.println(text.text()));

1119```

1120 

1121```ruby

1122require "openai"

1123 

1124client = OpenAI::Client.new

1125first = client.responses.create(

1126 model: "gpt-6-astra",

1127 store: true,

1128 reasoning: OpenAI::Models::Reasoning.new(effort: :low),

1129 input: "Draft a database migration plan."

1130)

1131puts(first.output_text)

1132response = client.responses.create(

1133 model: "gpt-6-astra",

1134 previous_response_id: first.id,

1135 # Keep the original request-level setting; the item updates the conversation.

1136 reasoning: OpenAI::Models::Reasoning.new(effort: :low),

1137 input: [

1138 OpenAI::Models::Responses::ResponseConfigurationUpdateItemParam.new(

1139 reasoning: OpenAI::Models::Responses::ResponseConfigurationUpdateItemParam::Reasoning.new(

1140 effort: :high

1141 )

1142 ),

1143 OpenAI::Models::Responses::EasyInputMessage.new(

1144 role: :user,

1145 content: "Analyze the failure modes and propose rollback steps."

1146 )

1147 ]

1148)

1149puts(response.output_text)

1150```

1151 

1152 

963Preserve updates with `previous_response_id`, or replay them in their original positions when [managing conversation history manually](https://developers.openai.com/api/docs/guides/conversation-state#manually-manage-conversation-state). The response's `reasoning.effort` continues to report the request-level setting, not the effort selected by the update.1153Preserve updates with `previous_response_id`, or replay them in their original positions when [managing conversation history manually](https://developers.openai.com/api/docs/guides/conversation-state#manually-manage-conversation-state). The response's `reasoning.effort` continues to report the request-level setting, not the effort selected by the update.

964 1154 

965Do not place two `configuration_update` items directly next to each other in the conversation history; the API rejects adjacent updates.1155Do not place two `configuration_update` items directly next to each other in the conversation history; the API rejects adjacent updates.

Details

61 -H "Authorization: Bearer ${OPENAI_API_KEY}"61 -H "Authorization: Bearer ${OPENAI_API_KEY}"

62```62```

63 63 

64Retrieve a project safety alert

65 

66```javascript

67import OpenAI from "openai";

68 

69const client = new OpenAI();

70const alertId = process.env.SAFETY_ALERT_ID;

71if (!alertId) throw new Error("Set SAFETY_ALERT_ID.");

72 

73const alert = await client.safety.alerts.retrieve(alertId);

74console.log(alert.error_type, alert.reason, alert.response_id);

75```

76 

77```python

78import os

79 

80from openai import OpenAI

81 

82client = OpenAI()

83alert = client.safety.alerts.retrieve(os.environ["SAFETY_ALERT_ID"])

84print(alert.error_type, alert.reason)

85```

86 

87```go

88package main

89 

90import (

91 "context"

92 "fmt"

93 "os"

94 

95 "github.com/openai/openai-go/v3"

96)

97 

98func main() {

99 client := openai.NewClient()

100 alert, err := client.Safety.Alerts.Get(context.Background(), os.Getenv("SAFETY_ALERT_ID"))

101 if err != nil {

102 panic(err)

103 }

104 fmt.Println(alert.ErrorType)

105 fmt.Println(alert.Reason)

106 fmt.Println(alert.RequestPaused)

107}

108```

109 

110```java

111import com.openai.models.safety.alerts.SafetyAlert;

112 

113SafetyAlert alert = client.safety().alerts().retrieve(System.getenv("SAFETY_ALERT_ID"));

114System.out.println(alert.errorType());

115alert.reason().ifPresent(System.out::println);

116System.out.println(alert.requestPaused());

117```

118 

119```ruby

120require "openai"

121 

122client = OpenAI::Client.new

123alert = client.safety.alerts.retrieve(ENV.fetch("SAFETY_ALERT_ID"))

124puts(alert.error_type)

125puts(alert.reason)

126puts(alert.request_paused)

127```

128 

129 

64Use the returned `request_id` and `response_id` to find the affected work in your application records. Treat the alert category as a concern to investigate. When `request_paused` is `true`, registering a safety block succeeded; this does not confirm that execution stopped or that earlier actions were reversed. Check your application's task state and tool records.130Use the returned `request_id` and `response_id` to find the affected work in your application records. Treat the alert category as a concern to investigate. When `request_paused` is `true`, registering a safety block succeeded; this does not confirm that execution stopped or that earlier actions were reversed. Check your application's task state and tool records.

65 131 

66The alert's `reason` can be `null`, including for Zero Data Retention (ZDR) requests. A non-null `reason` is a category description, not a transcript or full investigation report. Keep the records you need under your organization's data policies. See [Your data](https://developers.openai.com/api/docs/guides/your-data) for API data controls.132The alert's `reason` can be `null`, including for Zero Data Retention (ZDR) requests. A non-null `reason` is a category description, not a transcript or full investigation report. Keep the records you need under your organization's data policies. See [Your data](https://developers.openai.com/api/docs/guides/your-data) for API data controls.

guides/steering.md +51 −220

Details

53 53 

54```javascript54```javascript

55// Set OPENAI_API_KEY before running this example.55// Set OPENAI_API_KEY before running this example.

56// Install the WebSocket client: npm install ws56// Install the SDK and WebSocket transport: npm install openai ws

57 57 

58import WebSocket from "ws";58import OpenAI from "openai";

59import { ResponsesWS } from "openai/resources/responses/ws";

59 60 

60const ws = new WebSocket("wss://api.openai.com/v1/responses", {61const client = new OpenAI();

61 headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },62const ws = new ResponsesWS(client, {

62 handshakeTimeout: 10_000,63 handshakeTimeout: 10_000,

63});64});

64let initialResponseId = "";65let initialResponseId = "";


69 const output = await new Promise((resolve, reject) => {70 const output = await new Promise((resolve, reject) => {

70 timeout = setTimeout(() => {71 timeout = setTimeout(() => {

71 reject(new Error("Timed out waiting for the steered response."));72 reject(new Error("Timed out waiting for the steered response."));

72 ws.terminate();73 ws.close();

73 }, 120_000);74 }, 120_000);

74 ws.once("error", reject);75 ws.once("error", reject);

75 ws.once("close", () => {76 ws.once("close", () => {


77 new Error("Connection closed before the steered response finished.")78 new Error("Connection closed before the steered response finished.")

78 );79 );

79 });80 });

80 ws.once("open", () => {81 ws.on("event", (event) => {

81 ws.send(

82 JSON.stringify({

83 type: "response.create",

84 model: "gpt-6-astra",

85 reasoning: { effort: "medium" },

86 input: "Draft a project plan for building a task-tracking app.",

87 })

88 );

89 });

90 ws.on("message", (data) => {

91 try {82 try {

92 const event = JSON.parse(data.toString());

93 if (event.type === "response.created") {83 if (event.type === "response.created") {

94 if (!initialResponseId) {84 if (!initialResponseId) {

95 initialResponseId = event.response.id;85 initialResponseId = event.response.id;

96 // Simulate a user adding instructions while the response runs.86 // Simulate a user adding instructions while the response runs.

97 ws.send(87 ws.send({

98 JSON.stringify({

99 type: "response.steer",88 type: "response.steer",

100 previous_response_id: initialResponseId,89 previous_response_id: initialResponseId,

101 input:90 input:

102 "Keep the scope small enough for one developer to finish in two weeks.",91 "Keep the scope small enough for one developer to finish in two weeks.",

103 })92 });

104 );

105 } else {93 } else {

106 successorResponseId = event.response.id;94 successorResponseId = event.response.id;

107 }95 }


135 reject(error);123 reject(error);

136 }124 }

137 });125 });

126 ws.send({

127 type: "response.create",

128 model: "gpt-6-astra",

129 reasoning: { effort: "medium" },

130 input: "Draft a project plan for building a task-tracking app.",

131 });

138 });132 });

139 console.log(output);133 console.log(output);

140} finally {134} finally {


144```138```

145 139 

146```python140```python

147# Set OPENAI_API_KEY before running this example.141import asyncio

148# Install the WebSocket client: pip install websocket-client142 

149 143from openai import AsyncOpenAI

150import json144 

151import os145 

152import time146async def main():

153 147 client = AsyncOpenAI()

154from websocket import create_connection148 initial_response_id = None

155 149 successor_response_id = None

156ws = create_connection(150 

157 "wss://api.openai.com/v1/responses",151 async with client.responses.connect() as connection, asyncio.timeout(120):

158 header=[f"Authorization: Bearer {os.environ['OPENAI_API_KEY']}"],152 await connection.response.create(

159 timeout=10,153 model="gpt-6-astra",

160)154 reasoning={"effort": "medium"},

161initial_response_id = None155 input="Draft a project plan for building a task-tracking app.",

162successor_response_id = None

163deadline = time.monotonic() + 120

164 

165try:

166 ws.send(

167 json.dumps(

168 {

169 "type": "response.create",

170 "model": "gpt-6-astra",

171 "reasoning": {"effort": "medium"},

172 "input": "Draft a project plan for building a task-tracking app.",

173 }

174 )

175 )

176 while True:

177 remaining = deadline - time.monotonic()

178 if remaining <= 0:

179 raise TimeoutError("Timed out waiting for the steered response.")

180 ws.settimeout(remaining)

181 message = ws.recv()

182 if not message:

183 raise RuntimeError(

184 "Connection closed before the steered response finished."

185 )156 )

186 event = json.loads(message)157 async for event in connection:

187 if event["type"] == "response.created":158 if event.type == "response.created":

188 if initial_response_id is None:159 if initial_response_id is None:

189 initial_response_id = event["response"]["id"]160 initial_response_id = event.response.id

190 # Simulate a user adding instructions while the response runs.161 # Simulate a user adding instructions while the response runs.

191 ws.send(162 await connection.response.steer(

192 json.dumps(163 previous_response_id=initial_response_id,

193 {164 input="Keep the scope small enough for one developer to finish in two weeks.",

194 "type": "response.steer",

195 "previous_response_id": initial_response_id,

196 "input": "Keep the scope small enough for one developer to finish in two weeks.",

197 }

198 )

199 )165 )

200 else:166 else:

201 successor_response_id = event["response"]["id"]167 successor_response_id = event.response.id

202 elif event["type"] in {"response.steer.failed", "response.failed", "error"}:168 elif event.type in {"response.steer.failed", "response.failed", "error"}:

203 raise RuntimeError(json.dumps(event))169 raise RuntimeError(event.to_json())

204 elif event["type"] == "response.incomplete":170 elif event.type == "response.incomplete":

205 response = event["response"]171 response = event.response

206 if (172 if (

207 response["id"] != initial_response_id173 response.id != initial_response_id

208 or response.get("incomplete_details", {}).get("reason") != "steered"174 or response.incomplete_details is None

175 or response.incomplete_details.reason != "steered"

209 ):176 ):

210 raise RuntimeError(json.dumps(event))177 raise RuntimeError(event.to_json())

211 elif (178 elif (

212 event["type"] == "response.completed"179 event.type == "response.completed"

213 and event["response"]["id"] == successor_response_id180 and event.response.id == successor_response_id

214 ):181 ):

215 print(182 print(event.response.output_text)

216 "".join(183 return

217 part["text"]

218 for item in event["response"]["output"]

219 if item["type"] == "message"

220 for part in item["content"]

221 if part["type"] == "output_text"

222 )

223 )

224 break

225 # Acceptance only queues the input. Keep reading past the first response.184 # Acceptance only queues the input. Keep reading past the first response.

226finally:185 raise RuntimeError("Connection closed before the steered response finished.")

227 ws.close()

228```

229 

230```java

231// Set OPENAI_API_KEY before running this example.

232// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

233 

234import com.fasterxml.jackson.databind.JsonNode;

235import com.fasterxml.jackson.databind.ObjectMapper;

236import java.io.IOException;

237import java.net.URI;

238import java.net.http.HttpClient;

239import java.net.http.WebSocket;

240import java.time.Duration;

241import java.util.Map;

242import java.util.concurrent.CompletableFuture;

243import java.util.concurrent.CompletionStage;

244import java.util.concurrent.LinkedBlockingQueue;

245import java.util.concurrent.TimeUnit;

246import java.util.concurrent.TimeoutException;

247 

248ObjectMapper json = new ObjectMapper();

249var events = new LinkedBlockingQueue<Object>();

250WebSocket.Listener listener =

251 new WebSocket.Listener() {

252 private final StringBuilder fragments = new StringBuilder();

253 

254 @Override

255 public void onOpen(WebSocket socket) {

256 socket.request(1);

257 }

258 186 

259 @Override

260 public CompletionStage<?> onText(WebSocket socket, CharSequence data, boolean last) {

261 fragments.append(data);

262 if (last) {

263 events.offer(fragments.toString());

264 fragments.setLength(0);

265 }

266 socket.request(1);

267 return CompletableFuture.completedFuture(null);

268 }

269 

270 @Override

271 public void onError(WebSocket socket, Throwable error) {

272 events.offer(error);

273 }

274 

275 @Override

276 public CompletionStage<?> onClose(WebSocket socket, int code, String reason) {

277 events.offer(

278 new IOException("Connection closed before the steered response finished."));

279 return CompletableFuture.completedFuture(null);

280 }

281 };

282String baseUrl = System.getenv().getOrDefault("OPENAI_BASE_URL", "https://api.openai.com/v1/");

283if (!baseUrl.endsWith("/")) baseUrl += "/";

284URI endpoint = URI.create(baseUrl.replaceFirst("^http", "ws")).resolve("responses");

285WebSocket ws =

286 HttpClient.newHttpClient()

287 .newWebSocketBuilder()

288 .header("Authorization", "Bearer " + System.getenv("OPENAI_API_KEY"))

289 .connectTimeout(Duration.ofSeconds(10))

290 .buildAsync(endpoint, listener)

291 .get(10, TimeUnit.SECONDS);

292String initialResponseId = null;

293String successorResponseId = null;

294long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(120);

295 187 

296try {188asyncio.run(main())

297 ws.sendText(

298 json.writeValueAsString(

299 Map.of(

300 "type", "response.create",

301 "model", "gpt-6-astra",

302 "reasoning", Map.of("effort", "medium"),

303 "input", "Draft a project plan for building a task-tracking app.")),

304 true)

305 .get(10, TimeUnit.SECONDS);

306 while (true) {

307 Object message =

308 events.poll(Math.max(0, deadline - System.nanoTime()), TimeUnit.NANOSECONDS);

309 if (message == null)

310 throw new TimeoutException("Timed out waiting for the steered response.");

311 if (message instanceof Throwable error) throw new IOException("WebSocket failed", error);

312 JsonNode event = json.readTree((String) message);

313 String type = event.path("type").asText();

314 JsonNode response = event.path("response");

315 if (type.equals("response.created")) {

316 if (initialResponseId == null) {

317 initialResponseId = response.path("id").asText();

318 // Simulate a user adding instructions while the response runs.

319 ws.sendText(

320 json.writeValueAsString(

321 Map.of(

322 "type", "response.steer",

323 "previous_response_id", initialResponseId,

324 "input",

325 "Keep the scope small enough for one developer to finish in two weeks.")),

326 true)

327 .get(10, TimeUnit.SECONDS);

328 } else {

329 successorResponseId = response.path("id").asText();

330 }

331 } else if (type.equals("response.steer.failed")

332 || type.equals("response.failed")

333 || type.equals("error")) {

334 throw new IOException(event.toString());

335 } else if (type.equals("response.incomplete")) {

336 if (!response.path("id").asText().equals(initialResponseId)

337 || !response.path("incomplete_details").path("reason").asText().equals("steered")) {

338 throw new IOException(event.toString());

339 }

340 } else if (type.equals("response.completed")

341 && response.path("id").asText().equals(successorResponseId)) {

342 StringBuilder output = new StringBuilder();

343 for (JsonNode item : response.path("output")) {

344 if (!item.path("type").asText().equals("message")) continue;

345 for (JsonNode part : item.path("content")) {

346 if (part.path("type").asText().equals("output_text"))

347 output.append(part.path("text").asText());

348 }

349 }

350 System.out.println(output);

351 break;

352 }

353 // Acceptance only queues the input. Keep reading past the first response.

354 }

355} finally {

356 ws.abort();

357}

358```189```

359 190 

360```csharp191```csharp

Details

31 31 

32### Run the sample app32### Run the sample app

33 33 

34The [CUA sample app](https://github.com/openai/openai-cua-sample-app#first-run) includes the environment, tool handlers, local tasks, and outcome checks:34The [CUA sample app](https://github.com/openai/openai-cua-sample-app#first-run) includes JavaScript/Playwright and Python/PyAutoGUI implementations, with local tasks and a shared console:

35 35 

361. Follow the sample app's setup instructions in an isolated environment.361. Follow the setup instructions for your chosen implementation in an isolated environment.

372. Select **Code** mode and set the model to `gpt-6-astra`.372. Choose a built-in scenario and start a run.

383. Choose a built-in scenario and start a run. Inspect the actions and screenshots, then check the scenario's verification result.383. Inspect the actions, screenshots, and final state to assess whether the task succeeded.

39 39 

40Use the app's README for installation, desktop permissions, and supported environments. Review [Run safely](#run-safely) before adapting it to real sites or accounts.40Use the app's README for installation, desktop permissions, and supported environments. Review [Run safely](#run-safely) before adapting it to real sites or accounts.

41 41 


58import uuid58import uuid

59 59 

60from openai import OpenAI60from openai import OpenAI

61from openai.types.responses import (

62 FunctionToolParam,

63 ResponseInputParam,

64)

61 65 

62def run_computer_use(endpoint, prompt, model="gpt-6-astra"):66def run_computer_use(endpoint, prompt, model="gpt-6-astra"):

63 client = OpenAI()67 client = OpenAI()

64 session_id = str(uuid.uuid4())68 session_id = str(uuid.uuid4())

65 tools = [69 tools: list[FunctionToolParam] = [

66 {70 {

67 "type": "function",71 "type": "function",

68 "name": "exec_py",72 "name": "exec_py",


83 "strict": True,87 "strict": True,

84 }88 }

85 ]89 ]

86 next_input = [{"role": "user", "content": prompt}]90 next_input: ResponseInputParam = [{"role": "user", "content": prompt}]

87 previous_response_id = None91 previous_response_id = None

88 92 

89 for turn in range(20):93 for turn in range(20):


98 102 

99 calls = [item for item in response.output if item.type == "function_call"]103 calls = [item for item in response.output if item.type == "function_call"]

100 if not calls and any(104 if not calls and any(

101 item.type == "message" and getattr(item, "phase", None) != "commentary"105 item.type == "message" and item.phase != "commentary"

102 for item in response.output106 for item in response.output

103 ):107 ):

104 print(response.output_text)108 print(response.output_text)

Details

1894 1894 

1895```javascript1895```javascript

1896import readline from "node:readline/promises";1896import readline from "node:readline/promises";

1897import { z } from "zod";

1898 

1899const executionOutput = z

1900 .array(

1901 z.discriminatedUnion("type", [

1902 z.object({ type: z.literal("input_text"), text: z.string() }),

1903 z.object({

1904 type: z.literal("input_image"),

1905 image_url: z.string(),

1906 detail: z.literal("original"),

1907 }),

1908 ])

1909 )

1910 .nonempty();

1897 1911 

1912/** @returns {Promise<import("openai/resources/responses/responses").ResponseFunctionCallOutputItemList>} */

1898async function executeInSandbox(code, sessionId, endpoint) {1913async function executeInSandbox(code, sessionId, endpoint) {

1899 console.log(code);1914 console.log(code);

1900 const terminal = readline.createInterface({1915 const terminal = readline.createInterface({


1929 if (!response.ok) {1944 if (!response.ok) {

1930 throw new Error(`Execution service returned HTTP ${response.status}.`);1945 throw new Error(`Execution service returned HTTP ${response.status}.`);

1931 }1946 }

1932 const { output } = await response.json();1947 const result = executionOutput.safeParse((await response.json()).output);

1933 if (1948 if (!result.success) {

1934 !Array.isArray(output) ||

1935 output.length === 0 ||

1936 !output.every(

1937 (item) =>

1938 item &&

1939 ((item.type === "input_text" && typeof item.text === "string") ||

1940 (item.type === "input_image" &&

1941 typeof item.image_url === "string" &&

1942 item.detail === "original"))

1943 )

1944 ) {

1945 throw new Error(1949 throw new Error(

1946 "Expected input_text or an input_image with original detail."1950 "Expected input_text or an input_image with original detail."

1947 );1951 );

1948 }1952 }

1949 return output;1953 return result.data;

1950}1954}

1951```1955```

1952 1956 

1953```python1957```python

1954import os1958import os

1959from json import dumps, loads

1955from urllib import request1960from urllib import request

1956 1961 

1962from openai.types.responses import ResponseFunctionCallOutputItemListParam

1963 

1957 1964 

1958def execute_in_sandbox(code, session_id, endpoint):1965def execute_in_sandbox(

1966 code: str, session_id: str, endpoint: str

1967) -> ResponseFunctionCallOutputItemListParam:

1959 """Send approved code to your separately isolated execution service."""1968 """Send approved code to your separately isolated execution service."""

1960 print(code)1969 print(code)

1961 if input("Run this code in the isolated runtime? Type yes: ").strip() != "yes":1970 if input("Run this code in the isolated runtime? Type yes: ").strip() != "yes":


1965 token = os.environ.get("OPENAI_EXAMPLE_CODE_EXECUTION_TOKEN")1974 token = os.environ.get("OPENAI_EXAMPLE_CODE_EXECUTION_TOKEN")

1966 if token:1975 if token:

1967 headers["Authorization"] = f"Bearer {token}"1976 headers["Authorization"] = f"Bearer {token}"

1968 body = json.dumps(1977 body = dumps(

1969 {"session_id": session_id, "language": "python", "code": code}1978 {"session_id": session_id, "language": "python", "code": code}

1970 ).encode()1979 ).encode()

1971 sandbox_request = request.Request(1980 sandbox_request = request.Request(

1972 endpoint, data=body, headers=headers, method="POST"1981 endpoint, data=body, headers=headers, method="POST"

1973 )1982 )

1974 with request.urlopen(sandbox_request, timeout=30) as response:1983 with request.urlopen(sandbox_request, timeout=30) as response:

1975 payload = json.loads(response.read())1984 payload = loads(response.read())

1976 1985 

1977 output = payload.get("output") if isinstance(payload, dict) else None1986 output = payload.get("output") if isinstance(payload, dict) else None

1978 if not isinstance(output, list) or not output:1987 if not isinstance(output, list) or not output:

1979 raise ValueError("The execution service returned no observations.")1988 raise ValueError("The execution service returned no observations.")

1989 observations: ResponseFunctionCallOutputItemListParam = []

1980 for item in output:1990 for item in output:

1981 if not isinstance(item, dict):1991 if not isinstance(item, dict):

1982 raise ValueError("Invalid execution-service output item.")1992 raise ValueError("Invalid execution-service output item.")

1983 if item.get("type") == "input_text" and isinstance(item.get("text"), str):1993 if item.get("type") == "input_text" and isinstance(item.get("text"), str):

1994 observations.append({"type": "input_text", "text": item["text"]})

1984 continue1995 continue

1985 if (1996 if (

1986 item.get("type") == "input_image"1997 item.get("type") == "input_image"

1987 and isinstance(item.get("image_url"), str)1998 and isinstance(item.get("image_url"), str)

1988 and item.get("detail") == "original"1999 and item.get("detail") == "original"

1989 ):2000 ):

2001 observations.append(

2002 {

2003 "type": "input_image",

2004 "image_url": item["image_url"],

2005 "detail": "original",

2006 }

2007 )

1990 continue2008 continue

1991 raise ValueError("Expected input_text or an input_image with original detail.")2009 raise ValueError("Expected input_text or an input_image with original detail.")

1992 return output2010 return observations

1993```2011```

1994 2012 

1995 2013 

Details

6 [`shell`](https://developers.openai.com/api/docs/guides/tools-shell) tool with GPT-5.1 instead. [Learn6 [`shell`](https://developers.openai.com/api/docs/guides/tools-shell) tool with GPT-5.1 instead. [Learn

7 more](https://developers.openai.com/api/docs/guides/tools-shell).7 more](https://developers.openai.com/api/docs/guides/tools-shell).

8 8 

9Local shell is a tool that allows agents to run shell commands locally on a machine you or the user provides. It's designed to work with [Codex CLI](https://github.com/openai/codex) and [`codex-mini-latest`](https://developers.openai.com/api/docs/models/codex-mini-latest). Commands are executed inside your own runtime, **you are fully in control of which commands actually run** —the API only returns the instructions, but does not execute them on OpenAI infrastructure.9Local shell is a tool that allows agents to run shell commands locally on a machine you or the user provides. It's designed to work with [Codex CLI](https://github.com/openai/codex) and [`codex-mini-latest`](https://developers.openai.com/api/docs/models/codex-mini-latest). Commands are executed inside your own runtime, so **you are fully in control of which commands actually run**. The API only returns instructions; it does not execute them on OpenAI infrastructure.

10 10 

11Local shell is available through the [Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses) for use with [`codex-mini-latest`](https://developers.openai.com/api/docs/models/codex-mini-latest). It is not available on other models, or via the Chat Completions API.11Local shell is available through the [Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses) for use with [`codex-mini-latest`](https://developers.openai.com/api/docs/models/codex-mini-latest). It is not available on other models or via the Chat Completions API.

12 12 

13Running arbitrary shell commands can be dangerous. Always sandbox execution13Running arbitrary shell commands can be dangerous. Always sandbox execution

14or add strict allow- / deny-lists before forwarding a command to the system14or add strict allowlists or deny lists before forwarding a command to the system

15shell.15shell.

16 16 

17 17 


22 22 

23The local shell tool enables agents to run in a continuous loop with access to a terminal.23The local shell tool enables agents to run in a continuous loop with access to a terminal.

24 24 

25It sends shell commands, which your code executes on a local machine and then returns the output back to the model. This loop allows the model to complete the build-test-run loop without additional intervention by a user.25The model sends shell commands, which your code executes on a local machine before returning the output to the model. This loop allows the model to complete the build-test-run loop without additional user intervention.

26 26 

27As part of your code, you'll need to implement a loop that listens for `local_shell_call` output items and executes the commands they contain. We strongly recommend sandboxing the execution of these commands to prevent any unexpected commands from being executed.27Your code must implement a loop that listens for `local_shell_call` output items and executes the commands they contain. We strongly recommend sandboxing execution to prevent unexpected commands from running.

28 28 

29 29 

30 30 


32 32 

33 33 

34 34 

35These are the high-level steps you need to follow to integrate the computer use tool in your application:35These are the high-level steps you need to follow to integrate the local shell tool in your application:

36 36 

371. **Send a request to the model**:371. **Send a request to the model**:

38 Include the `local_shell` tool as part of the available tools.38 Include the `local_shell` tool as part of the available tools.


42 This tool call contains an action like `exec` with a command to execute.42 This tool call contains an action like `exec` with a command to execute.

43 43 

443. **Execute the requested action**:443. **Execute the requested action**:

45 Execute through code the corresponding action in the computer or container environment.45 Run the command in the local environment you control.

46 46 

474. **Return the action output**:474. **Return the action output**:

48 After executing the action, return the command output and metadata like status code to the model.48 After executing the action, return the command output to the model.

49 49 

505. **Repeat**:505. **Repeat**:

51 Send a new request with the updated state as a `local_shell_call_output`, and repeat this loop until the model stops requesting actions or you decide to stop.51 Send a new request with the updated state as a `local_shell_call_output`, and repeat this loop until the model stops requesting actions or you decide to stop.

52 52 

53## Example workflow53## Example workflow

54 54 

55Below is a minimal (Python) example showing the request/response loop. For55Below is a minimal example showing the request/response loop. Choose a language

56brevity, error handling and security checks are omitted—**do not execute56to see the equivalent workflow for its SDK. For brevity, production-grade

57untrusted commands in production without additional safeguards**.57sandboxing and security checks are omitted—**do not execute untrusted commands

58in production without additional safeguards**.

59 

60```javascript

61import { spawn } from "node:child_process";

62import process from "node:process";

63import OpenAI from "openai";

64 

65const client = new OpenAI();

66const MAX_TIMEOUT_MS = 10_000;

67 

68function runCommand(command, options) {

69 return new Promise((resolve) => {

70 let stdout = "";

71 let stderr = "";

72 let settled = false;

73 let groupPoll;

74 const child = spawn(command[0], command.slice(1), {

75 ...options,

76 detached: process.platform !== "win32",

77 stdio: ["ignore", "pipe", "pipe"],

78 });

79 const finish = (suffix = "") => {

80 if (settled) return;

81 settled = true;

82 clearTimeout(timer);

83 clearTimeout(groupPoll);

84 resolve(stdout + stderr + suffix);

85 };

86 const processGroupIsRunning = () => {

87 if (process.platform === "win32" || !child.pid) return false;

88 try {

89 process.kill(-child.pid, 0);

90 return true;

91 } catch {

92 return false;

93 }

94 };

95 const finishAfterProcessGroup = (suffix) => {

96 if (settled) return;

97 if (processGroupIsRunning()) {

98 groupPoll = setTimeout(() => finishAfterProcessGroup(suffix), 10);

99 } else {

100 finish(suffix);

101 }

102 };

103 const killProcessTree = () => {

104 try {

105 if (process.platform !== "win32" && child.pid) {

106 process.kill(-child.pid, "SIGKILL");

107 } else {

108 child.kill("SIGKILL");

109 }

110 } catch {

111 child.kill("SIGKILL");

112 }

113 child.stdout?.destroy();

114 child.stderr?.destroy();

115 };

116 const timer = setTimeout(() => {

117 killProcessTree();

118 finish("Command timed out.\n");

119 }, options.timeout);

120 

121 child.stdout?.on("data", (chunk) => {

122 stdout += chunk;

123 });

124 child.stderr?.on("data", (chunk) => {

125 stderr += chunk;

126 });

127 child.on("error", (error) => {

128 finish(`Command failed: ${error.message}.\n`);

129 });

130 child.on("close", (code, signal) => {

131 if (signal) {

132 finishAfterProcessGroup(`Command failed with signal ${signal}.\n`);

133 } else if (code !== 0) {

134 finishAfterProcessGroup(`Command failed with exit code ${code}.\n`);

135 } else {

136 finishAfterProcessGroup("");

137 }

138 });

139 });

140}

141 

142let response = await client.responses.create({

143 model: "codex-mini-latest",

144 tools: [{ type: "local_shell" }],

145 parallel_tool_calls: false,

146 input: "List files in the current directory.",

147});

148 

149while (true) {

150 const shellCall = response.output.find(

151 (item) => item.type === "local_shell_call"

152 );

153 if (!shellCall) break;

154 

155 const { command, env, timeout_ms, user, working_directory } =

156 shellCall.action;

157 let output;

158 if (user) {

159 output = `Unsupported execution user: ${user}.\n`;

160 } else if (command.length === 0) {

161 output = "Command is empty.\n";

162 } else {

163 const timeout =

164 timeout_ms && timeout_ms > 0

165 ? Math.min(timeout_ms, MAX_TIMEOUT_MS)

166 : MAX_TIMEOUT_MS;

167 try {

168 output = await runCommand(command, {

169 cwd: working_directory ?? process.cwd(),

170 env: { PATH: process.env.PATH ?? "", ...env },

171 timeout,

172 });

173 } catch (error) {

174 output = `Command failed: ${error instanceof Error ? error.message : String(error)}.\n`;

175 }

176 }

177 

178 response = await client.responses.create({

179 model: "codex-mini-latest",

180 tools: [{ type: "local_shell" }],

181 parallel_tool_calls: false,

182 previous_response_id: response.id,

183 input: [

184 {

185 type: "local_shell_call_output",

186 id: shellCall.call_id,

187 output,

188 },

189 ],

190 });

191}

192 

193console.log(response.output_text);

194```

58 195 

59```python196```python

60import os197import os

61import shlex198import signal

62import subprocess199import subprocess

200import time

201from contextlib import suppress

63from openai import OpenAI202from openai import OpenAI

64 203 

65client = OpenAI()204client = OpenAI()

205MAX_TIMEOUT_MS = 10_000

206 

207 

208def output_text(value):

209 if isinstance(value, bytes):

210 return value.decode(errors="replace")

211 return value or ""

212 

213 

214def process_group_is_running(pid):

215 if os.name == "nt":

216 return False

217 try:

218 os.killpg(pid, 0)

219 return True

220 except ProcessLookupError:

221 return False

222 except PermissionError:

223 return True

224 

66 225 

67# 1) Create the initial response request with the tool enabled

68response = client.responses.create(226response = client.responses.create(

69 model="codex-mini-latest",227 model="codex-mini-latest",

70 tools=[{"type": "local_shell"}],228 tools=[{"type": "local_shell"}],

71 input=[229 parallel_tool_calls=False,

72 {230 input="List files in the current directory.",

73 "role": "user",

74 "content": [

75 {"type": "input_text", "text": "List files in the current directory"},

76 ],

77 }

78 ],

79)231)

80 232 

81while True:233while True:

82 # 2) Look for a local_shell_call in the model's output items234 shell_call = next(

83 shell_calls = []235 (item for item in response.output if item.type == "local_shell_call"),

84 for item in response.output:236 None,

85 item_type = getattr(item, "type", None)237 )

86 if item_type == "local_shell_call":238 if shell_call is None:

87 shell_calls.append(item)

88 elif (

89 item_type == "tool_call"

90 and getattr(item, "tool_name", None) == "local_shell"

91 ):

92 shell_calls.append(item)

93 if not shell_calls:

94 # No more commands — the assistant is done.

95 break239 break

96 240 

97 call = shell_calls[0]241 action = shell_call.action

98 args = getattr(call, "action", None) or getattr(call, "arguments", None)242 if action.user:

99 243 output = f"Unsupported execution user: {action.user}.\n"

100 # 3) Execute the command locally (here we just trust the command!)244 elif not action.command:

101 # The command is already split into argv tokens.245 output = "Command is empty.\n"

102 def _get(obj, key, default=None):246 else:

103 if isinstance(obj, dict):247 timeout_ms = (

104 return obj.get(key, default)248 min(action.timeout_ms, MAX_TIMEOUT_MS)

105 return getattr(obj, key, default)249 if action.timeout_ms and action.timeout_ms > 0

106 250 else MAX_TIMEOUT_MS

107 timeout_ms = _get(args, "timeout_ms")251 )

108 command = _get(args, "command")252 deadline = time.monotonic() + timeout_ms / 1000

109 if not command:253 try:

110 break254 process = subprocess.Popen(

111 if isinstance(command, str):255 action.command,

112 command = shlex.split(command)256 cwd=action.working_directory or os.getcwd(),

113 completed = subprocess.run(257 env={"PATH": os.environ.get("PATH", ""), **action.env},

114 command,258 stdin=subprocess.DEVNULL,

115 cwd=_get(args, "working_directory") or os.getcwd(),259 stdout=subprocess.PIPE,

116 env={**os.environ, **(_get(args, "env") or {})},260 stderr=subprocess.PIPE,

117 capture_output=True,

118 text=True,261 text=True,

119 timeout=(timeout_ms / 1000) if timeout_ms else None,262 errors="replace",

263 start_new_session=True,

264 )

265 stdout, stderr = process.communicate(

266 timeout=max(deadline - time.monotonic(), 0)

267 )

268 while process_group_is_running(process.pid):

269 remaining = deadline - time.monotonic()

270 if remaining <= 0:

271 raise subprocess.TimeoutExpired(action.command, timeout_ms / 1000)

272 time.sleep(min(remaining, 0.01))

273 output = stdout + stderr

274 if process.returncode:

275 output += f"Command failed with exit code {process.returncode}.\n"

276 except subprocess.TimeoutExpired as error:

277 if os.name == "nt":

278 process.kill()

279 else:

280 with suppress(ProcessLookupError):

281 os.killpg(process.pid, signal.SIGKILL)

282 try:

283 stdout, stderr = process.communicate(

284 timeout=max(deadline - time.monotonic(), 0)

120 )285 )

286 except subprocess.TimeoutExpired as drain_error:

287 if process.stdout:

288 process.stdout.close()

289 if process.stderr:

290 process.stderr.close()

291 stdout = output_text(

292 drain_error.stdout

293 if drain_error.stdout is not None

294 else error.stdout

295 )

296 stderr = output_text(

297 drain_error.stderr

298 if drain_error.stderr is not None

299 else error.stderr

300 )

301 output = output_text(stdout) + output_text(stderr) + "Command timed out.\n"

302 except (OSError, TypeError, ValueError) as error:

303 output = f"Command failed: {error}.\n"

121 304 

122 output_item = {305 output_item = {

123 "type": "local_shell_call_output",306 "type": "local_shell_call_output",

124 "call_id": getattr(call, "call_id", None),307 "id": shell_call.call_id,

125 "output": completed.stdout + completed.stderr,308 "output": output,

126 }309 }

127 310 

128 # 4) Send the output back to the model to continue the conversation

129 response = client.responses.create(311 response = client.responses.create(

130 model="codex-mini-latest",312 model="codex-mini-latest",

131 tools=[{"type": "local_shell"}],313 tools=[{"type": "local_shell"}],

314 parallel_tool_calls=False,

132 previous_response_id=response.id,315 previous_response_id=response.id,

133 input=[output_item],316 input=[output_item],

134 )317 )

135 318 

136# Print the assistant's final answer

137print(response.output_text)319print(response.output_text)

138```320```

139 321 

322```go

323package main

324 

325import (

326 "bytes"

327 "context"

328 "errors"

329 "fmt"

330 "io"

331 "os"

332 "os/exec"

333 "path/filepath"

334 "sync"

335 "sync/atomic"

336 "syscall"

337 "time"

338 

339 "github.com/openai/openai-go/v3"

340 "github.com/openai/openai-go/v3/responses"

341)

342 

343const maxCommandTimeout = 10 * time.Second

344 

345func main() {

346 client := openai.NewClient()

347 tool := responses.ToolUnionParam{OfLocalShell: &responses.ToolLocalShellParam{}}

348 response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{

349 Model: "codex-mini-latest",

350 Tools: []responses.ToolUnionParam{tool},

351 ParallelToolCalls: openai.Bool(false),

352 Input: responses.ResponseNewParamsInputUnion{

353 OfString: openai.String("List files in the current directory."),

354 },

355 })

356 if err != nil {

357 panic(err)

358 }

359 

360 for {

361 var shellCall *responses.ResponseOutputItemLocalShellCall

362 for _, item := range response.Output {

363 if item.Type == "local_shell_call" {

364 call := item.AsLocalShellCall()

365 shellCall = &call

366 break

367 }

368 }

369 if shellCall == nil {

370 break

371 }

372 

373 action := shellCall.Action

374 var output []byte

375 if action.User != "" {

376 output = []byte(fmt.Sprintf("Unsupported execution user: %s.\n", action.User))

377 } else if len(action.Command) == 0 {

378 output = []byte("Command is empty.\n")

379 } else {

380 path := os.Getenv("PATH")

381 if actionPath, ok := action.Env["PATH"]; ok {

382 path = actionPath

383 }

384 executable, pathErr := commandPath(action.Command[0], path, action.WorkingDirectory)

385 if pathErr != nil {

386 output = []byte(fmt.Sprintf("Command failed: %v\n", pathErr))

387 } else {

388 timeout := maxCommandTimeout

389 if action.TimeoutMs > 0 && action.TimeoutMs < maxCommandTimeout.Milliseconds() {

390 timeout = time.Duration(action.TimeoutMs) * time.Millisecond

391 }

392 deadline := time.Now().Add(timeout)

393 ctx, cancel := context.WithTimeout(context.Background(), timeout)

394 command := exec.CommandContext(ctx, executable, action.Command[1:]...)

395 command.Args[0] = action.Command[0]

396 command.Dir = action.WorkingDirectory

397 command.Env = []string{"PATH=" + path}

398 command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

399 for key, value := range action.Env {

400 if key == "PATH" {

401 continue

402 }

403 command.Env = append(command.Env, key+"="+value)

404 }

405 killProcessGroup := func() {

406 if command.Process != nil {

407 _ = syscall.Kill(-command.Process.Pid, syscall.SIGKILL)

408 }

409 }

410 processGroupIsRunning := func() bool {

411 return command.Process != nil && syscall.Kill(-command.Process.Pid, 0) == nil

412 }

413 stdout, stdoutWriter, stdoutErr := os.Pipe()

414 stderr, stderrWriter, stderrErr := os.Pipe()

415 if stdoutErr != nil || stderrErr != nil {

416 if stdout != nil {

417 _ = stdout.Close()

418 }

419 if stdoutWriter != nil {

420 _ = stdoutWriter.Close()

421 }

422 if stderr != nil {

423 _ = stderr.Close()

424 }

425 if stderrWriter != nil {

426 _ = stderrWriter.Close()

427 }

428 output = []byte(fmt.Sprintf("Command failed: %v%v\n", stdoutErr, stderrErr))

429 } else {

430 command.Stdout = stdoutWriter

431 command.Stderr = stderrWriter

432 var combinedOutput bytes.Buffer

433 var outputLock sync.Mutex

434 var readers sync.WaitGroup

435 readOutput := func(reader io.ReadCloser) {

436 defer readers.Done()

437 data, _ := io.ReadAll(reader)

438 outputLock.Lock()

439 _, _ = combinedOutput.Write(data)

440 outputLock.Unlock()

441 }

442 var commandErr error

443 commandErr = command.Start()

444 if commandErr == nil {

445 _ = stdoutWriter.Close()

446 _ = stderrWriter.Close()

447 readers.Add(2)

448 go readOutput(stdout)

449 go readOutput(stderr)

450 var timedOut atomic.Bool

451 remaining := time.Until(deadline)

452 if remaining < 0 {

453 remaining = 0

454 }

455 markTimedOut := func() {

456 if timedOut.Swap(true) {

457 return

458 }

459 killProcessGroup()

460 _ = stdout.Close()

461 _ = stderr.Close()

462 }

463 timer := time.AfterFunc(remaining, markTimedOut)

464 commandErr = command.Wait()

465 if !time.Now().Before(deadline) ||

466 errors.Is(commandErr, context.DeadlineExceeded) ||

467 errors.Is(ctx.Err(), context.DeadlineExceeded) {

468 markTimedOut()

469 }

470 for processGroupIsRunning() && !timedOut.Load() {

471 time.Sleep(10 * time.Millisecond)

472 }

473 readers.Wait()

474 if !timer.Stop() || !time.Now().Before(deadline) {

475 markTimedOut()

476 }

477 output = combinedOutput.Bytes()

478 if timedOut.Load() || errors.Is(ctx.Err(), context.DeadlineExceeded) {

479 killProcessGroup()

480 output = append(output, "Command timed out.\n"...)

481 } else if commandErr != nil {

482 output = append(output, fmt.Sprintf("Command failed: %v\n", commandErr)...)

483 }

484 } else {

485 _ = stdout.Close()

486 _ = stderr.Close()

487 _ = stdoutWriter.Close()

488 _ = stderrWriter.Close()

489 output = append(output, fmt.Sprintf("Command failed: %v\n", commandErr)...)

490 }

491 }

492 cancel()

493 }

494 }

495 

496 response, err = client.Responses.New(context.Background(), responses.ResponseNewParams{

497 Model: "codex-mini-latest",

498 Tools: []responses.ToolUnionParam{tool},

499 ParallelToolCalls: openai.Bool(false),

500 PreviousResponseID: openai.String(response.ID),

501 Input: responses.ResponseNewParamsInputUnion{

502 OfInputItemList: []responses.ResponseInputItemUnionParam{{

503 OfLocalShellCallOutput: &responses.ResponseInputItemLocalShellCallOutputParam{

504 ID: shellCall.CallID,

505 Output: string(output),

506 },

507 }},

508 },

509 })

510 if err != nil {

511 panic(err)

512 }

513 }

514 

515 fmt.Println(response.OutputText())

516}

517 

518func commandPath(command string, path string, workingDirectory string) (string, error) {

519 if filepath.Base(command) != command {

520 return command, nil

521 }

522 baseDirectory, err := filepath.Abs(workingDirectory)

523 if err != nil {

524 return "", err

525 }

526 directories := filepath.SplitList(path)

527 if len(directories) == 0 {

528 directories = []string{""}

529 }

530 for _, directory := range directories {

531 if directory == "" {

532 directory = "."

533 }

534 if !filepath.IsAbs(directory) {

535 directory = filepath.Join(baseDirectory, directory)

536 }

537 candidate := filepath.Join(directory, command)

538 info, err := os.Stat(candidate)

539 if err == nil && !info.IsDir() && info.Mode()&0o111 != 0 {

540 return candidate, nil

541 }

542 }

543 return "", fmt.Errorf("command %q not found in PATH", command)

544}

545```

546 

547```java

548import com.openai.client.OpenAIClient;

549import com.openai.client.okhttp.OpenAIOkHttpClient;

550import com.openai.core.JsonValue;

551import com.openai.models.responses.ResponseCreateParams;

552import com.openai.models.responses.ResponseInputItem;

553import java.io.IOException;

554import java.io.UncheckedIOException;

555import java.nio.charset.StandardCharsets;

556import java.nio.file.Files;

557import java.nio.file.Path;

558import java.util.ArrayList;

559import java.util.LinkedHashMap;

560import java.util.List;

561import java.util.Locale;

562import java.util.Map;

563import java.util.concurrent.CompletableFuture;

564import java.util.concurrent.TimeUnit;

565import java.util.concurrent.TimeoutException;

566 

567ResponseCreateParams.Builder request =

568 ResponseCreateParams.builder()

569 .model("codex-mini-latest")

570 .input("List files in the current directory.")

571 .parallelToolCalls(false)

572 .putAdditionalBodyProperty(

573 "tools", JsonValue.from(List.of(Map.of("type", "local_shell"))));

574var response = client.responses().create(request.build());

575 

576while (true) {

577 var shellCall =

578 response.output().stream()

579 .flatMap(item -> item.localShellCall().stream())

580 .findFirst()

581 .orElse(null);

582 if (shellCall == null) {

583 break;

584 }

585 

586 var action = shellCall.action();

587 String output;

588 if (action.user().isPresent()) {

589 output = "Unsupported execution user: " + action.user().get() + ".\n";

590 } else if (action.command().isEmpty()) {

591 output = "Command is empty.\n";

592 } else {

593 try {

594 boolean usesShellSupervisor =

595 !System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("win");

596 String hostPath = System.getenv("PATH");

597 String childPath = hostPath;

598 Map<String, String> actionEnvironment = new LinkedHashMap<>();

599 for (Map.Entry<String, com.openai.core.JsonValue> variable :

600 action.env()._additionalProperties().entrySet()) {

601 String value = (String) variable.getValue().asString().orElseThrow();

602 actionEnvironment.put(variable.getKey(), value);

603 if (variable.getKey().equals("PATH")) {

604 childPath = value;

605 }

606 }

607 List<String> command;

608 if (usesShellSupervisor) {

609 String supervisorShell =

610 Files.isExecutable(Path.of("/bin/bash")) ? "/bin/bash" : "/bin/sh";

611 command =

612 new ArrayList<>(

613 List.of(

614 supervisorShell,

615 "-c",

616 "set -m; child=; "

617 + "cleanup() { test -z \"$child\" || "

618 + "kill -KILL -- \"-$child\" 2>/dev/null; }; "

619 + "trap cleanup TERM INT HUP; \"$@\" & child=$!; set +m; "

620 + "wait \"$child\" 2>/dev/null; status=$?; "

621 + "while kill -0 -- \"-$child\" 2>/dev/null; do sleep 0.01; done; "

622 + "exit \"$status\"",

623 "local-shell",

624 "/usr/bin/env",

625 "-i"));

626 if (childPath != null) {

627 command.add("PATH=" + childPath);

628 }

629 for (Map.Entry<String, String> variable : actionEnvironment.entrySet()) {

630 if (!variable.getKey().equals("PATH")) {

631 command.add(variable.getKey() + "=" + variable.getValue());

632 }

633 }

634 command.addAll(action.command());

635 } else {

636 command = new ArrayList<>(action.command());

637 }

638 ProcessBuilder processBuilder = new ProcessBuilder(command);

639 processBuilder.directory(action.workingDirectory().map(java.io.File::new).orElse(null));

640 processBuilder.environment().clear();

641 if (hostPath != null) {

642 processBuilder.environment().put("PATH", hostPath);

643 }

644 if (!usesShellSupervisor) {

645 processBuilder.environment().putAll(actionEnvironment);

646 }

647 Process process = processBuilder.redirectErrorStream(true).start();

648 process.getOutputStream().close();

649 var outputFuture =

650 CompletableFuture.supplyAsync(

651 () -> {

652 try {

653 return new String(

654 process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);

655 } catch (IOException error) {

656 throw new UncheckedIOException(error);

657 }

658 });

659 long timeoutMillis =

660 action

661 .timeoutMs()

662 .filter(timeout -> timeout > 0)

663 .map(timeout -> Math.min(timeout, MAX_TIMEOUT_MILLIS))

664 .orElse(MAX_TIMEOUT_MILLIS);

665 long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMillis);

666 boolean finished = process.waitFor(timeoutMillis, TimeUnit.MILLISECONDS);

667 if (!finished) {

668 destroyProcessTree(process, usesShellSupervisor);

669 }

670 try {

671 long remainingNanos = Math.max(1, deadlineNanos - System.nanoTime());

672 output = outputFuture.get(remainingNanos, TimeUnit.NANOSECONDS);

673 if (!finished) {

674 output = "Command timed out.\n" + output;

675 } else if (process.exitValue() != 0) {

676 output += "Command failed with exit code " + process.exitValue() + ".\n";

677 }

678 } catch (TimeoutException error) {

679 destroyProcessTree(process, usesShellSupervisor);

680 process.getInputStream().close();

681 output = "Command timed out.\n";

682 } catch (java.util.concurrent.ExecutionException error) {

683 output = "Command failed: " + error.getCause().getMessage() + ".\n";

684 }

685 } catch (IOException | IllegalArgumentException error) {

686 output = "Command failed: " + error.getMessage() + ".\n";

687 }

688 }

689 

690 response =

691 client

692 .responses()

693 .create(

694 request

695 .previousResponseId(response.id())

696 .inputOfResponse(

697 List.of(

698 ResponseInputItem.ofLocalShellCallOutput(

699 ResponseInputItem.LocalShellCallOutput.builder()

700 .id(shellCall.callId())

701 .output(output)

702 .build())))

703 .build());

704}

705 

706response.output().stream()

707 .flatMap(item -> item.message().stream())

708 .flatMap(message -> message.content().stream())

709 .flatMap(content -> content.outputText().stream())

710 .forEach(text -> System.out.println(text.text()));

711 

712private static void destroyProcessTree(Process process, boolean usesShellSupervisor) {

713 process.descendants().forEach(ProcessHandle::destroyForcibly);

714 if (usesShellSupervisor) {

715 process.destroy();

716 } else {

717 process.destroyForcibly();

718 }

719}

720```

721 

722```ruby

723require "open3"

724require "openai"

725require "timeout"

726 

727client = OpenAI::Client.new

728MAX_TIMEOUT_MS = 10_000

729response = client.responses.create(

730 model: "codex-mini-latest",

731 tools: [{type: :local_shell}],

732 parallel_tool_calls: false,

733 input: "List files in the current directory."

734)

735 

736loop do

737 shell_call = response.output.find do |item|

738 item.is_a?(OpenAI::Models::Responses::ResponseOutputItem::LocalShellCall)

739 end

740 break unless shell_call.is_a?(

741 OpenAI::Models::Responses::ResponseOutputItem::LocalShellCall

742 )

743 

744 action = shell_call.action

745 stdout = +""

746 stderr = +""

747 if action.user

748 stderr << "Unsupported execution user: #{action.user}.\n"

749 elsif action.command.empty?

750 stderr << "Command is empty.\n"

751 else

752 begin

753 executable = action.command.fetch(0)

754 environment = {"PATH" => ENV.fetch("PATH", "")}.merge(action.env.transform_keys(&:to_s))

755 status, timed_out = Open3.popen3(

756 environment,

757 [executable, executable],

758 *action.command.drop(1),

759 chdir: action.working_directory || Dir.pwd,

760 pgroup: true,

761 unsetenv_others: true

762 ) do |stdin, child_stdout, child_stderr, wait_thread|

763 stdin.close

764 stdout_reader = Thread.new {

765 begin

766 child_stdout.read

767 rescue

768 ""

769 end

770 }

771 stderr_reader = Thread.new {

772 begin

773 child_stderr.read

774 rescue

775 ""

776 end

777 }

778 timeout_ms = action.timeout_ms

779 timeout = if timeout_ms&.positive?

780 [timeout_ms, MAX_TIMEOUT_MS].min / 1000.0

781 else

782 MAX_TIMEOUT_MS / 1000.0

783 end

784 deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout

785 

786 command_timed_out = false

787 wait_status = begin

788 status = Timeout.timeout(timeout) { wait_thread.value }

789 remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)

790 raise Timeout::Error if remaining <= 0

791 

792 stdout << Timeout.timeout(remaining) { stdout_reader.value }

793 remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)

794 raise Timeout::Error if remaining <= 0

795 

796 stderr << Timeout.timeout(remaining) { stderr_reader.value }

797 group_running = proc do

798 Process.kill(0, -wait_thread.pid)

799 true

800 rescue Errno::ESRCH

801 false

802 rescue Errno::EPERM

803 true

804 end

805 while group_running.call

806 remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)

807 raise Timeout::Error if remaining <= 0

808 

809 sleep [remaining, 0.01].min

810 end

811 status

812 rescue Timeout::Error

813 command_timed_out = true

814 begin

815 Process.kill("TERM", -wait_thread.pid)

816 Process.kill("KILL", -wait_thread.pid)

817 rescue Errno::ESRCH

818 nil

819 end

820 child_stdout.close

821 child_stderr.close

822 stdout_reader.kill

823 stderr_reader.kill

824 stderr << "Command timed out.\n"

825 wait_thread.value

826 end

827 [wait_status, command_timed_out]

828 end

829 exit_status = status.exitstatus

830 if exit_status && !status.success? && !timed_out

831 stderr << "Command failed with exit code #{exit_status}.\n"

832 elsif status.signaled? && !timed_out

833 stderr << "Command failed with signal #{status.termsig}.\n"

834 end

835 rescue SystemCallError, ArgumentError, TypeError => error

836 stderr << "Command failed: #{error.message}.\n"

837 end

838 end

839 

840 response = client.responses.create(

841 model: "codex-mini-latest",

842 tools: [{type: :local_shell}],

843 parallel_tool_calls: false,

844 previous_response_id: response.id,

845 input: [{

846 type: :local_shell_call_output,

847 id: shell_call.call_id,

848 output: (stdout + stderr).encode("UTF-8", invalid: :replace, undef: :replace)

849 }]

850 )

851end

852 

853puts(response.output_text)

854```

855 

140 856 

141## Best practices857## Best practices

142 858 

143- **Sandbox or containerize** execution. Consider using Docker, firejail, or a859- **Sandbox or containerize** execution. Consider using Docker or a jailed user

144 jailed user account.860 account.

145- **Impose resource limits** (time, memory, network). The `timeout_ms`861- **Impose resource limits** (time, memory, network). The `timeout_ms`

146 provided by the model is only a hint—you should enforce your own limits.862 provided by the model is only a hint—you should enforce your own limits.

147- **Filter or scrutinize** high-risk commands (e.g. `rm`, `curl`, network863- **Filter or scrutinize** high-risk commands (for example, `rm`, `curl`, network

148 utilities).864 utilities).

149- **Log every command and its output** for auditability and debugging.865- **Log every command and its output** for auditing and debugging.

150 866 

151### Error handling867### Error handling

152 868 

153If the command fails on your side (non-zero exit code, timeout, etc.) you can still send a `local_shell_call_output`; include the error message in the `output` field.869If the command fails on your side, for example, with a non-zero exit code or timeout, you can still send a `local_shell_call_output`; include the error message in the `output` field.

154 870 

155The model can choose to recover or try executing a different command. If you send malformed data (e.g. missing `call_id`) the API returns a standard `400` validation error.871The model can choose to recover or try executing a different command. If you send malformed data (for example, a missing `id`) the API returns a standard `400` validation error.

Details

459 )459 )

460```460```

461 461 

462```go

463package main

464 

465import (

466 "context"

467 "encoding/json"

468 "fmt"

469 

470 "github.com/openai/openai-go/v3"

471 "github.com/openai/openai-go/v3/responses"

472)

473 

474type toolArguments struct {

475 SKU string `json:"sku"`

476}

477 

478func main() {

479 client := openai.NewClient()

480 input := responses.ResponseInputParam{

481 responses.ResponseInputItemParamOfMessage(

482 "Compare inventory with demand for sku_123.",

483 responses.EasyInputMessageRoleUser,

484 ),

485 }

486 tools := []responses.ToolUnionParam{

487 functionTool(

488 "get_inventory",

489 "Return an object with sku (string) and available_units (number).",

490 "available_units",

491 ),

492 functionTool(

493 "get_demand",

494 "Return an object with sku (string) and requested_units (number).",

495 "requested_units",

496 ),

497 programmaticTool(),

498 }

499 

500 for {

501 response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{

502 Model: "gpt-6-astra",

503 Store: openai.Bool(false),

504 Input: responses.ResponseNewParamsInputUnion{OfInputItemList: input},

505 Tools: tools,

506 })

507 if err != nil {

508 panic(err)

509 }

510 if response.Status != "completed" {

511 panic(fmt.Errorf("response ended with status %s", response.Status))

512 }

513 

514 // Preserve every output item, including program and reasoning items.

515 input = append(input, outputAsInput(response.Output)...)

516 

517 calls := functionCalls(response.Output)

518 if len(calls) == 0 {

519 if text, ok := finalMessageText(response); ok {

520 fmt.Println(text)

521 break

522 }

523 continue

524 }

525 

526 for _, call := range calls {

527 result, err := runTool(call.Name, call.Arguments)

528 if err != nil {

529 panic(err)

530 }

531 output, err := json.Marshal(result)

532 if err != nil {

533 panic(err)

534 }

535 

536 toolOutput := responses.ResponseInputItemParamOfFunctionCallOutput(string(output))

537 toolOutput.OfFunctionCallOutput.CallID = openai.String(call.CallID)

538 caller := call.Caller.AsProgram()

539 if caller.CallerID == "" {

540 panic("function call is missing its program caller")

541 }

542 // Preserve caller so the runtime can resume the correct program.

543 toolOutput.OfFunctionCallOutput.Caller.OfProgram =

544 &responses.ResponseInputItemFunctionCallOutputCallerProgramParam{

545 CallerID: caller.CallerID,

546 }

547 input = append(input, toolOutput)

548 }

549 }

550}

551 

552func functionTool(name, description, resultField string) responses.ToolUnionParam {

553 parameters := map[string]any{

554 "type": "object",

555 "properties": map[string]any{

556 "sku": map[string]any{"type": "string"},

557 },

558 "required": []string{"sku"},

559 "additionalProperties": false,

560 }

561 outputSchema := map[string]any{

562 "type": "object",

563 "properties": map[string]any{

564 "sku": map[string]any{"type": "string"},

565 resultField: map[string]any{"type": "number"},

566 },

567 "required": []string{"sku", resultField},

568 "additionalProperties": false,

569 }

570 tool := responses.ToolParamOfFunction(name, parameters, true)

571 tool.OfFunction.Description = openai.String(description)

572 tool.OfFunction.AllowedCallers = []string{"programmatic"}

573 tool.OfFunction.OutputSchema = outputSchema

574 return tool

575}

576 

577func programmaticTool() responses.ToolUnionParam {

578 tool := responses.NewToolProgrammaticToolCallingParam()

579 return responses.ToolUnionParam{OfProgrammaticToolCalling: &tool}

580}

581 

582func outputAsInput(

583 output []responses.ResponseOutputItemUnion,

584) []responses.ResponseInputItemUnionParam {

585 input := make([]responses.ResponseInputItemUnionParam, 0, len(output))

586 for _, item := range output {

587 var converted responses.ResponseInputItemUnion

588 if err := json.Unmarshal([]byte(item.RawJSON()), &converted); err != nil {

589 panic(err)

590 }

591 input = append(input, converted.ToParam())

592 }

593 return input

594}

595 

596func functionCalls(

597 output []responses.ResponseOutputItemUnion,

598) []responses.ResponseFunctionToolCall {

599 calls := make([]responses.ResponseFunctionToolCall, 0)

600 for _, item := range output {

601 if item.Type == "function_call" {

602 calls = append(calls, item.AsFunctionCall())

603 }

604 }

605 return calls

606}

607 

608func finalMessageText(response *responses.Response) (string, bool) {

609 for _, item := range response.Output {

610 if item.Type != "message" {

611 continue

612 }

613 text := response.OutputText()

614 if text != "" {

615 return text, true

616 }

617 for _, content := range item.AsMessage().Content {

618 if content.Type == "refusal" {

619 return content.AsRefusal().Refusal, true

620 }

621 }

622 return "", true

623 }

624 return "", false

625}

626 

627func runTool(name, argumentsJSON string) (map[string]any, error) {

628 var arguments toolArguments

629 if err := json.Unmarshal([]byte(argumentsJSON), &arguments); err != nil {

630 return nil, fmt.Errorf("parse %s arguments: %w", name, err)

631 }

632 

633 switch name {

634 case "get_inventory":

635 return map[string]any{"sku": arguments.SKU, "available_units": 42}, nil

636 case "get_demand":

637 return map[string]any{"sku": arguments.SKU, "requested_units": 31}, nil

638 default:

639 return nil, fmt.Errorf("unknown tool: %s", name)

640 }

641}

642```

643 

644```ruby

645require "json"

646require "openai"

647 

648client = OpenAI::Client.new

649 

650def get_inventory(sku:)

651 {sku: sku, available_units: 42}

652end

653 

654def get_demand(sku:)

655 {sku: sku, requested_units: 31}

656end

657 

658implementations = {

659 "get_inventory" => method(:get_inventory),

660 "get_demand" => method(:get_demand)

661}

662tools = [

663 {

664 type: :function,

665 name: "get_inventory",

666 description: "Return an object with sku (string) and available_units (number).",

667 parameters: {

668 type: :object,

669 properties: {sku: {type: :string}},

670 required: ["sku"],

671 additionalProperties: false

672 },

673 output_schema: {

674 type: :object,

675 properties: {

676 sku: {type: :string},

677 available_units: {type: :number}

678 },

679 required: %w[sku available_units],

680 additionalProperties: false

681 },

682 allowed_callers: [:programmatic],

683 strict: true

684 },

685 {

686 type: :function,

687 name: "get_demand",

688 description: "Return an object with sku (string) and requested_units (number).",

689 parameters: {

690 type: :object,

691 properties: {sku: {type: :string}},

692 required: ["sku"],

693 additionalProperties: false

694 },

695 output_schema: {

696 type: :object,

697 properties: {

698 sku: {type: :string},

699 requested_units: {type: :number}

700 },

701 required: %w[sku requested_units],

702 additionalProperties: false

703 },

704 allowed_callers: [:programmatic],

705 strict: true

706 },

707 {type: :programmatic_tool_calling}

708]

709input = [{role: :user, content: "Compare inventory with demand for sku_123."}]

710 

711loop do

712 response = client.responses.create(

713 model: "gpt-6-astra",

714 store: false,

715 input: input,

716 tools: tools

717 )

718 unless response.status == OpenAI::Responses::ResponseStatus::COMPLETED

719 raise "Response ended with status #{response.status}"

720 end

721 

722 # Preserve every output item, including program and reasoning items.

723 input.concat(response.output)

724 calls = response.output.grep(OpenAI::Models::Responses::ResponseFunctionToolCall)

725 

726 if calls.empty?

727 message = response.output.find do |item|

728 item.is_a?(OpenAI::Models::Responses::ResponseOutputMessage)

729 end

730 next unless message.is_a?(OpenAI::Models::Responses::ResponseOutputMessage)

731 

732 refusal = message.content.find do |content|

733 content.is_a?(OpenAI::Models::Responses::ResponseOutputRefusal)

734 end

735 text = response.output_text

736 if text.empty? &&

737 refusal.is_a?(OpenAI::Models::Responses::ResponseOutputRefusal)

738 text = refusal.refusal

739 end

740 puts(text)

741 break

742 end

743 

744 calls.each do |call|

745 implementation = implementations.fetch(call.name) do

746 raise ArgumentError, "Unknown tool: #{call.name}"

747 end

748 result = implementation.call(**JSON.parse(call.arguments, symbolize_names: true))

749 output = {

750 type: :function_call_output,

751 call_id: call.call_id,

752 output: JSON.generate(result)

753 }

754 # Preserve caller so the runtime can resume the correct program.

755 output[:caller] = call.caller_.to_h if call.caller_

756 input << output

757 end

758end

759```

760 

462 761 

463When you store responses, you can continue from `previous_response_id` instead of resending all earlier response items. Send the new `function_call_output` items as the next input. With `store: false`, replay the complete sequence in order, including every `program`, reasoning, function-call, function-call-output, and `program_output` item.762When you store responses, you can continue from `previous_response_id` instead of resending all earlier response items. Send the new `function_call_output` items as the next input. With `store: false`, replay the complete sequence in order, including every `program`, reasoning, function-call, function-call-output, and `program_output` item.

464 763 

Details

14 14 

15## Connect and create responses15## Connect and create responses

16 16 

17Install the WebSocket dependencies with `pip install "openai[realtime]>=3.8.0"` for Python or `npm install openai@^7.10.0 ws` for JavaScript.

18 

17In WebSocket mode, start each turn by sending a `response.create` event from the client. The payload mirrors the normal [Responses create body](https://developers.openai.com/api/reference/resources/responses/methods/create), except that transport-specific fields like `stream` and `background` are not used.19In WebSocket mode, start each turn by sending a `response.create` event from the client. The payload mirrors the normal [Responses create body](https://developers.openai.com/api/reference/resources/responses/methods/create), except that transport-specific fields like `stream` and `background` are not used.

18 20 

19```python21```javascript

20from websocket import create_connection22import OpenAI from "openai";

21import json23import { ResponsesWS } from "openai/resources/responses/ws";

22import os

23 24 

24ws = create_connection(25const client = new OpenAI();

25 "wss://api.openai.com/v1/responses",

26 header=[

27 f"Authorization: Bearer {os.environ['OPENAI_API_KEY']}",

28 ],

29)

30 26 

31ws.send(27const ws = new ResponsesWS(client);

32 json.dumps(28try {

29 ws.send({

30 type: "response.create",

31 stream_id: "main",

32 model: "gpt-6-astra",

33 store: false,

34 input: [

33 {35 {

34 "type": "response.create",36 type: "message",

35 "stream_id": "main",37 role: "user",

36 "model": "gpt-6-astra",38 content: [{ type: "input_text", text: "Find fizz_buzz()" }],

37 "store": False,39 },

38 "input": [40 ],

41 tools: [],

42 });

43 let completed = false;

44 for await (const event of ws) {

45 if (event.type === "error") throw event.error;

46 if (event.type !== "message") continue;

47 const message = event.message;

48 if (message.type === "response.output_text.delta") {

49 process.stdout.write(message.delta);

50 } else if (message.type === "response.completed") {

51 completed = true;

52 break;

53 } else if (

54 message.type === "response.failed" ||

55 message.type === "response.incomplete"

56 ) {

57 throw new Error(JSON.stringify(message));

58 }

59 }

60 if (!completed)

61 throw new Error("Connection closed before the response finished.");

62} finally {

63 ws.close();

64}

65```

66 

67```python

68from openai import OpenAI

69 

70client = OpenAI()

71 

72with client.responses.connect() as connection:

73 connection.response.create(

74 stream_id="main",

75 model="gpt-6-astra",

76 store=False,

77 input=[

39 {78 {

40 "type": "message",79 "type": "message",

41 "role": "user",80 "role": "user",

42 "content": [{"type": "input_text", "text": "Find fizz_buzz()"}],81 "content": [{"type": "input_text", "text": "Find fizz_buzz()"}],

43 }82 }

44 ],83 ],

45 "tools": [],84 tools=[],

46 }

47 )85 )

48)86 for event in connection:

87 if event.type == "response.completed":

88 print(event.response.output_text)

89 break

90 if event.type in {"response.failed", "response.incomplete", "error"}:

91 raise RuntimeError(event.to_json())

49```92```

50 93 

51 94 


60- `previous_response_id` set to the prior response ID.103- `previous_response_id` set to the prior response ID.

61- `input` containing only new items (for example, tool outputs and the next user message).104- `input` containing only new items (for example, tool outputs and the next user message).

62 105 

63```python106```javascript

64ws.send(107import OpenAI from "openai";

65 json.dumps(108import { ResponsesWS } from "openai/resources/responses/ws";

66 {109 

67 "type": "response.create",110const client = new OpenAI();

68 "stream_id": "main",111const model = "gpt-6-astra";

69 "model": "gpt-6-astra",112/** @type {OpenAI.Responses.FunctionTool[]} */

70 "store": False,113const tools = [

71 "previous_response_id": "resp_123",

72 "input": [

73 {114 {

74 "type": "function_call_output",115 type: "function",

75 "call_id": "call_123",116 name: "get_test_results",

76 "output": "tool result",117 description: "Return a local demo test result.",

118 parameters: { type: "object", properties: {}, additionalProperties: false },

119 strict: true,

77 },120 },

121];

122 

123/** @param {ResponsesWS} ws */

124async function waitForResponse(ws) {

125 for await (const event of ws) {

126 if (event.type === "error") throw event.error;

127 if (event.type !== "message") continue;

128 const message = event.message;

129 if (message.type === "response.output_text.delta") {

130 process.stdout.write(message.delta);

131 } else if (message.type === "response.completed") {

132 return message.response;

133 } else if (

134 message.type === "response.failed" ||

135 message.type === "response.incomplete"

136 ) {

137 throw new Error(JSON.stringify(message));

138 }

139 }

140 throw new Error("Connection closed before the response finished.");

141}

142 

143const ws = new ResponsesWS(client);

144try {

145 ws.send({

146 type: "response.create",

147 stream_id: "main",

148 model,

149 store: false,

150 input: "Find the failing test and suggest a fix.",

151 tools,

152 tool_choice: { type: "function", name: "get_test_results" },

153 parallel_tool_calls: false,

154 });

155 const first = await waitForResponse(ws);

156 const call = first.output.find((item) => item.type === "function_call");

157 if (!call || call.name !== "get_test_results") {

158 throw new Error("Expected a get_test_results function call.");

159 }

160 const result = {

161 test: "test_fizz_buzz",

162 failure: 'Expected "FizzBuzz" for 15, got "Fizz".',

163 };

164 

165 // Continue on the same socket with the actual response and tool-call IDs.

166 ws.send({

167 type: "response.create",

168 stream_id: "main",

169 model,

170 store: false,

171 previous_response_id: first.id,

172 input: [

78 {173 {

79 "type": "message",174 type: "function_call_output",

80 "role": "user",175 call_id: call.call_id,

81 "content": [{"type": "input_text", "text": "Now optimize it."}],176 output: JSON.stringify(result),

82 },177 },

178 { role: "user", content: "Now optimize it." },

83 ],179 ],

84 "tools": [],180 tools,

181 tool_choice: "none",

182 });

183 await waitForResponse(ws);

184} finally {

185 ws.close();

186}

187```

188 

189```python

190import json

191 

192from openai import OpenAI

193from openai.resources.responses.responses import ResponsesConnection

194from openai.types.responses import FunctionToolParam, Response

195 

196client = OpenAI()

197model = "gpt-6-astra"

198tools: list[FunctionToolParam] = [

199 {

200 "type": "function",

201 "name": "get_test_results",

202 "description": "Read the demo test results.",

203 "parameters": {

204 "type": "object",

205 "properties": {},

206 "required": [],

207 "additionalProperties": False,

208 },

209 "strict": True,

210 }

211]

212 

213 

214def get_test_results():

215 # Demo data. Replace this function with your test runner.

216 return {

217 "test": "test_fizz_buzz",

218 "failure": 'Expected "FizzBuzz" for 15, got "Fizz".',

85 }219 }

220 

221 

222def wait_for_response(connection: ResponsesConnection) -> Response:

223 for event in connection:

224 if event.type == "response.completed":

225 return event.response

226 if event.type in {"response.failed", "response.incomplete", "error"}:

227 raise RuntimeError(event.to_json())

228 raise RuntimeError("Connection closed before the response finished.")

229 

230 

231with client.responses.connect() as connection:

232 connection.response.create(

233 stream_id="main",

234 model=model,

235 store=False,

236 input="Find the failing test and suggest a fix.",

237 tools=tools,

238 tool_choice={"type": "function", "name": "get_test_results"},

239 parallel_tool_calls=False,

86 )240 )

87)241 response = wait_for_response(connection)

242 call = next(item for item in response.output if item.type == "function_call")

243 if call.name != "get_test_results" or json.loads(call.arguments) != {}:

244 raise ValueError("Expected a get_test_results call with no arguments")

245 

246 # Continue on the same connection using the actual response and tool-call IDs.

247 connection.response.create(

248 stream_id="main",

249 model=model,

250 store=False,

251 previous_response_id=response.id,

252 input=[

253 {

254 "type": "function_call_output",

255 "call_id": call.call_id,

256 "output": json.dumps(get_test_results()),

257 },

258 {"role": "user", "content": "Now optimize it."},

259 ],

260 tools=tools,

261 tool_choice="none",

262 )

263 print(wait_for_response(connection).output_text)

88```264```

89 265 

90 266 


115 291 

116Start a new chain by omitting `previous_response_id` or setting it to `null`. Pass the compacted output as-is; do not prune the returned window.292Start a new chain by omitting `previous_response_id` or setting it to `null`. Pass the compacted output as-is; do not prune the returned window.

117 293 

294```javascript

295import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";

296 

297// Compact your current window with an HTTP request.

298const compacted = await client.responses.compact({

299 model: "gpt-6-astra",

300 input: longInputItems,

301});

302const nextInput = toResponseInputItems(compacted.output);

303nextInput.push({

304 type: "message",

305 role: "user",

306 content: [{ type: "input_text", text: "Continue from here." }],

307});

308 

309// Start a new response on the WebSocket using the compacted window.

310const ws = new ResponsesWS(client);

311try {

312 ws.send({

313 type: "response.create",

314 stream_id: "main",

315 model: "gpt-6-astra",

316 store: false,

317 input: nextInput,

318 tools: [],

319 });

320 let completed = false;

321 for await (const event of ws) {

322 if (event.type === "error") throw event.error;

323 if (event.type !== "message") continue;

324 const message = event.message;

325 if (message.type === "response.output_text.delta") {

326 process.stdout.write(message.delta);

327 } else if (message.type === "response.completed") {

328 completed = true;

329 break;

330 } else if (

331 message.type === "response.failed" ||

332 message.type === "response.incomplete"

333 ) {

334 throw new Error(JSON.stringify(message));

335 }

336 }

337 if (!completed)

338 throw new Error("Connection closed before the response finished.");

339} finally {

340 ws.close();

341}

342```

343 

118```python344```python

119# Compact your current window (HTTP call)345from typing import cast

346 

347from openai import OpenAI

348from openai.types.responses import ResponseInputParam

349 

350# Compact your current window (HTTP call).

120compacted = client.responses.compact(351compacted = client.responses.compact(

121 model="gpt-6-astra",352 model="gpt-6-astra",

122 input=long_input_items_array,353 input=long_input_items_array,

123)354)

124 355next_input = cast(

125# Start a new response on the WebSocket using the compacted window356 ResponseInputParam,

126ws.send(357 [item.to_dict() for item in compacted.output],

127 json.dumps(358)

128 {359next_input.append(

129 "type": "response.create",

130 "stream_id": "main",

131 "model": "gpt-6-astra",

132 "store": False,

133 "input": [

134 *compacted.output,

135 {360 {

136 "type": "message",361 "type": "message",

137 "role": "user",362 "role": "user",

138 "content": [{"type": "input_text", "text": "Continue from here."}],363 "content": [{"type": "input_text", "text": "Continue from here."}],

139 },

140 ],

141 "tools": [],

142 }364 }

143 )

144)365)

366 

367# Start a new response on the WebSocket using the compacted window.

368with client.responses.connect() as connection:

369 connection.response.create(

370 stream_id="main",

371 model="gpt-6-astra",

372 store=False,

373 input=next_input,

374 tools=[],

375 )

376 for event in connection:

377 if event.type == "response.completed":

378 print(event.response.output_text)

379 break

380 if event.type in {"response.failed", "response.incomplete", "error"}:

381 raise RuntimeError(event.to_json())

145```382```

146 383 

147 384 


187 424 

188```text425```text

189# One socket, two independent conversations.426# One socket, two independent conversations.

190send_create("planner", "Draft a deployment plan.")427send_create(connection, "planner", "Draft a deployment plan.")

191send_create("research", "List deployment risks.")428send_create(connection, "research", "List deployment risks.")

192 429 

193# Fork the planner response, then continue the original branch in parallel.430# Fork the planner response, then continue the original branch in parallel.

194send_create(431send_create(

432 connection,

195 "critic",433 "critic",

196 "Find gaps in this plan.",434 "Find gaps in this plan.",

197 previous_response_id=planner_response_id,435 previous_response_id=planner_response_id,

198)436)

437wait_for_in_progress(connection, "critic")

199send_create(438send_create(

439 connection,

200 "planner",440 "planner",

201 "Add rollback steps.",441 "Add rollback steps.",

202 previous_response_id=planner_response_id,442 previous_response_id=planner_response_id,


207 447 

208Run parallel conversations, then fork one448Run parallel conversations, then fork one

209 449 

210```python450```javascript

211import json451import OpenAI from "openai";

212import os452import { ResponsesWS } from "openai/resources/responses/ws";

453 

454const client = new OpenAI();

455 

456/** @type {Map<string, string>} */

457const latestResponseIdByLane = new Map();

458 

459/**

460 * @param {ResponsesWS} ws

461 * @param {string} streamId

462 * @param {string} text

463 * @param {string} [previousResponseId]

464 */

465function sendCreate(

466 ws,

467 streamId,

468 text,

469 previousResponseId = latestResponseIdByLane.get(streamId)

470) {

471 ws.send({

472 type: "response.create",

473 stream_id: streamId,

474 model: "gpt-6-astra",

475 store: false,

476 input: [

477 {

478 type: "message",

479 role: "user",

480 content: [{ type: "input_text", text }],

481 },

482 ],

483 previous_response_id: previousResponseId,

484 });

485}

213 486 

214from websocket import create_connection487/** @param {ReturnType<ResponsesWS["stream"]>} events */

488async function readMessage(events) {

489 while (true) {

490 const { value: event, done } = await events.next();

491 if (done)

492 throw new Error("Connection closed before all responses finished.");

493 if (event.type === "error") throw event.error;

494 if (event.type !== "message") continue;

495 const message = event.message;

496 if (

497 message.type === "response.failed" ||

498 message.type === "response.incomplete"

499 ) {

500 throw new Error(

501 `Lane ${message.stream_id} failed: ${JSON.stringify(message)}`

502 );

503 }

504 return message;

505 }

506}

215 507 

216ws = create_connection(508/** @param {ReturnType<ResponsesWS["stream"]>} events @param {Set<string>} expectedStreamIds */

217 "wss://api.openai.com/v1/responses",509async function drainUntilComplete(events, expectedStreamIds) {

218 header=[f"Authorization: Bearer {os.environ['OPENAI_API_KEY']}"],510 const remaining = new Set(expectedStreamIds);

219)511 while (remaining.size > 0) {

512 const message = await readMessage(events);

513 const streamId = message.stream_id;

514 if (!streamId || !remaining.has(streamId)) continue;

515 if (message.type === "response.completed") {

516 latestResponseIdByLane.set(streamId, message.response.id);

517 remaining.delete(streamId);

518 }

519 }

520}

521 

522/** @param {ReturnType<ResponsesWS["stream"]>} events @param {string} streamId */

523async function waitForInProgress(events, streamId) {

524 while (true) {

525 const message = await readMessage(events);

526 if (

527 message.type === "response.in_progress" &&

528 message.stream_id === streamId

529 )

530 return;

531 }

532}

220 533 

221latest_response_id_by_lane = {}534const ws = new ResponsesWS(client);

535// Keep one iterator so events stay queued while moving between phases.

536const events = ws.stream();

537try {

538 // Run two independent conversations in parallel.

539 sendCreate(

540 ws,

541 "planner",

542 "Draft a deployment plan for a stateless API service."

543 );

544 sendCreate(

545 ws,

546 "research",

547 "List common deployment risks for a stateless API service."

548 );

549 await drainUntilComplete(events, new Set(["planner", "research"]));

550 

551 // Fork the planner conversation and continue its original branch in parallel.

552 const plannerResponseId = latestResponseIdByLane.get("planner");

553 sendCreate(

554 ws,

555 "critic",

556 "Find gaps in this deployment plan.",

557 plannerResponseId

558 );

559 // Let the fork load its parent before advancing the original lane's cache.

560 await waitForInProgress(events, "critic");

561 sendCreate(

562 ws,

563 "planner",

564 "Add rollback and monitoring steps to the plan.",

565 plannerResponseId

566 );

567 await drainUntilComplete(events, new Set(["critic", "planner"]));

568} finally {

569 await events.return?.();

570 ws.close();

571}

572```

222 573 

574```python

575from openai import OpenAI

576from openai.resources.responses.responses import ResponsesConnection

577 

578client = OpenAI()

579latest_response_id_by_lane: dict[str, str] = {}

223 580 

224def send_create(stream_id, text, previous_response_id=None):581 

225 payload = {582def send_create(

226 "type": "response.create",583 connection: ResponsesConnection,

227 "stream_id": stream_id,584 stream_id: str,

228 "model": "gpt-6-astra",585 text: str,

229 "store": False,586 previous_response_id: str | None = None,

230 "input": [587):

588 if previous_response_id is None:

589 previous_response_id = latest_response_id_by_lane.get(stream_id)

590 connection.response.create(

591 stream_id=stream_id,

592 model="gpt-6-astra",

593 store=False,

594 input=[

231 {595 {

232 "type": "message",596 "type": "message",

233 "role": "user",597 "role": "user",

234 "content": [{"type": "input_text", "text": text}],598 "content": [{"type": "input_text", "text": text}],

235 }599 }

236 ],600 ],

237 }601 previous_response_id=previous_response_id,

238 if previous_response_id is None:602 )

239 previous_response_id = latest_response_id_by_lane.get(stream_id)

240 if previous_response_id:

241 payload["previous_response_id"] = previous_response_id

242 ws.send(json.dumps(payload))

243 

244 603 

245def drain_until_complete(expected_stream_ids):

246 completed = set()

247 while completed != expected_stream_ids:

248 event = json.loads(ws.recv())

249 stream_id = event.get("stream_id")

250 event_type = event.get("type")

251 604 

252 if event_type == "error" and stream_id is None:605def drain_until_complete(

253 raise RuntimeError(f"connection error: {event}")606 connection: ResponsesConnection, expected_stream_ids: set[str]

254 if stream_id not in expected_stream_ids:607):

608 completed: set[str] = set()

609 for event in connection:

610 stream_id = event.stream_id

611 if event.type == "error" and stream_id is None:

612 raise RuntimeError(f"Connection error: {event.to_json()}")

613 if stream_id is None or stream_id not in expected_stream_ids:

255 continue614 continue

256 615 

257 if event_type == "response.completed":616 if event.type == "response.completed":

258 latest_response_id_by_lane[stream_id] = event["response"]["id"]617 latest_response_id_by_lane[stream_id] = event.response.id

259 completed.add(stream_id)618 completed.add(stream_id)

260 elif event_type in {"response.failed", "response.incomplete", "error"}:619 if completed == expected_stream_ids:

261 raise RuntimeError(f"lane {stream_id} failed: {event}")620 return

621 elif event.type in {"response.failed", "response.incomplete", "error"}:

622 raise RuntimeError(f"Lane {stream_id} failed: {event.to_json()}")

623 raise RuntimeError("Connection closed before all responses finished.")

624 

625 

626def wait_for_in_progress(connection: ResponsesConnection, expected_stream_id: str):

627 for event in connection:

628 if event.type == "error" and event.stream_id is None:

629 raise RuntimeError(f"Connection error: {event.to_json()}")

630 if event.stream_id != expected_stream_id:

631 continue

632 if event.type == "response.in_progress":

633 return

634 if event.type in {"response.failed", "response.incomplete", "error"}:

635 raise RuntimeError(f"Lane {expected_stream_id} failed: {event.to_json()}")

636 raise RuntimeError("Connection closed before the fork started.")

262 637 

263 638 

264# 1. Run two independent conversations in parallel.639with client.responses.connect() as connection:

265send_create("planner", "Draft a deployment plan for a stateless API service.")640 # 1. Run two independent conversations in parallel.

266send_create("research", "List common deployment risks for a stateless API service.")641 send_create(

267drain_until_complete({"planner", "research"})642 connection, "planner", "Draft a deployment plan for a stateless API service."

643 )

644 send_create(

645 connection,

646 "research",

647 "List common deployment risks for a stateless API service.",

648 )

649 drain_until_complete(connection, {"planner", "research"})

268 650 

269# 2. Fork the planner conversation and continue the original branch in parallel.651 # 2. Fork the planner conversation and continue the original branch in parallel.

270planner_response_id = latest_response_id_by_lane["planner"]652 planner_response_id = latest_response_id_by_lane["planner"]

271send_create(653 send_create(

654 connection,

272 "critic",655 "critic",

273 "Find gaps in this deployment plan.",656 "Find gaps in this deployment plan.",

274 previous_response_id=planner_response_id,657 previous_response_id=planner_response_id,

275)658 )

276send_create(659 # Let the fork bind its parent before advancing the original lane.

660 wait_for_in_progress(connection, "critic")

661 send_create(

662 connection,

277 "planner",663 "planner",

278 "Add rollback and monitoring steps to the plan.",664 "Add rollback and monitoring steps to the plan.",

279 previous_response_id=planner_response_id,665 previous_response_id=planner_response_id,

280)666 )

281drain_until_complete({"critic", "planner"})667 drain_until_complete(connection, {"critic", "planner"})

282 

283ws.close()

284```668```

285 669 

286 670 

Details

74 74 

75Before configuring workload identity federation, export the AWS-issued token as `TOKEN`, then run this script locally to inspect its claims:75Before configuring workload identity federation, export the AWS-issued token as `TOKEN`, then run this script locally to inspect its claims:

76 76 

77```javascript

78const parts = process.env.TOKEN?.split(".") ?? [];

79if (parts.length !== 3) {

80 throw new Error("Expected a compact JWT with three segments");

81}

82if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

83 throw new Error("JWT payload is not valid Base64URL");

84}

85 

86const bytes = Buffer.from(parts[1], "base64url");

87if (bytes.toString("base64url") !== parts[1]) {

88 throw new Error("JWT payload is not valid Base64URL");

89}

90const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

91const claims = JSON.parse(decoded);

92if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

93 throw new Error("JWT payload is not a JSON object");

94}

95console.log(decoded);

96```

97 

77```python98```python

78import base6499import base64

79import json100import json

80import os101import os

102import re

103 

104 

105def reject_non_json_constant(value):

106 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

107 

108 

109parts = os.environ.get("TOKEN", "").split(".")

110if len(parts) != 3:

111 raise ValueError("Expected a compact JWT with three segments")

112 

113payload = parts[1]

114if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

115 raise ValueError("JWT payload is not valid Base64URL")

116padded_payload = payload + "=" * (-len(payload) % 4)

117decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

118if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

119 raise ValueError("JWT payload is not valid Base64URL")

120decoded_text = decoded.decode("utf-8")

121claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

122if not isinstance(claims, dict):

123 raise ValueError("JWT payload is not a JSON object")

124print(decoded_text)

125```

126 

127```go

128package main

129 

130import (

131 "bytes"

132 "encoding/base64"

133 "encoding/json"

134 "fmt"

135 "os"

136 "strings"

137 "unicode/utf8"

138)

139 

140func decodeSegment(segment string) (json.RawMessage, error) {

141 if !isBase64URLSegment(segment) {

142 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

143 }

144 decoded, err := base64.RawURLEncoding.DecodeString(segment)

145 if err != nil {

146 return nil, err

147 }

148 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

149 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

150 }

151 if !utf8.Valid(decoded) {

152 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

153 }

154 

155 var value json.RawMessage

156 if err := json.Unmarshal(decoded, &value); err != nil {

157 return nil, err

158 }

159 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

160 return nil, fmt.Errorf("JWT segment is not a JSON object")

161 }

162 return value, nil

163}

164 

165func isBase64URLSegment(segment string) bool {

166 if segment == "" || len(segment)%4 == 1 {

167 return false

168 }

169 for _, character := range segment {

170 if !('A' <= character && character <= 'Z') &&

171 !('a' <= character && character <= 'z') &&

172 !('0' <= character && character <= '9') &&

173 character != '-' &&

174 character != '_' {

175 return false

176 }

177 }

178 return true

179}

180 

181func main() {

182 parts := strings.Split(os.Getenv("TOKEN"), ".")

183 if len(parts) != 3 {

184 panic("Expected a compact JWT with three segments")

185 }

186 

187 payload, err := decodeSegment(parts[1])

188 if err != nil {

189 panic(err)

190 }

191 formatted, err := json.MarshalIndent(payload, "", " ")

192 if err != nil {

193 panic(err)

194 }

195 fmt.Println(string(formatted))

196}

197```

198 

199```java

200// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

201import com.fasterxml.jackson.databind.DeserializationFeature;

202import com.fasterxml.jackson.databind.JsonNode;

203import com.fasterxml.jackson.databind.ObjectMapper;

204import java.io.IOException;

205import java.nio.ByteBuffer;

206import java.nio.charset.CharacterCodingException;

207import java.nio.charset.CodingErrorAction;

208import java.nio.charset.StandardCharsets;

209import java.util.Base64;

210 

211public final class DecodeJwtPayloadExample {

212 private static final ObjectMapper JSON =

213 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

214 

215 private DecodeJwtPayloadExample() {}

216 

217 static String decodeUtf8(byte[] bytes) throws IOException {

218 try {

219 return StandardCharsets.UTF_8

220 .newDecoder()

221 .onMalformedInput(CodingErrorAction.REPORT)

222 .onUnmappableCharacter(CodingErrorAction.REPORT)

223 .decode(ByteBuffer.wrap(bytes))

224 .toString();

225 } catch (CharacterCodingException exception) {

226 throw new IOException("JWT segment is not valid UTF-8", exception);

227 }

228 }

229 

230 static String decodeSegment(String segment) throws IOException {

231 if (!isBase64UrlSegment(segment)) {

232 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

233 }

234 byte[] bytes = Base64.getUrlDecoder().decode(segment);

235 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

236 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

237 }

238 String decoded = decodeUtf8(bytes);

239 JsonNode value = JSON.readTree(decoded);

240 if (value == null || value.isMissingNode() || !value.isObject()) {

241 throw new IOException("JWT segment is not a JSON object");

242 }

243 return decoded;

244 }

245 

246 static boolean isBase64UrlSegment(String segment) {

247 if (segment.isEmpty() || segment.length() % 4 == 1) {

248 return false;

249 }

250 return segment

251 .chars()

252 .allMatch(

253 character ->

254 character >= 'A' && character <= 'Z'

255 || character >= 'a' && character <= 'z'

256 || character >= '0' && character <= '9'

257 || character == '-'

258 || character == '_');

259 }

260 

261 static String[] requireCompactJwt(String token) {

262 if (token == null) {

263 throw new IllegalArgumentException("Expected a compact JWT with three segments");

264 }

265 String[] parts = token.split("\\.", -1);

266 if (parts.length != 3) {

267 throw new IllegalArgumentException("Expected a compact JWT with three segments");

268 }

269 return parts;

270 }

271 

272 public static void main(String[] args) throws IOException {

273 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

274 System.out.println(decodeSegment(parts[1]));

275 }

276}

277```

278 

279```csharp

280using System.Text;

281using System.Text.Json;

282 

283static string DecodeSegment(string segment)

284{

285 if (

286 segment.Length % 4 == 1 ||

287 segment.Any(

288 character =>

289 !(

290 character is >= 'A' and <= 'Z' ||

291 character is >= 'a' and <= 'z' ||

292 character is >= '0' and <= '9' ||

293 character is '-' or '_'

294 )

295 )

296 )

297 {

298 throw new FormatException("JWT segment is not valid Base64URL");

299 }

300 

301 byte[] decoded = Convert.FromBase64String(

302 segment.Replace('-', '+').Replace('_', '/') +

303 new string('=', (4 - segment.Length % 4) % 4)

304 );

305 string canonicalSegment = Convert

306 .ToBase64String(decoded)

307 .TrimEnd('=')

308 .Replace('+', '-')

309 .Replace('/', '_');

310 if (canonicalSegment != segment)

311 {

312 throw new FormatException("JWT segment is not valid Base64URL");

313 }

314 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

315 using JsonDocument document = JsonDocument.Parse(decodedJson);

316 if (document.RootElement.ValueKind is not JsonValueKind.Object)

317 {

318 throw new FormatException("JWT segment is not a JSON object");

319 }

320 return decodedJson;

321}

322 

323string? token = Environment.GetEnvironmentVariable("TOKEN");

324if (token is null)

325{

326 throw new InvalidOperationException(

327 "Expected a compact JWT with three segments"

328 );

329}

330string[] parts = token.Split('.');

331if (parts.Length != 3)

332{

333 throw new InvalidOperationException(

334 "Expected a compact JWT with three segments"

335 );

336}

337 

338Console.WriteLine(DecodeSegment(parts[1]));

339```

340 

341```ruby

342require "base64"

343require "json"

344 

345parts = ENV.fetch("TOKEN", "").split(".", -1)

346raise "Expected a compact JWT with three segments" unless parts.length == 3

347 

348unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

349 raise "JWT payload is not valid Base64URL"

350end

351 

352begin

353 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

354rescue ArgumentError

355 raise "JWT payload is not valid Base64URL"

356end

357unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

358 raise "JWT payload is not valid Base64URL"

359end

360payload.force_encoding(Encoding::UTF_8)

361raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

362 

363claims = JSON.parse(payload)

364raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

81 365 

82payload = os.environ["TOKEN"].split(".")[1]366puts(payload)

83payload += "=" * (-len(payload) % 4)

84print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

85```367```

86 368 

87 369 


578 860 

579Then run this script:861Then run this script:

580 862 

863```javascript

864const parts = process.env.TOKEN?.split(".") ?? [];

865if (parts.length !== 3) {

866 throw new Error("Expected a compact JWT with three segments");

867}

868if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

869 throw new Error("JWT payload is not valid Base64URL");

870}

871 

872const bytes = Buffer.from(parts[1], "base64url");

873if (bytes.toString("base64url") !== parts[1]) {

874 throw new Error("JWT payload is not valid Base64URL");

875}

876const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

877const claims = JSON.parse(decoded);

878if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

879 throw new Error("JWT payload is not a JSON object");

880}

881console.log(decoded);

882```

883 

581```python884```python

582import base64885import base64

583import json886import json

584import os887import os

888import re

889 

890 

891def reject_non_json_constant(value):

892 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

893 

894 

895parts = os.environ.get("TOKEN", "").split(".")

896if len(parts) != 3:

897 raise ValueError("Expected a compact JWT with three segments")

898 

899payload = parts[1]

900if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

901 raise ValueError("JWT payload is not valid Base64URL")

902padded_payload = payload + "=" * (-len(payload) % 4)

903decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

904if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

905 raise ValueError("JWT payload is not valid Base64URL")

906decoded_text = decoded.decode("utf-8")

907claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

908if not isinstance(claims, dict):

909 raise ValueError("JWT payload is not a JSON object")

910print(decoded_text)

911```

912 

913```go

914package main

915 

916import (

917 "bytes"

918 "encoding/base64"

919 "encoding/json"

920 "fmt"

921 "os"

922 "strings"

923 "unicode/utf8"

924)

925 

926func decodeSegment(segment string) (json.RawMessage, error) {

927 if !isBase64URLSegment(segment) {

928 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

929 }

930 decoded, err := base64.RawURLEncoding.DecodeString(segment)

931 if err != nil {

932 return nil, err

933 }

934 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

935 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

936 }

937 if !utf8.Valid(decoded) {

938 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

939 }

940 

941 var value json.RawMessage

942 if err := json.Unmarshal(decoded, &value); err != nil {

943 return nil, err

944 }

945 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

946 return nil, fmt.Errorf("JWT segment is not a JSON object")

947 }

948 return value, nil

949}

950 

951func isBase64URLSegment(segment string) bool {

952 if segment == "" || len(segment)%4 == 1 {

953 return false

954 }

955 for _, character := range segment {

956 if !('A' <= character && character <= 'Z') &&

957 !('a' <= character && character <= 'z') &&

958 !('0' <= character && character <= '9') &&

959 character != '-' &&

960 character != '_' {

961 return false

962 }

963 }

964 return true

965}

966 

967func main() {

968 parts := strings.Split(os.Getenv("TOKEN"), ".")

969 if len(parts) != 3 {

970 panic("Expected a compact JWT with three segments")

971 }

972 

973 payload, err := decodeSegment(parts[1])

974 if err != nil {

975 panic(err)

976 }

977 formatted, err := json.MarshalIndent(payload, "", " ")

978 if err != nil {

979 panic(err)

980 }

981 fmt.Println(string(formatted))

982}

983```

984 

985```java

986// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

987import com.fasterxml.jackson.databind.DeserializationFeature;

988import com.fasterxml.jackson.databind.JsonNode;

989import com.fasterxml.jackson.databind.ObjectMapper;

990import java.io.IOException;

991import java.nio.ByteBuffer;

992import java.nio.charset.CharacterCodingException;

993import java.nio.charset.CodingErrorAction;

994import java.nio.charset.StandardCharsets;

995import java.util.Base64;

996 

997public final class DecodeJwtPayloadExample {

998 private static final ObjectMapper JSON =

999 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

1000 

1001 private DecodeJwtPayloadExample() {}

1002 

1003 static String decodeUtf8(byte[] bytes) throws IOException {

1004 try {

1005 return StandardCharsets.UTF_8

1006 .newDecoder()

1007 .onMalformedInput(CodingErrorAction.REPORT)

1008 .onUnmappableCharacter(CodingErrorAction.REPORT)

1009 .decode(ByteBuffer.wrap(bytes))

1010 .toString();

1011 } catch (CharacterCodingException exception) {

1012 throw new IOException("JWT segment is not valid UTF-8", exception);

1013 }

1014 }

1015 

1016 static String decodeSegment(String segment) throws IOException {

1017 if (!isBase64UrlSegment(segment)) {

1018 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

1019 }

1020 byte[] bytes = Base64.getUrlDecoder().decode(segment);

1021 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

1022 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

1023 }

1024 String decoded = decodeUtf8(bytes);

1025 JsonNode value = JSON.readTree(decoded);

1026 if (value == null || value.isMissingNode() || !value.isObject()) {

1027 throw new IOException("JWT segment is not a JSON object");

1028 }

1029 return decoded;

1030 }

1031 

1032 static boolean isBase64UrlSegment(String segment) {

1033 if (segment.isEmpty() || segment.length() % 4 == 1) {

1034 return false;

1035 }

1036 return segment

1037 .chars()

1038 .allMatch(

1039 character ->

1040 character >= 'A' && character <= 'Z'

1041 || character >= 'a' && character <= 'z'

1042 || character >= '0' && character <= '9'

1043 || character == '-'

1044 || character == '_');

1045 }

1046 

1047 static String[] requireCompactJwt(String token) {

1048 if (token == null) {

1049 throw new IllegalArgumentException("Expected a compact JWT with three segments");

1050 }

1051 String[] parts = token.split("\\.", -1);

1052 if (parts.length != 3) {

1053 throw new IllegalArgumentException("Expected a compact JWT with three segments");

1054 }

1055 return parts;

1056 }

1057 

1058 public static void main(String[] args) throws IOException {

1059 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

1060 System.out.println(decodeSegment(parts[1]));

1061 }

1062}

1063```

1064 

1065```csharp

1066using System.Text;

1067using System.Text.Json;

1068 

1069static string DecodeSegment(string segment)

1070{

1071 if (

1072 segment.Length % 4 == 1 ||

1073 segment.Any(

1074 character =>

1075 !(

1076 character is >= 'A' and <= 'Z' ||

1077 character is >= 'a' and <= 'z' ||

1078 character is >= '0' and <= '9' ||

1079 character is '-' or '_'

1080 )

1081 )

1082 )

1083 {

1084 throw new FormatException("JWT segment is not valid Base64URL");

1085 }

1086 

1087 byte[] decoded = Convert.FromBase64String(

1088 segment.Replace('-', '+').Replace('_', '/') +

1089 new string('=', (4 - segment.Length % 4) % 4)

1090 );

1091 string canonicalSegment = Convert

1092 .ToBase64String(decoded)

1093 .TrimEnd('=')

1094 .Replace('+', '-')

1095 .Replace('/', '_');

1096 if (canonicalSegment != segment)

1097 {

1098 throw new FormatException("JWT segment is not valid Base64URL");

1099 }

1100 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

1101 using JsonDocument document = JsonDocument.Parse(decodedJson);

1102 if (document.RootElement.ValueKind is not JsonValueKind.Object)

1103 {

1104 throw new FormatException("JWT segment is not a JSON object");

1105 }

1106 return decodedJson;

1107}

1108 

1109string? token = Environment.GetEnvironmentVariable("TOKEN");

1110if (token is null)

1111{

1112 throw new InvalidOperationException(

1113 "Expected a compact JWT with three segments"

1114 );

1115}

1116string[] parts = token.Split('.');

1117if (parts.Length != 3)

1118{

1119 throw new InvalidOperationException(

1120 "Expected a compact JWT with three segments"

1121 );

1122}

1123 

1124Console.WriteLine(DecodeSegment(parts[1]));

1125```

1126 

1127```ruby

1128require "base64"

1129require "json"

1130 

1131parts = ENV.fetch("TOKEN", "").split(".", -1)

1132raise "Expected a compact JWT with three segments" unless parts.length == 3

1133 

1134unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

1135 raise "JWT payload is not valid Base64URL"

1136end

1137 

1138begin

1139 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

1140rescue ArgumentError

1141 raise "JWT payload is not valid Base64URL"

1142end

1143unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

1144 raise "JWT payload is not valid Base64URL"

1145end

1146payload.force_encoding(Encoding::UTF_8)

1147raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

1148 

1149claims = JSON.parse(payload)

1150raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

585 1151 

586payload = os.environ["TOKEN"].split(".")[1]1152puts(payload)

587payload += "=" * (-len(payload) % 4)

588print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

589```1153```

590 1154 

591 1155 

Details

50 50 

51Before configuring workload identity federation, export the GitHub OIDC token as `TOKEN`, then run this script in the workflow runner to inspect its claims:51Before configuring workload identity federation, export the GitHub OIDC token as `TOKEN`, then run this script in the workflow runner to inspect its claims:

52 52 

53```javascript

54const parts = process.env.TOKEN?.split(".") ?? [];

55if (parts.length !== 3) {

56 throw new Error("Expected a compact JWT with three segments");

57}

58if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

59 throw new Error("JWT payload is not valid Base64URL");

60}

61 

62const bytes = Buffer.from(parts[1], "base64url");

63if (bytes.toString("base64url") !== parts[1]) {

64 throw new Error("JWT payload is not valid Base64URL");

65}

66const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

67const claims = JSON.parse(decoded);

68if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

69 throw new Error("JWT payload is not a JSON object");

70}

71console.log(decoded);

72```

73 

53```python74```python

54import base6475import base64

55import json76import json

56import os77import os

78import re

79 

80 

81def reject_non_json_constant(value):

82 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

83 

84 

85parts = os.environ.get("TOKEN", "").split(".")

86if len(parts) != 3:

87 raise ValueError("Expected a compact JWT with three segments")

88 

89payload = parts[1]

90if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

91 raise ValueError("JWT payload is not valid Base64URL")

92padded_payload = payload + "=" * (-len(payload) % 4)

93decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

94if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

95 raise ValueError("JWT payload is not valid Base64URL")

96decoded_text = decoded.decode("utf-8")

97claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

98if not isinstance(claims, dict):

99 raise ValueError("JWT payload is not a JSON object")

100print(decoded_text)

101```

102 

103```go

104package main

105 

106import (

107 "bytes"

108 "encoding/base64"

109 "encoding/json"

110 "fmt"

111 "os"

112 "strings"

113 "unicode/utf8"

114)

115 

116func decodeSegment(segment string) (json.RawMessage, error) {

117 if !isBase64URLSegment(segment) {

118 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

119 }

120 decoded, err := base64.RawURLEncoding.DecodeString(segment)

121 if err != nil {

122 return nil, err

123 }

124 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

125 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

126 }

127 if !utf8.Valid(decoded) {

128 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

129 }

130 

131 var value json.RawMessage

132 if err := json.Unmarshal(decoded, &value); err != nil {

133 return nil, err

134 }

135 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

136 return nil, fmt.Errorf("JWT segment is not a JSON object")

137 }

138 return value, nil

139}

140 

141func isBase64URLSegment(segment string) bool {

142 if segment == "" || len(segment)%4 == 1 {

143 return false

144 }

145 for _, character := range segment {

146 if !('A' <= character && character <= 'Z') &&

147 !('a' <= character && character <= 'z') &&

148 !('0' <= character && character <= '9') &&

149 character != '-' &&

150 character != '_' {

151 return false

152 }

153 }

154 return true

155}

156 

157func main() {

158 parts := strings.Split(os.Getenv("TOKEN"), ".")

159 if len(parts) != 3 {

160 panic("Expected a compact JWT with three segments")

161 }

162 

163 payload, err := decodeSegment(parts[1])

164 if err != nil {

165 panic(err)

166 }

167 formatted, err := json.MarshalIndent(payload, "", " ")

168 if err != nil {

169 panic(err)

170 }

171 fmt.Println(string(formatted))

172}

173```

174 

175```java

176// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

177import com.fasterxml.jackson.databind.DeserializationFeature;

178import com.fasterxml.jackson.databind.JsonNode;

179import com.fasterxml.jackson.databind.ObjectMapper;

180import java.io.IOException;

181import java.nio.ByteBuffer;

182import java.nio.charset.CharacterCodingException;

183import java.nio.charset.CodingErrorAction;

184import java.nio.charset.StandardCharsets;

185import java.util.Base64;

186 

187public final class DecodeJwtPayloadExample {

188 private static final ObjectMapper JSON =

189 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

190 

191 private DecodeJwtPayloadExample() {}

192 

193 static String decodeUtf8(byte[] bytes) throws IOException {

194 try {

195 return StandardCharsets.UTF_8

196 .newDecoder()

197 .onMalformedInput(CodingErrorAction.REPORT)

198 .onUnmappableCharacter(CodingErrorAction.REPORT)

199 .decode(ByteBuffer.wrap(bytes))

200 .toString();

201 } catch (CharacterCodingException exception) {

202 throw new IOException("JWT segment is not valid UTF-8", exception);

203 }

204 }

205 

206 static String decodeSegment(String segment) throws IOException {

207 if (!isBase64UrlSegment(segment)) {

208 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

209 }

210 byte[] bytes = Base64.getUrlDecoder().decode(segment);

211 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

212 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

213 }

214 String decoded = decodeUtf8(bytes);

215 JsonNode value = JSON.readTree(decoded);

216 if (value == null || value.isMissingNode() || !value.isObject()) {

217 throw new IOException("JWT segment is not a JSON object");

218 }

219 return decoded;

220 }

221 

222 static boolean isBase64UrlSegment(String segment) {

223 if (segment.isEmpty() || segment.length() % 4 == 1) {

224 return false;

225 }

226 return segment

227 .chars()

228 .allMatch(

229 character ->

230 character >= 'A' && character <= 'Z'

231 || character >= 'a' && character <= 'z'

232 || character >= '0' && character <= '9'

233 || character == '-'

234 || character == '_');

235 }

236 

237 static String[] requireCompactJwt(String token) {

238 if (token == null) {

239 throw new IllegalArgumentException("Expected a compact JWT with three segments");

240 }

241 String[] parts = token.split("\\.", -1);

242 if (parts.length != 3) {

243 throw new IllegalArgumentException("Expected a compact JWT with three segments");

244 }

245 return parts;

246 }

247 

248 public static void main(String[] args) throws IOException {

249 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

250 System.out.println(decodeSegment(parts[1]));

251 }

252}

253```

254 

255```csharp

256using System.Text;

257using System.Text.Json;

258 

259static string DecodeSegment(string segment)

260{

261 if (

262 segment.Length % 4 == 1 ||

263 segment.Any(

264 character =>

265 !(

266 character is >= 'A' and <= 'Z' ||

267 character is >= 'a' and <= 'z' ||

268 character is >= '0' and <= '9' ||

269 character is '-' or '_'

270 )

271 )

272 )

273 {

274 throw new FormatException("JWT segment is not valid Base64URL");

275 }

276 

277 byte[] decoded = Convert.FromBase64String(

278 segment.Replace('-', '+').Replace('_', '/') +

279 new string('=', (4 - segment.Length % 4) % 4)

280 );

281 string canonicalSegment = Convert

282 .ToBase64String(decoded)

283 .TrimEnd('=')

284 .Replace('+', '-')

285 .Replace('/', '_');

286 if (canonicalSegment != segment)

287 {

288 throw new FormatException("JWT segment is not valid Base64URL");

289 }

290 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

291 using JsonDocument document = JsonDocument.Parse(decodedJson);

292 if (document.RootElement.ValueKind is not JsonValueKind.Object)

293 {

294 throw new FormatException("JWT segment is not a JSON object");

295 }

296 return decodedJson;

297}

298 

299string? token = Environment.GetEnvironmentVariable("TOKEN");

300if (token is null)

301{

302 throw new InvalidOperationException(

303 "Expected a compact JWT with three segments"

304 );

305}

306string[] parts = token.Split('.');

307if (parts.Length != 3)

308{

309 throw new InvalidOperationException(

310 "Expected a compact JWT with three segments"

311 );

312}

313 

314Console.WriteLine(DecodeSegment(parts[1]));

315```

316 

317```ruby

318require "base64"

319require "json"

320 

321parts = ENV.fetch("TOKEN", "").split(".", -1)

322raise "Expected a compact JWT with three segments" unless parts.length == 3

323 

324unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

325 raise "JWT payload is not valid Base64URL"

326end

327 

328begin

329 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

330rescue ArgumentError

331 raise "JWT payload is not valid Base64URL"

332end

333unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

334 raise "JWT payload is not valid Base64URL"

335end

336payload.force_encoding(Encoding::UTF_8)

337raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

338 

339claims = JSON.parse(payload)

340raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

57 341 

58payload = os.environ["TOKEN"].split(".")[1]342puts(payload)

59payload += "=" * (-len(payload) % 4)

60print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

61```343```

62 344 

63 345 

Details

50 50 

51Before configuring workload identity federation, export the Google identity token as `TOKEN`, then run this script locally to inspect its claims:51Before configuring workload identity federation, export the Google identity token as `TOKEN`, then run this script locally to inspect its claims:

52 52 

53```javascript

54const parts = process.env.TOKEN?.split(".") ?? [];

55if (parts.length !== 3) {

56 throw new Error("Expected a compact JWT with three segments");

57}

58if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

59 throw new Error("JWT payload is not valid Base64URL");

60}

61 

62const bytes = Buffer.from(parts[1], "base64url");

63if (bytes.toString("base64url") !== parts[1]) {

64 throw new Error("JWT payload is not valid Base64URL");

65}

66const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

67const claims = JSON.parse(decoded);

68if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

69 throw new Error("JWT payload is not a JSON object");

70}

71console.log(decoded);

72```

73 

53```python74```python

54import base6475import base64

55import json76import json

56import os77import os

78import re

79 

80 

81def reject_non_json_constant(value):

82 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

83 

84 

85parts = os.environ.get("TOKEN", "").split(".")

86if len(parts) != 3:

87 raise ValueError("Expected a compact JWT with three segments")

88 

89payload = parts[1]

90if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

91 raise ValueError("JWT payload is not valid Base64URL")

92padded_payload = payload + "=" * (-len(payload) % 4)

93decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

94if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

95 raise ValueError("JWT payload is not valid Base64URL")

96decoded_text = decoded.decode("utf-8")

97claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

98if not isinstance(claims, dict):

99 raise ValueError("JWT payload is not a JSON object")

100print(decoded_text)

101```

102 

103```go

104package main

105 

106import (

107 "bytes"

108 "encoding/base64"

109 "encoding/json"

110 "fmt"

111 "os"

112 "strings"

113 "unicode/utf8"

114)

115 

116func decodeSegment(segment string) (json.RawMessage, error) {

117 if !isBase64URLSegment(segment) {

118 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

119 }

120 decoded, err := base64.RawURLEncoding.DecodeString(segment)

121 if err != nil {

122 return nil, err

123 }

124 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

125 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

126 }

127 if !utf8.Valid(decoded) {

128 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

129 }

130 

131 var value json.RawMessage

132 if err := json.Unmarshal(decoded, &value); err != nil {

133 return nil, err

134 }

135 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

136 return nil, fmt.Errorf("JWT segment is not a JSON object")

137 }

138 return value, nil

139}

140 

141func isBase64URLSegment(segment string) bool {

142 if segment == "" || len(segment)%4 == 1 {

143 return false

144 }

145 for _, character := range segment {

146 if !('A' <= character && character <= 'Z') &&

147 !('a' <= character && character <= 'z') &&

148 !('0' <= character && character <= '9') &&

149 character != '-' &&

150 character != '_' {

151 return false

152 }

153 }

154 return true

155}

156 

157func main() {

158 parts := strings.Split(os.Getenv("TOKEN"), ".")

159 if len(parts) != 3 {

160 panic("Expected a compact JWT with three segments")

161 }

162 

163 payload, err := decodeSegment(parts[1])

164 if err != nil {

165 panic(err)

166 }

167 formatted, err := json.MarshalIndent(payload, "", " ")

168 if err != nil {

169 panic(err)

170 }

171 fmt.Println(string(formatted))

172}

173```

174 

175```java

176// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

177import com.fasterxml.jackson.databind.DeserializationFeature;

178import com.fasterxml.jackson.databind.JsonNode;

179import com.fasterxml.jackson.databind.ObjectMapper;

180import java.io.IOException;

181import java.nio.ByteBuffer;

182import java.nio.charset.CharacterCodingException;

183import java.nio.charset.CodingErrorAction;

184import java.nio.charset.StandardCharsets;

185import java.util.Base64;

186 

187public final class DecodeJwtPayloadExample {

188 private static final ObjectMapper JSON =

189 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

190 

191 private DecodeJwtPayloadExample() {}

192 

193 static String decodeUtf8(byte[] bytes) throws IOException {

194 try {

195 return StandardCharsets.UTF_8

196 .newDecoder()

197 .onMalformedInput(CodingErrorAction.REPORT)

198 .onUnmappableCharacter(CodingErrorAction.REPORT)

199 .decode(ByteBuffer.wrap(bytes))

200 .toString();

201 } catch (CharacterCodingException exception) {

202 throw new IOException("JWT segment is not valid UTF-8", exception);

203 }

204 }

205 

206 static String decodeSegment(String segment) throws IOException {

207 if (!isBase64UrlSegment(segment)) {

208 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

209 }

210 byte[] bytes = Base64.getUrlDecoder().decode(segment);

211 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

212 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

213 }

214 String decoded = decodeUtf8(bytes);

215 JsonNode value = JSON.readTree(decoded);

216 if (value == null || value.isMissingNode() || !value.isObject()) {

217 throw new IOException("JWT segment is not a JSON object");

218 }

219 return decoded;

220 }

221 

222 static boolean isBase64UrlSegment(String segment) {

223 if (segment.isEmpty() || segment.length() % 4 == 1) {

224 return false;

225 }

226 return segment

227 .chars()

228 .allMatch(

229 character ->

230 character >= 'A' && character <= 'Z'

231 || character >= 'a' && character <= 'z'

232 || character >= '0' && character <= '9'

233 || character == '-'

234 || character == '_');

235 }

236 

237 static String[] requireCompactJwt(String token) {

238 if (token == null) {

239 throw new IllegalArgumentException("Expected a compact JWT with three segments");

240 }

241 String[] parts = token.split("\\.", -1);

242 if (parts.length != 3) {

243 throw new IllegalArgumentException("Expected a compact JWT with three segments");

244 }

245 return parts;

246 }

247 

248 public static void main(String[] args) throws IOException {

249 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

250 System.out.println(decodeSegment(parts[1]));

251 }

252}

253```

254 

255```csharp

256using System.Text;

257using System.Text.Json;

258 

259static string DecodeSegment(string segment)

260{

261 if (

262 segment.Length % 4 == 1 ||

263 segment.Any(

264 character =>

265 !(

266 character is >= 'A' and <= 'Z' ||

267 character is >= 'a' and <= 'z' ||

268 character is >= '0' and <= '9' ||

269 character is '-' or '_'

270 )

271 )

272 )

273 {

274 throw new FormatException("JWT segment is not valid Base64URL");

275 }

276 

277 byte[] decoded = Convert.FromBase64String(

278 segment.Replace('-', '+').Replace('_', '/') +

279 new string('=', (4 - segment.Length % 4) % 4)

280 );

281 string canonicalSegment = Convert

282 .ToBase64String(decoded)

283 .TrimEnd('=')

284 .Replace('+', '-')

285 .Replace('/', '_');

286 if (canonicalSegment != segment)

287 {

288 throw new FormatException("JWT segment is not valid Base64URL");

289 }

290 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

291 using JsonDocument document = JsonDocument.Parse(decodedJson);

292 if (document.RootElement.ValueKind is not JsonValueKind.Object)

293 {

294 throw new FormatException("JWT segment is not a JSON object");

295 }

296 return decodedJson;

297}

298 

299string? token = Environment.GetEnvironmentVariable("TOKEN");

300if (token is null)

301{

302 throw new InvalidOperationException(

303 "Expected a compact JWT with three segments"

304 );

305}

306string[] parts = token.Split('.');

307if (parts.Length != 3)

308{

309 throw new InvalidOperationException(

310 "Expected a compact JWT with three segments"

311 );

312}

313 

314Console.WriteLine(DecodeSegment(parts[1]));

315```

316 

317```ruby

318require "base64"

319require "json"

320 

321parts = ENV.fetch("TOKEN", "").split(".", -1)

322raise "Expected a compact JWT with three segments" unless parts.length == 3

323 

324unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

325 raise "JWT payload is not valid Base64URL"

326end

327 

328begin

329 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

330rescue ArgumentError

331 raise "JWT payload is not valid Base64URL"

332end

333unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

334 raise "JWT payload is not valid Base64URL"

335end

336payload.force_encoding(Encoding::UTF_8)

337raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

338 

339claims = JSON.parse(payload)

340raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

57 341 

58payload = os.environ["TOKEN"].split(".")[1]342puts(payload)

59payload += "=" * (-len(payload) % 4)

60print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

61```343```

62 344 

63 345 


605 887 

606Then run this script:888Then run this script:

607 889 

890```javascript

891const parts = process.env.TOKEN?.split(".") ?? [];

892if (parts.length !== 3) {

893 throw new Error("Expected a compact JWT with three segments");

894}

895if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

896 throw new Error("JWT payload is not valid Base64URL");

897}

898 

899const bytes = Buffer.from(parts[1], "base64url");

900if (bytes.toString("base64url") !== parts[1]) {

901 throw new Error("JWT payload is not valid Base64URL");

902}

903const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

904const claims = JSON.parse(decoded);

905if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

906 throw new Error("JWT payload is not a JSON object");

907}

908console.log(decoded);

909```

910 

608```python911```python

609import base64912import base64

610import json913import json

611import os914import os

915import re

916 

917 

918def reject_non_json_constant(value):

919 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

920 

921 

922parts = os.environ.get("TOKEN", "").split(".")

923if len(parts) != 3:

924 raise ValueError("Expected a compact JWT with three segments")

925 

926payload = parts[1]

927if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

928 raise ValueError("JWT payload is not valid Base64URL")

929padded_payload = payload + "=" * (-len(payload) % 4)

930decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

931if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

932 raise ValueError("JWT payload is not valid Base64URL")

933decoded_text = decoded.decode("utf-8")

934claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

935if not isinstance(claims, dict):

936 raise ValueError("JWT payload is not a JSON object")

937print(decoded_text)

938```

939 

940```go

941package main

942 

943import (

944 "bytes"

945 "encoding/base64"

946 "encoding/json"

947 "fmt"

948 "os"

949 "strings"

950 "unicode/utf8"

951)

952 

953func decodeSegment(segment string) (json.RawMessage, error) {

954 if !isBase64URLSegment(segment) {

955 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

956 }

957 decoded, err := base64.RawURLEncoding.DecodeString(segment)

958 if err != nil {

959 return nil, err

960 }

961 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

962 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

963 }

964 if !utf8.Valid(decoded) {

965 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

966 }

967 

968 var value json.RawMessage

969 if err := json.Unmarshal(decoded, &value); err != nil {

970 return nil, err

971 }

972 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

973 return nil, fmt.Errorf("JWT segment is not a JSON object")

974 }

975 return value, nil

976}

977 

978func isBase64URLSegment(segment string) bool {

979 if segment == "" || len(segment)%4 == 1 {

980 return false

981 }

982 for _, character := range segment {

983 if !('A' <= character && character <= 'Z') &&

984 !('a' <= character && character <= 'z') &&

985 !('0' <= character && character <= '9') &&

986 character != '-' &&

987 character != '_' {

988 return false

989 }

990 }

991 return true

992}

993 

994func main() {

995 parts := strings.Split(os.Getenv("TOKEN"), ".")

996 if len(parts) != 3 {

997 panic("Expected a compact JWT with three segments")

998 }

999 

1000 payload, err := decodeSegment(parts[1])

1001 if err != nil {

1002 panic(err)

1003 }

1004 formatted, err := json.MarshalIndent(payload, "", " ")

1005 if err != nil {

1006 panic(err)

1007 }

1008 fmt.Println(string(formatted))

1009}

1010```

1011 

1012```java

1013// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

1014import com.fasterxml.jackson.databind.DeserializationFeature;

1015import com.fasterxml.jackson.databind.JsonNode;

1016import com.fasterxml.jackson.databind.ObjectMapper;

1017import java.io.IOException;

1018import java.nio.ByteBuffer;

1019import java.nio.charset.CharacterCodingException;

1020import java.nio.charset.CodingErrorAction;

1021import java.nio.charset.StandardCharsets;

1022import java.util.Base64;

1023 

1024public final class DecodeJwtPayloadExample {

1025 private static final ObjectMapper JSON =

1026 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

1027 

1028 private DecodeJwtPayloadExample() {}

1029 

1030 static String decodeUtf8(byte[] bytes) throws IOException {

1031 try {

1032 return StandardCharsets.UTF_8

1033 .newDecoder()

1034 .onMalformedInput(CodingErrorAction.REPORT)

1035 .onUnmappableCharacter(CodingErrorAction.REPORT)

1036 .decode(ByteBuffer.wrap(bytes))

1037 .toString();

1038 } catch (CharacterCodingException exception) {

1039 throw new IOException("JWT segment is not valid UTF-8", exception);

1040 }

1041 }

1042 

1043 static String decodeSegment(String segment) throws IOException {

1044 if (!isBase64UrlSegment(segment)) {

1045 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

1046 }

1047 byte[] bytes = Base64.getUrlDecoder().decode(segment);

1048 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

1049 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

1050 }

1051 String decoded = decodeUtf8(bytes);

1052 JsonNode value = JSON.readTree(decoded);

1053 if (value == null || value.isMissingNode() || !value.isObject()) {

1054 throw new IOException("JWT segment is not a JSON object");

1055 }

1056 return decoded;

1057 }

1058 

1059 static boolean isBase64UrlSegment(String segment) {

1060 if (segment.isEmpty() || segment.length() % 4 == 1) {

1061 return false;

1062 }

1063 return segment

1064 .chars()

1065 .allMatch(

1066 character ->

1067 character >= 'A' && character <= 'Z'

1068 || character >= 'a' && character <= 'z'

1069 || character >= '0' && character <= '9'

1070 || character == '-'

1071 || character == '_');

1072 }

1073 

1074 static String[] requireCompactJwt(String token) {

1075 if (token == null) {

1076 throw new IllegalArgumentException("Expected a compact JWT with three segments");

1077 }

1078 String[] parts = token.split("\\.", -1);

1079 if (parts.length != 3) {

1080 throw new IllegalArgumentException("Expected a compact JWT with three segments");

1081 }

1082 return parts;

1083 }

1084 

1085 public static void main(String[] args) throws IOException {

1086 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

1087 System.out.println(decodeSegment(parts[1]));

1088 }

1089}

1090```

1091 

1092```csharp

1093using System.Text;

1094using System.Text.Json;

1095 

1096static string DecodeSegment(string segment)

1097{

1098 if (

1099 segment.Length % 4 == 1 ||

1100 segment.Any(

1101 character =>

1102 !(

1103 character is >= 'A' and <= 'Z' ||

1104 character is >= 'a' and <= 'z' ||

1105 character is >= '0' and <= '9' ||

1106 character is '-' or '_'

1107 )

1108 )

1109 )

1110 {

1111 throw new FormatException("JWT segment is not valid Base64URL");

1112 }

1113 

1114 byte[] decoded = Convert.FromBase64String(

1115 segment.Replace('-', '+').Replace('_', '/') +

1116 new string('=', (4 - segment.Length % 4) % 4)

1117 );

1118 string canonicalSegment = Convert

1119 .ToBase64String(decoded)

1120 .TrimEnd('=')

1121 .Replace('+', '-')

1122 .Replace('/', '_');

1123 if (canonicalSegment != segment)

1124 {

1125 throw new FormatException("JWT segment is not valid Base64URL");

1126 }

1127 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

1128 using JsonDocument document = JsonDocument.Parse(decodedJson);

1129 if (document.RootElement.ValueKind is not JsonValueKind.Object)

1130 {

1131 throw new FormatException("JWT segment is not a JSON object");

1132 }

1133 return decodedJson;

1134}

1135 

1136string? token = Environment.GetEnvironmentVariable("TOKEN");

1137if (token is null)

1138{

1139 throw new InvalidOperationException(

1140 "Expected a compact JWT with three segments"

1141 );

1142}

1143string[] parts = token.Split('.');

1144if (parts.Length != 3)

1145{

1146 throw new InvalidOperationException(

1147 "Expected a compact JWT with three segments"

1148 );

1149}

1150 

1151Console.WriteLine(DecodeSegment(parts[1]));

1152```

1153 

1154```ruby

1155require "base64"

1156require "json"

1157 

1158parts = ENV.fetch("TOKEN", "").split(".", -1)

1159raise "Expected a compact JWT with three segments" unless parts.length == 3

1160 

1161unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

1162 raise "JWT payload is not valid Base64URL"

1163end

1164 

1165begin

1166 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

1167rescue ArgumentError

1168 raise "JWT payload is not valid Base64URL"

1169end

1170unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

1171 raise "JWT payload is not valid Base64URL"

1172end

1173payload.force_encoding(Encoding::UTF_8)

1174raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

1175 

1176claims = JSON.parse(payload)

1177raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

612 1178 

613payload = os.environ["TOKEN"].split(".")[1]1179puts(payload)

614payload += "=" * (-len(payload) % 4)

615print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

616```1180```

617 1181 

618 1182 

Details

68 68 

69Then run this script:69Then run this script:

70 70 

71```javascript

72const parts = process.env.TOKEN?.split(".") ?? [];

73if (parts.length !== 3) {

74 throw new Error("Expected a compact JWT with three segments");

75}

76if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

77 throw new Error("JWT payload is not valid Base64URL");

78}

79 

80const bytes = Buffer.from(parts[1], "base64url");

81if (bytes.toString("base64url") !== parts[1]) {

82 throw new Error("JWT payload is not valid Base64URL");

83}

84const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

85const claims = JSON.parse(decoded);

86if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

87 throw new Error("JWT payload is not a JSON object");

88}

89console.log(decoded);

90```

91 

71```python92```python

72import base6493import base64

73import json94import json

74import os95import os

96import re

97 

98 

99def reject_non_json_constant(value):

100 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

101 

102 

103parts = os.environ.get("TOKEN", "").split(".")

104if len(parts) != 3:

105 raise ValueError("Expected a compact JWT with three segments")

106 

107payload = parts[1]

108if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

109 raise ValueError("JWT payload is not valid Base64URL")

110padded_payload = payload + "=" * (-len(payload) % 4)

111decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

112if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

113 raise ValueError("JWT payload is not valid Base64URL")

114decoded_text = decoded.decode("utf-8")

115claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

116if not isinstance(claims, dict):

117 raise ValueError("JWT payload is not a JSON object")

118print(decoded_text)

119```

120 

121```go

122package main

123 

124import (

125 "bytes"

126 "encoding/base64"

127 "encoding/json"

128 "fmt"

129 "os"

130 "strings"

131 "unicode/utf8"

132)

133 

134func decodeSegment(segment string) (json.RawMessage, error) {

135 if !isBase64URLSegment(segment) {

136 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

137 }

138 decoded, err := base64.RawURLEncoding.DecodeString(segment)

139 if err != nil {

140 return nil, err

141 }

142 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

143 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

144 }

145 if !utf8.Valid(decoded) {

146 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

147 }

148 

149 var value json.RawMessage

150 if err := json.Unmarshal(decoded, &value); err != nil {

151 return nil, err

152 }

153 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

154 return nil, fmt.Errorf("JWT segment is not a JSON object")

155 }

156 return value, nil

157}

158 

159func isBase64URLSegment(segment string) bool {

160 if segment == "" || len(segment)%4 == 1 {

161 return false

162 }

163 for _, character := range segment {

164 if !('A' <= character && character <= 'Z') &&

165 !('a' <= character && character <= 'z') &&

166 !('0' <= character && character <= '9') &&

167 character != '-' &&

168 character != '_' {

169 return false

170 }

171 }

172 return true

173}

174 

175func main() {

176 parts := strings.Split(os.Getenv("TOKEN"), ".")

177 if len(parts) != 3 {

178 panic("Expected a compact JWT with three segments")

179 }

180 

181 payload, err := decodeSegment(parts[1])

182 if err != nil {

183 panic(err)

184 }

185 formatted, err := json.MarshalIndent(payload, "", " ")

186 if err != nil {

187 panic(err)

188 }

189 fmt.Println(string(formatted))

190}

191```

192 

193```java

194// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

195import com.fasterxml.jackson.databind.DeserializationFeature;

196import com.fasterxml.jackson.databind.JsonNode;

197import com.fasterxml.jackson.databind.ObjectMapper;

198import java.io.IOException;

199import java.nio.ByteBuffer;

200import java.nio.charset.CharacterCodingException;

201import java.nio.charset.CodingErrorAction;

202import java.nio.charset.StandardCharsets;

203import java.util.Base64;

204 

205public final class DecodeJwtPayloadExample {

206 private static final ObjectMapper JSON =

207 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

208 

209 private DecodeJwtPayloadExample() {}

210 

211 static String decodeUtf8(byte[] bytes) throws IOException {

212 try {

213 return StandardCharsets.UTF_8

214 .newDecoder()

215 .onMalformedInput(CodingErrorAction.REPORT)

216 .onUnmappableCharacter(CodingErrorAction.REPORT)

217 .decode(ByteBuffer.wrap(bytes))

218 .toString();

219 } catch (CharacterCodingException exception) {

220 throw new IOException("JWT segment is not valid UTF-8", exception);

221 }

222 }

223 

224 static String decodeSegment(String segment) throws IOException {

225 if (!isBase64UrlSegment(segment)) {

226 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

227 }

228 byte[] bytes = Base64.getUrlDecoder().decode(segment);

229 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

230 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

231 }

232 String decoded = decodeUtf8(bytes);

233 JsonNode value = JSON.readTree(decoded);

234 if (value == null || value.isMissingNode() || !value.isObject()) {

235 throw new IOException("JWT segment is not a JSON object");

236 }

237 return decoded;

238 }

239 

240 static boolean isBase64UrlSegment(String segment) {

241 if (segment.isEmpty() || segment.length() % 4 == 1) {

242 return false;

243 }

244 return segment

245 .chars()

246 .allMatch(

247 character ->

248 character >= 'A' && character <= 'Z'

249 || character >= 'a' && character <= 'z'

250 || character >= '0' && character <= '9'

251 || character == '-'

252 || character == '_');

253 }

254 

255 static String[] requireCompactJwt(String token) {

256 if (token == null) {

257 throw new IllegalArgumentException("Expected a compact JWT with three segments");

258 }

259 String[] parts = token.split("\\.", -1);

260 if (parts.length != 3) {

261 throw new IllegalArgumentException("Expected a compact JWT with three segments");

262 }

263 return parts;

264 }

265 

266 public static void main(String[] args) throws IOException {

267 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

268 System.out.println(decodeSegment(parts[1]));

269 }

270}

271```

272 

273```csharp

274using System.Text;

275using System.Text.Json;

276 

277static string DecodeSegment(string segment)

278{

279 if (

280 segment.Length % 4 == 1 ||

281 segment.Any(

282 character =>

283 !(

284 character is >= 'A' and <= 'Z' ||

285 character is >= 'a' and <= 'z' ||

286 character is >= '0' and <= '9' ||

287 character is '-' or '_'

288 )

289 )

290 )

291 {

292 throw new FormatException("JWT segment is not valid Base64URL");

293 }

294 

295 byte[] decoded = Convert.FromBase64String(

296 segment.Replace('-', '+').Replace('_', '/') +

297 new string('=', (4 - segment.Length % 4) % 4)

298 );

299 string canonicalSegment = Convert

300 .ToBase64String(decoded)

301 .TrimEnd('=')

302 .Replace('+', '-')

303 .Replace('/', '_');

304 if (canonicalSegment != segment)

305 {

306 throw new FormatException("JWT segment is not valid Base64URL");

307 }

308 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

309 using JsonDocument document = JsonDocument.Parse(decodedJson);

310 if (document.RootElement.ValueKind is not JsonValueKind.Object)

311 {

312 throw new FormatException("JWT segment is not a JSON object");

313 }

314 return decodedJson;

315}

316 

317string? token = Environment.GetEnvironmentVariable("TOKEN");

318if (token is null)

319{

320 throw new InvalidOperationException(

321 "Expected a compact JWT with three segments"

322 );

323}

324string[] parts = token.Split('.');

325if (parts.Length != 3)

326{

327 throw new InvalidOperationException(

328 "Expected a compact JWT with three segments"

329 );

330}

331 

332Console.WriteLine(DecodeSegment(parts[1]));

333```

334 

335```ruby

336require "base64"

337require "json"

338 

339parts = ENV.fetch("TOKEN", "").split(".", -1)

340raise "Expected a compact JWT with three segments" unless parts.length == 3

341 

342unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

343 raise "JWT payload is not valid Base64URL"

344end

345 

346begin

347 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

348rescue ArgumentError

349 raise "JWT payload is not valid Base64URL"

350end

351unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

352 raise "JWT payload is not valid Base64URL"

353end

354payload.force_encoding(Encoding::UTF_8)

355raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

356 

357claims = JSON.parse(payload)

358raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

75 359 

76payload = os.environ["TOKEN"].split(".")[1]360puts(payload)

77payload += "=" * (-len(payload) % 4)

78print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

79```361```

80 362 

81 363 

Details

46 46 

47Before configuring workload identity federation, export the Microsoft Entra token as `TOKEN`, then run this script locally to inspect its claims:47Before configuring workload identity federation, export the Microsoft Entra token as `TOKEN`, then run this script locally to inspect its claims:

48 48 

49```javascript

50const parts = process.env.TOKEN?.split(".") ?? [];

51if (parts.length !== 3) {

52 throw new Error("Expected a compact JWT with three segments");

53}

54if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

55 throw new Error("JWT payload is not valid Base64URL");

56}

57 

58const bytes = Buffer.from(parts[1], "base64url");

59if (bytes.toString("base64url") !== parts[1]) {

60 throw new Error("JWT payload is not valid Base64URL");

61}

62const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

63const claims = JSON.parse(decoded);

64if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

65 throw new Error("JWT payload is not a JSON object");

66}

67console.log(decoded);

68```

69 

49```python70```python

50import base6471import base64

51import json72import json

52import os73import os

74import re

75 

76 

77def reject_non_json_constant(value):

78 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

79 

80 

81parts = os.environ.get("TOKEN", "").split(".")

82if len(parts) != 3:

83 raise ValueError("Expected a compact JWT with three segments")

84 

85payload = parts[1]

86if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

87 raise ValueError("JWT payload is not valid Base64URL")

88padded_payload = payload + "=" * (-len(payload) % 4)

89decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

90if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

91 raise ValueError("JWT payload is not valid Base64URL")

92decoded_text = decoded.decode("utf-8")

93claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

94if not isinstance(claims, dict):

95 raise ValueError("JWT payload is not a JSON object")

96print(decoded_text)

97```

98 

99```go

100package main

101 

102import (

103 "bytes"

104 "encoding/base64"

105 "encoding/json"

106 "fmt"

107 "os"

108 "strings"

109 "unicode/utf8"

110)

111 

112func decodeSegment(segment string) (json.RawMessage, error) {

113 if !isBase64URLSegment(segment) {

114 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

115 }

116 decoded, err := base64.RawURLEncoding.DecodeString(segment)

117 if err != nil {

118 return nil, err

119 }

120 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

121 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

122 }

123 if !utf8.Valid(decoded) {

124 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

125 }

126 

127 var value json.RawMessage

128 if err := json.Unmarshal(decoded, &value); err != nil {

129 return nil, err

130 }

131 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

132 return nil, fmt.Errorf("JWT segment is not a JSON object")

133 }

134 return value, nil

135}

136 

137func isBase64URLSegment(segment string) bool {

138 if segment == "" || len(segment)%4 == 1 {

139 return false

140 }

141 for _, character := range segment {

142 if !('A' <= character && character <= 'Z') &&

143 !('a' <= character && character <= 'z') &&

144 !('0' <= character && character <= '9') &&

145 character != '-' &&

146 character != '_' {

147 return false

148 }

149 }

150 return true

151}

152 

153func main() {

154 parts := strings.Split(os.Getenv("TOKEN"), ".")

155 if len(parts) != 3 {

156 panic("Expected a compact JWT with three segments")

157 }

158 

159 payload, err := decodeSegment(parts[1])

160 if err != nil {

161 panic(err)

162 }

163 formatted, err := json.MarshalIndent(payload, "", " ")

164 if err != nil {

165 panic(err)

166 }

167 fmt.Println(string(formatted))

168}

169```

170 

171```java

172// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

173import com.fasterxml.jackson.databind.DeserializationFeature;

174import com.fasterxml.jackson.databind.JsonNode;

175import com.fasterxml.jackson.databind.ObjectMapper;

176import java.io.IOException;

177import java.nio.ByteBuffer;

178import java.nio.charset.CharacterCodingException;

179import java.nio.charset.CodingErrorAction;

180import java.nio.charset.StandardCharsets;

181import java.util.Base64;

182 

183public final class DecodeJwtPayloadExample {

184 private static final ObjectMapper JSON =

185 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

186 

187 private DecodeJwtPayloadExample() {}

188 

189 static String decodeUtf8(byte[] bytes) throws IOException {

190 try {

191 return StandardCharsets.UTF_8

192 .newDecoder()

193 .onMalformedInput(CodingErrorAction.REPORT)

194 .onUnmappableCharacter(CodingErrorAction.REPORT)

195 .decode(ByteBuffer.wrap(bytes))

196 .toString();

197 } catch (CharacterCodingException exception) {

198 throw new IOException("JWT segment is not valid UTF-8", exception);

199 }

200 }

201 

202 static String decodeSegment(String segment) throws IOException {

203 if (!isBase64UrlSegment(segment)) {

204 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

205 }

206 byte[] bytes = Base64.getUrlDecoder().decode(segment);

207 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

208 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

209 }

210 String decoded = decodeUtf8(bytes);

211 JsonNode value = JSON.readTree(decoded);

212 if (value == null || value.isMissingNode() || !value.isObject()) {

213 throw new IOException("JWT segment is not a JSON object");

214 }

215 return decoded;

216 }

217 

218 static boolean isBase64UrlSegment(String segment) {

219 if (segment.isEmpty() || segment.length() % 4 == 1) {

220 return false;

221 }

222 return segment

223 .chars()

224 .allMatch(

225 character ->

226 character >= 'A' && character <= 'Z'

227 || character >= 'a' && character <= 'z'

228 || character >= '0' && character <= '9'

229 || character == '-'

230 || character == '_');

231 }

232 

233 static String[] requireCompactJwt(String token) {

234 if (token == null) {

235 throw new IllegalArgumentException("Expected a compact JWT with three segments");

236 }

237 String[] parts = token.split("\\.", -1);

238 if (parts.length != 3) {

239 throw new IllegalArgumentException("Expected a compact JWT with three segments");

240 }

241 return parts;

242 }

243 

244 public static void main(String[] args) throws IOException {

245 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

246 System.out.println(decodeSegment(parts[1]));

247 }

248}

249```

250 

251```csharp

252using System.Text;

253using System.Text.Json;

254 

255static string DecodeSegment(string segment)

256{

257 if (

258 segment.Length % 4 == 1 ||

259 segment.Any(

260 character =>

261 !(

262 character is >= 'A' and <= 'Z' ||

263 character is >= 'a' and <= 'z' ||

264 character is >= '0' and <= '9' ||

265 character is '-' or '_'

266 )

267 )

268 )

269 {

270 throw new FormatException("JWT segment is not valid Base64URL");

271 }

272 

273 byte[] decoded = Convert.FromBase64String(

274 segment.Replace('-', '+').Replace('_', '/') +

275 new string('=', (4 - segment.Length % 4) % 4)

276 );

277 string canonicalSegment = Convert

278 .ToBase64String(decoded)

279 .TrimEnd('=')

280 .Replace('+', '-')

281 .Replace('/', '_');

282 if (canonicalSegment != segment)

283 {

284 throw new FormatException("JWT segment is not valid Base64URL");

285 }

286 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

287 using JsonDocument document = JsonDocument.Parse(decodedJson);

288 if (document.RootElement.ValueKind is not JsonValueKind.Object)

289 {

290 throw new FormatException("JWT segment is not a JSON object");

291 }

292 return decodedJson;

293}

294 

295string? token = Environment.GetEnvironmentVariable("TOKEN");

296if (token is null)

297{

298 throw new InvalidOperationException(

299 "Expected a compact JWT with three segments"

300 );

301}

302string[] parts = token.Split('.');

303if (parts.Length != 3)

304{

305 throw new InvalidOperationException(

306 "Expected a compact JWT with three segments"

307 );

308}

309 

310Console.WriteLine(DecodeSegment(parts[1]));

311```

312 

313```ruby

314require "base64"

315require "json"

316 

317parts = ENV.fetch("TOKEN", "").split(".", -1)

318raise "Expected a compact JWT with three segments" unless parts.length == 3

319 

320unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

321 raise "JWT payload is not valid Base64URL"

322end

323 

324begin

325 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

326rescue ArgumentError

327 raise "JWT payload is not valid Base64URL"

328end

329unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

330 raise "JWT payload is not valid Base64URL"

331end

332payload.force_encoding(Encoding::UTF_8)

333raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

334 

335claims = JSON.parse(payload)

336raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

53 337 

54payload = os.environ["TOKEN"].split(".")[1]338puts(payload)

55payload += "=" * (-len(payload) % 4)

56print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

57```339```

58 340 

59 341 


642 924 

643Then run this script:925Then run this script:

644 926 

927```javascript

928const parts = process.env.TOKEN?.split(".") ?? [];

929if (parts.length !== 3) {

930 throw new Error("Expected a compact JWT with three segments");

931}

932if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

933 throw new Error("JWT payload is not valid Base64URL");

934}

935 

936const bytes = Buffer.from(parts[1], "base64url");

937if (bytes.toString("base64url") !== parts[1]) {

938 throw new Error("JWT payload is not valid Base64URL");

939}

940const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

941const claims = JSON.parse(decoded);

942if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

943 throw new Error("JWT payload is not a JSON object");

944}

945console.log(decoded);

946```

947 

645```python948```python

646import base64949import base64

647import json950import json

648import os951import os

952import re

953 

954 

955def reject_non_json_constant(value):

956 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

957 

958 

959parts = os.environ.get("TOKEN", "").split(".")

960if len(parts) != 3:

961 raise ValueError("Expected a compact JWT with three segments")

962 

963payload = parts[1]

964if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

965 raise ValueError("JWT payload is not valid Base64URL")

966padded_payload = payload + "=" * (-len(payload) % 4)

967decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

968if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

969 raise ValueError("JWT payload is not valid Base64URL")

970decoded_text = decoded.decode("utf-8")

971claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

972if not isinstance(claims, dict):

973 raise ValueError("JWT payload is not a JSON object")

974print(decoded_text)

975```

976 

977```go

978package main

979 

980import (

981 "bytes"

982 "encoding/base64"

983 "encoding/json"

984 "fmt"

985 "os"

986 "strings"

987 "unicode/utf8"

988)

989 

990func decodeSegment(segment string) (json.RawMessage, error) {

991 if !isBase64URLSegment(segment) {

992 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

993 }

994 decoded, err := base64.RawURLEncoding.DecodeString(segment)

995 if err != nil {

996 return nil, err

997 }

998 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

999 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

1000 }

1001 if !utf8.Valid(decoded) {

1002 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

1003 }

1004 

1005 var value json.RawMessage

1006 if err := json.Unmarshal(decoded, &value); err != nil {

1007 return nil, err

1008 }

1009 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

1010 return nil, fmt.Errorf("JWT segment is not a JSON object")

1011 }

1012 return value, nil

1013}

1014 

1015func isBase64URLSegment(segment string) bool {

1016 if segment == "" || len(segment)%4 == 1 {

1017 return false

1018 }

1019 for _, character := range segment {

1020 if !('A' <= character && character <= 'Z') &&

1021 !('a' <= character && character <= 'z') &&

1022 !('0' <= character && character <= '9') &&

1023 character != '-' &&

1024 character != '_' {

1025 return false

1026 }

1027 }

1028 return true

1029}

1030 

1031func main() {

1032 parts := strings.Split(os.Getenv("TOKEN"), ".")

1033 if len(parts) != 3 {

1034 panic("Expected a compact JWT with three segments")

1035 }

1036 

1037 payload, err := decodeSegment(parts[1])

1038 if err != nil {

1039 panic(err)

1040 }

1041 formatted, err := json.MarshalIndent(payload, "", " ")

1042 if err != nil {

1043 panic(err)

1044 }

1045 fmt.Println(string(formatted))

1046}

1047```

1048 

1049```java

1050// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

1051import com.fasterxml.jackson.databind.DeserializationFeature;

1052import com.fasterxml.jackson.databind.JsonNode;

1053import com.fasterxml.jackson.databind.ObjectMapper;

1054import java.io.IOException;

1055import java.nio.ByteBuffer;

1056import java.nio.charset.CharacterCodingException;

1057import java.nio.charset.CodingErrorAction;

1058import java.nio.charset.StandardCharsets;

1059import java.util.Base64;

1060 

1061public final class DecodeJwtPayloadExample {

1062 private static final ObjectMapper JSON =

1063 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

1064 

1065 private DecodeJwtPayloadExample() {}

1066 

1067 static String decodeUtf8(byte[] bytes) throws IOException {

1068 try {

1069 return StandardCharsets.UTF_8

1070 .newDecoder()

1071 .onMalformedInput(CodingErrorAction.REPORT)

1072 .onUnmappableCharacter(CodingErrorAction.REPORT)

1073 .decode(ByteBuffer.wrap(bytes))

1074 .toString();

1075 } catch (CharacterCodingException exception) {

1076 throw new IOException("JWT segment is not valid UTF-8", exception);

1077 }

1078 }

1079 

1080 static String decodeSegment(String segment) throws IOException {

1081 if (!isBase64UrlSegment(segment)) {

1082 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

1083 }

1084 byte[] bytes = Base64.getUrlDecoder().decode(segment);

1085 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

1086 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

1087 }

1088 String decoded = decodeUtf8(bytes);

1089 JsonNode value = JSON.readTree(decoded);

1090 if (value == null || value.isMissingNode() || !value.isObject()) {

1091 throw new IOException("JWT segment is not a JSON object");

1092 }

1093 return decoded;

1094 }

1095 

1096 static boolean isBase64UrlSegment(String segment) {

1097 if (segment.isEmpty() || segment.length() % 4 == 1) {

1098 return false;

1099 }

1100 return segment

1101 .chars()

1102 .allMatch(

1103 character ->

1104 character >= 'A' && character <= 'Z'

1105 || character >= 'a' && character <= 'z'

1106 || character >= '0' && character <= '9'

1107 || character == '-'

1108 || character == '_');

1109 }

1110 

1111 static String[] requireCompactJwt(String token) {

1112 if (token == null) {

1113 throw new IllegalArgumentException("Expected a compact JWT with three segments");

1114 }

1115 String[] parts = token.split("\\.", -1);

1116 if (parts.length != 3) {

1117 throw new IllegalArgumentException("Expected a compact JWT with three segments");

1118 }

1119 return parts;

1120 }

1121 

1122 public static void main(String[] args) throws IOException {

1123 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

1124 System.out.println(decodeSegment(parts[1]));

1125 }

1126}

1127```

1128 

1129```csharp

1130using System.Text;

1131using System.Text.Json;

1132 

1133static string DecodeSegment(string segment)

1134{

1135 if (

1136 segment.Length % 4 == 1 ||

1137 segment.Any(

1138 character =>

1139 !(

1140 character is >= 'A' and <= 'Z' ||

1141 character is >= 'a' and <= 'z' ||

1142 character is >= '0' and <= '9' ||

1143 character is '-' or '_'

1144 )

1145 )

1146 )

1147 {

1148 throw new FormatException("JWT segment is not valid Base64URL");

1149 }

1150 

1151 byte[] decoded = Convert.FromBase64String(

1152 segment.Replace('-', '+').Replace('_', '/') +

1153 new string('=', (4 - segment.Length % 4) % 4)

1154 );

1155 string canonicalSegment = Convert

1156 .ToBase64String(decoded)

1157 .TrimEnd('=')

1158 .Replace('+', '-')

1159 .Replace('/', '_');

1160 if (canonicalSegment != segment)

1161 {

1162 throw new FormatException("JWT segment is not valid Base64URL");

1163 }

1164 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

1165 using JsonDocument document = JsonDocument.Parse(decodedJson);

1166 if (document.RootElement.ValueKind is not JsonValueKind.Object)

1167 {

1168 throw new FormatException("JWT segment is not a JSON object");

1169 }

1170 return decodedJson;

1171}

1172 

1173string? token = Environment.GetEnvironmentVariable("TOKEN");

1174if (token is null)

1175{

1176 throw new InvalidOperationException(

1177 "Expected a compact JWT with three segments"

1178 );

1179}

1180string[] parts = token.Split('.');

1181if (parts.Length != 3)

1182{

1183 throw new InvalidOperationException(

1184 "Expected a compact JWT with three segments"

1185 );

1186}

1187 

1188Console.WriteLine(DecodeSegment(parts[1]));

1189```

1190 

1191```ruby

1192require "base64"

1193require "json"

1194 

1195parts = ENV.fetch("TOKEN", "").split(".", -1)

1196raise "Expected a compact JWT with three segments" unless parts.length == 3

1197 

1198unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

1199 raise "JWT payload is not valid Base64URL"

1200end

1201 

1202begin

1203 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

1204rescue ArgumentError

1205 raise "JWT payload is not valid Base64URL"

1206end

1207unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

1208 raise "JWT payload is not valid Base64URL"

1209end

1210payload.force_encoding(Encoding::UTF_8)

1211raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

1212 

1213claims = JSON.parse(payload)

1214raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

649 1215 

650payload = os.environ["TOKEN"].split(".")[1]1216puts(payload)

651payload += "=" * (-len(payload) % 4)

652print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

653```1217```

654 1218 

655 1219 

Details

41 41 

42Set `TOKEN` to an access token generated by the actual OCI workload, then use the existing local JWT decoder to inspect its claims:42Set `TOKEN` to an access token generated by the actual OCI workload, then use the existing local JWT decoder to inspect its claims:

43 43 

44```javascript

45const parts = process.env.TOKEN?.split(".") ?? [];

46if (parts.length !== 3) {

47 throw new Error("Expected a compact JWT with three segments");

48}

49if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {

50 throw new Error("JWT payload is not valid Base64URL");

51}

52 

53const bytes = Buffer.from(parts[1], "base64url");

54if (bytes.toString("base64url") !== parts[1]) {

55 throw new Error("JWT payload is not valid Base64URL");

56}

57const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

58const claims = JSON.parse(decoded);

59if (claims === null || Array.isArray(claims) || typeof claims !== "object") {

60 throw new Error("JWT payload is not a JSON object");

61}

62console.log(decoded);

63```

64 

44```python65```python

45import base6466import base64

46import json67import json

47import os68import os

69import re

70 

71 

72def reject_non_json_constant(value):

73 raise ValueError(f"JWT payload contains non-JSON constant: {value}")

74 

75 

76parts = os.environ.get("TOKEN", "").split(".")

77if len(parts) != 3:

78 raise ValueError("Expected a compact JWT with three segments")

79 

80payload = parts[1]

81if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:

82 raise ValueError("JWT payload is not valid Base64URL")

83padded_payload = payload + "=" * (-len(payload) % 4)

84decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)

85if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:

86 raise ValueError("JWT payload is not valid Base64URL")

87decoded_text = decoded.decode("utf-8")

88claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)

89if not isinstance(claims, dict):

90 raise ValueError("JWT payload is not a JSON object")

91print(decoded_text)

92```

93 

94```go

95package main

96 

97import (

98 "bytes"

99 "encoding/base64"

100 "encoding/json"

101 "fmt"

102 "os"

103 "strings"

104 "unicode/utf8"

105)

106 

107func decodeSegment(segment string) (json.RawMessage, error) {

108 if !isBase64URLSegment(segment) {

109 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

110 }

111 decoded, err := base64.RawURLEncoding.DecodeString(segment)

112 if err != nil {

113 return nil, err

114 }

115 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

116 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

117 }

118 if !utf8.Valid(decoded) {

119 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

120 }

121 

122 var value json.RawMessage

123 if err := json.Unmarshal(decoded, &value); err != nil {

124 return nil, err

125 }

126 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

127 return nil, fmt.Errorf("JWT segment is not a JSON object")

128 }

129 return value, nil

130}

131 

132func isBase64URLSegment(segment string) bool {

133 if segment == "" || len(segment)%4 == 1 {

134 return false

135 }

136 for _, character := range segment {

137 if !('A' <= character && character <= 'Z') &&

138 !('a' <= character && character <= 'z') &&

139 !('0' <= character && character <= '9') &&

140 character != '-' &&

141 character != '_' {

142 return false

143 }

144 }

145 return true

146}

147 

148func main() {

149 parts := strings.Split(os.Getenv("TOKEN"), ".")

150 if len(parts) != 3 {

151 panic("Expected a compact JWT with three segments")

152 }

153 

154 payload, err := decodeSegment(parts[1])

155 if err != nil {

156 panic(err)

157 }

158 formatted, err := json.MarshalIndent(payload, "", " ")

159 if err != nil {

160 panic(err)

161 }

162 fmt.Println(string(formatted))

163}

164```

165 

166```java

167// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

168import com.fasterxml.jackson.databind.DeserializationFeature;

169import com.fasterxml.jackson.databind.JsonNode;

170import com.fasterxml.jackson.databind.ObjectMapper;

171import java.io.IOException;

172import java.nio.ByteBuffer;

173import java.nio.charset.CharacterCodingException;

174import java.nio.charset.CodingErrorAction;

175import java.nio.charset.StandardCharsets;

176import java.util.Base64;

177 

178public final class DecodeJwtPayloadExample {

179 private static final ObjectMapper JSON =

180 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

181 

182 private DecodeJwtPayloadExample() {}

183 

184 static String decodeUtf8(byte[] bytes) throws IOException {

185 try {

186 return StandardCharsets.UTF_8

187 .newDecoder()

188 .onMalformedInput(CodingErrorAction.REPORT)

189 .onUnmappableCharacter(CodingErrorAction.REPORT)

190 .decode(ByteBuffer.wrap(bytes))

191 .toString();

192 } catch (CharacterCodingException exception) {

193 throw new IOException("JWT segment is not valid UTF-8", exception);

194 }

195 }

196 

197 static String decodeSegment(String segment) throws IOException {

198 if (!isBase64UrlSegment(segment)) {

199 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

200 }

201 byte[] bytes = Base64.getUrlDecoder().decode(segment);

202 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

203 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

204 }

205 String decoded = decodeUtf8(bytes);

206 JsonNode value = JSON.readTree(decoded);

207 if (value == null || value.isMissingNode() || !value.isObject()) {

208 throw new IOException("JWT segment is not a JSON object");

209 }

210 return decoded;

211 }

212 

213 static boolean isBase64UrlSegment(String segment) {

214 if (segment.isEmpty() || segment.length() % 4 == 1) {

215 return false;

216 }

217 return segment

218 .chars()

219 .allMatch(

220 character ->

221 character >= 'A' && character <= 'Z'

222 || character >= 'a' && character <= 'z'

223 || character >= '0' && character <= '9'

224 || character == '-'

225 || character == '_');

226 }

227 

228 static String[] requireCompactJwt(String token) {

229 if (token == null) {

230 throw new IllegalArgumentException("Expected a compact JWT with three segments");

231 }

232 String[] parts = token.split("\\.", -1);

233 if (parts.length != 3) {

234 throw new IllegalArgumentException("Expected a compact JWT with three segments");

235 }

236 return parts;

237 }

238 

239 public static void main(String[] args) throws IOException {

240 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

241 System.out.println(decodeSegment(parts[1]));

242 }

243}

244```

245 

246```csharp

247using System.Text;

248using System.Text.Json;

249 

250static string DecodeSegment(string segment)

251{

252 if (

253 segment.Length % 4 == 1 ||

254 segment.Any(

255 character =>

256 !(

257 character is >= 'A' and <= 'Z' ||

258 character is >= 'a' and <= 'z' ||

259 character is >= '0' and <= '9' ||

260 character is '-' or '_'

261 )

262 )

263 )

264 {

265 throw new FormatException("JWT segment is not valid Base64URL");

266 }

267 

268 byte[] decoded = Convert.FromBase64String(

269 segment.Replace('-', '+').Replace('_', '/') +

270 new string('=', (4 - segment.Length % 4) % 4)

271 );

272 string canonicalSegment = Convert

273 .ToBase64String(decoded)

274 .TrimEnd('=')

275 .Replace('+', '-')

276 .Replace('/', '_');

277 if (canonicalSegment != segment)

278 {

279 throw new FormatException("JWT segment is not valid Base64URL");

280 }

281 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

282 using JsonDocument document = JsonDocument.Parse(decodedJson);

283 if (document.RootElement.ValueKind is not JsonValueKind.Object)

284 {

285 throw new FormatException("JWT segment is not a JSON object");

286 }

287 return decodedJson;

288}

289 

290string? token = Environment.GetEnvironmentVariable("TOKEN");

291if (token is null)

292{

293 throw new InvalidOperationException(

294 "Expected a compact JWT with three segments"

295 );

296}

297string[] parts = token.Split('.');

298if (parts.Length != 3)

299{

300 throw new InvalidOperationException(

301 "Expected a compact JWT with three segments"

302 );

303}

304 

305Console.WriteLine(DecodeSegment(parts[1]));

306```

307 

308```ruby

309require "base64"

310require "json"

311 

312parts = ENV.fetch("TOKEN", "").split(".", -1)

313raise "Expected a compact JWT with three segments" unless parts.length == 3

314 

315unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1

316 raise "JWT payload is not valid Base64URL"

317end

318 

319begin

320 payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))

321rescue ArgumentError

322 raise "JWT payload is not valid Base64URL"

323end

324unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]

325 raise "JWT payload is not valid Base64URL"

326end

327payload.force_encoding(Encoding::UTF_8)

328raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?

329 

330claims = JSON.parse(payload)

331raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)

48 332 

49payload = os.environ["TOKEN"].split(".")[1]333puts(payload)

50payload += "=" * (-len(payload) % 4)

51print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))

52```334```

53 335 

54 336 

Details

85 85 

86## Verify the token86## Verify the token

87 87 

88Before configuring workload identity federation, export the JWT-SVID as `TOKEN`, then run this script locally to inspect its header and claims:88Before configuring workload identity federation, export the JWT-SVID as `TOKEN`, then run one of these examples locally to inspect its header and claims:

89 

90```javascript

91const parts = process.env.TOKEN?.split(".") ?? [];

92if (parts.length !== 3) {

93 throw new Error("Expected a compact JWT with three segments");

94}

95 

96const decode = (segment) => {

97 if (!/^[A-Za-z0-9_-]+$/.test(segment) || segment.length % 4 === 1) {

98 throw new Error("JWT segment is not valid Base64URL");

99 }

100 const bytes = Buffer.from(segment, "base64url");

101 if (bytes.toString("base64url") !== segment) {

102 throw new Error("JWT segment is not valid Base64URL");

103 }

104 const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

105 const value = JSON.parse(decoded);

106 if (value === null || Array.isArray(value) || typeof value !== "object") {

107 throw new Error("JWT segment is not a JSON object");

108 }

109 return decoded;

110};

111 

112console.log("Header:");

113console.log(decode(parts[0]));

114console.log("\nPayload:");

115console.log(decode(parts[1]));

116```

89 117 

90```python118```python

91import base64119import base64

92import json120import json

93import os121import os

122import re

123 

124 

125def reject_non_json_constant(value):

126 raise ValueError(f"JWT segment contains non-JSON constant: {value}")

94 127 

95parts = os.environ["TOKEN"].split(".")128 

129parts = os.environ.get("TOKEN", "").split(".")

96if len(parts) != 3:130if len(parts) != 3:

97 raise ValueError("Expected a compact JWT with three segments")131 raise ValueError("Expected a compact JWT with three segments")

98 132 

99 133 

100def decode(segment):134def decode(segment):

101 segment += "=" * (-len(segment) % 4)135 if re.fullmatch(r"[A-Za-z0-9_-]+", segment) is None or len(segment) % 4 == 1:

102 return json.loads(base64.urlsafe_b64decode(segment))136 raise ValueError("JWT segment is not valid Base64URL")

137 padded_segment = segment + "=" * (-len(segment) % 4)

138 decoded = base64.b64decode(padded_segment, altchars=b"-_", validate=True)

139 if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != segment:

140 raise ValueError("JWT segment is not valid Base64URL")

141 decoded_text = decoded.decode("utf-8")

142 value = json.loads(decoded_text, parse_constant=reject_non_json_constant)

143 if not isinstance(value, dict):

144 raise ValueError("JWT segment is not a JSON object")

145 return decoded_text

103 146 

104 147 

105print("Header:")148print("Header:")

106print(json.dumps(decode(parts[0]), indent=2))149print(decode(parts[0]))

107print("\nPayload:")150print("\nPayload:")

108print(json.dumps(decode(parts[1]), indent=2))151print(decode(parts[1]))

152```

153 

154```go

155package main

156 

157import (

158 "bytes"

159 "encoding/base64"

160 "encoding/json"

161 "fmt"

162 "os"

163 "strings"

164 "unicode/utf8"

165)

166 

167func decodeSegment(segment string) (json.RawMessage, error) {

168 if !isBase64URLSegment(segment) {

169 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

170 }

171 decoded, err := base64.RawURLEncoding.DecodeString(segment)

172 if err != nil {

173 return nil, err

174 }

175 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

176 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

177 }

178 if !utf8.Valid(decoded) {

179 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

180 }

181 

182 var value json.RawMessage

183 if err := json.Unmarshal(decoded, &value); err != nil {

184 return nil, err

185 }

186 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

187 return nil, fmt.Errorf("JWT segment is not a JSON object")

188 }

189 return value, nil

190}

191 

192func isBase64URLSegment(segment string) bool {

193 if segment == "" || len(segment)%4 == 1 {

194 return false

195 }

196 for _, character := range segment {

197 if !('A' <= character && character <= 'Z') &&

198 !('a' <= character && character <= 'z') &&

199 !('0' <= character && character <= '9') &&

200 character != '-' &&

201 character != '_' {

202 return false

203 }

204 }

205 return true

206}

207 

208func printJSON(label string, value json.RawMessage) error {

209 formatted, err := json.MarshalIndent(value, "", " ")

210 if err != nil {

211 return err

212 }

213 fmt.Printf("%s:\n%s\n", label, formatted)

214 return nil

215}

216 

217func main() {

218 parts := strings.Split(os.Getenv("TOKEN"), ".")

219 if len(parts) != 3 {

220 panic("Expected a compact JWT with three segments")

221 }

222 

223 header, err := decodeSegment(parts[0])

224 if err != nil {

225 panic(err)

226 }

227 payload, err := decodeSegment(parts[1])

228 if err != nil {

229 panic(err)

230 }

231 if err := printJSON("Header", header); err != nil {

232 panic(err)

233 }

234 fmt.Println()

235 if err := printJSON("Payload", payload); err != nil {

236 panic(err)

237 }

238}

239```

240 

241```java

242// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

243import com.fasterxml.jackson.databind.DeserializationFeature;

244import com.fasterxml.jackson.databind.JsonNode;

245import com.fasterxml.jackson.databind.ObjectMapper;

246import java.io.IOException;

247import java.nio.ByteBuffer;

248import java.nio.charset.CharacterCodingException;

249import java.nio.charset.CodingErrorAction;

250import java.nio.charset.StandardCharsets;

251import java.util.Base64;

252 

253public final class DecodeJwtExample {

254 private static final ObjectMapper JSON =

255 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

256 

257 private DecodeJwtExample() {}

258 

259 static String decodeUtf8(byte[] bytes) throws IOException {

260 try {

261 return StandardCharsets.UTF_8

262 .newDecoder()

263 .onMalformedInput(CodingErrorAction.REPORT)

264 .onUnmappableCharacter(CodingErrorAction.REPORT)

265 .decode(ByteBuffer.wrap(bytes))

266 .toString();

267 } catch (CharacterCodingException exception) {

268 throw new IOException("JWT segment is not valid UTF-8", exception);

269 }

270 }

271 

272 static String decodeSegment(String segment) throws IOException {

273 if (!isBase64UrlSegment(segment)) {

274 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

275 }

276 byte[] bytes = Base64.getUrlDecoder().decode(segment);

277 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

278 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

279 }

280 String decoded = decodeUtf8(bytes);

281 JsonNode value = JSON.readTree(decoded);

282 if (value == null || value.isMissingNode() || !value.isObject()) {

283 throw new IOException("JWT segment is not a JSON object");

284 }

285 return decoded;

286 }

287 

288 static boolean isBase64UrlSegment(String segment) {

289 if (segment.isEmpty() || segment.length() % 4 == 1) {

290 return false;

291 }

292 return segment

293 .chars()

294 .allMatch(

295 character ->

296 character >= 'A' && character <= 'Z'

297 || character >= 'a' && character <= 'z'

298 || character >= '0' && character <= '9'

299 || character == '-'

300 || character == '_');

301 }

302 

303 static String[] requireCompactJwt(String token) {

304 if (token == null) {

305 throw new IllegalArgumentException("Expected a compact JWT with three segments");

306 }

307 String[] parts = token.split("\\.", -1);

308 if (parts.length != 3) {

309 throw new IllegalArgumentException("Expected a compact JWT with three segments");

310 }

311 return parts;

312 }

313 

314 public static void main(String[] args) throws IOException {

315 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

316 System.out.println("Header:");

317 System.out.println(decodeSegment(parts[0]));

318 System.out.println("\nPayload:");

319 System.out.println(decodeSegment(parts[1]));

320 }

321}

322```

323 

324```csharp

325using System.Text;

326using System.Text.Json;

327 

328static string DecodeSegment(string segment)

329{

330 if (

331 segment.Length % 4 == 1 ||

332 segment.Any(

333 character =>

334 !(

335 character is >= 'A' and <= 'Z' ||

336 character is >= 'a' and <= 'z' ||

337 character is >= '0' and <= '9' ||

338 character is '-' or '_'

339 )

340 )

341 )

342 {

343 throw new FormatException("JWT segment is not valid Base64URL");

344 }

345 

346 byte[] decoded = Convert.FromBase64String(

347 segment.Replace('-', '+').Replace('_', '/') +

348 new string('=', (4 - segment.Length % 4) % 4)

349 );

350 string canonicalSegment = Convert

351 .ToBase64String(decoded)

352 .TrimEnd('=')

353 .Replace('+', '-')

354 .Replace('/', '_');

355 if (canonicalSegment != segment)

356 {

357 throw new FormatException("JWT segment is not valid Base64URL");

358 }

359 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

360 using JsonDocument document = JsonDocument.Parse(decodedJson);

361 if (document.RootElement.ValueKind is not JsonValueKind.Object)

362 {

363 throw new FormatException("JWT segment is not a JSON object");

364 }

365 return decodedJson;

366}

367 

368string? token = Environment.GetEnvironmentVariable("TOKEN");

369if (token is null)

370{

371 throw new InvalidOperationException(

372 "Expected a compact JWT with three segments"

373 );

374}

375string[] parts = token.Split('.');

376if (parts.Length != 3)

377{

378 throw new InvalidOperationException(

379 "Expected a compact JWT with three segments"

380 );

381}

382 

383Console.WriteLine("Header:");

384Console.WriteLine(DecodeSegment(parts[0]));

385Console.WriteLine("\nPayload:");

386Console.WriteLine(DecodeSegment(parts[1]));

387```

388 

389```ruby

390require "base64"

391require "json"

392 

393parts = ENV.fetch("TOKEN", "").split(".", -1)

394raise "Expected a compact JWT with three segments" unless parts.length == 3

395 

396decode = lambda do |segment|

397 unless segment.match?(/\A[A-Za-z0-9_-]+\z/) && segment.length % 4 != 1

398 raise "JWT segment is not valid Base64URL"

399 end

400 

401 padded = segment.ljust((segment.length + 3) & ~3, "=")

402 begin

403 decoded = Base64.urlsafe_decode64(padded)

404 rescue ArgumentError

405 raise "JWT segment is not valid Base64URL"

406 end

407 unless Base64.urlsafe_encode64(decoded, padding: false) == segment

408 raise "JWT segment is not valid Base64URL"

409 end

410 decoded.force_encoding(Encoding::UTF_8)

411 raise "JWT segment is not valid UTF-8" unless decoded.valid_encoding?

412 

413 value = JSON.parse(decoded)

414 raise "JWT segment is not a JSON object" unless value.is_a?(Hash)

415 

416 decoded

417end

418 

419puts("Header:")

420puts(decode.call(parts[0]))

421puts("\nPayload:")

422puts(decode.call(parts[1]))

109```423```

110 424 

111 425 

112This command decodes the JWT without verifying the token signature. Use a local decoder for production tokens, and avoid pasting production tokens into third-party tools.426Each example decodes the JWT without verifying the token signature. Use a local decoder for production tokens, and avoid pasting production tokens into third-party tools.

113 427 

114A decoded SPIFFE JWT-SVID will look similar to:428A decoded SPIFFE JWT-SVID will look similar to:

115 429 

libraries.md +1 −1

Details

173<dependency>173<dependency>

174 <groupId>com.openai</groupId>174 <groupId>com.openai</groupId>

175 <artifactId>openai-java</artifactId>175 <artifactId>openai-java</artifactId>

176 <version>4.57.0</version>176 <version>4.58.0</version>

177</dependency>177</dependency>

178```178```

179 179 

models.md +0 −2

Details

10 10 

11## Featured models11## Featured models

12 12 

13GPT‑6 Astra is rolling out today for enterprises in our [Trusted Access Program⁠](https://openai.com/form/enterprise-trusted-access-for-cyber/), with access through API and our Plus, Pro, Business and Enterprise plans coming in the coming days.

14 

15- [GPT-6 Astra](/api/docs/models/gpt-6-astra.md): Our most capable model, built for the hardest end-to-end work13- [GPT-6 Astra](/api/docs/models/gpt-6-astra.md): Our most capable model, built for the hardest end-to-end work

16- [GPT-5.6 Sol](/api/docs/models/gpt-5.6-sol.md): Flagship model for complex professional work14- [GPT-5.6 Sol](/api/docs/models/gpt-5.6-sol.md): Flagship model for complex professional work

17- [GPT-5.6 Terra](/api/docs/models/gpt-5.6-terra.md): GPT-5.6 model that balances intelligence and cost15- [GPT-5.6 Terra](/api/docs/models/gpt-5.6-terra.md): GPT-5.6 model that balances intelligence and cost

models/all.md +0 −2

Details

10 10 

11## Featured models11## Featured models

12 12 

13GPT‑6 Astra is rolling out today for enterprises in our [Trusted Access Program⁠](https://openai.com/form/enterprise-trusted-access-for-cyber/), with access through API and our Plus, Pro, Business and Enterprise plans coming in the coming days.

14 

15- [GPT-6 Astra](/api/docs/models/gpt-6-astra.md): Our most capable model, built for the hardest end-to-end work13- [GPT-6 Astra](/api/docs/models/gpt-6-astra.md): Our most capable model, built for the hardest end-to-end work

16- [GPT-5.6 Sol](/api/docs/models/gpt-5.6-sol.md): Flagship model for complex professional work14- [GPT-5.6 Sol](/api/docs/models/gpt-5.6-sol.md): Flagship model for complex professional work

17- [GPT-5.6 Terra](/api/docs/models/gpt-5.6-terra.md): GPT-5.6 model that balances intelligence and cost15- [GPT-5.6 Terra](/api/docs/models/gpt-5.6-terra.md): GPT-5.6 model that balances intelligence and cost

quickstart.md +1 −1

Details

190<dependency>190<dependency>

191 <groupId>com.openai</groupId>191 <groupId>com.openai</groupId>

192 <artifactId>openai-java</artifactId>192 <artifactId>openai-java</artifactId>

193 <version>4.57.0</version>193 <version>4.58.0</version>

194</dependency>194</dependency>

195```195```

196 196