[{"data":1,"prerenderedAt":3342},["ShallowReactive",2],{"navigation":3,"page:\u002Fopenai":45},{"title":4,"links":5},"API reference",[6,9,12,15,18,21,24,27,30,33,36,39,42],{"title":7,"href":8},"Accounts","\u002Freference\u002Faccounts",{"title":10,"href":11},"Connect","\u002Freference\u002Fconnect",{"title":13,"href":14},"Calendars","\u002Freference\u002Fcalendars",{"title":16,"href":17},"Events","\u002Freference\u002Fevents",{"title":19,"href":20},"Availability","\u002Freference\u002Favailability",{"title":22,"href":23},"Meetings","\u002Freference\u002Fmeetings",{"title":25,"href":26},"Invitations","\u002Freference\u002Finvitations",{"title":28,"href":29},"Webhooks","\u002Freference\u002Fwebhooks",{"title":31,"href":32},"Metadata","\u002Freference\u002Fmetadata",{"title":34,"href":35},"Keys","\u002Freference\u002Fkeys",{"title":37,"href":38},"Sandbox","\u002Freference\u002Fsandbox",{"title":40,"href":41},"Usage","\u002Freference\u002Fusage",{"title":43,"href":44},"Logs","\u002Freference\u002Flogs",{"kind":46,"title":47,"description":48,"sections":49,"doc":65},"guide","OpenAI","Add a calendar assistant to your app with OpenAI and Pyas, with Chat Completions or the Responses API. It reads each user's Google and Outlook calendars, and asks them before it books, moves or deletes anything.",[50,53,56,59,62],{"id":51,"title":52},"install","Install",{"id":54,"title":55},"the-assistant","The assistant",{"id":57,"title":58},"your-routes","Your routes",{"id":60,"title":61},"show-the-change","Show the change",{"id":63,"title":64},"try-it","Try it",{"id":66,"title":47,"body":67,"description":48,"extension":3334,"hero":3335,"meta":3336,"navigation":248,"path":3337,"rawbody":3338,"sections":49,"seo":3339,"stem":73,"v1":3340,"__hash__":3341},"docs\u002Fopenai.md",{"type":68,"value":69,"toc":3327},"minimark",[70,74,81,95,98,126,129,162,164,175,2406,2412,2414,2417,2933,2947,2949,2955,3247,3258,3279,3286,3294,3296,3316,3323],[71,72,47],"h1",{"id":73},"openai",[75,76,77],"lead",{},[78,79,80],"p",{},"This guide adds a calendar assistant to your server with OpenAI's API, with Chat Completions or the Responses API. The assistant uses Pyas's tools for the signed-in user only, and each change waits for them to approve it.",[78,82,83,84,88,89,94],{},"The code was tested with ",[85,86,87],"code",{},"gpt-5.5",". Any model that calls functions works. See ",[90,91,93],"a",{"href":92},"\u002Fai-in-your-app","AI in your app"," for how the tools work.",[96,97,52],"h2",{"id":51},[99,100,105],"pre",{"className":101,"code":102,"language":103,"meta":104,"style":104},"language-bash shiki shiki-themes css-variables","npm install @pyas\u002Fai-tools openai\n","bash","",[85,106,107],{"__ignoreMap":104},[108,109,112,116,120,123],"span",{"class":110,"line":111},"line",1,[108,113,115],{"class":114},"s1IGz","npm",[108,117,119],{"class":118},"sTB4W"," install",[108,121,122],{"class":118}," @pyas\u002Fai-tools",[108,124,125],{"class":118}," openai\n",[78,127,128],{},"Set these on your server:",[99,130,132],{"className":101,"code":131,"language":103,"meta":104,"style":104},"PYAS_API_KEY=pyas_sk_test_...   # a test key from API keys in the Pyas dashboard\nOPENAI_API_KEY=sk-...\n",[85,133,134,151],{"__ignoreMap":104},[108,135,136,140,144,147],{"class":110,"line":111},[108,137,139],{"class":138},"sqSWz","PYAS_API_KEY",[108,141,143],{"class":142},"sag7P","=",[108,145,146],{"class":118},"pyas_sk_test_...",[108,148,150],{"class":149},"ssgCD","   # a test key from API keys in the Pyas dashboard\n",[108,152,154,157,159],{"class":110,"line":153},2,[108,155,156],{"class":138},"OPENAI_API_KEY",[108,158,143],{"class":142},[108,160,161],{"class":118},"sk-...\n",[96,163,55],{"id":54},[78,165,166,167,170,171,174],{},"The assistant has two functions. ",[85,168,169],{},"ask"," sends the person's message. ",[85,172,173],{},"answer"," sends which of the waiting changes they approved. Each returns what to show the person: a reply, or the changes to approve.",[176,177,178,1385],"code-group",{},[99,179,184],{"className":180,"code":181,"filename":182,"language":183,"meta":104,"style":104},"language-ts shiki shiki-themes css-variables","import OpenAI from 'openai'\nimport type { ChatCompletionMessageParam } from 'openai\u002Fresources\u002Fchat\u002Fcompletions'\nimport { pyasFunctionDefinitions, runPyasToolCalls, type PyasPendingToolCall } from '@pyas\u002Fai-tools\u002Fopenai'\n\nconst openai = new OpenAI()\n\n\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\nexport interface Conversation {\n    messages: ChatCompletionMessageParam[]\n    \u002F** Changes waiting for the person, with the results that go back to the model alongside them. *\u002F\n    waiting?: { results: ChatCompletionMessageParam[]; pending: PyasPendingToolCall[] }\n}\n\n\u002F** What to show the person: the assistant's reply, or the changes waiting for their approval. *\u002F\nexport type Turn =\n    | { reply: string }\n    | { changes: Array\u003CPick\u003CPyasPendingToolCall, 'id' | 'title' | 'input' | 'event'>> }\n\nexport function newConversation(): Conversation {\n    \u002F\u002F The same for the whole conversation. Today's date lets the model work out \"tomorrow\".\n    const today = new Date().toISOString().slice(0, 10)\n    return { messages: [{ role: 'system', content: `You help the user manage their calendar. Today is ${today}.` }] }\n}\n\n\u002F** The person sends a message. *\u002F\nexport async function ask(conversation: Conversation, userId: string, text: string): Promise\u003CTurn> {\n    conversation.messages.push({ role: 'user', content: text })\n    return run(conversation, userId)\n}\n\n\u002F** The person answers the changes: the ids they approved. Every other change is declined. *\u002F\nexport async function answer(conversation: Conversation, userId: string, approved: string[]): Promise\u003CTurn> {\n    if (!conversation.waiting) throw new Error('No change is waiting for an answer.')\n    const { results, pending } = conversation.waiting\n    const declined = pending.map((change) => change.id).filter((id) => !approved.includes(id))\n    const answered = await runPyasToolCalls(pending.map((change) => change.call), { subjectId: userId, approved, declined })\n    conversation.messages.push(...results, ...answered.results)\n    conversation.waiting = undefined\n    return run(conversation, userId)\n}\n\nasync function run(conversation: Conversation, userId: string): Promise\u003CTurn> {\n    const options = { subjectId: userId }\n    for (let step = 0; step \u003C 10; step++) {\n        const response = await openai.chat.completions.create({\n            model: 'gpt-5.5',\n            messages: conversation.messages,\n            tools: pyasFunctionDefinitions(options),\n        })\n        const message = response.choices[0].message\n        conversation.messages.push(message)\n        if (!message.tool_calls?.length) return { reply: message.content ?? '' }\n\n        \u002F\u002F Reads run now. Changes come back in pending, and nothing is sent to Pyas until the person approves.\n        const { results, pending } = await runPyasToolCalls(message.tool_calls, options)\n        if (pending.length) {\n            conversation.waiting = { results, pending }\n            return { changes: pending.map(({ id, title, input, event }) => ({ id, title, input, event })) }\n        }\n        conversation.messages.push(...results)\n    }\n    return { reply: 'I stopped before finishing. Please ask again.' }\n}\n","Chat Completions","ts",[85,185,186,201,216,243,250,272,277,283,298,313,319,346,352,357,363,376,393,441,446,466,472,512,556,561,566,572,626,658,673,678,683,689,737,770,797,844,893,922,935,948,953,958,994,1011,1046,1077,1091,1106,1122,1128,1148,1163,1210,1215,1221,1254,1269,1286,1336,1342,1360,1366,1380],{"__ignoreMap":104},[108,187,188,191,194,197],{"class":110,"line":111},[108,189,190],{"class":142},"import",[108,192,193],{"class":138}," OpenAI ",[108,195,196],{"class":142},"from",[108,198,200],{"class":199},"sk1Au"," 'openai'\n",[108,202,203,205,208,211,213],{"class":110,"line":153},[108,204,190],{"class":142},[108,206,207],{"class":142}," type",[108,209,210],{"class":138}," { ChatCompletionMessageParam } ",[108,212,196],{"class":142},[108,214,215],{"class":199}," 'openai\u002Fresources\u002Fchat\u002Fcompletions'\n",[108,217,219,221,224,228,231,233,235,238,240],{"class":110,"line":218},3,[108,220,190],{"class":142},[108,222,223],{"class":138}," { pyasFunctionDefinitions",[108,225,227],{"class":226},"sEEt9",",",[108,229,230],{"class":138}," runPyasToolCalls",[108,232,227],{"class":226},[108,234,207],{"class":142},[108,236,237],{"class":138}," PyasPendingToolCall } ",[108,239,196],{"class":142},[108,241,242],{"class":199}," '@pyas\u002Fai-tools\u002Fopenai'\n",[108,244,246],{"class":110,"line":245},4,[108,247,249],{"emptyLinePlaceholder":248},true,"\n",[108,251,253,256,260,263,266,269],{"class":110,"line":252},5,[108,254,255],{"class":142},"const",[108,257,259],{"class":258},"sXULy"," openai",[108,261,262],{"class":142}," =",[108,264,265],{"class":142}," new",[108,267,268],{"class":114}," OpenAI",[108,270,271],{"class":138},"()\n",[108,273,275],{"class":110,"line":274},6,[108,276,249],{"emptyLinePlaceholder":248},[108,278,280],{"class":110,"line":279},7,[108,281,282],{"class":149},"\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\n",[108,284,286,289,292,295],{"class":110,"line":285},8,[108,287,288],{"class":142},"export",[108,290,291],{"class":142}," interface",[108,293,294],{"class":114}," Conversation",[108,296,297],{"class":138}," {\n",[108,299,301,304,307,310],{"class":110,"line":300},9,[108,302,303],{"class":138},"    messages",[108,305,306],{"class":142},":",[108,308,309],{"class":114}," ChatCompletionMessageParam",[108,311,312],{"class":138},"[]\n",[108,314,316],{"class":110,"line":315},10,[108,317,318],{"class":149},"    \u002F** Changes waiting for the person, with the results that go back to the model alongside them. *\u002F\n",[108,320,322,325,328,331,333,335,338,340,343],{"class":110,"line":321},11,[108,323,324],{"class":138},"    waiting",[108,326,327],{"class":142},"?:",[108,329,330],{"class":138}," { results",[108,332,306],{"class":142},[108,334,309],{"class":114},[108,336,337],{"class":138},"[]; pending",[108,339,306],{"class":142},[108,341,342],{"class":114}," PyasPendingToolCall",[108,344,345],{"class":138},"[] }\n",[108,347,349],{"class":110,"line":348},12,[108,350,351],{"class":138},"}\n",[108,353,355],{"class":110,"line":354},13,[108,356,249],{"emptyLinePlaceholder":248},[108,358,360],{"class":110,"line":359},14,[108,361,362],{"class":149},"\u002F** What to show the person: the assistant's reply, or the changes waiting for their approval. *\u002F\n",[108,364,366,368,370,373],{"class":110,"line":365},15,[108,367,288],{"class":142},[108,369,207],{"class":142},[108,371,372],{"class":114}," Turn",[108,374,375],{"class":142}," =\n",[108,377,379,382,385,387,390],{"class":110,"line":378},16,[108,380,381],{"class":142},"    |",[108,383,384],{"class":138}," { reply",[108,386,306],{"class":142},[108,388,389],{"class":258}," string",[108,391,392],{"class":138}," }\n",[108,394,396,398,401,403,406,409,412,414,417,419,422,425,428,430,433,435,438],{"class":110,"line":395},17,[108,397,381],{"class":142},[108,399,400],{"class":138}," { changes",[108,402,306],{"class":142},[108,404,405],{"class":114}," Array",[108,407,408],{"class":138},"\u003C",[108,410,411],{"class":114},"Pick",[108,413,408],{"class":138},[108,415,416],{"class":114},"PyasPendingToolCall",[108,418,227],{"class":226},[108,420,421],{"class":199}," 'id'",[108,423,424],{"class":142}," |",[108,426,427],{"class":199}," 'title'",[108,429,424],{"class":142},[108,431,432],{"class":199}," 'input'",[108,434,424],{"class":142},[108,436,437],{"class":199}," 'event'",[108,439,440],{"class":138},">> }\n",[108,442,444],{"class":110,"line":443},18,[108,445,249],{"emptyLinePlaceholder":248},[108,447,449,451,454,457,460,462,464],{"class":110,"line":448},19,[108,450,288],{"class":142},[108,452,453],{"class":142}," function",[108,455,456],{"class":114}," newConversation",[108,458,459],{"class":138},"()",[108,461,306],{"class":142},[108,463,294],{"class":114},[108,465,297],{"class":138},[108,467,469],{"class":110,"line":468},20,[108,470,471],{"class":149},"    \u002F\u002F The same for the whole conversation. Today's date lets the model work out \"tomorrow\".\n",[108,473,475,478,481,483,485,488,490,493,495,498,501,504,506,509],{"class":110,"line":474},21,[108,476,477],{"class":142},"    const",[108,479,480],{"class":258}," today",[108,482,262],{"class":142},[108,484,265],{"class":142},[108,486,487],{"class":114}," Date",[108,489,459],{"class":138},[108,491,492],{"class":114},".toISOString",[108,494,459],{"class":138},[108,496,497],{"class":114},".slice",[108,499,500],{"class":138},"(",[108,502,503],{"class":258},"0",[108,505,227],{"class":226},[108,507,508],{"class":258}," 10",[108,510,511],{"class":138},")\n",[108,513,515,518,521,523,526,528,531,533,536,538,541,544,547,550,553],{"class":110,"line":514},22,[108,516,517],{"class":142},"    return",[108,519,520],{"class":138}," { messages",[108,522,306],{"class":142},[108,524,525],{"class":138}," [{ role",[108,527,306],{"class":142},[108,529,530],{"class":199}," 'system'",[108,532,227],{"class":226},[108,534,535],{"class":138}," content",[108,537,306],{"class":142},[108,539,540],{"class":199}," `You help the user manage their calendar. Today is ",[108,542,543],{"class":142},"${",[108,545,546],{"class":138},"today",[108,548,549],{"class":142},"}",[108,551,552],{"class":199},".`",[108,554,555],{"class":138}," }] }\n",[108,557,559],{"class":110,"line":558},23,[108,560,351],{"class":138},[108,562,564],{"class":110,"line":563},24,[108,565,249],{"emptyLinePlaceholder":248},[108,567,569],{"class":110,"line":568},25,[108,570,571],{"class":149},"\u002F** The person sends a message. *\u002F\n",[108,573,575,577,580,582,585,588,590,592,594,597,599,601,603,606,608,610,613,615,618,620,623],{"class":110,"line":574},26,[108,576,288],{"class":142},[108,578,579],{"class":142}," async",[108,581,453],{"class":142},[108,583,584],{"class":114}," ask",[108,586,587],{"class":138},"(conversation",[108,589,306],{"class":142},[108,591,294],{"class":114},[108,593,227],{"class":226},[108,595,596],{"class":138}," userId",[108,598,306],{"class":142},[108,600,389],{"class":258},[108,602,227],{"class":226},[108,604,605],{"class":138}," text",[108,607,306],{"class":142},[108,609,389],{"class":258},[108,611,612],{"class":138},")",[108,614,306],{"class":142},[108,616,617],{"class":114}," Promise",[108,619,408],{"class":138},[108,621,622],{"class":114},"Turn",[108,624,625],{"class":138},"> {\n",[108,627,629,632,635,638,641,644,646,649,651,653,655],{"class":110,"line":628},27,[108,630,631],{"class":258},"    conversation",[108,633,634],{"class":114},".",[108,636,637],{"class":258},"messages",[108,639,640],{"class":114},".push",[108,642,643],{"class":138},"({ role",[108,645,306],{"class":142},[108,647,648],{"class":199}," 'user'",[108,650,227],{"class":226},[108,652,535],{"class":138},[108,654,306],{"class":142},[108,656,657],{"class":138}," text })\n",[108,659,661,663,666,668,670],{"class":110,"line":660},28,[108,662,517],{"class":142},[108,664,665],{"class":114}," run",[108,667,587],{"class":138},[108,669,227],{"class":226},[108,671,672],{"class":138}," userId)\n",[108,674,676],{"class":110,"line":675},29,[108,677,351],{"class":138},[108,679,681],{"class":110,"line":680},30,[108,682,249],{"emptyLinePlaceholder":248},[108,684,686],{"class":110,"line":685},31,[108,687,688],{"class":149},"\u002F** The person answers the changes: the ids they approved. Every other change is declined. *\u002F\n",[108,690,692,694,696,698,701,703,705,707,709,711,713,715,717,720,722,724,727,729,731,733,735],{"class":110,"line":691},32,[108,693,288],{"class":142},[108,695,579],{"class":142},[108,697,453],{"class":142},[108,699,700],{"class":114}," answer",[108,702,587],{"class":138},[108,704,306],{"class":142},[108,706,294],{"class":114},[108,708,227],{"class":226},[108,710,596],{"class":138},[108,712,306],{"class":142},[108,714,389],{"class":258},[108,716,227],{"class":226},[108,718,719],{"class":138}," approved",[108,721,306],{"class":142},[108,723,389],{"class":258},[108,725,726],{"class":138},"[])",[108,728,306],{"class":142},[108,730,617],{"class":114},[108,732,408],{"class":138},[108,734,622],{"class":114},[108,736,625],{"class":138},[108,738,740,743,746,749,752,755,758,760,763,765,768],{"class":110,"line":739},33,[108,741,742],{"class":142},"    if",[108,744,745],{"class":138}," (",[108,747,748],{"class":142},"!",[108,750,751],{"class":258},"conversation",[108,753,754],{"class":138},".waiting) ",[108,756,757],{"class":142},"throw",[108,759,265],{"class":142},[108,761,762],{"class":114}," Error",[108,764,500],{"class":138},[108,766,767],{"class":199},"'No change is waiting for an answer.'",[108,769,511],{"class":138},[108,771,773,775,778,781,783,786,789,791,794],{"class":110,"line":772},34,[108,774,477],{"class":142},[108,776,777],{"class":138}," { ",[108,779,780],{"class":258},"results",[108,782,227],{"class":226},[108,784,785],{"class":258}," pending",[108,787,788],{"class":138}," } ",[108,790,143],{"class":142},[108,792,793],{"class":258}," conversation",[108,795,796],{"class":138},".waiting\n",[108,798,800,802,805,807,809,812,815,818,821,824,827,830,832,835,838,841],{"class":110,"line":799},35,[108,801,477],{"class":142},[108,803,804],{"class":258}," declined",[108,806,262],{"class":142},[108,808,785],{"class":258},[108,810,811],{"class":114},".map",[108,813,814],{"class":138},"((change) ",[108,816,817],{"class":142},"=>",[108,819,820],{"class":258}," change",[108,822,823],{"class":138},".id)",[108,825,826],{"class":114},".filter",[108,828,829],{"class":138},"((id) ",[108,831,817],{"class":142},[108,833,834],{"class":142}," !",[108,836,837],{"class":258},"approved",[108,839,840],{"class":114},".includes",[108,842,843],{"class":138},"(id))\n",[108,845,847,849,852,854,857,859,861,864,866,868,870,872,875,877,880,882,884,886,888,890],{"class":110,"line":846},36,[108,848,477],{"class":142},[108,850,851],{"class":258}," answered",[108,853,262],{"class":142},[108,855,856],{"class":142}," await",[108,858,230],{"class":114},[108,860,500],{"class":138},[108,862,863],{"class":258},"pending",[108,865,811],{"class":114},[108,867,814],{"class":138},[108,869,817],{"class":142},[108,871,820],{"class":258},[108,873,874],{"class":138},".call)",[108,876,227],{"class":226},[108,878,879],{"class":138}," { subjectId",[108,881,306],{"class":142},[108,883,596],{"class":138},[108,885,227],{"class":226},[108,887,719],{"class":138},[108,889,227],{"class":226},[108,891,892],{"class":138}," declined })\n",[108,894,896,898,900,902,904,906,909,911,913,916,919],{"class":110,"line":895},37,[108,897,631],{"class":258},[108,899,634],{"class":114},[108,901,637],{"class":258},[108,903,640],{"class":114},[108,905,500],{"class":138},[108,907,908],{"class":142},"...",[108,910,780],{"class":138},[108,912,227],{"class":226},[108,914,915],{"class":142}," ...",[108,917,918],{"class":258},"answered",[108,920,921],{"class":138},".results)\n",[108,923,925,927,930,932],{"class":110,"line":924},38,[108,926,631],{"class":258},[108,928,929],{"class":138},".waiting ",[108,931,143],{"class":142},[108,933,934],{"class":258}," undefined\n",[108,936,938,940,942,944,946],{"class":110,"line":937},39,[108,939,517],{"class":142},[108,941,665],{"class":114},[108,943,587],{"class":138},[108,945,227],{"class":226},[108,947,672],{"class":138},[108,949,951],{"class":110,"line":950},40,[108,952,351],{"class":138},[108,954,956],{"class":110,"line":955},41,[108,957,249],{"emptyLinePlaceholder":248},[108,959,961,964,966,968,970,972,974,976,978,980,982,984,986,988,990,992],{"class":110,"line":960},42,[108,962,963],{"class":142},"async",[108,965,453],{"class":142},[108,967,665],{"class":114},[108,969,587],{"class":138},[108,971,306],{"class":142},[108,973,294],{"class":114},[108,975,227],{"class":226},[108,977,596],{"class":138},[108,979,306],{"class":142},[108,981,389],{"class":258},[108,983,612],{"class":138},[108,985,306],{"class":142},[108,987,617],{"class":114},[108,989,408],{"class":138},[108,991,622],{"class":114},[108,993,625],{"class":138},[108,995,997,999,1002,1004,1006,1008],{"class":110,"line":996},43,[108,998,477],{"class":142},[108,1000,1001],{"class":258}," options",[108,1003,262],{"class":142},[108,1005,879],{"class":138},[108,1007,306],{"class":142},[108,1009,1010],{"class":138}," userId }\n",[108,1012,1014,1017,1019,1022,1025,1027,1030,1033,1035,1037,1040,1043],{"class":110,"line":1013},44,[108,1015,1016],{"class":142},"    for",[108,1018,745],{"class":138},[108,1020,1021],{"class":142},"let",[108,1023,1024],{"class":138}," step ",[108,1026,143],{"class":142},[108,1028,1029],{"class":258}," 0",[108,1031,1032],{"class":138},"; step ",[108,1034,408],{"class":142},[108,1036,508],{"class":258},[108,1038,1039],{"class":138},"; step",[108,1041,1042],{"class":142},"++",[108,1044,1045],{"class":138},") {\n",[108,1047,1049,1052,1055,1057,1059,1061,1063,1066,1068,1071,1074],{"class":110,"line":1048},45,[108,1050,1051],{"class":142},"        const",[108,1053,1054],{"class":258}," response",[108,1056,262],{"class":142},[108,1058,856],{"class":142},[108,1060,259],{"class":258},[108,1062,634],{"class":114},[108,1064,1065],{"class":258},"chat",[108,1067,634],{"class":114},[108,1069,1070],{"class":258},"completions",[108,1072,1073],{"class":114},".create",[108,1075,1076],{"class":138},"({\n",[108,1078,1080,1083,1085,1088],{"class":110,"line":1079},46,[108,1081,1082],{"class":138},"            model",[108,1084,306],{"class":142},[108,1086,1087],{"class":199}," 'gpt-5.5'",[108,1089,1090],{"class":226},",\n",[108,1092,1094,1097,1099,1101,1104],{"class":110,"line":1093},47,[108,1095,1096],{"class":138},"            messages",[108,1098,306],{"class":142},[108,1100,793],{"class":258},[108,1102,1103],{"class":138},".messages",[108,1105,1090],{"class":226},[108,1107,1109,1112,1114,1117,1120],{"class":110,"line":1108},48,[108,1110,1111],{"class":138},"            tools",[108,1113,306],{"class":142},[108,1115,1116],{"class":114}," pyasFunctionDefinitions",[108,1118,1119],{"class":138},"(options)",[108,1121,1090],{"class":226},[108,1123,1125],{"class":110,"line":1124},49,[108,1126,1127],{"class":138},"        })\n",[108,1129,1131,1133,1136,1138,1140,1143,1145],{"class":110,"line":1130},50,[108,1132,1051],{"class":142},[108,1134,1135],{"class":258}," message",[108,1137,262],{"class":142},[108,1139,1054],{"class":258},[108,1141,1142],{"class":138},".choices[",[108,1144,503],{"class":258},[108,1146,1147],{"class":138},"].message\n",[108,1149,1151,1154,1156,1158,1160],{"class":110,"line":1150},51,[108,1152,1153],{"class":258},"        conversation",[108,1155,634],{"class":114},[108,1157,637],{"class":258},[108,1159,640],{"class":114},[108,1161,1162],{"class":138},"(message)\n",[108,1164,1166,1169,1171,1173,1176,1178,1181,1184,1187,1190,1193,1195,1197,1199,1202,1205,1208],{"class":110,"line":1165},52,[108,1167,1168],{"class":142},"        if",[108,1170,745],{"class":138},[108,1172,748],{"class":142},[108,1174,1175],{"class":258},"message",[108,1177,634],{"class":138},[108,1179,1180],{"class":258},"tool_calls",[108,1182,1183],{"class":138},"?.",[108,1185,1186],{"class":258},"length",[108,1188,1189],{"class":138},") ",[108,1191,1192],{"class":142},"return",[108,1194,384],{"class":138},[108,1196,306],{"class":142},[108,1198,1135],{"class":258},[108,1200,1201],{"class":138},".content ",[108,1203,1204],{"class":142},"??",[108,1206,1207],{"class":199}," ''",[108,1209,392],{"class":138},[108,1211,1213],{"class":110,"line":1212},53,[108,1214,249],{"emptyLinePlaceholder":248},[108,1216,1218],{"class":110,"line":1217},54,[108,1219,1220],{"class":149},"        \u002F\u002F Reads run now. Changes come back in pending, and nothing is sent to Pyas until the person approves.\n",[108,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1249,1251],{"class":110,"line":1223},55,[108,1225,1051],{"class":142},[108,1227,777],{"class":138},[108,1229,780],{"class":258},[108,1231,227],{"class":226},[108,1233,785],{"class":258},[108,1235,788],{"class":138},[108,1237,143],{"class":142},[108,1239,856],{"class":142},[108,1241,230],{"class":114},[108,1243,500],{"class":138},[108,1245,1175],{"class":258},[108,1247,1248],{"class":138},".tool_calls",[108,1250,227],{"class":226},[108,1252,1253],{"class":138}," options)\n",[108,1255,1257,1259,1261,1263,1265,1267],{"class":110,"line":1256},56,[108,1258,1168],{"class":142},[108,1260,745],{"class":138},[108,1262,863],{"class":258},[108,1264,634],{"class":138},[108,1266,1186],{"class":258},[108,1268,1045],{"class":138},[108,1270,1272,1275,1277,1279,1281,1283],{"class":110,"line":1271},57,[108,1273,1274],{"class":258},"            conversation",[108,1276,929],{"class":138},[108,1278,143],{"class":142},[108,1280,330],{"class":138},[108,1282,227],{"class":226},[108,1284,1285],{"class":138}," pending }\n",[108,1287,1289,1292,1294,1296,1298,1300,1303,1305,1308,1310,1313,1315,1318,1320,1323,1325,1327,1329,1331,1333],{"class":110,"line":1288},58,[108,1290,1291],{"class":142},"            return",[108,1293,400],{"class":138},[108,1295,306],{"class":142},[108,1297,785],{"class":258},[108,1299,811],{"class":114},[108,1301,1302],{"class":138},"(({ id",[108,1304,227],{"class":226},[108,1306,1307],{"class":138}," title",[108,1309,227],{"class":226},[108,1311,1312],{"class":138}," input",[108,1314,227],{"class":226},[108,1316,1317],{"class":138}," event }) ",[108,1319,817],{"class":142},[108,1321,1322],{"class":138}," ({ id",[108,1324,227],{"class":226},[108,1326,1307],{"class":138},[108,1328,227],{"class":226},[108,1330,1312],{"class":138},[108,1332,227],{"class":226},[108,1334,1335],{"class":138}," event })) }\n",[108,1337,1339],{"class":110,"line":1338},59,[108,1340,1341],{"class":138},"        }\n",[108,1343,1345,1347,1349,1351,1353,1355,1357],{"class":110,"line":1344},60,[108,1346,1153],{"class":258},[108,1348,634],{"class":114},[108,1350,637],{"class":258},[108,1352,640],{"class":114},[108,1354,500],{"class":138},[108,1356,908],{"class":142},[108,1358,1359],{"class":138},"results)\n",[108,1361,1363],{"class":110,"line":1362},61,[108,1364,1365],{"class":138},"    }\n",[108,1367,1369,1371,1373,1375,1378],{"class":110,"line":1368},62,[108,1370,517],{"class":142},[108,1372,384],{"class":138},[108,1374,306],{"class":142},[108,1376,1377],{"class":199}," 'I stopped before finishing. Please ask again.'",[108,1379,392],{"class":138},[108,1381,1383],{"class":110,"line":1382},63,[108,1384,351],{"class":138},[99,1386,1389],{"className":180,"code":1387,"filename":1388,"language":183,"meta":104,"style":104},"import OpenAI from 'openai'\nimport type { ResponseInputItem } from 'openai\u002Fresources\u002Fresponses\u002Fresponses'\nimport { pyasFunctionDefinitions, runPyasFunctionCalls, type PyasPendingFunctionCall } from '@pyas\u002Fai-tools\u002Fopenai'\n\nconst openai = new OpenAI()\n\n\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\nexport interface Conversation {\n    instructions: string\n    input: ResponseInputItem[]\n    \u002F** Changes waiting for the person, with the results that go back to the model alongside them. *\u002F\n    waiting?: { results: ResponseInputItem[]; pending: PyasPendingFunctionCall[] }\n}\n\n\u002F** What to show the person: the assistant's reply, or the changes waiting for their approval. *\u002F\nexport type Turn =\n    | { reply: string }\n    | { changes: Array\u003CPick\u003CPyasPendingFunctionCall, 'id' | 'title' | 'input' | 'event'>> }\n\nexport function newConversation(): Conversation {\n    \u002F\u002F The same for the whole conversation. Today's date lets the model work out \"tomorrow\".\n    const today = new Date().toISOString().slice(0, 10)\n    return { instructions: `You help the user manage their calendar. Today is ${today}.`, input: [] }\n}\n\n\u002F** The person sends a message. *\u002F\nexport async function ask(conversation: Conversation, userId: string, text: string): Promise\u003CTurn> {\n    conversation.input.push({ role: 'user', content: text })\n    return run(conversation, userId)\n}\n\n\u002F** The person answers the changes: the ids they approved. Every other change is declined. *\u002F\nexport async function answer(conversation: Conversation, userId: string, approved: string[]): Promise\u003CTurn> {\n    if (!conversation.waiting) throw new Error('No change is waiting for an answer.')\n    const { results, pending } = conversation.waiting\n    const declined = pending.map((change) => change.id).filter((id) => !approved.includes(id))\n    const answered = await runPyasFunctionCalls(pending.map((change) => change.call), { subjectId: userId, approved, declined })\n    conversation.input.push(...results, ...answered.results)\n    conversation.waiting = undefined\n    return run(conversation, userId)\n}\n\nasync function run(conversation: Conversation, userId: string): Promise\u003CTurn> {\n    const options = { subjectId: userId }\n    for (let step = 0; step \u003C 10; step++) {\n        const response = await openai.responses.create({\n            model: 'gpt-5.5',\n            instructions: conversation.instructions,\n            input: conversation.input,\n            tools: pyasFunctionDefinitions({ ...options, api: 'responses' }),\n        })\n        conversation.input.push(...(response.output as ResponseInputItem[]))\n        const calls = response.output.filter((item) => item.type === 'function_call')\n        if (!calls.length) return { reply: response.output_text }\n\n        \u002F\u002F Reads run now. Changes come back in pending, and nothing is sent to Pyas until the person approves.\n        const { results, pending } = await runPyasFunctionCalls(calls, options)\n        if (pending.length) {\n            conversation.waiting = { results, pending }\n            return { changes: pending.map(({ id, title, input, event }) => ({ id, title, input, event })) }\n        }\n        conversation.input.push(...results)\n    }\n    return { reply: 'I stopped before finishing. Please ask again.' }\n}\n","Responses API",[85,1390,1391,1401,1415,1437,1441,1455,1459,1463,1473,1483,1495,1499,1520,1524,1528,1532,1542,1554,1591,1595,1611,1615,1645,1673,1677,1681,1685,1729,1754,1766,1770,1774,1778,1822,1846,1866,1900,1942,1966,1976,1988,1992,1996,2030,2044,2070,2091,2101,2115,2129,2160,2164,2194,2231,2259,2263,2267,2294,2308,2322,2364,2368,2384,2388,2401],{"__ignoreMap":104},[108,1392,1393,1395,1397,1399],{"class":110,"line":111},[108,1394,190],{"class":142},[108,1396,193],{"class":138},[108,1398,196],{"class":142},[108,1400,200],{"class":199},[108,1402,1403,1405,1407,1410,1412],{"class":110,"line":153},[108,1404,190],{"class":142},[108,1406,207],{"class":142},[108,1408,1409],{"class":138}," { ResponseInputItem } ",[108,1411,196],{"class":142},[108,1413,1414],{"class":199}," 'openai\u002Fresources\u002Fresponses\u002Fresponses'\n",[108,1416,1417,1419,1421,1423,1426,1428,1430,1433,1435],{"class":110,"line":218},[108,1418,190],{"class":142},[108,1420,223],{"class":138},[108,1422,227],{"class":226},[108,1424,1425],{"class":138}," runPyasFunctionCalls",[108,1427,227],{"class":226},[108,1429,207],{"class":142},[108,1431,1432],{"class":138}," PyasPendingFunctionCall } ",[108,1434,196],{"class":142},[108,1436,242],{"class":199},[108,1438,1439],{"class":110,"line":245},[108,1440,249],{"emptyLinePlaceholder":248},[108,1442,1443,1445,1447,1449,1451,1453],{"class":110,"line":252},[108,1444,255],{"class":142},[108,1446,259],{"class":258},[108,1448,262],{"class":142},[108,1450,265],{"class":142},[108,1452,268],{"class":114},[108,1454,271],{"class":138},[108,1456,1457],{"class":110,"line":274},[108,1458,249],{"emptyLinePlaceholder":248},[108,1460,1461],{"class":110,"line":279},[108,1462,282],{"class":149},[108,1464,1465,1467,1469,1471],{"class":110,"line":285},[108,1466,288],{"class":142},[108,1468,291],{"class":142},[108,1470,294],{"class":114},[108,1472,297],{"class":138},[108,1474,1475,1478,1480],{"class":110,"line":300},[108,1476,1477],{"class":138},"    instructions",[108,1479,306],{"class":142},[108,1481,1482],{"class":258}," string\n",[108,1484,1485,1488,1490,1493],{"class":110,"line":315},[108,1486,1487],{"class":138},"    input",[108,1489,306],{"class":142},[108,1491,1492],{"class":114}," ResponseInputItem",[108,1494,312],{"class":138},[108,1496,1497],{"class":110,"line":321},[108,1498,318],{"class":149},[108,1500,1501,1503,1505,1507,1509,1511,1513,1515,1518],{"class":110,"line":348},[108,1502,324],{"class":138},[108,1504,327],{"class":142},[108,1506,330],{"class":138},[108,1508,306],{"class":142},[108,1510,1492],{"class":114},[108,1512,337],{"class":138},[108,1514,306],{"class":142},[108,1516,1517],{"class":114}," PyasPendingFunctionCall",[108,1519,345],{"class":138},[108,1521,1522],{"class":110,"line":354},[108,1523,351],{"class":138},[108,1525,1526],{"class":110,"line":359},[108,1527,249],{"emptyLinePlaceholder":248},[108,1529,1530],{"class":110,"line":365},[108,1531,362],{"class":149},[108,1533,1534,1536,1538,1540],{"class":110,"line":378},[108,1535,288],{"class":142},[108,1537,207],{"class":142},[108,1539,372],{"class":114},[108,1541,375],{"class":142},[108,1543,1544,1546,1548,1550,1552],{"class":110,"line":395},[108,1545,381],{"class":142},[108,1547,384],{"class":138},[108,1549,306],{"class":142},[108,1551,389],{"class":258},[108,1553,392],{"class":138},[108,1555,1556,1558,1560,1562,1564,1566,1568,1570,1573,1575,1577,1579,1581,1583,1585,1587,1589],{"class":110,"line":443},[108,1557,381],{"class":142},[108,1559,400],{"class":138},[108,1561,306],{"class":142},[108,1563,405],{"class":114},[108,1565,408],{"class":138},[108,1567,411],{"class":114},[108,1569,408],{"class":138},[108,1571,1572],{"class":114},"PyasPendingFunctionCall",[108,1574,227],{"class":226},[108,1576,421],{"class":199},[108,1578,424],{"class":142},[108,1580,427],{"class":199},[108,1582,424],{"class":142},[108,1584,432],{"class":199},[108,1586,424],{"class":142},[108,1588,437],{"class":199},[108,1590,440],{"class":138},[108,1592,1593],{"class":110,"line":448},[108,1594,249],{"emptyLinePlaceholder":248},[108,1596,1597,1599,1601,1603,1605,1607,1609],{"class":110,"line":468},[108,1598,288],{"class":142},[108,1600,453],{"class":142},[108,1602,456],{"class":114},[108,1604,459],{"class":138},[108,1606,306],{"class":142},[108,1608,294],{"class":114},[108,1610,297],{"class":138},[108,1612,1613],{"class":110,"line":474},[108,1614,471],{"class":149},[108,1616,1617,1619,1621,1623,1625,1627,1629,1631,1633,1635,1637,1639,1641,1643],{"class":110,"line":514},[108,1618,477],{"class":142},[108,1620,480],{"class":258},[108,1622,262],{"class":142},[108,1624,265],{"class":142},[108,1626,487],{"class":114},[108,1628,459],{"class":138},[108,1630,492],{"class":114},[108,1632,459],{"class":138},[108,1634,497],{"class":114},[108,1636,500],{"class":138},[108,1638,503],{"class":258},[108,1640,227],{"class":226},[108,1642,508],{"class":258},[108,1644,511],{"class":138},[108,1646,1647,1649,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670],{"class":110,"line":558},[108,1648,517],{"class":142},[108,1650,1651],{"class":138}," { instructions",[108,1653,306],{"class":142},[108,1655,540],{"class":199},[108,1657,543],{"class":142},[108,1659,546],{"class":138},[108,1661,549],{"class":142},[108,1663,552],{"class":199},[108,1665,227],{"class":226},[108,1667,1312],{"class":138},[108,1669,306],{"class":142},[108,1671,1672],{"class":138}," [] }\n",[108,1674,1675],{"class":110,"line":563},[108,1676,351],{"class":138},[108,1678,1679],{"class":110,"line":568},[108,1680,249],{"emptyLinePlaceholder":248},[108,1682,1683],{"class":110,"line":574},[108,1684,571],{"class":149},[108,1686,1687,1689,1691,1693,1695,1697,1699,1701,1703,1705,1707,1709,1711,1713,1715,1717,1719,1721,1723,1725,1727],{"class":110,"line":628},[108,1688,288],{"class":142},[108,1690,579],{"class":142},[108,1692,453],{"class":142},[108,1694,584],{"class":114},[108,1696,587],{"class":138},[108,1698,306],{"class":142},[108,1700,294],{"class":114},[108,1702,227],{"class":226},[108,1704,596],{"class":138},[108,1706,306],{"class":142},[108,1708,389],{"class":258},[108,1710,227],{"class":226},[108,1712,605],{"class":138},[108,1714,306],{"class":142},[108,1716,389],{"class":258},[108,1718,612],{"class":138},[108,1720,306],{"class":142},[108,1722,617],{"class":114},[108,1724,408],{"class":138},[108,1726,622],{"class":114},[108,1728,625],{"class":138},[108,1730,1731,1733,1735,1738,1740,1742,1744,1746,1748,1750,1752],{"class":110,"line":660},[108,1732,631],{"class":258},[108,1734,634],{"class":114},[108,1736,1737],{"class":258},"input",[108,1739,640],{"class":114},[108,1741,643],{"class":138},[108,1743,306],{"class":142},[108,1745,648],{"class":199},[108,1747,227],{"class":226},[108,1749,535],{"class":138},[108,1751,306],{"class":142},[108,1753,657],{"class":138},[108,1755,1756,1758,1760,1762,1764],{"class":110,"line":675},[108,1757,517],{"class":142},[108,1759,665],{"class":114},[108,1761,587],{"class":138},[108,1763,227],{"class":226},[108,1765,672],{"class":138},[108,1767,1768],{"class":110,"line":680},[108,1769,351],{"class":138},[108,1771,1772],{"class":110,"line":685},[108,1773,249],{"emptyLinePlaceholder":248},[108,1775,1776],{"class":110,"line":691},[108,1777,688],{"class":149},[108,1779,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820],{"class":110,"line":739},[108,1781,288],{"class":142},[108,1783,579],{"class":142},[108,1785,453],{"class":142},[108,1787,700],{"class":114},[108,1789,587],{"class":138},[108,1791,306],{"class":142},[108,1793,294],{"class":114},[108,1795,227],{"class":226},[108,1797,596],{"class":138},[108,1799,306],{"class":142},[108,1801,389],{"class":258},[108,1803,227],{"class":226},[108,1805,719],{"class":138},[108,1807,306],{"class":142},[108,1809,389],{"class":258},[108,1811,726],{"class":138},[108,1813,306],{"class":142},[108,1815,617],{"class":114},[108,1817,408],{"class":138},[108,1819,622],{"class":114},[108,1821,625],{"class":138},[108,1823,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844],{"class":110,"line":772},[108,1825,742],{"class":142},[108,1827,745],{"class":138},[108,1829,748],{"class":142},[108,1831,751],{"class":258},[108,1833,754],{"class":138},[108,1835,757],{"class":142},[108,1837,265],{"class":142},[108,1839,762],{"class":114},[108,1841,500],{"class":138},[108,1843,767],{"class":199},[108,1845,511],{"class":138},[108,1847,1848,1850,1852,1854,1856,1858,1860,1862,1864],{"class":110,"line":799},[108,1849,477],{"class":142},[108,1851,777],{"class":138},[108,1853,780],{"class":258},[108,1855,227],{"class":226},[108,1857,785],{"class":258},[108,1859,788],{"class":138},[108,1861,143],{"class":142},[108,1863,793],{"class":258},[108,1865,796],{"class":138},[108,1867,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898],{"class":110,"line":846},[108,1869,477],{"class":142},[108,1871,804],{"class":258},[108,1873,262],{"class":142},[108,1875,785],{"class":258},[108,1877,811],{"class":114},[108,1879,814],{"class":138},[108,1881,817],{"class":142},[108,1883,820],{"class":258},[108,1885,823],{"class":138},[108,1887,826],{"class":114},[108,1889,829],{"class":138},[108,1891,817],{"class":142},[108,1893,834],{"class":142},[108,1895,837],{"class":258},[108,1897,840],{"class":114},[108,1899,843],{"class":138},[108,1901,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940],{"class":110,"line":895},[108,1903,477],{"class":142},[108,1905,851],{"class":258},[108,1907,262],{"class":142},[108,1909,856],{"class":142},[108,1911,1425],{"class":114},[108,1913,500],{"class":138},[108,1915,863],{"class":258},[108,1917,811],{"class":114},[108,1919,814],{"class":138},[108,1921,817],{"class":142},[108,1923,820],{"class":258},[108,1925,874],{"class":138},[108,1927,227],{"class":226},[108,1929,879],{"class":138},[108,1931,306],{"class":142},[108,1933,596],{"class":138},[108,1935,227],{"class":226},[108,1937,719],{"class":138},[108,1939,227],{"class":226},[108,1941,892],{"class":138},[108,1943,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964],{"class":110,"line":924},[108,1945,631],{"class":258},[108,1947,634],{"class":114},[108,1949,1737],{"class":258},[108,1951,640],{"class":114},[108,1953,500],{"class":138},[108,1955,908],{"class":142},[108,1957,780],{"class":138},[108,1959,227],{"class":226},[108,1961,915],{"class":142},[108,1963,918],{"class":258},[108,1965,921],{"class":138},[108,1967,1968,1970,1972,1974],{"class":110,"line":937},[108,1969,631],{"class":258},[108,1971,929],{"class":138},[108,1973,143],{"class":142},[108,1975,934],{"class":258},[108,1977,1978,1980,1982,1984,1986],{"class":110,"line":950},[108,1979,517],{"class":142},[108,1981,665],{"class":114},[108,1983,587],{"class":138},[108,1985,227],{"class":226},[108,1987,672],{"class":138},[108,1989,1990],{"class":110,"line":955},[108,1991,351],{"class":138},[108,1993,1994],{"class":110,"line":960},[108,1995,249],{"emptyLinePlaceholder":248},[108,1997,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028],{"class":110,"line":996},[108,1999,963],{"class":142},[108,2001,453],{"class":142},[108,2003,665],{"class":114},[108,2005,587],{"class":138},[108,2007,306],{"class":142},[108,2009,294],{"class":114},[108,2011,227],{"class":226},[108,2013,596],{"class":138},[108,2015,306],{"class":142},[108,2017,389],{"class":258},[108,2019,612],{"class":138},[108,2021,306],{"class":142},[108,2023,617],{"class":114},[108,2025,408],{"class":138},[108,2027,622],{"class":114},[108,2029,625],{"class":138},[108,2031,2032,2034,2036,2038,2040,2042],{"class":110,"line":1013},[108,2033,477],{"class":142},[108,2035,1001],{"class":258},[108,2037,262],{"class":142},[108,2039,879],{"class":138},[108,2041,306],{"class":142},[108,2043,1010],{"class":138},[108,2045,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068],{"class":110,"line":1048},[108,2047,1016],{"class":142},[108,2049,745],{"class":138},[108,2051,1021],{"class":142},[108,2053,1024],{"class":138},[108,2055,143],{"class":142},[108,2057,1029],{"class":258},[108,2059,1032],{"class":138},[108,2061,408],{"class":142},[108,2063,508],{"class":258},[108,2065,1039],{"class":138},[108,2067,1042],{"class":142},[108,2069,1045],{"class":138},[108,2071,2072,2074,2076,2078,2080,2082,2084,2087,2089],{"class":110,"line":1079},[108,2073,1051],{"class":142},[108,2075,1054],{"class":258},[108,2077,262],{"class":142},[108,2079,856],{"class":142},[108,2081,259],{"class":258},[108,2083,634],{"class":114},[108,2085,2086],{"class":258},"responses",[108,2088,1073],{"class":114},[108,2090,1076],{"class":138},[108,2092,2093,2095,2097,2099],{"class":110,"line":1093},[108,2094,1082],{"class":138},[108,2096,306],{"class":142},[108,2098,1087],{"class":199},[108,2100,1090],{"class":226},[108,2102,2103,2106,2108,2110,2113],{"class":110,"line":1108},[108,2104,2105],{"class":138},"            instructions",[108,2107,306],{"class":142},[108,2109,793],{"class":258},[108,2111,2112],{"class":138},".instructions",[108,2114,1090],{"class":226},[108,2116,2117,2120,2122,2124,2127],{"class":110,"line":1124},[108,2118,2119],{"class":138},"            input",[108,2121,306],{"class":142},[108,2123,793],{"class":258},[108,2125,2126],{"class":138},".input",[108,2128,1090],{"class":226},[108,2130,2131,2133,2135,2137,2140,2142,2145,2147,2150,2152,2155,2158],{"class":110,"line":1130},[108,2132,1111],{"class":138},[108,2134,306],{"class":142},[108,2136,1116],{"class":114},[108,2138,2139],{"class":138},"({ ",[108,2141,908],{"class":142},[108,2143,2144],{"class":138},"options",[108,2146,227],{"class":226},[108,2148,2149],{"class":138}," api",[108,2151,306],{"class":142},[108,2153,2154],{"class":199}," 'responses'",[108,2156,2157],{"class":138}," })",[108,2159,1090],{"class":226},[108,2161,2162],{"class":110,"line":1150},[108,2163,1127],{"class":138},[108,2165,2166,2168,2170,2172,2174,2176,2178,2180,2183,2186,2189,2191],{"class":110,"line":1165},[108,2167,1153],{"class":258},[108,2169,634],{"class":114},[108,2171,1737],{"class":258},[108,2173,640],{"class":114},[108,2175,500],{"class":138},[108,2177,908],{"class":142},[108,2179,500],{"class":138},[108,2181,2182],{"class":258},"response",[108,2184,2185],{"class":138},".output ",[108,2187,2188],{"class":142},"as",[108,2190,1492],{"class":114},[108,2192,2193],{"class":138},"[]))\n",[108,2195,2196,2198,2201,2203,2205,2207,2210,2212,2215,2217,2220,2223,2226,2229],{"class":110,"line":1212},[108,2197,1051],{"class":142},[108,2199,2200],{"class":258}," calls",[108,2202,262],{"class":142},[108,2204,1054],{"class":258},[108,2206,634],{"class":114},[108,2208,2209],{"class":258},"output",[108,2211,826],{"class":114},[108,2213,2214],{"class":138},"((item) ",[108,2216,817],{"class":142},[108,2218,2219],{"class":258}," item",[108,2221,2222],{"class":138},".type ",[108,2224,2225],{"class":142},"===",[108,2227,2228],{"class":199}," 'function_call'",[108,2230,511],{"class":138},[108,2232,2233,2235,2237,2239,2242,2244,2246,2248,2250,2252,2254,2256],{"class":110,"line":1217},[108,2234,1168],{"class":142},[108,2236,745],{"class":138},[108,2238,748],{"class":142},[108,2240,2241],{"class":258},"calls",[108,2243,634],{"class":138},[108,2245,1186],{"class":258},[108,2247,1189],{"class":138},[108,2249,1192],{"class":142},[108,2251,384],{"class":138},[108,2253,306],{"class":142},[108,2255,1054],{"class":258},[108,2257,2258],{"class":138},".output_text }\n",[108,2260,2261],{"class":110,"line":1223},[108,2262,249],{"emptyLinePlaceholder":248},[108,2264,2265],{"class":110,"line":1256},[108,2266,1220],{"class":149},[108,2268,2269,2271,2273,2275,2277,2279,2281,2283,2285,2287,2290,2292],{"class":110,"line":1271},[108,2270,1051],{"class":142},[108,2272,777],{"class":138},[108,2274,780],{"class":258},[108,2276,227],{"class":226},[108,2278,785],{"class":258},[108,2280,788],{"class":138},[108,2282,143],{"class":142},[108,2284,856],{"class":142},[108,2286,1425],{"class":114},[108,2288,2289],{"class":138},"(calls",[108,2291,227],{"class":226},[108,2293,1253],{"class":138},[108,2295,2296,2298,2300,2302,2304,2306],{"class":110,"line":1288},[108,2297,1168],{"class":142},[108,2299,745],{"class":138},[108,2301,863],{"class":258},[108,2303,634],{"class":138},[108,2305,1186],{"class":258},[108,2307,1045],{"class":138},[108,2309,2310,2312,2314,2316,2318,2320],{"class":110,"line":1338},[108,2311,1274],{"class":258},[108,2313,929],{"class":138},[108,2315,143],{"class":142},[108,2317,330],{"class":138},[108,2319,227],{"class":226},[108,2321,1285],{"class":138},[108,2323,2324,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362],{"class":110,"line":1344},[108,2325,1291],{"class":142},[108,2327,400],{"class":138},[108,2329,306],{"class":142},[108,2331,785],{"class":258},[108,2333,811],{"class":114},[108,2335,1302],{"class":138},[108,2337,227],{"class":226},[108,2339,1307],{"class":138},[108,2341,227],{"class":226},[108,2343,1312],{"class":138},[108,2345,227],{"class":226},[108,2347,1317],{"class":138},[108,2349,817],{"class":142},[108,2351,1322],{"class":138},[108,2353,227],{"class":226},[108,2355,1307],{"class":138},[108,2357,227],{"class":226},[108,2359,1312],{"class":138},[108,2361,227],{"class":226},[108,2363,1335],{"class":138},[108,2365,2366],{"class":110,"line":1362},[108,2367,1341],{"class":138},[108,2369,2370,2372,2374,2376,2378,2380,2382],{"class":110,"line":1368},[108,2371,1153],{"class":258},[108,2373,634],{"class":114},[108,2375,1737],{"class":258},[108,2377,640],{"class":114},[108,2379,500],{"class":138},[108,2381,908],{"class":142},[108,2383,1359],{"class":138},[108,2385,2386],{"class":110,"line":1382},[108,2387,1365],{"class":138},[108,2389,2391,2393,2395,2397,2399],{"class":110,"line":2390},64,[108,2392,517],{"class":142},[108,2394,384],{"class":138},[108,2396,306],{"class":142},[108,2398,1377],{"class":199},[108,2400,392],{"class":138},[108,2402,2404],{"class":110,"line":2403},65,[108,2405,351],{"class":138},[78,2407,2408,2409,2411],{},"The tools that only read run as soon as the model calls them. A change comes back in ",[85,2410,863],{},", and nothing is sent to Pyas until the person approves it. The model's other calls in the same turn wait with it, so everything goes back to the model together.",[96,2413,58],{"id":57},[78,2415,2416],{},"Your server keeps each conversation between requests and passes the signed-in person's ID. With Express:",[99,2418,2421],{"className":180,"code":2419,"filename":2420,"language":183,"meta":104,"style":104},"import express from 'express'\nimport { answer, ask, newConversation, type Conversation } from '.\u002Fassistant.js'\n\n\u002F** The signed-in person's ID in your app, from your sign-in (here, the sandbox's Avery Brooks). *\u002F\nfunction currentUserId(_req: express.Request): string | null {\n    return 'sandbox_user_1'\n}\n\n\u002F\u002F One conversation per person. In production, keep them in your database.\nconst conversations = new Map\u003Cstring, Conversation>()\n\nconst app = express()\napp.use(express.json())\n\napp.post('\u002Fchat', async (req, res) => {\n    const userId = currentUserId(req)\n    if (!userId) return res.status(401).json({ error: 'Sign in to use your calendar.' })\n    const conversation = conversations.get(userId) ?? newConversation()\n    conversations.set(userId, conversation)\n    res.json(await ask(conversation, userId, req.body.text))\n})\n\n\u002F\u002F The person's answer: { \"approved\": [\"call_...\"] }. Changes left out are declined.\napp.post('\u002Fchat\u002Fanswer', async (req, res) => {\n    const userId = currentUserId(req)\n    if (!userId) return res.status(401).json({ error: 'Sign in to use your calendar.' })\n    const conversation = conversations.get(userId)\n    if (!conversation?.waiting) return res.status(409).json({ error: 'No change is waiting for an answer.' })\n    res.json(await answer(conversation, userId, req.body.approved ?? []))\n})\n\napp.listen(3457)\n","server.ts",[85,2422,2423,2435,2462,2466,2471,2505,2512,2516,2520,2525,2551,2555,2568,2587,2591,2619,2632,2671,2693,2709,2742,2747,2751,2756,2781,2793,2825,2840,2877,2911,2915,2919],{"__ignoreMap":104},[108,2424,2425,2427,2430,2432],{"class":110,"line":111},[108,2426,190],{"class":142},[108,2428,2429],{"class":138}," express ",[108,2431,196],{"class":142},[108,2433,2434],{"class":199}," 'express'\n",[108,2436,2437,2439,2442,2444,2446,2448,2450,2452,2454,2457,2459],{"class":110,"line":153},[108,2438,190],{"class":142},[108,2440,2441],{"class":138}," { answer",[108,2443,227],{"class":226},[108,2445,584],{"class":138},[108,2447,227],{"class":226},[108,2449,456],{"class":138},[108,2451,227],{"class":226},[108,2453,207],{"class":142},[108,2455,2456],{"class":138}," Conversation } ",[108,2458,196],{"class":142},[108,2460,2461],{"class":199}," '.\u002Fassistant.js'\n",[108,2463,2464],{"class":110,"line":218},[108,2465,249],{"emptyLinePlaceholder":248},[108,2467,2468],{"class":110,"line":245},[108,2469,2470],{"class":149},"\u002F** The signed-in person's ID in your app, from your sign-in (here, the sandbox's Avery Brooks). *\u002F\n",[108,2472,2473,2476,2479,2482,2484,2487,2489,2492,2494,2496,2498,2500,2503],{"class":110,"line":252},[108,2474,2475],{"class":142},"function",[108,2477,2478],{"class":114}," currentUserId",[108,2480,2481],{"class":138},"(_req",[108,2483,306],{"class":142},[108,2485,2486],{"class":114}," express",[108,2488,634],{"class":138},[108,2490,2491],{"class":114},"Request",[108,2493,612],{"class":138},[108,2495,306],{"class":142},[108,2497,389],{"class":258},[108,2499,424],{"class":142},[108,2501,2502],{"class":258}," null",[108,2504,297],{"class":138},[108,2506,2507,2509],{"class":110,"line":274},[108,2508,517],{"class":142},[108,2510,2511],{"class":199}," 'sandbox_user_1'\n",[108,2513,2514],{"class":110,"line":279},[108,2515,351],{"class":138},[108,2517,2518],{"class":110,"line":285},[108,2519,249],{"emptyLinePlaceholder":248},[108,2521,2522],{"class":110,"line":300},[108,2523,2524],{"class":149},"\u002F\u002F One conversation per person. In production, keep them in your database.\n",[108,2526,2527,2529,2532,2534,2536,2539,2541,2544,2546,2548],{"class":110,"line":315},[108,2528,255],{"class":142},[108,2530,2531],{"class":258}," conversations",[108,2533,262],{"class":142},[108,2535,265],{"class":142},[108,2537,2538],{"class":114}," Map",[108,2540,408],{"class":138},[108,2542,2543],{"class":258},"string",[108,2545,227],{"class":226},[108,2547,294],{"class":114},[108,2549,2550],{"class":138},">()\n",[108,2552,2553],{"class":110,"line":321},[108,2554,249],{"emptyLinePlaceholder":248},[108,2556,2557,2559,2562,2564,2566],{"class":110,"line":348},[108,2558,255],{"class":142},[108,2560,2561],{"class":258}," app",[108,2563,262],{"class":142},[108,2565,2486],{"class":114},[108,2567,271],{"class":138},[108,2569,2570,2573,2576,2578,2581,2584],{"class":110,"line":354},[108,2571,2572],{"class":258},"app",[108,2574,2575],{"class":114},".use",[108,2577,500],{"class":138},[108,2579,2580],{"class":258},"express",[108,2582,2583],{"class":114},".json",[108,2585,2586],{"class":138},"())\n",[108,2588,2589],{"class":110,"line":359},[108,2590,249],{"emptyLinePlaceholder":248},[108,2592,2593,2595,2598,2600,2603,2605,2607,2610,2612,2615,2617],{"class":110,"line":365},[108,2594,2572],{"class":258},[108,2596,2597],{"class":114},".post",[108,2599,500],{"class":138},[108,2601,2602],{"class":199},"'\u002Fchat'",[108,2604,227],{"class":226},[108,2606,579],{"class":142},[108,2608,2609],{"class":138}," (req",[108,2611,227],{"class":226},[108,2613,2614],{"class":138}," res) ",[108,2616,817],{"class":142},[108,2618,297],{"class":138},[108,2620,2621,2623,2625,2627,2629],{"class":110,"line":378},[108,2622,477],{"class":142},[108,2624,596],{"class":258},[108,2626,262],{"class":142},[108,2628,2478],{"class":114},[108,2630,2631],{"class":138},"(req)\n",[108,2633,2634,2636,2638,2640,2643,2645,2648,2651,2653,2656,2658,2660,2663,2665,2668],{"class":110,"line":395},[108,2635,742],{"class":142},[108,2637,745],{"class":138},[108,2639,748],{"class":142},[108,2641,2642],{"class":138},"userId) ",[108,2644,1192],{"class":142},[108,2646,2647],{"class":258}," res",[108,2649,2650],{"class":114},".status",[108,2652,500],{"class":138},[108,2654,2655],{"class":258},"401",[108,2657,612],{"class":138},[108,2659,2583],{"class":114},[108,2661,2662],{"class":138},"({ error",[108,2664,306],{"class":142},[108,2666,2667],{"class":199}," 'Sign in to use your calendar.'",[108,2669,2670],{"class":138}," })\n",[108,2672,2673,2675,2677,2679,2681,2684,2687,2689,2691],{"class":110,"line":443},[108,2674,477],{"class":142},[108,2676,793],{"class":258},[108,2678,262],{"class":142},[108,2680,2531],{"class":258},[108,2682,2683],{"class":114},".get",[108,2685,2686],{"class":138},"(userId) ",[108,2688,1204],{"class":142},[108,2690,456],{"class":114},[108,2692,271],{"class":138},[108,2694,2695,2698,2701,2704,2706],{"class":110,"line":448},[108,2696,2697],{"class":258},"    conversations",[108,2699,2700],{"class":114},".set",[108,2702,2703],{"class":138},"(userId",[108,2705,227],{"class":226},[108,2707,2708],{"class":138}," conversation)\n",[108,2710,2711,2714,2716,2718,2721,2723,2725,2727,2729,2731,2734,2736,2739],{"class":110,"line":468},[108,2712,2713],{"class":258},"    res",[108,2715,2583],{"class":114},[108,2717,500],{"class":138},[108,2719,2720],{"class":142},"await",[108,2722,584],{"class":114},[108,2724,587],{"class":138},[108,2726,227],{"class":226},[108,2728,596],{"class":138},[108,2730,227],{"class":226},[108,2732,2733],{"class":258}," req",[108,2735,634],{"class":138},[108,2737,2738],{"class":258},"body",[108,2740,2741],{"class":138},".text))\n",[108,2743,2744],{"class":110,"line":474},[108,2745,2746],{"class":138},"})\n",[108,2748,2749],{"class":110,"line":514},[108,2750,249],{"emptyLinePlaceholder":248},[108,2752,2753],{"class":110,"line":558},[108,2754,2755],{"class":149},"\u002F\u002F The person's answer: { \"approved\": [\"call_...\"] }. Changes left out are declined.\n",[108,2757,2758,2760,2762,2764,2767,2769,2771,2773,2775,2777,2779],{"class":110,"line":563},[108,2759,2572],{"class":258},[108,2761,2597],{"class":114},[108,2763,500],{"class":138},[108,2765,2766],{"class":199},"'\u002Fchat\u002Fanswer'",[108,2768,227],{"class":226},[108,2770,579],{"class":142},[108,2772,2609],{"class":138},[108,2774,227],{"class":226},[108,2776,2614],{"class":138},[108,2778,817],{"class":142},[108,2780,297],{"class":138},[108,2782,2783,2785,2787,2789,2791],{"class":110,"line":568},[108,2784,477],{"class":142},[108,2786,596],{"class":258},[108,2788,262],{"class":142},[108,2790,2478],{"class":114},[108,2792,2631],{"class":138},[108,2794,2795,2797,2799,2801,2803,2805,2807,2809,2811,2813,2815,2817,2819,2821,2823],{"class":110,"line":574},[108,2796,742],{"class":142},[108,2798,745],{"class":138},[108,2800,748],{"class":142},[108,2802,2642],{"class":138},[108,2804,1192],{"class":142},[108,2806,2647],{"class":258},[108,2808,2650],{"class":114},[108,2810,500],{"class":138},[108,2812,2655],{"class":258},[108,2814,612],{"class":138},[108,2816,2583],{"class":114},[108,2818,2662],{"class":138},[108,2820,306],{"class":142},[108,2822,2667],{"class":199},[108,2824,2670],{"class":138},[108,2826,2827,2829,2831,2833,2835,2837],{"class":110,"line":628},[108,2828,477],{"class":142},[108,2830,793],{"class":258},[108,2832,262],{"class":142},[108,2834,2531],{"class":258},[108,2836,2683],{"class":114},[108,2838,2839],{"class":138},"(userId)\n",[108,2841,2842,2844,2846,2848,2850,2853,2855,2857,2859,2861,2864,2866,2868,2870,2872,2875],{"class":110,"line":660},[108,2843,742],{"class":142},[108,2845,745],{"class":138},[108,2847,748],{"class":142},[108,2849,751],{"class":258},[108,2851,2852],{"class":138},"?.waiting) ",[108,2854,1192],{"class":142},[108,2856,2647],{"class":258},[108,2858,2650],{"class":114},[108,2860,500],{"class":138},[108,2862,2863],{"class":258},"409",[108,2865,612],{"class":138},[108,2867,2583],{"class":114},[108,2869,2662],{"class":138},[108,2871,306],{"class":142},[108,2873,2874],{"class":199}," 'No change is waiting for an answer.'",[108,2876,2670],{"class":138},[108,2878,2879,2881,2883,2885,2887,2889,2891,2893,2895,2897,2899,2901,2903,2906,2908],{"class":110,"line":675},[108,2880,2713],{"class":258},[108,2882,2583],{"class":114},[108,2884,500],{"class":138},[108,2886,2720],{"class":142},[108,2888,700],{"class":114},[108,2890,587],{"class":138},[108,2892,227],{"class":226},[108,2894,596],{"class":138},[108,2896,227],{"class":226},[108,2898,2733],{"class":258},[108,2900,634],{"class":138},[108,2902,2738],{"class":258},[108,2904,2905],{"class":138},".approved ",[108,2907,1204],{"class":142},[108,2909,2910],{"class":138}," []))\n",[108,2912,2913],{"class":110,"line":680},[108,2914,2746],{"class":138},[108,2916,2917],{"class":110,"line":685},[108,2918,249],{"emptyLinePlaceholder":248},[108,2920,2921,2923,2926,2928,2931],{"class":110,"line":691},[108,2922,2572],{"class":258},[108,2924,2925],{"class":114},".listen",[108,2927,500],{"class":138},[108,2929,2930],{"class":258},"3457",[108,2932,511],{"class":138},[78,2934,2935,2938,2939,2942,2943,2946],{},[85,2936,2937],{},"currentUserId"," stands for your sign-in. It returns the ID you saved as ",[85,2940,2941],{},"subjectId"," when the person connected their calendar. To try the guide in the sandbox, have it return ",[85,2944,2945],{},"'sandbox_user_1'",", the sandbox person Avery Brooks.",[96,2948,61],{"id":60},[78,2950,2951,2952,2954],{},"When the model wants to change something, ",[85,2953,169],{}," returns the changes instead of a reply:",[99,2956,2960],{"className":2957,"code":2958,"language":2959,"meta":104,"style":104},"language-json shiki shiki-themes css-variables","{\n  \"changes\": [\n    {\n      \"id\": \"call_E8cjRdIVEYHkd79LZw8UG4de\",\n      \"title\": \"Delete an event\",\n      \"input\": { \"accountId\": \"sbx_f7bb6446c0_1\", \"eventId\": \"sbc90a16505a04a760bc4a34a0\" },\n      \"event\": {\n        \"id\": \"sbc90a16505a04a760bc4a34a0\",\n        \"title\": \"Design Review\",\n        \"startsAt\": \"2026-11-10T20:00:00Z\",\n        \"endsAt\": \"2026-11-10T21:00:00Z\",\n        \"startsAtLocal\": \"2026-11-10T15:00:00-05:00\",\n        \"endsAtLocal\": \"2026-11-10T16:00:00-05:00\",\n        \"timezone\": \"America\u002FNew_York\",\n        \"isAllDay\": false,\n        \"location\": \"Room 4B\",\n        \"attendees\": [\n          { \"email\": \"teammate1.1@example.com\", \"response\": \"accepted\" },\n          { \"email\": \"teammate2.1@example.com\", \"response\": \"needsAction\" }\n        ],\n        \"version\": \"\\\"1\\\"\"\n      }\n    }\n  ]\n}\n","json",[85,2961,2962,2967,2977,2982,2994,3006,3038,3047,3058,3070,3082,3094,3106,3118,3130,3142,3154,3163,3190,3212,3219,3229,3234,3238,3243],{"__ignoreMap":104},[108,2963,2964],{"class":110,"line":111},[108,2965,2966],{"class":138},"{\n",[108,2968,2969,2972,2974],{"class":110,"line":153},[108,2970,2971],{"class":142},"  \"changes\"",[108,2973,306],{"class":226},[108,2975,2976],{"class":138}," [\n",[108,2978,2979],{"class":110,"line":218},[108,2980,2981],{"class":138},"    {\n",[108,2983,2984,2987,2989,2992],{"class":110,"line":245},[108,2985,2986],{"class":142},"      \"id\"",[108,2988,306],{"class":226},[108,2990,2991],{"class":199}," \"call_E8cjRdIVEYHkd79LZw8UG4de\"",[108,2993,1090],{"class":226},[108,2995,2996,2999,3001,3004],{"class":110,"line":252},[108,2997,2998],{"class":142},"      \"title\"",[108,3000,306],{"class":226},[108,3002,3003],{"class":199}," \"Delete an event\"",[108,3005,1090],{"class":226},[108,3007,3008,3011,3013,3015,3018,3020,3023,3025,3028,3030,3033,3036],{"class":110,"line":274},[108,3009,3010],{"class":142},"      \"input\"",[108,3012,306],{"class":226},[108,3014,777],{"class":138},[108,3016,3017],{"class":142},"\"accountId\"",[108,3019,306],{"class":226},[108,3021,3022],{"class":199}," \"sbx_f7bb6446c0_1\"",[108,3024,227],{"class":226},[108,3026,3027],{"class":142}," \"eventId\"",[108,3029,306],{"class":226},[108,3031,3032],{"class":199}," \"sbc90a16505a04a760bc4a34a0\"",[108,3034,3035],{"class":138}," }",[108,3037,1090],{"class":226},[108,3039,3040,3043,3045],{"class":110,"line":279},[108,3041,3042],{"class":142},"      \"event\"",[108,3044,306],{"class":226},[108,3046,297],{"class":138},[108,3048,3049,3052,3054,3056],{"class":110,"line":285},[108,3050,3051],{"class":142},"        \"id\"",[108,3053,306],{"class":226},[108,3055,3032],{"class":199},[108,3057,1090],{"class":226},[108,3059,3060,3063,3065,3068],{"class":110,"line":300},[108,3061,3062],{"class":142},"        \"title\"",[108,3064,306],{"class":226},[108,3066,3067],{"class":199}," \"Design Review\"",[108,3069,1090],{"class":226},[108,3071,3072,3075,3077,3080],{"class":110,"line":315},[108,3073,3074],{"class":142},"        \"startsAt\"",[108,3076,306],{"class":226},[108,3078,3079],{"class":199}," \"2026-11-10T20:00:00Z\"",[108,3081,1090],{"class":226},[108,3083,3084,3087,3089,3092],{"class":110,"line":321},[108,3085,3086],{"class":142},"        \"endsAt\"",[108,3088,306],{"class":226},[108,3090,3091],{"class":199}," \"2026-11-10T21:00:00Z\"",[108,3093,1090],{"class":226},[108,3095,3096,3099,3101,3104],{"class":110,"line":348},[108,3097,3098],{"class":142},"        \"startsAtLocal\"",[108,3100,306],{"class":226},[108,3102,3103],{"class":199}," \"2026-11-10T15:00:00-05:00\"",[108,3105,1090],{"class":226},[108,3107,3108,3111,3113,3116],{"class":110,"line":354},[108,3109,3110],{"class":142},"        \"endsAtLocal\"",[108,3112,306],{"class":226},[108,3114,3115],{"class":199}," \"2026-11-10T16:00:00-05:00\"",[108,3117,1090],{"class":226},[108,3119,3120,3123,3125,3128],{"class":110,"line":359},[108,3121,3122],{"class":142},"        \"timezone\"",[108,3124,306],{"class":226},[108,3126,3127],{"class":199}," \"America\u002FNew_York\"",[108,3129,1090],{"class":226},[108,3131,3132,3135,3137,3140],{"class":110,"line":365},[108,3133,3134],{"class":142},"        \"isAllDay\"",[108,3136,306],{"class":226},[108,3138,3139],{"class":258}," false",[108,3141,1090],{"class":226},[108,3143,3144,3147,3149,3152],{"class":110,"line":378},[108,3145,3146],{"class":142},"        \"location\"",[108,3148,306],{"class":226},[108,3150,3151],{"class":199}," \"Room 4B\"",[108,3153,1090],{"class":226},[108,3155,3156,3159,3161],{"class":110,"line":395},[108,3157,3158],{"class":142},"        \"attendees\"",[108,3160,306],{"class":226},[108,3162,2976],{"class":138},[108,3164,3165,3168,3171,3173,3176,3178,3181,3183,3186,3188],{"class":110,"line":443},[108,3166,3167],{"class":138},"          { ",[108,3169,3170],{"class":142},"\"email\"",[108,3172,306],{"class":226},[108,3174,3175],{"class":199}," \"teammate1.1@example.com\"",[108,3177,227],{"class":226},[108,3179,3180],{"class":142}," \"response\"",[108,3182,306],{"class":226},[108,3184,3185],{"class":199}," \"accepted\"",[108,3187,3035],{"class":138},[108,3189,1090],{"class":226},[108,3191,3192,3194,3196,3198,3201,3203,3205,3207,3210],{"class":110,"line":448},[108,3193,3167],{"class":138},[108,3195,3170],{"class":142},[108,3197,306],{"class":226},[108,3199,3200],{"class":199}," \"teammate2.1@example.com\"",[108,3202,227],{"class":226},[108,3204,3180],{"class":142},[108,3206,306],{"class":226},[108,3208,3209],{"class":199}," \"needsAction\"",[108,3211,392],{"class":138},[108,3213,3214,3217],{"class":110,"line":468},[108,3215,3216],{"class":138},"        ]",[108,3218,1090],{"class":226},[108,3220,3221,3224,3226],{"class":110,"line":474},[108,3222,3223],{"class":142},"        \"version\"",[108,3225,306],{"class":226},[108,3227,3228],{"class":199}," \"\\\"1\\\"\"\n",[108,3230,3231],{"class":110,"line":514},[108,3232,3233],{"class":138},"      }\n",[108,3235,3236],{"class":110,"line":558},[108,3237,1365],{"class":138},[108,3239,3240],{"class":110,"line":563},[108,3241,3242],{"class":138},"  ]\n",[108,3244,3245],{"class":110,"line":568},[108,3246,351],{"class":138},[78,3248,3249,3250,3254,3255,306],{},"Show each change with ",[3251,3252,3253],"strong",{},"Approve"," and ",[3251,3256,3257],{},"Decline",[3259,3260,3261,3268],"ul",{},[3262,3263,3264,3267],"li",{},[85,3265,3266],{},"title"," says what the change does.",[3262,3269,3270,3273,3274,3276,3277,634],{},[85,3271,3272],{},"event"," is the event as it is now in Pyas, for a change to an event that already exists. Show its title and time: it's what the change is really about, whatever the model says. A new event has no ",[85,3275,3272],{},", so show the title and times from ",[85,3278,1737],{},[78,3280,3281,3282,3285],{},"Send the IDs the person approved to ",[85,3283,3284],{},"\u002Fchat\u002Fanswer",". The others are declined: the model is told, and they're never sent to Pyas.",[3287,3288],"screenshot",{"alt":3289,"caption":3290,"height":3291,"src":3292,"width":3293},"A chat. The person asked to move their 1:1 with Sarah to 4:30 PM. A card shows the event's current time and the new time, with Approve and Decline buttons.","One way to show a change: the event's current time from Pyas, and the new time the model asked for.",399,"\u002Fimages\u002Fapproval-in-your-app.png",660,[96,3295,64],{"id":63},[3297,3298,3299,3307,3310,3313],"ol",{},[3262,3300,3301,3302,3304,3305,634],{},"Run your server with a test key, and with ",[85,3303,2937],{}," returning ",[85,3306,2945],{},[3262,3308,3309],{},"Ask \"What's on my calendar tomorrow?\". The reply lists Avery's events, without asking anything.",[3262,3311,3312],{},"Ask \"Book a 30 minute meeting tomorrow afternoon when I'm free.\" You get one change, \"Create an event\". Approve it, and the reply says it's booked.",[3262,3314,3315],{},"Ask it to delete that meeting, and decline. The meeting stays, and the reply says it was left as it was.",[78,3317,3318,3319,634],{},"To go live, see ",[90,3320,3322],{"href":3321},"\u002Fai-in-your-app#go-live","Go live",[3324,3325,3326],"style",{},"html pre.shiki code .s1IGz, html code.shiki .s1IGz{--shiki-default:var(--shiki-token-function)}html pre.shiki code .sTB4W, html code.shiki .sTB4W{--shiki-default:var(--shiki-token-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sqSWz, html code.shiki .sqSWz{--shiki-default:var(--shiki-foreground)}html pre.shiki code .sag7P, html code.shiki .sag7P{--shiki-default:var(--shiki-token-keyword)}html pre.shiki code .ssgCD, html code.shiki .ssgCD{--shiki-default:var(--shiki-token-comment)}html pre.shiki code .sk1Au, html code.shiki .sk1Au{--shiki-default:var(--shiki-token-string-expression)}html pre.shiki code .sEEt9, html code.shiki .sEEt9{--shiki-default:var(--shiki-token-punctuation)}html pre.shiki code .sXULy, html code.shiki .sXULy{--shiki-default:var(--shiki-token-constant)}",{"title":104,"searchDepth":218,"depth":218,"links":3328},[3329,3330,3331,3332,3333],{"id":51,"depth":153,"text":52},{"id":54,"depth":153,"text":55},{"id":57,"depth":153,"text":58},{"id":60,"depth":153,"text":61},{"id":63,"depth":153,"text":64},"md",false,{},"\u002Fopenai","---\ntitle: OpenAI\ndescription: Add a calendar assistant to your app with OpenAI and Pyas, with Chat Completions or the Responses API. It reads each user's Google and Outlook calendars, and asks them before it books, moves or deletes anything.\nsections:\n  - { id: install, title: Install }\n  - { id: the-assistant, title: The assistant }\n  - { id: your-routes, title: Your routes }\n  - { id: show-the-change, title: Show the change }\n  - { id: try-it, title: Try it }\n---\n\n# OpenAI\n\n::lead\nThis guide adds a calendar assistant to your server with OpenAI's API, with Chat Completions or the Responses API. The assistant uses Pyas's tools for the signed-in user only, and each change waits for them to approve it.\n::\n\nThe code was tested with `gpt-5.5`. Any model that calls functions works. See [AI in your app](\u002Fai-in-your-app) for how the tools work.\n\n## Install\n\n```bash\nnpm install @pyas\u002Fai-tools openai\n```\n\nSet these on your server:\n\n```bash\nPYAS_API_KEY=pyas_sk_test_...   # a test key from API keys in the Pyas dashboard\nOPENAI_API_KEY=sk-...\n```\n\n## The assistant\n\nThe assistant has two functions. `ask` sends the person's message. `answer` sends which of the waiting changes they approved. Each returns what to show the person: a reply, or the changes to approve.\n\n::code-group\n```ts [Chat Completions]\nimport OpenAI from 'openai'\nimport type { ChatCompletionMessageParam } from 'openai\u002Fresources\u002Fchat\u002Fcompletions'\nimport { pyasFunctionDefinitions, runPyasToolCalls, type PyasPendingToolCall } from '@pyas\u002Fai-tools\u002Fopenai'\n\nconst openai = new OpenAI()\n\n\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\nexport interface Conversation {\n    messages: ChatCompletionMessageParam[]\n    \u002F** Changes waiting for the person, with the results that go back to the model alongside them. *\u002F\n    waiting?: { results: ChatCompletionMessageParam[]; pending: PyasPendingToolCall[] }\n}\n\n\u002F** What to show the person: the assistant's reply, or the changes waiting for their approval. *\u002F\nexport type Turn =\n    | { reply: string }\n    | { changes: Array\u003CPick\u003CPyasPendingToolCall, 'id' | 'title' | 'input' | 'event'>> }\n\nexport function newConversation(): Conversation {\n    \u002F\u002F The same for the whole conversation. Today's date lets the model work out \"tomorrow\".\n    const today = new Date().toISOString().slice(0, 10)\n    return { messages: [{ role: 'system', content: `You help the user manage their calendar. Today is ${today}.` }] }\n}\n\n\u002F** The person sends a message. *\u002F\nexport async function ask(conversation: Conversation, userId: string, text: string): Promise\u003CTurn> {\n    conversation.messages.push({ role: 'user', content: text })\n    return run(conversation, userId)\n}\n\n\u002F** The person answers the changes: the ids they approved. Every other change is declined. *\u002F\nexport async function answer(conversation: Conversation, userId: string, approved: string[]): Promise\u003CTurn> {\n    if (!conversation.waiting) throw new Error('No change is waiting for an answer.')\n    const { results, pending } = conversation.waiting\n    const declined = pending.map((change) => change.id).filter((id) => !approved.includes(id))\n    const answered = await runPyasToolCalls(pending.map((change) => change.call), { subjectId: userId, approved, declined })\n    conversation.messages.push(...results, ...answered.results)\n    conversation.waiting = undefined\n    return run(conversation, userId)\n}\n\nasync function run(conversation: Conversation, userId: string): Promise\u003CTurn> {\n    const options = { subjectId: userId }\n    for (let step = 0; step \u003C 10; step++) {\n        const response = await openai.chat.completions.create({\n            model: 'gpt-5.5',\n            messages: conversation.messages,\n            tools: pyasFunctionDefinitions(options),\n        })\n        const message = response.choices[0].message\n        conversation.messages.push(message)\n        if (!message.tool_calls?.length) return { reply: message.content ?? '' }\n\n        \u002F\u002F Reads run now. Changes come back in pending, and nothing is sent to Pyas until the person approves.\n        const { results, pending } = await runPyasToolCalls(message.tool_calls, options)\n        if (pending.length) {\n            conversation.waiting = { results, pending }\n            return { changes: pending.map(({ id, title, input, event }) => ({ id, title, input, event })) }\n        }\n        conversation.messages.push(...results)\n    }\n    return { reply: 'I stopped before finishing. Please ask again.' }\n}\n```\n\n```ts [Responses API]\nimport OpenAI from 'openai'\nimport type { ResponseInputItem } from 'openai\u002Fresources\u002Fresponses\u002Fresponses'\nimport { pyasFunctionDefinitions, runPyasFunctionCalls, type PyasPendingFunctionCall } from '@pyas\u002Fai-tools\u002Fopenai'\n\nconst openai = new OpenAI()\n\n\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\nexport interface Conversation {\n    instructions: string\n    input: ResponseInputItem[]\n    \u002F** Changes waiting for the person, with the results that go back to the model alongside them. *\u002F\n    waiting?: { results: ResponseInputItem[]; pending: PyasPendingFunctionCall[] }\n}\n\n\u002F** What to show the person: the assistant's reply, or the changes waiting for their approval. *\u002F\nexport type Turn =\n    | { reply: string }\n    | { changes: Array\u003CPick\u003CPyasPendingFunctionCall, 'id' | 'title' | 'input' | 'event'>> }\n\nexport function newConversation(): Conversation {\n    \u002F\u002F The same for the whole conversation. Today's date lets the model work out \"tomorrow\".\n    const today = new Date().toISOString().slice(0, 10)\n    return { instructions: `You help the user manage their calendar. Today is ${today}.`, input: [] }\n}\n\n\u002F** The person sends a message. *\u002F\nexport async function ask(conversation: Conversation, userId: string, text: string): Promise\u003CTurn> {\n    conversation.input.push({ role: 'user', content: text })\n    return run(conversation, userId)\n}\n\n\u002F** The person answers the changes: the ids they approved. Every other change is declined. *\u002F\nexport async function answer(conversation: Conversation, userId: string, approved: string[]): Promise\u003CTurn> {\n    if (!conversation.waiting) throw new Error('No change is waiting for an answer.')\n    const { results, pending } = conversation.waiting\n    const declined = pending.map((change) => change.id).filter((id) => !approved.includes(id))\n    const answered = await runPyasFunctionCalls(pending.map((change) => change.call), { subjectId: userId, approved, declined })\n    conversation.input.push(...results, ...answered.results)\n    conversation.waiting = undefined\n    return run(conversation, userId)\n}\n\nasync function run(conversation: Conversation, userId: string): Promise\u003CTurn> {\n    const options = { subjectId: userId }\n    for (let step = 0; step \u003C 10; step++) {\n        const response = await openai.responses.create({\n            model: 'gpt-5.5',\n            instructions: conversation.instructions,\n            input: conversation.input,\n            tools: pyasFunctionDefinitions({ ...options, api: 'responses' }),\n        })\n        conversation.input.push(...(response.output as ResponseInputItem[]))\n        const calls = response.output.filter((item) => item.type === 'function_call')\n        if (!calls.length) return { reply: response.output_text }\n\n        \u002F\u002F Reads run now. Changes come back in pending, and nothing is sent to Pyas until the person approves.\n        const { results, pending } = await runPyasFunctionCalls(calls, options)\n        if (pending.length) {\n            conversation.waiting = { results, pending }\n            return { changes: pending.map(({ id, title, input, event }) => ({ id, title, input, event })) }\n        }\n        conversation.input.push(...results)\n    }\n    return { reply: 'I stopped before finishing. Please ask again.' }\n}\n```\n::\n\nThe tools that only read run as soon as the model calls them. A change comes back in `pending`, and nothing is sent to Pyas until the person approves it. The model's other calls in the same turn wait with it, so everything goes back to the model together.\n\n## Your routes\n\nYour server keeps each conversation between requests and passes the signed-in person's ID. With Express:\n\n```ts [server.ts]\nimport express from 'express'\nimport { answer, ask, newConversation, type Conversation } from '.\u002Fassistant.js'\n\n\u002F** The signed-in person's ID in your app, from your sign-in (here, the sandbox's Avery Brooks). *\u002F\nfunction currentUserId(_req: express.Request): string | null {\n    return 'sandbox_user_1'\n}\n\n\u002F\u002F One conversation per person. In production, keep them in your database.\nconst conversations = new Map\u003Cstring, Conversation>()\n\nconst app = express()\napp.use(express.json())\n\napp.post('\u002Fchat', async (req, res) => {\n    const userId = currentUserId(req)\n    if (!userId) return res.status(401).json({ error: 'Sign in to use your calendar.' })\n    const conversation = conversations.get(userId) ?? newConversation()\n    conversations.set(userId, conversation)\n    res.json(await ask(conversation, userId, req.body.text))\n})\n\n\u002F\u002F The person's answer: { \"approved\": [\"call_...\"] }. Changes left out are declined.\napp.post('\u002Fchat\u002Fanswer', async (req, res) => {\n    const userId = currentUserId(req)\n    if (!userId) return res.status(401).json({ error: 'Sign in to use your calendar.' })\n    const conversation = conversations.get(userId)\n    if (!conversation?.waiting) return res.status(409).json({ error: 'No change is waiting for an answer.' })\n    res.json(await answer(conversation, userId, req.body.approved ?? []))\n})\n\napp.listen(3457)\n```\n\n`currentUserId` stands for your sign-in. It returns the ID you saved as `subjectId` when the person connected their calendar. To try the guide in the sandbox, have it return `'sandbox_user_1'`, the sandbox person Avery Brooks.\n\n## Show the change\n\nWhen the model wants to change something, `ask` returns the changes instead of a reply:\n\n```json\n{\n  \"changes\": [\n    {\n      \"id\": \"call_E8cjRdIVEYHkd79LZw8UG4de\",\n      \"title\": \"Delete an event\",\n      \"input\": { \"accountId\": \"sbx_f7bb6446c0_1\", \"eventId\": \"sbc90a16505a04a760bc4a34a0\" },\n      \"event\": {\n        \"id\": \"sbc90a16505a04a760bc4a34a0\",\n        \"title\": \"Design Review\",\n        \"startsAt\": \"2026-11-10T20:00:00Z\",\n        \"endsAt\": \"2026-11-10T21:00:00Z\",\n        \"startsAtLocal\": \"2026-11-10T15:00:00-05:00\",\n        \"endsAtLocal\": \"2026-11-10T16:00:00-05:00\",\n        \"timezone\": \"America\u002FNew_York\",\n        \"isAllDay\": false,\n        \"location\": \"Room 4B\",\n        \"attendees\": [\n          { \"email\": \"teammate1.1@example.com\", \"response\": \"accepted\" },\n          { \"email\": \"teammate2.1@example.com\", \"response\": \"needsAction\" }\n        ],\n        \"version\": \"\\\"1\\\"\"\n      }\n    }\n  ]\n}\n```\n\nShow each change with **Approve** and **Decline**:\n\n- `title` says what the change does.\n- `event` is the event as it is now in Pyas, for a change to an event that already exists. Show its title and time: it's what the change is really about, whatever the model says. A new event has no `event`, so show the title and times from `input`.\n\nSend the IDs the person approved to `\u002Fchat\u002Fanswer`. The others are declined: the model is told, and they're never sent to Pyas.\n\n::screenshot{src=\"\u002Fimages\u002Fapproval-in-your-app.png\" alt=\"A chat. The person asked to move their 1:1 with Sarah to 4:30 PM. A card shows the event's current time and the new time, with Approve and Decline buttons.\" width=\"660\" height=\"399\" caption=\"One way to show a change: the event's current time from Pyas, and the new time the model asked for.\"}\n::\n\n## Try it\n\n1. Run your server with a test key, and with `currentUserId` returning `'sandbox_user_1'`.\n2. Ask \"What's on my calendar tomorrow?\". The reply lists Avery's events, without asking anything.\n3. Ask \"Book a 30 minute meeting tomorrow afternoon when I'm free.\" You get one change, \"Create an event\". Approve it, and the reply says it's booked.\n4. Ask it to delete that meeting, and decline. The meeting stays, and the reply says it was left as it was.\n\nTo go live, see [Go live](\u002Fai-in-your-app#go-live).\n",{"title":47,"description":48},null,"OmVaQ83iX-DQ3o3qxvGHYU6Ro7gn0b0xLcl0URO3Rh4",1794697271169]