[{"data":1,"prerenderedAt":2432},["ShallowReactive",2],{"navigation":3,"page:\u002Fclaude-api":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","Claude API","Add a calendar assistant to your app with the Claude API and Pyas. 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":2424,"hero":2425,"meta":2426,"navigation":255,"path":2427,"rawbody":2428,"sections":49,"seo":2429,"stem":73,"v1":2430,"__hash__":2431},"docs\u002Fclaude-api.md",{"type":68,"value":69,"toc":2417},"minimark",[70,74,81,100,103,131,134,167,169,180,1486,1496,1499,1501,1504,2022,2036,2038,2044,2336,2347,2369,2376,2384,2386,2406,2413],[71,72,47],"h1",{"id":73},"claude-api",[75,76,77],"lead",{},[78,79,80],"p",{},"This guide adds a calendar assistant to your server with Anthropic's Messages 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,95,99],{},"The code was tested with ",[85,86,87],"code",{},"claude-sonnet-5-5",". See ",[90,91,93],"a",{"href":92},"\u002Fai-in-your-app","AI in your app"," for how the tools work. To use Pyas yourself in the Claude app, see ",[90,96,98],{"href":97},"\u002Fclaude","Claude"," instead.",[101,102,52],"h2",{"id":51},[104,105,110],"pre",{"className":106,"code":107,"language":108,"meta":109,"style":109},"language-bash shiki shiki-themes css-variables","npm install @pyas\u002Fai-tools @anthropic-ai\u002Fsdk\n","bash","",[85,111,112],{"__ignoreMap":109},[113,114,117,121,125,128],"span",{"class":115,"line":116},"line",1,[113,118,120],{"class":119},"s1IGz","npm",[113,122,124],{"class":123},"sTB4W"," install",[113,126,127],{"class":123}," @pyas\u002Fai-tools",[113,129,130],{"class":123}," @anthropic-ai\u002Fsdk\n",[78,132,133],{},"Set these on your server:",[104,135,137],{"className":106,"code":136,"language":108,"meta":109,"style":109},"PYAS_API_KEY=pyas_sk_test_...   # a test key from API keys in the Pyas dashboard\nANTHROPIC_API_KEY=sk-ant-...\n",[85,138,139,156],{"__ignoreMap":109},[113,140,141,145,149,152],{"class":115,"line":116},[113,142,144],{"class":143},"sqSWz","PYAS_API_KEY",[113,146,148],{"class":147},"sag7P","=",[113,150,151],{"class":123},"pyas_sk_test_...",[113,153,155],{"class":154},"ssgCD","   # a test key from API keys in the Pyas dashboard\n",[113,157,159,162,164],{"class":115,"line":158},2,[113,160,161],{"class":143},"ANTHROPIC_API_KEY",[113,163,148],{"class":147},[113,165,166],{"class":123},"sk-ant-...\n",[101,168,55],{"id":54},[78,170,171,172,175,176,179],{},"The assistant has two functions. ",[85,173,174],{},"ask"," sends the person's message. ",[85,177,178],{},"answer"," sends which of the waiting changes they approved. Each returns what to show the person: a reply, or the changes to approve.",[104,181,186],{"className":182,"code":183,"filename":184,"language":185,"meta":109,"style":109},"language-ts shiki shiki-themes css-variables","import Anthropic from '@anthropic-ai\u002Fsdk'\nimport type { MessageParam, ToolResultBlockParam } from '@anthropic-ai\u002Fsdk\u002Fresources\u002Fmessages'\nimport { pyasAnthropicTools, runPyasToolUses, type PyasPendingToolUse } from '@pyas\u002Fai-tools\u002Fanthropic'\n\nconst anthropic = new Anthropic()\n\n\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\nexport interface Conversation {\n    system: string\n    messages: MessageParam[]\n    \u002F** Changes waiting for the person, with the results that go back to Claude alongside them. *\u002F\n    waiting?: { results: ToolResultBlockParam[]; pending: PyasPendingToolUse[] }\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\u003CPyasPendingToolUse, 'id' | 'title' | 'input' | 'event'>> }\n\nexport function newConversation(): Conversation {\n    \u002F\u002F The same for the whole conversation: Claude rejects a system prompt that changes partway.\n    const today = new Date().toISOString().slice(0, 10)\n    return { system: `You help the user manage their calendar. Today is ${today}.`, messages: [] }\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 runPyasToolUses(pending.map((change) => change.call), { subjectId: userId, approved, declined })\n    \u002F\u002F Every tool_result of the turn goes back in one message.\n    conversation.messages.push({ role: 'user', content: [...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 message = await anthropic.messages.create({\n            model: 'claude-sonnet-5-5',\n            max_tokens: 4096,\n            system: conversation.system,\n            messages: conversation.messages,\n            tools: pyasAnthropicTools(options),\n        })\n        conversation.messages.push({ role: 'assistant', content: message.content })\n        if (message.stop_reason !== 'tool_use') {\n            return { reply: message.content.flatMap((block) => (block.type === 'text' ? [block.text] : [])).join('\\n') }\n        }\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 runPyasToolUses(message.content, 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({ role: 'user', content: results })\n    }\n    return { reply: 'I stopped before finishing. Please ask again.' }\n}\n","assistant.ts","ts",[85,187,188,203,225,250,257,279,284,290,305,317,331,337,365,371,376,382,395,412,460,465,485,491,531,567,572,577,583,637,670,685,690,695,701,749,782,809,856,905,911,953,966,979,984,989,1025,1042,1077,1102,1116,1129,1144,1159,1175,1181,1211,1233,1298,1304,1309,1315,1348,1364,1381,1430,1435,1461,1467,1481],{"__ignoreMap":109},[113,189,190,193,196,199],{"class":115,"line":116},[113,191,192],{"class":147},"import",[113,194,195],{"class":143}," Anthropic ",[113,197,198],{"class":147},"from",[113,200,202],{"class":201},"sk1Au"," '@anthropic-ai\u002Fsdk'\n",[113,204,205,207,210,213,217,220,222],{"class":115,"line":158},[113,206,192],{"class":147},[113,208,209],{"class":147}," type",[113,211,212],{"class":143}," { MessageParam",[113,214,216],{"class":215},"sEEt9",",",[113,218,219],{"class":143}," ToolResultBlockParam } ",[113,221,198],{"class":147},[113,223,224],{"class":201}," '@anthropic-ai\u002Fsdk\u002Fresources\u002Fmessages'\n",[113,226,228,230,233,235,238,240,242,245,247],{"class":115,"line":227},3,[113,229,192],{"class":147},[113,231,232],{"class":143}," { pyasAnthropicTools",[113,234,216],{"class":215},[113,236,237],{"class":143}," runPyasToolUses",[113,239,216],{"class":215},[113,241,209],{"class":147},[113,243,244],{"class":143}," PyasPendingToolUse } ",[113,246,198],{"class":147},[113,248,249],{"class":201}," '@pyas\u002Fai-tools\u002Fanthropic'\n",[113,251,253],{"class":115,"line":252},4,[113,254,256],{"emptyLinePlaceholder":255},true,"\n",[113,258,260,263,267,270,273,276],{"class":115,"line":259},5,[113,261,262],{"class":147},"const",[113,264,266],{"class":265},"sXULy"," anthropic",[113,268,269],{"class":147}," =",[113,271,272],{"class":147}," new",[113,274,275],{"class":119}," Anthropic",[113,277,278],{"class":143},"()\n",[113,280,282],{"class":115,"line":281},6,[113,283,256],{"emptyLinePlaceholder":255},[113,285,287],{"class":115,"line":286},7,[113,288,289],{"class":154},"\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\n",[113,291,293,296,299,302],{"class":115,"line":292},8,[113,294,295],{"class":147},"export",[113,297,298],{"class":147}," interface",[113,300,301],{"class":119}," Conversation",[113,303,304],{"class":143}," {\n",[113,306,308,311,314],{"class":115,"line":307},9,[113,309,310],{"class":143},"    system",[113,312,313],{"class":147},":",[113,315,316],{"class":265}," string\n",[113,318,320,323,325,328],{"class":115,"line":319},10,[113,321,322],{"class":143},"    messages",[113,324,313],{"class":147},[113,326,327],{"class":119}," MessageParam",[113,329,330],{"class":143},"[]\n",[113,332,334],{"class":115,"line":333},11,[113,335,336],{"class":154},"    \u002F** Changes waiting for the person, with the results that go back to Claude alongside them. *\u002F\n",[113,338,340,343,346,349,351,354,357,359,362],{"class":115,"line":339},12,[113,341,342],{"class":143},"    waiting",[113,344,345],{"class":147},"?:",[113,347,348],{"class":143}," { results",[113,350,313],{"class":147},[113,352,353],{"class":119}," ToolResultBlockParam",[113,355,356],{"class":143},"[]; pending",[113,358,313],{"class":147},[113,360,361],{"class":119}," PyasPendingToolUse",[113,363,364],{"class":143},"[] }\n",[113,366,368],{"class":115,"line":367},13,[113,369,370],{"class":143},"}\n",[113,372,374],{"class":115,"line":373},14,[113,375,256],{"emptyLinePlaceholder":255},[113,377,379],{"class":115,"line":378},15,[113,380,381],{"class":154},"\u002F** What to show the person: the assistant's reply, or the changes waiting for their approval. *\u002F\n",[113,383,385,387,389,392],{"class":115,"line":384},16,[113,386,295],{"class":147},[113,388,209],{"class":147},[113,390,391],{"class":119}," Turn",[113,393,394],{"class":147}," =\n",[113,396,398,401,404,406,409],{"class":115,"line":397},17,[113,399,400],{"class":147},"    |",[113,402,403],{"class":143}," { reply",[113,405,313],{"class":147},[113,407,408],{"class":265}," string",[113,410,411],{"class":143}," }\n",[113,413,415,417,420,422,425,428,431,433,436,438,441,444,447,449,452,454,457],{"class":115,"line":414},18,[113,416,400],{"class":147},[113,418,419],{"class":143}," { changes",[113,421,313],{"class":147},[113,423,424],{"class":119}," Array",[113,426,427],{"class":143},"\u003C",[113,429,430],{"class":119},"Pick",[113,432,427],{"class":143},[113,434,435],{"class":119},"PyasPendingToolUse",[113,437,216],{"class":215},[113,439,440],{"class":201}," 'id'",[113,442,443],{"class":147}," |",[113,445,446],{"class":201}," 'title'",[113,448,443],{"class":147},[113,450,451],{"class":201}," 'input'",[113,453,443],{"class":147},[113,455,456],{"class":201}," 'event'",[113,458,459],{"class":143},">> }\n",[113,461,463],{"class":115,"line":462},19,[113,464,256],{"emptyLinePlaceholder":255},[113,466,468,470,473,476,479,481,483],{"class":115,"line":467},20,[113,469,295],{"class":147},[113,471,472],{"class":147}," function",[113,474,475],{"class":119}," newConversation",[113,477,478],{"class":143},"()",[113,480,313],{"class":147},[113,482,301],{"class":119},[113,484,304],{"class":143},[113,486,488],{"class":115,"line":487},21,[113,489,490],{"class":154},"    \u002F\u002F The same for the whole conversation: Claude rejects a system prompt that changes partway.\n",[113,492,494,497,500,502,504,507,509,512,514,517,520,523,525,528],{"class":115,"line":493},22,[113,495,496],{"class":147},"    const",[113,498,499],{"class":265}," today",[113,501,269],{"class":147},[113,503,272],{"class":147},[113,505,506],{"class":119}," Date",[113,508,478],{"class":143},[113,510,511],{"class":119},".toISOString",[113,513,478],{"class":143},[113,515,516],{"class":119},".slice",[113,518,519],{"class":143},"(",[113,521,522],{"class":265},"0",[113,524,216],{"class":215},[113,526,527],{"class":265}," 10",[113,529,530],{"class":143},")\n",[113,532,534,537,540,542,545,548,551,554,557,559,562,564],{"class":115,"line":533},23,[113,535,536],{"class":147},"    return",[113,538,539],{"class":143}," { system",[113,541,313],{"class":147},[113,543,544],{"class":201}," `You help the user manage their calendar. Today is ",[113,546,547],{"class":147},"${",[113,549,550],{"class":143},"today",[113,552,553],{"class":147},"}",[113,555,556],{"class":201},".`",[113,558,216],{"class":215},[113,560,561],{"class":143}," messages",[113,563,313],{"class":147},[113,565,566],{"class":143}," [] }\n",[113,568,570],{"class":115,"line":569},24,[113,571,370],{"class":143},[113,573,575],{"class":115,"line":574},25,[113,576,256],{"emptyLinePlaceholder":255},[113,578,580],{"class":115,"line":579},26,[113,581,582],{"class":154},"\u002F** The person sends a message. *\u002F\n",[113,584,586,588,591,593,596,599,601,603,605,608,610,612,614,617,619,621,624,626,629,631,634],{"class":115,"line":585},27,[113,587,295],{"class":147},[113,589,590],{"class":147}," async",[113,592,472],{"class":147},[113,594,595],{"class":119}," ask",[113,597,598],{"class":143},"(conversation",[113,600,313],{"class":147},[113,602,301],{"class":119},[113,604,216],{"class":215},[113,606,607],{"class":143}," userId",[113,609,313],{"class":147},[113,611,408],{"class":265},[113,613,216],{"class":215},[113,615,616],{"class":143}," text",[113,618,313],{"class":147},[113,620,408],{"class":265},[113,622,623],{"class":143},")",[113,625,313],{"class":147},[113,627,628],{"class":119}," Promise",[113,630,427],{"class":143},[113,632,633],{"class":119},"Turn",[113,635,636],{"class":143},"> {\n",[113,638,640,643,646,649,652,655,657,660,662,665,667],{"class":115,"line":639},28,[113,641,642],{"class":265},"    conversation",[113,644,645],{"class":119},".",[113,647,648],{"class":265},"messages",[113,650,651],{"class":119},".push",[113,653,654],{"class":143},"({ role",[113,656,313],{"class":147},[113,658,659],{"class":201}," 'user'",[113,661,216],{"class":215},[113,663,664],{"class":143}," content",[113,666,313],{"class":147},[113,668,669],{"class":143}," text })\n",[113,671,673,675,678,680,682],{"class":115,"line":672},29,[113,674,536],{"class":147},[113,676,677],{"class":119}," run",[113,679,598],{"class":143},[113,681,216],{"class":215},[113,683,684],{"class":143}," userId)\n",[113,686,688],{"class":115,"line":687},30,[113,689,370],{"class":143},[113,691,693],{"class":115,"line":692},31,[113,694,256],{"emptyLinePlaceholder":255},[113,696,698],{"class":115,"line":697},32,[113,699,700],{"class":154},"\u002F** The person answers the changes: the ids they approved. Every other change is declined. *\u002F\n",[113,702,704,706,708,710,713,715,717,719,721,723,725,727,729,732,734,736,739,741,743,745,747],{"class":115,"line":703},33,[113,705,295],{"class":147},[113,707,590],{"class":147},[113,709,472],{"class":147},[113,711,712],{"class":119}," answer",[113,714,598],{"class":143},[113,716,313],{"class":147},[113,718,301],{"class":119},[113,720,216],{"class":215},[113,722,607],{"class":143},[113,724,313],{"class":147},[113,726,408],{"class":265},[113,728,216],{"class":215},[113,730,731],{"class":143}," approved",[113,733,313],{"class":147},[113,735,408],{"class":265},[113,737,738],{"class":143},"[])",[113,740,313],{"class":147},[113,742,628],{"class":119},[113,744,427],{"class":143},[113,746,633],{"class":119},[113,748,636],{"class":143},[113,750,752,755,758,761,764,767,770,772,775,777,780],{"class":115,"line":751},34,[113,753,754],{"class":147},"    if",[113,756,757],{"class":143}," (",[113,759,760],{"class":147},"!",[113,762,763],{"class":265},"conversation",[113,765,766],{"class":143},".waiting) ",[113,768,769],{"class":147},"throw",[113,771,272],{"class":147},[113,773,774],{"class":119}," Error",[113,776,519],{"class":143},[113,778,779],{"class":201},"'No change is waiting for an answer.'",[113,781,530],{"class":143},[113,783,785,787,790,793,795,798,801,803,806],{"class":115,"line":784},35,[113,786,496],{"class":147},[113,788,789],{"class":143}," { ",[113,791,792],{"class":265},"results",[113,794,216],{"class":215},[113,796,797],{"class":265}," pending",[113,799,800],{"class":143}," } ",[113,802,148],{"class":147},[113,804,805],{"class":265}," conversation",[113,807,808],{"class":143},".waiting\n",[113,810,812,814,817,819,821,824,827,830,833,836,839,842,844,847,850,853],{"class":115,"line":811},36,[113,813,496],{"class":147},[113,815,816],{"class":265}," declined",[113,818,269],{"class":147},[113,820,797],{"class":265},[113,822,823],{"class":119},".map",[113,825,826],{"class":143},"((change) ",[113,828,829],{"class":147},"=>",[113,831,832],{"class":265}," change",[113,834,835],{"class":143},".id)",[113,837,838],{"class":119},".filter",[113,840,841],{"class":143},"((id) ",[113,843,829],{"class":147},[113,845,846],{"class":147}," !",[113,848,849],{"class":265},"approved",[113,851,852],{"class":119},".includes",[113,854,855],{"class":143},"(id))\n",[113,857,859,861,864,866,869,871,873,876,878,880,882,884,887,889,892,894,896,898,900,902],{"class":115,"line":858},37,[113,860,496],{"class":147},[113,862,863],{"class":265}," answered",[113,865,269],{"class":147},[113,867,868],{"class":147}," await",[113,870,237],{"class":119},[113,872,519],{"class":143},[113,874,875],{"class":265},"pending",[113,877,823],{"class":119},[113,879,826],{"class":143},[113,881,829],{"class":147},[113,883,832],{"class":265},[113,885,886],{"class":143},".call)",[113,888,216],{"class":215},[113,890,891],{"class":143}," { subjectId",[113,893,313],{"class":147},[113,895,607],{"class":143},[113,897,216],{"class":215},[113,899,731],{"class":143},[113,901,216],{"class":215},[113,903,904],{"class":143}," declined })\n",[113,906,908],{"class":115,"line":907},38,[113,909,910],{"class":154},"    \u002F\u002F Every tool_result of the turn goes back in one message.\n",[113,912,914,916,918,920,922,924,926,928,930,932,934,937,940,942,944,947,950],{"class":115,"line":913},39,[113,915,642],{"class":265},[113,917,645],{"class":119},[113,919,648],{"class":265},[113,921,651],{"class":119},[113,923,654],{"class":143},[113,925,313],{"class":147},[113,927,659],{"class":201},[113,929,216],{"class":215},[113,931,664],{"class":143},[113,933,313],{"class":147},[113,935,936],{"class":143}," [",[113,938,939],{"class":147},"...",[113,941,792],{"class":143},[113,943,216],{"class":215},[113,945,946],{"class":147}," ...",[113,948,949],{"class":265},"answered",[113,951,952],{"class":143},".results] })\n",[113,954,956,958,961,963],{"class":115,"line":955},40,[113,957,642],{"class":265},[113,959,960],{"class":143},".waiting ",[113,962,148],{"class":147},[113,964,965],{"class":265}," undefined\n",[113,967,969,971,973,975,977],{"class":115,"line":968},41,[113,970,536],{"class":147},[113,972,677],{"class":119},[113,974,598],{"class":143},[113,976,216],{"class":215},[113,978,684],{"class":143},[113,980,982],{"class":115,"line":981},42,[113,983,370],{"class":143},[113,985,987],{"class":115,"line":986},43,[113,988,256],{"emptyLinePlaceholder":255},[113,990,992,995,997,999,1001,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021,1023],{"class":115,"line":991},44,[113,993,994],{"class":147},"async",[113,996,472],{"class":147},[113,998,677],{"class":119},[113,1000,598],{"class":143},[113,1002,313],{"class":147},[113,1004,301],{"class":119},[113,1006,216],{"class":215},[113,1008,607],{"class":143},[113,1010,313],{"class":147},[113,1012,408],{"class":265},[113,1014,623],{"class":143},[113,1016,313],{"class":147},[113,1018,628],{"class":119},[113,1020,427],{"class":143},[113,1022,633],{"class":119},[113,1024,636],{"class":143},[113,1026,1028,1030,1033,1035,1037,1039],{"class":115,"line":1027},45,[113,1029,496],{"class":147},[113,1031,1032],{"class":265}," options",[113,1034,269],{"class":147},[113,1036,891],{"class":143},[113,1038,313],{"class":147},[113,1040,1041],{"class":143}," userId }\n",[113,1043,1045,1048,1050,1053,1056,1058,1061,1064,1066,1068,1071,1074],{"class":115,"line":1044},46,[113,1046,1047],{"class":147},"    for",[113,1049,757],{"class":143},[113,1051,1052],{"class":147},"let",[113,1054,1055],{"class":143}," step ",[113,1057,148],{"class":147},[113,1059,1060],{"class":265}," 0",[113,1062,1063],{"class":143},"; step ",[113,1065,427],{"class":147},[113,1067,527],{"class":265},[113,1069,1070],{"class":143},"; step",[113,1072,1073],{"class":147},"++",[113,1075,1076],{"class":143},") {\n",[113,1078,1080,1083,1086,1088,1090,1092,1094,1096,1099],{"class":115,"line":1079},47,[113,1081,1082],{"class":147},"        const",[113,1084,1085],{"class":265}," message",[113,1087,269],{"class":147},[113,1089,868],{"class":147},[113,1091,266],{"class":265},[113,1093,645],{"class":119},[113,1095,648],{"class":265},[113,1097,1098],{"class":119},".create",[113,1100,1101],{"class":143},"({\n",[113,1103,1105,1108,1110,1113],{"class":115,"line":1104},48,[113,1106,1107],{"class":143},"            model",[113,1109,313],{"class":147},[113,1111,1112],{"class":201}," 'claude-sonnet-5-5'",[113,1114,1115],{"class":215},",\n",[113,1117,1119,1122,1124,1127],{"class":115,"line":1118},49,[113,1120,1121],{"class":143},"            max_tokens",[113,1123,313],{"class":147},[113,1125,1126],{"class":265}," 4096",[113,1128,1115],{"class":215},[113,1130,1132,1135,1137,1139,1142],{"class":115,"line":1131},50,[113,1133,1134],{"class":143},"            system",[113,1136,313],{"class":147},[113,1138,805],{"class":265},[113,1140,1141],{"class":143},".system",[113,1143,1115],{"class":215},[113,1145,1147,1150,1152,1154,1157],{"class":115,"line":1146},51,[113,1148,1149],{"class":143},"            messages",[113,1151,313],{"class":147},[113,1153,805],{"class":265},[113,1155,1156],{"class":143},".messages",[113,1158,1115],{"class":215},[113,1160,1162,1165,1167,1170,1173],{"class":115,"line":1161},52,[113,1163,1164],{"class":143},"            tools",[113,1166,313],{"class":147},[113,1168,1169],{"class":119}," pyasAnthropicTools",[113,1171,1172],{"class":143},"(options)",[113,1174,1115],{"class":215},[113,1176,1178],{"class":115,"line":1177},53,[113,1179,1180],{"class":143},"        })\n",[113,1182,1184,1187,1189,1191,1193,1195,1197,1200,1202,1204,1206,1208],{"class":115,"line":1183},54,[113,1185,1186],{"class":265},"        conversation",[113,1188,645],{"class":119},[113,1190,648],{"class":265},[113,1192,651],{"class":119},[113,1194,654],{"class":143},[113,1196,313],{"class":147},[113,1198,1199],{"class":201}," 'assistant'",[113,1201,216],{"class":215},[113,1203,664],{"class":143},[113,1205,313],{"class":147},[113,1207,1085],{"class":265},[113,1209,1210],{"class":143},".content })\n",[113,1212,1214,1217,1219,1222,1225,1228,1231],{"class":115,"line":1213},55,[113,1215,1216],{"class":147},"        if",[113,1218,757],{"class":143},[113,1220,1221],{"class":265},"message",[113,1223,1224],{"class":143},".stop_reason ",[113,1226,1227],{"class":147},"!==",[113,1229,1230],{"class":201}," 'tool_use'",[113,1232,1076],{"class":143},[113,1234,1236,1239,1241,1243,1245,1247,1250,1253,1256,1258,1260,1263,1266,1269,1272,1275,1277,1279,1282,1284,1287,1290,1292,1295],{"class":115,"line":1235},56,[113,1237,1238],{"class":147},"            return",[113,1240,403],{"class":143},[113,1242,313],{"class":147},[113,1244,1085],{"class":265},[113,1246,645],{"class":119},[113,1248,1249],{"class":265},"content",[113,1251,1252],{"class":119},".flatMap",[113,1254,1255],{"class":143},"((block) ",[113,1257,829],{"class":147},[113,1259,757],{"class":143},[113,1261,1262],{"class":265},"block",[113,1264,1265],{"class":143},".type ",[113,1267,1268],{"class":147},"===",[113,1270,1271],{"class":201}," 'text'",[113,1273,1274],{"class":147}," ?",[113,1276,936],{"class":143},[113,1278,1262],{"class":265},[113,1280,1281],{"class":143},".text] ",[113,1283,313],{"class":147},[113,1285,1286],{"class":143}," []))",[113,1288,1289],{"class":119},".join",[113,1291,519],{"class":143},[113,1293,1294],{"class":201},"'\\n'",[113,1296,1297],{"class":143},") }\n",[113,1299,1301],{"class":115,"line":1300},57,[113,1302,1303],{"class":143},"        }\n",[113,1305,1307],{"class":115,"line":1306},58,[113,1308,256],{"emptyLinePlaceholder":255},[113,1310,1312],{"class":115,"line":1311},59,[113,1313,1314],{"class":154},"        \u002F\u002F Reads run now. Changes come back in pending, and nothing is sent to Pyas until the person approves.\n",[113,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1343,1345],{"class":115,"line":1317},60,[113,1319,1082],{"class":147},[113,1321,789],{"class":143},[113,1323,792],{"class":265},[113,1325,216],{"class":215},[113,1327,797],{"class":265},[113,1329,800],{"class":143},[113,1331,148],{"class":147},[113,1333,868],{"class":147},[113,1335,237],{"class":119},[113,1337,519],{"class":143},[113,1339,1221],{"class":265},[113,1341,1342],{"class":143},".content",[113,1344,216],{"class":215},[113,1346,1347],{"class":143}," options)\n",[113,1349,1351,1353,1355,1357,1359,1362],{"class":115,"line":1350},61,[113,1352,1216],{"class":147},[113,1354,757],{"class":143},[113,1356,875],{"class":265},[113,1358,645],{"class":143},[113,1360,1361],{"class":265},"length",[113,1363,1076],{"class":143},[113,1365,1367,1370,1372,1374,1376,1378],{"class":115,"line":1366},62,[113,1368,1369],{"class":265},"            conversation",[113,1371,960],{"class":143},[113,1373,148],{"class":147},[113,1375,348],{"class":143},[113,1377,216],{"class":215},[113,1379,1380],{"class":143}," pending }\n",[113,1382,1384,1386,1388,1390,1392,1394,1397,1399,1402,1404,1407,1409,1412,1414,1417,1419,1421,1423,1425,1427],{"class":115,"line":1383},63,[113,1385,1238],{"class":147},[113,1387,419],{"class":143},[113,1389,313],{"class":147},[113,1391,797],{"class":265},[113,1393,823],{"class":119},[113,1395,1396],{"class":143},"(({ id",[113,1398,216],{"class":215},[113,1400,1401],{"class":143}," title",[113,1403,216],{"class":215},[113,1405,1406],{"class":143}," input",[113,1408,216],{"class":215},[113,1410,1411],{"class":143}," event }) ",[113,1413,829],{"class":147},[113,1415,1416],{"class":143}," ({ id",[113,1418,216],{"class":215},[113,1420,1401],{"class":143},[113,1422,216],{"class":215},[113,1424,1406],{"class":143},[113,1426,216],{"class":215},[113,1428,1429],{"class":143}," event })) }\n",[113,1431,1433],{"class":115,"line":1432},64,[113,1434,1303],{"class":143},[113,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458],{"class":115,"line":1437},65,[113,1439,1186],{"class":265},[113,1441,645],{"class":119},[113,1443,648],{"class":265},[113,1445,651],{"class":119},[113,1447,654],{"class":143},[113,1449,313],{"class":147},[113,1451,659],{"class":201},[113,1453,216],{"class":215},[113,1455,664],{"class":143},[113,1457,313],{"class":147},[113,1459,1460],{"class":143}," results })\n",[113,1462,1464],{"class":115,"line":1463},66,[113,1465,1466],{"class":143},"    }\n",[113,1468,1470,1472,1474,1476,1479],{"class":115,"line":1469},67,[113,1471,536],{"class":147},[113,1473,403],{"class":143},[113,1475,313],{"class":147},[113,1477,1478],{"class":201}," 'I stopped before finishing. Please ask again.'",[113,1480,411],{"class":143},[113,1482,1484],{"class":115,"line":1483},68,[113,1485,370],{"class":143},[78,1487,1488,1489,1491,1492,1495],{},"The tools that only read run as soon as Claude calls them. A change comes back in ",[85,1490,875],{},", and nothing is sent to Pyas until the person approves it. Claude needs every ",[85,1493,1494],{},"tool_result"," of a turn in one message, so the results of the other calls wait with the change and go back together.",[78,1497,1498],{},"Keep the system prompt the same for the whole conversation. Claude rejects a conversation whose system prompt changes partway, so put today's date in it, not the time.",[101,1500,58],{"id":57},[78,1502,1503],{},"Your server keeps each conversation between requests and passes the signed-in person's ID. With Express:",[104,1505,1508],{"className":182,"code":1506,"filename":1507,"language":185,"meta":109,"style":109},"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,1509,1510,1522,1549,1553,1558,1592,1599,1603,1607,1612,1638,1642,1655,1674,1678,1706,1719,1759,1782,1798,1831,1836,1840,1845,1870,1882,1914,1929,1966,2000,2004,2008],{"__ignoreMap":109},[113,1511,1512,1514,1517,1519],{"class":115,"line":116},[113,1513,192],{"class":147},[113,1515,1516],{"class":143}," express ",[113,1518,198],{"class":147},[113,1520,1521],{"class":201}," 'express'\n",[113,1523,1524,1526,1529,1531,1533,1535,1537,1539,1541,1544,1546],{"class":115,"line":158},[113,1525,192],{"class":147},[113,1527,1528],{"class":143}," { answer",[113,1530,216],{"class":215},[113,1532,595],{"class":143},[113,1534,216],{"class":215},[113,1536,475],{"class":143},[113,1538,216],{"class":215},[113,1540,209],{"class":147},[113,1542,1543],{"class":143}," Conversation } ",[113,1545,198],{"class":147},[113,1547,1548],{"class":201}," '.\u002Fassistant.js'\n",[113,1550,1551],{"class":115,"line":227},[113,1552,256],{"emptyLinePlaceholder":255},[113,1554,1555],{"class":115,"line":252},[113,1556,1557],{"class":154},"\u002F** The signed-in person's ID in your app, from your sign-in (here, the sandbox's Avery Brooks). *\u002F\n",[113,1559,1560,1563,1566,1569,1571,1574,1576,1579,1581,1583,1585,1587,1590],{"class":115,"line":259},[113,1561,1562],{"class":147},"function",[113,1564,1565],{"class":119}," currentUserId",[113,1567,1568],{"class":143},"(_req",[113,1570,313],{"class":147},[113,1572,1573],{"class":119}," express",[113,1575,645],{"class":143},[113,1577,1578],{"class":119},"Request",[113,1580,623],{"class":143},[113,1582,313],{"class":147},[113,1584,408],{"class":265},[113,1586,443],{"class":147},[113,1588,1589],{"class":265}," null",[113,1591,304],{"class":143},[113,1593,1594,1596],{"class":115,"line":281},[113,1595,536],{"class":147},[113,1597,1598],{"class":201}," 'sandbox_user_1'\n",[113,1600,1601],{"class":115,"line":286},[113,1602,370],{"class":143},[113,1604,1605],{"class":115,"line":292},[113,1606,256],{"emptyLinePlaceholder":255},[113,1608,1609],{"class":115,"line":307},[113,1610,1611],{"class":154},"\u002F\u002F One conversation per person. In production, keep them in your database.\n",[113,1613,1614,1616,1619,1621,1623,1626,1628,1631,1633,1635],{"class":115,"line":319},[113,1615,262],{"class":147},[113,1617,1618],{"class":265}," conversations",[113,1620,269],{"class":147},[113,1622,272],{"class":147},[113,1624,1625],{"class":119}," Map",[113,1627,427],{"class":143},[113,1629,1630],{"class":265},"string",[113,1632,216],{"class":215},[113,1634,301],{"class":119},[113,1636,1637],{"class":143},">()\n",[113,1639,1640],{"class":115,"line":333},[113,1641,256],{"emptyLinePlaceholder":255},[113,1643,1644,1646,1649,1651,1653],{"class":115,"line":339},[113,1645,262],{"class":147},[113,1647,1648],{"class":265}," app",[113,1650,269],{"class":147},[113,1652,1573],{"class":119},[113,1654,278],{"class":143},[113,1656,1657,1660,1663,1665,1668,1671],{"class":115,"line":367},[113,1658,1659],{"class":265},"app",[113,1661,1662],{"class":119},".use",[113,1664,519],{"class":143},[113,1666,1667],{"class":265},"express",[113,1669,1670],{"class":119},".json",[113,1672,1673],{"class":143},"())\n",[113,1675,1676],{"class":115,"line":373},[113,1677,256],{"emptyLinePlaceholder":255},[113,1679,1680,1682,1685,1687,1690,1692,1694,1697,1699,1702,1704],{"class":115,"line":378},[113,1681,1659],{"class":265},[113,1683,1684],{"class":119},".post",[113,1686,519],{"class":143},[113,1688,1689],{"class":201},"'\u002Fchat'",[113,1691,216],{"class":215},[113,1693,590],{"class":147},[113,1695,1696],{"class":143}," (req",[113,1698,216],{"class":215},[113,1700,1701],{"class":143}," res) ",[113,1703,829],{"class":147},[113,1705,304],{"class":143},[113,1707,1708,1710,1712,1714,1716],{"class":115,"line":384},[113,1709,496],{"class":147},[113,1711,607],{"class":265},[113,1713,269],{"class":147},[113,1715,1565],{"class":119},[113,1717,1718],{"class":143},"(req)\n",[113,1720,1721,1723,1725,1727,1730,1733,1736,1739,1741,1744,1746,1748,1751,1753,1756],{"class":115,"line":397},[113,1722,754],{"class":147},[113,1724,757],{"class":143},[113,1726,760],{"class":147},[113,1728,1729],{"class":143},"userId) ",[113,1731,1732],{"class":147},"return",[113,1734,1735],{"class":265}," res",[113,1737,1738],{"class":119},".status",[113,1740,519],{"class":143},[113,1742,1743],{"class":265},"401",[113,1745,623],{"class":143},[113,1747,1670],{"class":119},[113,1749,1750],{"class":143},"({ error",[113,1752,313],{"class":147},[113,1754,1755],{"class":201}," 'Sign in to use your calendar.'",[113,1757,1758],{"class":143}," })\n",[113,1760,1761,1763,1765,1767,1769,1772,1775,1778,1780],{"class":115,"line":414},[113,1762,496],{"class":147},[113,1764,805],{"class":265},[113,1766,269],{"class":147},[113,1768,1618],{"class":265},[113,1770,1771],{"class":119},".get",[113,1773,1774],{"class":143},"(userId) ",[113,1776,1777],{"class":147},"??",[113,1779,475],{"class":119},[113,1781,278],{"class":143},[113,1783,1784,1787,1790,1793,1795],{"class":115,"line":462},[113,1785,1786],{"class":265},"    conversations",[113,1788,1789],{"class":119},".set",[113,1791,1792],{"class":143},"(userId",[113,1794,216],{"class":215},[113,1796,1797],{"class":143}," conversation)\n",[113,1799,1800,1803,1805,1807,1810,1812,1814,1816,1818,1820,1823,1825,1828],{"class":115,"line":467},[113,1801,1802],{"class":265},"    res",[113,1804,1670],{"class":119},[113,1806,519],{"class":143},[113,1808,1809],{"class":147},"await",[113,1811,595],{"class":119},[113,1813,598],{"class":143},[113,1815,216],{"class":215},[113,1817,607],{"class":143},[113,1819,216],{"class":215},[113,1821,1822],{"class":265}," req",[113,1824,645],{"class":143},[113,1826,1827],{"class":265},"body",[113,1829,1830],{"class":143},".text))\n",[113,1832,1833],{"class":115,"line":487},[113,1834,1835],{"class":143},"})\n",[113,1837,1838],{"class":115,"line":493},[113,1839,256],{"emptyLinePlaceholder":255},[113,1841,1842],{"class":115,"line":533},[113,1843,1844],{"class":154},"\u002F\u002F The person's answer: { \"approved\": [\"call_...\"] }. Changes left out are declined.\n",[113,1846,1847,1849,1851,1853,1856,1858,1860,1862,1864,1866,1868],{"class":115,"line":569},[113,1848,1659],{"class":265},[113,1850,1684],{"class":119},[113,1852,519],{"class":143},[113,1854,1855],{"class":201},"'\u002Fchat\u002Fanswer'",[113,1857,216],{"class":215},[113,1859,590],{"class":147},[113,1861,1696],{"class":143},[113,1863,216],{"class":215},[113,1865,1701],{"class":143},[113,1867,829],{"class":147},[113,1869,304],{"class":143},[113,1871,1872,1874,1876,1878,1880],{"class":115,"line":574},[113,1873,496],{"class":147},[113,1875,607],{"class":265},[113,1877,269],{"class":147},[113,1879,1565],{"class":119},[113,1881,1718],{"class":143},[113,1883,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912],{"class":115,"line":579},[113,1885,754],{"class":147},[113,1887,757],{"class":143},[113,1889,760],{"class":147},[113,1891,1729],{"class":143},[113,1893,1732],{"class":147},[113,1895,1735],{"class":265},[113,1897,1738],{"class":119},[113,1899,519],{"class":143},[113,1901,1743],{"class":265},[113,1903,623],{"class":143},[113,1905,1670],{"class":119},[113,1907,1750],{"class":143},[113,1909,313],{"class":147},[113,1911,1755],{"class":201},[113,1913,1758],{"class":143},[113,1915,1916,1918,1920,1922,1924,1926],{"class":115,"line":585},[113,1917,496],{"class":147},[113,1919,805],{"class":265},[113,1921,269],{"class":147},[113,1923,1618],{"class":265},[113,1925,1771],{"class":119},[113,1927,1928],{"class":143},"(userId)\n",[113,1930,1931,1933,1935,1937,1939,1942,1944,1946,1948,1950,1953,1955,1957,1959,1961,1964],{"class":115,"line":639},[113,1932,754],{"class":147},[113,1934,757],{"class":143},[113,1936,760],{"class":147},[113,1938,763],{"class":265},[113,1940,1941],{"class":143},"?.waiting) ",[113,1943,1732],{"class":147},[113,1945,1735],{"class":265},[113,1947,1738],{"class":119},[113,1949,519],{"class":143},[113,1951,1952],{"class":265},"409",[113,1954,623],{"class":143},[113,1956,1670],{"class":119},[113,1958,1750],{"class":143},[113,1960,313],{"class":147},[113,1962,1963],{"class":201}," 'No change is waiting for an answer.'",[113,1965,1758],{"class":143},[113,1967,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1995,1997],{"class":115,"line":672},[113,1969,1802],{"class":265},[113,1971,1670],{"class":119},[113,1973,519],{"class":143},[113,1975,1809],{"class":147},[113,1977,712],{"class":119},[113,1979,598],{"class":143},[113,1981,216],{"class":215},[113,1983,607],{"class":143},[113,1985,216],{"class":215},[113,1987,1822],{"class":265},[113,1989,645],{"class":143},[113,1991,1827],{"class":265},[113,1993,1994],{"class":143},".approved ",[113,1996,1777],{"class":147},[113,1998,1999],{"class":143}," []))\n",[113,2001,2002],{"class":115,"line":687},[113,2003,1835],{"class":143},[113,2005,2006],{"class":115,"line":692},[113,2007,256],{"emptyLinePlaceholder":255},[113,2009,2010,2012,2015,2017,2020],{"class":115,"line":697},[113,2011,1659],{"class":265},[113,2013,2014],{"class":119},".listen",[113,2016,519],{"class":143},[113,2018,2019],{"class":265},"3457",[113,2021,530],{"class":143},[78,2023,2024,2027,2028,2031,2032,2035],{},[85,2025,2026],{},"currentUserId"," stands for your sign-in. It returns the ID you saved as ",[85,2029,2030],{},"subjectId"," when the person connected their calendar. To try the guide in the sandbox, have it return ",[85,2033,2034],{},"'sandbox_user_1'",", the sandbox person Avery Brooks.",[101,2037,61],{"id":60},[78,2039,2040,2041,2043],{},"When Claude wants to change something, ",[85,2042,174],{}," returns the changes instead of a reply:",[104,2045,2049],{"className":2046,"code":2047,"language":2048,"meta":109,"style":109},"language-json shiki shiki-themes css-variables","{\n  \"changes\": [\n    {\n      \"id\": \"toolu_01FXjdyHnNFWqNYuc66PPHc1\",\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,2050,2051,2056,2066,2071,2083,2095,2127,2136,2147,2159,2171,2183,2195,2207,2219,2231,2243,2252,2279,2301,2308,2318,2323,2327,2332],{"__ignoreMap":109},[113,2052,2053],{"class":115,"line":116},[113,2054,2055],{"class":143},"{\n",[113,2057,2058,2061,2063],{"class":115,"line":158},[113,2059,2060],{"class":147},"  \"changes\"",[113,2062,313],{"class":215},[113,2064,2065],{"class":143}," [\n",[113,2067,2068],{"class":115,"line":227},[113,2069,2070],{"class":143},"    {\n",[113,2072,2073,2076,2078,2081],{"class":115,"line":252},[113,2074,2075],{"class":147},"      \"id\"",[113,2077,313],{"class":215},[113,2079,2080],{"class":201}," \"toolu_01FXjdyHnNFWqNYuc66PPHc1\"",[113,2082,1115],{"class":215},[113,2084,2085,2088,2090,2093],{"class":115,"line":259},[113,2086,2087],{"class":147},"      \"title\"",[113,2089,313],{"class":215},[113,2091,2092],{"class":201}," \"Delete an event\"",[113,2094,1115],{"class":215},[113,2096,2097,2100,2102,2104,2107,2109,2112,2114,2117,2119,2122,2125],{"class":115,"line":281},[113,2098,2099],{"class":147},"      \"input\"",[113,2101,313],{"class":215},[113,2103,789],{"class":143},[113,2105,2106],{"class":147},"\"accountId\"",[113,2108,313],{"class":215},[113,2110,2111],{"class":201}," \"sbx_f7bb6446c0_1\"",[113,2113,216],{"class":215},[113,2115,2116],{"class":147}," \"eventId\"",[113,2118,313],{"class":215},[113,2120,2121],{"class":201}," \"sbc90a16505a04a760bc4a34a0\"",[113,2123,2124],{"class":143}," }",[113,2126,1115],{"class":215},[113,2128,2129,2132,2134],{"class":115,"line":286},[113,2130,2131],{"class":147},"      \"event\"",[113,2133,313],{"class":215},[113,2135,304],{"class":143},[113,2137,2138,2141,2143,2145],{"class":115,"line":292},[113,2139,2140],{"class":147},"        \"id\"",[113,2142,313],{"class":215},[113,2144,2121],{"class":201},[113,2146,1115],{"class":215},[113,2148,2149,2152,2154,2157],{"class":115,"line":307},[113,2150,2151],{"class":147},"        \"title\"",[113,2153,313],{"class":215},[113,2155,2156],{"class":201}," \"Design Review\"",[113,2158,1115],{"class":215},[113,2160,2161,2164,2166,2169],{"class":115,"line":319},[113,2162,2163],{"class":147},"        \"startsAt\"",[113,2165,313],{"class":215},[113,2167,2168],{"class":201}," \"2026-11-10T20:00:00Z\"",[113,2170,1115],{"class":215},[113,2172,2173,2176,2178,2181],{"class":115,"line":333},[113,2174,2175],{"class":147},"        \"endsAt\"",[113,2177,313],{"class":215},[113,2179,2180],{"class":201}," \"2026-11-10T21:00:00Z\"",[113,2182,1115],{"class":215},[113,2184,2185,2188,2190,2193],{"class":115,"line":339},[113,2186,2187],{"class":147},"        \"startsAtLocal\"",[113,2189,313],{"class":215},[113,2191,2192],{"class":201}," \"2026-11-10T15:00:00-05:00\"",[113,2194,1115],{"class":215},[113,2196,2197,2200,2202,2205],{"class":115,"line":367},[113,2198,2199],{"class":147},"        \"endsAtLocal\"",[113,2201,313],{"class":215},[113,2203,2204],{"class":201}," \"2026-11-10T16:00:00-05:00\"",[113,2206,1115],{"class":215},[113,2208,2209,2212,2214,2217],{"class":115,"line":373},[113,2210,2211],{"class":147},"        \"timezone\"",[113,2213,313],{"class":215},[113,2215,2216],{"class":201}," \"America\u002FNew_York\"",[113,2218,1115],{"class":215},[113,2220,2221,2224,2226,2229],{"class":115,"line":378},[113,2222,2223],{"class":147},"        \"isAllDay\"",[113,2225,313],{"class":215},[113,2227,2228],{"class":265}," false",[113,2230,1115],{"class":215},[113,2232,2233,2236,2238,2241],{"class":115,"line":384},[113,2234,2235],{"class":147},"        \"location\"",[113,2237,313],{"class":215},[113,2239,2240],{"class":201}," \"Room 4B\"",[113,2242,1115],{"class":215},[113,2244,2245,2248,2250],{"class":115,"line":397},[113,2246,2247],{"class":147},"        \"attendees\"",[113,2249,313],{"class":215},[113,2251,2065],{"class":143},[113,2253,2254,2257,2260,2262,2265,2267,2270,2272,2275,2277],{"class":115,"line":414},[113,2255,2256],{"class":143},"          { ",[113,2258,2259],{"class":147},"\"email\"",[113,2261,313],{"class":215},[113,2263,2264],{"class":201}," \"teammate1.1@example.com\"",[113,2266,216],{"class":215},[113,2268,2269],{"class":147}," \"response\"",[113,2271,313],{"class":215},[113,2273,2274],{"class":201}," \"accepted\"",[113,2276,2124],{"class":143},[113,2278,1115],{"class":215},[113,2280,2281,2283,2285,2287,2290,2292,2294,2296,2299],{"class":115,"line":462},[113,2282,2256],{"class":143},[113,2284,2259],{"class":147},[113,2286,313],{"class":215},[113,2288,2289],{"class":201}," \"teammate2.1@example.com\"",[113,2291,216],{"class":215},[113,2293,2269],{"class":147},[113,2295,313],{"class":215},[113,2297,2298],{"class":201}," \"needsAction\"",[113,2300,411],{"class":143},[113,2302,2303,2306],{"class":115,"line":467},[113,2304,2305],{"class":143},"        ]",[113,2307,1115],{"class":215},[113,2309,2310,2313,2315],{"class":115,"line":487},[113,2311,2312],{"class":147},"        \"version\"",[113,2314,313],{"class":215},[113,2316,2317],{"class":201}," \"\\\"1\\\"\"\n",[113,2319,2320],{"class":115,"line":493},[113,2321,2322],{"class":143},"      }\n",[113,2324,2325],{"class":115,"line":533},[113,2326,1466],{"class":143},[113,2328,2329],{"class":115,"line":569},[113,2330,2331],{"class":143},"  ]\n",[113,2333,2334],{"class":115,"line":574},[113,2335,370],{"class":143},[78,2337,2338,2339,2343,2344,313],{},"Show each change with ",[2340,2341,2342],"strong",{},"Approve"," and ",[2340,2345,2346],{},"Decline",[2348,2349,2350,2357],"ul",{},[2351,2352,2353,2356],"li",{},[85,2354,2355],{},"title"," says what the change does.",[2351,2358,2359,2362,2363,2365,2366,645],{},[85,2360,2361],{},"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 Claude says. A new event has no ",[85,2364,2361],{},", so show the title and times from ",[85,2367,2368],{},"input",[78,2370,2371,2372,2375],{},"Send the IDs the person approved to ",[85,2373,2374],{},"\u002Fchat\u002Fanswer",". The others are declined: Claude is told, and they're never sent to Pyas.",[2377,2378],"screenshot",{"alt":2379,"caption":2380,"height":2381,"src":2382,"width":2383},"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 Claude asked for.",399,"\u002Fimages\u002Fapproval-in-your-app.png",660,[101,2385,64],{"id":63},[2387,2388,2389,2397,2400,2403],"ol",{},[2351,2390,2391,2392,2394,2395,645],{},"Run your server with a test key, and with ",[85,2393,2026],{}," returning ",[85,2396,2034],{},[2351,2398,2399],{},"Ask \"What's on my calendar tomorrow?\". The reply lists Avery's events, without asking anything.",[2351,2401,2402],{},"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.",[2351,2404,2405],{},"Ask Claude to delete that meeting, and decline. The meeting stays, and the reply says it wasn't deleted.",[78,2407,2408,2409,645],{},"To go live, see ",[90,2410,2412],{"href":2411},"\u002Fai-in-your-app#go-live","Go live",[2414,2415,2416],"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":109,"searchDepth":227,"depth":227,"links":2418},[2419,2420,2421,2422,2423],{"id":51,"depth":158,"text":52},{"id":54,"depth":158,"text":55},{"id":57,"depth":158,"text":58},{"id":60,"depth":158,"text":61},{"id":63,"depth":158,"text":64},"md",false,{},"\u002Fclaude-api","---\ntitle: Claude API\ndescription: Add a calendar assistant to your app with the Claude API and Pyas. 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# Claude API\n\n::lead\nThis guide adds a calendar assistant to your server with Anthropic's Messages 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 `claude-sonnet-5-5`. See [AI in your app](\u002Fai-in-your-app) for how the tools work. To use Pyas yourself in the Claude app, see [Claude](\u002Fclaude) instead.\n\n## Install\n\n```bash\nnpm install @pyas\u002Fai-tools @anthropic-ai\u002Fsdk\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\nANTHROPIC_API_KEY=sk-ant-...\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```ts [assistant.ts]\nimport Anthropic from '@anthropic-ai\u002Fsdk'\nimport type { MessageParam, ToolResultBlockParam } from '@anthropic-ai\u002Fsdk\u002Fresources\u002Fmessages'\nimport { pyasAnthropicTools, runPyasToolUses, type PyasPendingToolUse } from '@pyas\u002Fai-tools\u002Fanthropic'\n\nconst anthropic = new Anthropic()\n\n\u002F** One person's conversation with the assistant. Save it between requests, for example in your database. *\u002F\nexport interface Conversation {\n    system: string\n    messages: MessageParam[]\n    \u002F** Changes waiting for the person, with the results that go back to Claude alongside them. *\u002F\n    waiting?: { results: ToolResultBlockParam[]; pending: PyasPendingToolUse[] }\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\u003CPyasPendingToolUse, 'id' | 'title' | 'input' | 'event'>> }\n\nexport function newConversation(): Conversation {\n    \u002F\u002F The same for the whole conversation: Claude rejects a system prompt that changes partway.\n    const today = new Date().toISOString().slice(0, 10)\n    return { system: `You help the user manage their calendar. Today is ${today}.`, messages: [] }\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 runPyasToolUses(pending.map((change) => change.call), { subjectId: userId, approved, declined })\n    \u002F\u002F Every tool_result of the turn goes back in one message.\n    conversation.messages.push({ role: 'user', content: [...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 message = await anthropic.messages.create({\n            model: 'claude-sonnet-5-5',\n            max_tokens: 4096,\n            system: conversation.system,\n            messages: conversation.messages,\n            tools: pyasAnthropicTools(options),\n        })\n        conversation.messages.push({ role: 'assistant', content: message.content })\n        if (message.stop_reason !== 'tool_use') {\n            return { reply: message.content.flatMap((block) => (block.type === 'text' ? [block.text] : [])).join('\\n') }\n        }\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 runPyasToolUses(message.content, 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({ role: 'user', content: results })\n    }\n    return { reply: 'I stopped before finishing. Please ask again.' }\n}\n```\n\nThe tools that only read run as soon as Claude calls them. A change comes back in `pending`, and nothing is sent to Pyas until the person approves it. Claude needs every `tool_result` of a turn in one message, so the results of the other calls wait with the change and go back together.\n\nKeep the system prompt the same for the whole conversation. Claude rejects a conversation whose system prompt changes partway, so put today's date in it, not the time.\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 Claude wants to change something, `ask` returns the changes instead of a reply:\n\n```json\n{\n  \"changes\": [\n    {\n      \"id\": \"toolu_01FXjdyHnNFWqNYuc66PPHc1\",\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 Claude 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: Claude 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 Claude 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 Claude to delete that meeting, and decline. The meeting stays, and the reply says it wasn't deleted.\n\nTo go live, see [Go live](\u002Fai-in-your-app#go-live).\n",{"title":47,"description":48},null,"cfMtqUb-BGw-JnOJ9L24Hro6X-Ou4RnQf-ZjQckNSAI",1794697271176]